Showing preview only (1,158K chars total). Download the full file or copy to clipboard to get everything.
Repository: veelenga/ameba
Branch: master
Commit: 77bf37bb5d3f
Files: 362
Total size: 1.0 MB
Directory structure:
gitextract_e8xowmtu/
├── .ameba.yml
├── .ameba.yml.schema.json
├── .dockerignore
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ ├── add-docs-version-to-json-index.yml
│ ├── build-and-deploy-docs.yml
│ ├── ci.yml
│ ├── compare-json-schema.yml
│ ├── docker-image.yml
│ ├── docs.yml
│ ├── release.yml
│ ├── typos.yml
│ └── update-json-schema.yml
├── .gitignore
├── .typos.toml
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── bench/
│ └── check_sources.cr
├── bin/
│ └── ameba.cr
├── shard.yml
├── spec/
│ ├── ameba/
│ │ ├── ast/
│ │ │ ├── flow_expression_spec.cr
│ │ │ ├── liveness_analyzer_spec.cr
│ │ │ ├── scope_spec.cr
│ │ │ ├── util_spec.cr
│ │ │ ├── variabling/
│ │ │ │ ├── argument_spec.cr
│ │ │ │ ├── assignment_spec.cr
│ │ │ │ ├── reference_spec.cr
│ │ │ │ ├── type_dec_variable_spec.cr
│ │ │ │ └── variable_spec.cr
│ │ │ └── visitors/
│ │ │ ├── counting_visitor_spec.cr
│ │ │ ├── elseif_aware_node_visitor_spec.cr
│ │ │ ├── flow_expression_visitor_spec.cr
│ │ │ ├── implicit_return_visitor_spec.cr
│ │ │ ├── node_visitor_spec.cr
│ │ │ ├── redundant_control_expression_visitor_spec.cr
│ │ │ ├── scope_calls_with_self_receiver_visitor_spec.cr
│ │ │ ├── scope_visitor_spec.cr
│ │ │ └── top_level_nodes_visitor_spec.cr
│ │ ├── base_spec.cr
│ │ ├── cli/
│ │ │ └── cmd_spec.cr
│ │ ├── config_spec.cr
│ │ ├── ext/
│ │ │ └── location_spec.cr
│ │ ├── formatter/
│ │ │ ├── disabled_formatter_spec.cr
│ │ │ ├── dot_formatter_spec.cr
│ │ │ ├── explain_formatter_spec.cr
│ │ │ ├── flycheck_formatter_spec.cr
│ │ │ ├── github_actions_formatter_spec.cr
│ │ │ ├── json_formatter_spec.cr
│ │ │ ├── todo_formatter_spec.cr
│ │ │ └── util_spec.cr
│ │ ├── glob_utils_spec.cr
│ │ ├── inline_comments_spec.cr
│ │ ├── issue_spec.cr
│ │ ├── presenter/
│ │ │ ├── rule_collection_presenter_spec.cr
│ │ │ ├── rule_presenter_spec.cr
│ │ │ └── rule_versions_presenter_spec.cr
│ │ ├── reportable_spec.cr
│ │ ├── rule/
│ │ │ ├── base_spec.cr
│ │ │ ├── documentation/
│ │ │ │ ├── admonition_spec.cr
│ │ │ │ └── documentation_spec.cr
│ │ │ ├── layout/
│ │ │ │ ├── line_length_spec.cr
│ │ │ │ ├── trailing_blank_lines_spec.cr
│ │ │ │ └── trailing_whitespace_spec.cr
│ │ │ ├── lint/
│ │ │ │ ├── ambiguous_assignment_spec.cr
│ │ │ │ ├── assignment_in_call_argument_spec.cr
│ │ │ │ ├── bad_directive_spec.cr
│ │ │ │ ├── comparison_to_boolean_spec.cr
│ │ │ │ ├── debug_calls_spec.cr
│ │ │ │ ├── debugger_statement_spec.cr
│ │ │ │ ├── duplicate_branch_spec.cr
│ │ │ │ ├── duplicate_enum_value_spec.cr
│ │ │ │ ├── duplicate_method_signature_spec.cr
│ │ │ │ ├── duplicate_when_condition_spec.cr
│ │ │ │ ├── duplicated_require_spec.cr
│ │ │ │ ├── else_nil_spec.cr
│ │ │ │ ├── empty_ensure_spec.cr
│ │ │ │ ├── empty_expression_spec.cr
│ │ │ │ ├── empty_loop_spec.cr
│ │ │ │ ├── enum_member_name_conflict_spec.cr
│ │ │ │ ├── formatting_spec.cr
│ │ │ │ ├── hash_duplicated_key_spec.cr
│ │ │ │ ├── literal_assignments_in_expressions_spec.cr
│ │ │ │ ├── literal_in_condition_spec.cr
│ │ │ │ ├── literal_in_interpolation_spec.cr
│ │ │ │ ├── literals_comparison_spec.cr
│ │ │ │ ├── missing_block_argument_spec.cr
│ │ │ │ ├── non_existent_rule_spec.cr
│ │ │ │ ├── not_nil_after_no_bang_spec.cr
│ │ │ │ ├── not_nil_spec.cr
│ │ │ │ ├── percent_arrays_spec.cr
│ │ │ │ ├── rand_zero_spec.cr
│ │ │ │ ├── redundant_string_cercion_spec.cr
│ │ │ │ ├── redundant_with_index_spec.cr
│ │ │ │ ├── redundant_with_object_spec.cr
│ │ │ │ ├── require_parentheses_spec.cr
│ │ │ │ ├── self_initialize_definition_spec.cr
│ │ │ │ ├── shadowed_argument_spec.cr
│ │ │ │ ├── shadowed_exception_spec.cr
│ │ │ │ ├── shadowing_outer_local_var_spec.cr
│ │ │ │ ├── shared_var_in_fiber_spec.cr
│ │ │ │ ├── signal_trap_spec.cr
│ │ │ │ ├── spec_eq_with_bool_or_nil_literal_spec.cr
│ │ │ │ ├── spec_filename_spec.cr
│ │ │ │ ├── spec_focus_spec.cr
│ │ │ │ ├── syntax_spec.cr
│ │ │ │ ├── top_level_operator_definition_spec.cr
│ │ │ │ ├── trailing_rescue_exception_spec.cr
│ │ │ │ ├── typos_spec.cr
│ │ │ │ ├── unneeded_disable_directive_spec.cr
│ │ │ │ ├── unreachable_code_spec.cr
│ │ │ │ ├── unused_argument_spec.cr
│ │ │ │ ├── unused_block_argument_spec.cr
│ │ │ │ ├── unused_expression_spec.cr
│ │ │ │ ├── unused_rescue_variable_spec.cr
│ │ │ │ ├── useless_assign_spec.cr
│ │ │ │ ├── useless_condition_in_when_spec.cr
│ │ │ │ ├── useless_visibility_modifier_spec.cr
│ │ │ │ ├── void_outside_lib_spec.cr
│ │ │ │ └── whitespace_around_macro_expression_spec.cr
│ │ │ ├── metrics/
│ │ │ │ └── cyclomatic_complexity_spec.cr
│ │ │ ├── naming/
│ │ │ │ ├── accessor_method_name_spec.cr
│ │ │ │ ├── ascii_identifiers_spec.cr
│ │ │ │ ├── binary_operator_parameter_name_spec.cr
│ │ │ │ ├── block_parameter_name_spec.cr
│ │ │ │ ├── constant_names_spec.cr
│ │ │ │ ├── filename_spec.cr
│ │ │ │ ├── method_names_spec.cr
│ │ │ │ ├── predicate_name_spec.cr
│ │ │ │ ├── query_bool_methods_spec.cr
│ │ │ │ ├── rescued_exceptions_variable_name_spec.cr
│ │ │ │ ├── type_names_spec.cr
│ │ │ │ └── variable_names_spec.cr
│ │ │ ├── performance/
│ │ │ │ ├── any_after_filter_spec.cr
│ │ │ │ ├── any_instead_of_present_spec.cr
│ │ │ │ ├── base_spec.cr
│ │ │ │ ├── chained_call_with_no_bang_spec.cr
│ │ │ │ ├── compact_after_map_spec.cr
│ │ │ │ ├── excessive_allocations_spec.cr
│ │ │ │ ├── first_last_after_filter_spec.cr
│ │ │ │ ├── flatten_after_map_spec.cr
│ │ │ │ ├── map_instead_of_block_spec.cr
│ │ │ │ ├── minmax_after_map_spec.cr
│ │ │ │ ├── size_after_filter_spec.cr
│ │ │ │ └── times_map_spec.cr
│ │ │ ├── style/
│ │ │ │ ├── array_literal_syntax_spec.cr
│ │ │ │ ├── call_parentheses_spec.cr
│ │ │ │ ├── elsif_spec.cr
│ │ │ │ ├── guard_clause_spec.cr
│ │ │ │ ├── hash_literal_syntax_spec.cr
│ │ │ │ ├── heredoc_escape_spec.cr
│ │ │ │ ├── heredoc_indent_spec.cr
│ │ │ │ ├── is_a_filter_spec.cr
│ │ │ │ ├── is_a_nil_spec.cr
│ │ │ │ ├── large_numbers_spec.cr
│ │ │ │ ├── multiline_curly_block_spec.cr
│ │ │ │ ├── multiline_string_literal_spec.cr
│ │ │ │ ├── negated_conditions_in_unless_spec.cr
│ │ │ │ ├── parentheses_around_condition_spec.cr
│ │ │ │ ├── percent_literal_delimiters_spec.cr
│ │ │ │ ├── redundant_begin_spec.cr
│ │ │ │ ├── redundant_next_spec.cr
│ │ │ │ ├── redundant_nil_in_control_expression_spec.cr
│ │ │ │ ├── redundant_return_spec.cr
│ │ │ │ ├── redundant_self_spec.cr
│ │ │ │ ├── unless_else_spec.cr
│ │ │ │ ├── verbose_block_spec.cr
│ │ │ │ ├── verbose_nil_type_spec.cr
│ │ │ │ └── while_true_spec.cr
│ │ │ └── typing/
│ │ │ ├── macro_call_argument_type_restriction_spec.cr
│ │ │ ├── method_parameter_type_restriction_spec.cr
│ │ │ ├── method_return_type_restriction_spec.cr
│ │ │ └── proc_literal_return_type_restriction_spec.cr
│ │ ├── runner_spec.cr
│ │ ├── severity_spec.cr
│ │ ├── source/
│ │ │ └── rewriter_spec.cr
│ │ ├── source_spec.cr
│ │ ├── spec/
│ │ │ └── annotated_source_spec.cr
│ │ ├── tokenizer_spec.cr
│ │ └── version_spec.cr
│ ├── ameba_spec.cr
│ ├── fixtures/
│ │ └── .ameba.yml
│ └── spec_helper.cr
└── src/
├── ameba/
│ ├── ast/
│ │ ├── flow_expression.cr
│ │ ├── liveness_analyzer.cr
│ │ ├── scope.cr
│ │ ├── util.cr
│ │ ├── variabling/
│ │ │ ├── argument.cr
│ │ │ ├── assignment.cr
│ │ │ ├── ivariable.cr
│ │ │ ├── reference.cr
│ │ │ ├── type_dec_variable.cr
│ │ │ └── variable.cr
│ │ └── visitors/
│ │ ├── base_visitor.cr
│ │ ├── counting_visitor.cr
│ │ ├── elseif_aware_node_visitor.cr
│ │ ├── flow_expression_visitor.cr
│ │ ├── implicit_return_visitor.cr
│ │ ├── macro_reference_finder.cr
│ │ ├── node_visitor.cr
│ │ ├── redundant_control_expression_visitor.cr
│ │ ├── scope_calls_with_self_receiver_visitor.cr
│ │ ├── scope_visitor.cr
│ │ └── top_level_nodes_visitor.cr
│ ├── cli/
│ │ └── cmd.cr
│ ├── config/
│ │ ├── loader.cr
│ │ └── rule_config.cr
│ ├── config.cr
│ ├── ext/
│ │ └── location.cr
│ ├── formatter/
│ │ ├── base_formatter.cr
│ │ ├── disabled_formatter.cr
│ │ ├── dot_formatter.cr
│ │ ├── explain_formatter.cr
│ │ ├── flycheck_formatter.cr
│ │ ├── github_actions_formatter.cr
│ │ ├── json_formatter.cr
│ │ ├── todo_formatter.cr
│ │ └── util.cr
│ ├── glob_utils.cr
│ ├── inline_comments.cr
│ ├── issue.cr
│ ├── json_schema/
│ │ └── builder.cr
│ ├── presenter/
│ │ ├── base_presenter.cr
│ │ ├── rule_collection_presenter.cr
│ │ ├── rule_presenter.cr
│ │ └── rule_versions_presenter.cr
│ ├── reportable.cr
│ ├── rule/
│ │ ├── base.cr
│ │ ├── documentation/
│ │ │ ├── admonition.cr
│ │ │ └── documentation.cr
│ │ ├── layout/
│ │ │ ├── line_length.cr
│ │ │ ├── trailing_blank_lines.cr
│ │ │ └── trailing_whitespace.cr
│ │ ├── lint/
│ │ │ ├── ambiguous_assignment.cr
│ │ │ ├── assignment_in_call_argument.cr
│ │ │ ├── bad_directive.cr
│ │ │ ├── comparison_to_boolean.cr
│ │ │ ├── debug_calls.cr
│ │ │ ├── debugger_statement.cr
│ │ │ ├── duplicate_branch.cr
│ │ │ ├── duplicate_enum_value.cr
│ │ │ ├── duplicate_method_signature.cr
│ │ │ ├── duplicate_when_condition.cr
│ │ │ ├── duplicated_require.cr
│ │ │ ├── else_nil.cr
│ │ │ ├── empty_ensure.cr
│ │ │ ├── empty_expression.cr
│ │ │ ├── empty_loop.cr
│ │ │ ├── enum_member_name_conflict.cr
│ │ │ ├── formatting.cr
│ │ │ ├── hash_duplicated_key.cr
│ │ │ ├── literal_assignments_in_expressions.cr
│ │ │ ├── literal_in_condition.cr
│ │ │ ├── literal_in_interpolation.cr
│ │ │ ├── literals_comparison.cr
│ │ │ ├── missing_block_argument.cr
│ │ │ ├── non_existent_rule.cr
│ │ │ ├── not_nil.cr
│ │ │ ├── not_nil_after_no_bang.cr
│ │ │ ├── percent_arrays.cr
│ │ │ ├── rand_zero.cr
│ │ │ ├── redundant_string_coercion.cr
│ │ │ ├── redundant_with_index.cr
│ │ │ ├── redundant_with_object.cr
│ │ │ ├── require_parentheses.cr
│ │ │ ├── self_initialize_definition.cr
│ │ │ ├── shadowed_argument.cr
│ │ │ ├── shadowed_exception.cr
│ │ │ ├── shadowing_outer_local_var.cr
│ │ │ ├── shared_var_in_fiber.cr
│ │ │ ├── signal_trap.cr
│ │ │ ├── spec_eq_with_bool_or_nil_literal.cr
│ │ │ ├── spec_filename.cr
│ │ │ ├── spec_focus.cr
│ │ │ ├── syntax.cr
│ │ │ ├── top_level_operator_definition.cr
│ │ │ ├── trailing_rescue_exception.cr
│ │ │ ├── typos.cr
│ │ │ ├── unneeded_disable_directive.cr
│ │ │ ├── unreachable_code.cr
│ │ │ ├── unused_argument.cr
│ │ │ ├── unused_block_argument.cr
│ │ │ ├── unused_expression.cr
│ │ │ ├── unused_rescue_variable.cr
│ │ │ ├── useless_assign.cr
│ │ │ ├── useless_condition_in_when.cr
│ │ │ ├── useless_visibility_modifier.cr
│ │ │ ├── void_outside_lib.cr
│ │ │ └── whitespace_around_macro_expression.cr
│ │ ├── metrics/
│ │ │ └── cyclomatic_complexity.cr
│ │ ├── naming/
│ │ │ ├── accessor_method_name.cr
│ │ │ ├── ascii_identifiers.cr
│ │ │ ├── binary_operator_parameter_name.cr
│ │ │ ├── block_parameter_name.cr
│ │ │ ├── constant_names.cr
│ │ │ ├── filename.cr
│ │ │ ├── method_names.cr
│ │ │ ├── predicate_name.cr
│ │ │ ├── query_bool_methods.cr
│ │ │ ├── rescued_exceptions_variable_name.cr
│ │ │ ├── type_names.cr
│ │ │ └── variable_names.cr
│ │ ├── performance/
│ │ │ ├── any_after_filter.cr
│ │ │ ├── any_instead_of_present.cr
│ │ │ ├── base.cr
│ │ │ ├── chained_call_with_no_bang.cr
│ │ │ ├── compact_after_map.cr
│ │ │ ├── excessive_allocations.cr
│ │ │ ├── first_last_after_filter.cr
│ │ │ ├── flatten_after_map.cr
│ │ │ ├── map_instead_of_block.cr
│ │ │ ├── minmax_after_map.cr
│ │ │ ├── size_after_filter.cr
│ │ │ └── times_map.cr
│ │ ├── style/
│ │ │ ├── array_literal_syntax.cr
│ │ │ ├── call_parentheses.cr
│ │ │ ├── elsif.cr
│ │ │ ├── guard_clause.cr
│ │ │ ├── hash_literal_syntax.cr
│ │ │ ├── heredoc_escape.cr
│ │ │ ├── heredoc_indent.cr
│ │ │ ├── is_a_filter.cr
│ │ │ ├── is_a_nil.cr
│ │ │ ├── large_numbers.cr
│ │ │ ├── multiline_curly_block.cr
│ │ │ ├── multiline_string_literal.cr
│ │ │ ├── negated_conditions_in_unless.cr
│ │ │ ├── parentheses_around_condition.cr
│ │ │ ├── percent_literal_delimiters.cr
│ │ │ ├── redundant_begin.cr
│ │ │ ├── redundant_next.cr
│ │ │ ├── redundant_nil_in_control_expression.cr
│ │ │ ├── redundant_return.cr
│ │ │ ├── redundant_self.cr
│ │ │ ├── unless_else.cr
│ │ │ ├── verbose_block.cr
│ │ │ ├── verbose_nil_type.cr
│ │ │ └── while_true.cr
│ │ └── typing/
│ │ ├── macro_call_argument_type_restriction.cr
│ │ ├── method_parameter_type_restriction.cr
│ │ ├── method_return_type_restriction.cr
│ │ └── proc_literal_return_type_restriction.cr
│ ├── runner.cr
│ ├── severity.cr
│ ├── source/
│ │ ├── corrector.cr
│ │ ├── rewriter/
│ │ │ └── action.cr
│ │ └── rewriter.cr
│ ├── source.cr
│ ├── spec/
│ │ ├── annotated_source.cr
│ │ ├── be_valid.cr
│ │ ├── expect_issue.cr
│ │ ├── support.cr
│ │ └── util.cr
│ ├── tokenizer.cr
│ └── version.cr
├── ameba.cr
├── cli.cr
├── contrib/
│ └── read_type_doc.cr
└── json-schema-builder.cr
================================================
FILE CONTENTS
================================================
================================================
FILE: .ameba.yml
================================================
Lint/Typos:
Enabled: true
Excluded:
- spec/ameba/rule/lint/typos_spec.cr
Style/Elsif:
Enabled: true
================================================
FILE: .ameba.yml.schema.json
================================================
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://crystal-ameba.github.io/.ameba.yml.schema.json",
"title": ".ameba.yml",
"description": "Configuration rules for the Crystal language Ameba linter",
"type": "object",
"additionalProperties": false,
"$defs": {
"Severity": {
"type": "string",
"enum": [
"Error",
"Warning",
"Convention"
]
},
"Globs": {
"type": "array",
"title": "Globbed files and paths",
"description": "An array of wildcards (or paths) to include to the inspection",
"items": {
"type": "string",
"examples": [
"src/**/*.{cr,ecr}",
"!lib"
]
}
},
"Excluded": {
"type": "array",
"title": "Excluded files and paths",
"description": "An array of wildcards (or paths) to exclude from the source list",
"items": {
"type": "string",
"examples": [
"spec/fixtures/**",
"spec/**/*.manual_spec.cr"
]
}
},
"BaseRule": {
"type": "object",
"properties": {
"SinceVersion": {
"type": "string"
},
"Enabled": {
"type": "boolean",
"default": true
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Convention"
},
"Excluded": {
"$ref": "#/$defs/Excluded"
}
}
}
},
"properties": {
"Version": {
"type": "string",
"description": "The version of Ameba to limit rules to",
"examples": [
"1.7.0",
"1.6.4"
]
},
"Formatter": {
"type": "object",
"description": "The formatter to use for Ameba",
"properties": {
"Name": {
"type": "string",
"enum": [
"progress",
"todo",
"flycheck",
"silent",
"disabled",
"json",
"github-actions"
]
}
}
},
"Globs": {
"$ref": "#/$defs/Globs"
},
"Excluded": {
"$ref": "#/$defs/Excluded"
},
"Documentation/Admonition": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Documentation/Admonition.html",
"title": "Documentation/Admonition",
"description": "Reports documentation admonitions",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.6.0"
},
"Enabled": {
"type": "boolean",
"default": false
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
},
"Admonitions": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"TODO",
"FIXME",
"BUG"
]
},
"Timezone": {
"type": "string",
"default": "UTC"
}
}
},
"Documentation/Documentation": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Documentation/Documentation.html",
"title": "Documentation/Documentation",
"description": "Enforces public types to be documented",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.5.0"
},
"Enabled": {
"type": "boolean",
"default": false
},
"IgnoreClasses": {
"type": "boolean",
"default": false
},
"IgnoreModules": {
"type": "boolean",
"default": true
},
"IgnoreEnums": {
"type": "boolean",
"default": false
},
"IgnoreDefs": {
"type": "boolean",
"default": true
},
"IgnoreMacros": {
"type": "boolean",
"default": false
},
"IgnoreMacroHooks": {
"type": "boolean",
"default": true
},
"RequireExample": {
"type": "boolean",
"default": false
}
}
},
"Layout/LineLength": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Layout/LineLength.html",
"title": "Layout/LineLength",
"description": "Disallows lines longer than `MaxLength` number of symbols",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.1.0"
},
"Enabled": {
"type": "boolean",
"default": false
},
"MaxLength": {
"type": "number",
"default": 140
}
}
},
"Layout/TrailingBlankLines": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Layout/TrailingBlankLines.html",
"title": "Layout/TrailingBlankLines",
"description": "Disallows trailing blank lines",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.1.0"
}
}
},
"Layout/TrailingWhitespace": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Layout/TrailingWhitespace.html",
"title": "Layout/TrailingWhitespace",
"description": "Disallows trailing whitespace",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.1.0"
}
}
},
"Lint/AmbiguousAssignment": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/AmbiguousAssignment.html",
"title": "Lint/AmbiguousAssignment",
"description": "Disallows ambiguous `=-/=+/=!`",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.0.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/AssignmentInCallArgument": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/AssignmentInCallArgument.html",
"title": "Lint/AssignmentInCallArgument",
"description": "Disallows variable assignment in call arguments",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/BadDirective": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/BadDirective.html",
"title": "Lint/BadDirective",
"description": "Reports bad comment directives",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.13.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/ComparisonToBoolean": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/ComparisonToBoolean.html",
"title": "Lint/ComparisonToBoolean",
"description": "Disallows comparison to booleans",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.1.0"
},
"Enabled": {
"type": "boolean",
"default": false
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/DebugCalls": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/DebugCalls.html",
"title": "Lint/DebugCalls",
"description": "Disallows debug-related calls",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.0.0"
},
"MethodNames": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"p",
"p!",
"pp",
"pp!"
]
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/DebuggerStatement": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/DebuggerStatement.html",
"title": "Lint/DebuggerStatement",
"description": "Disallows calls to `debugger`",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.1.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/DuplicateBranch": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/DuplicateBranch.html",
"title": "Lint/DuplicateBranch",
"description": "Reports duplicated branch bodies",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Enabled": {
"type": "boolean",
"default": false
},
"IgnoreLiteralBranches": {
"type": "boolean",
"default": false
},
"IgnoreConstantBranches": {
"type": "boolean",
"default": false
},
"IgnoreDuplicateElseBranch": {
"type": "boolean",
"default": false
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/DuplicateEnumValue": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/DuplicateEnumValue.html",
"title": "Lint/DuplicateEnumValue",
"description": "Reports duplicated `enum` member values",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/DuplicateMethodSignature": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/DuplicateMethodSignature.html",
"title": "Lint/DuplicateMethodSignature",
"description": "Reports repeated method signatures",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/DuplicateWhenCondition": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/DuplicateWhenCondition.html",
"title": "Lint/DuplicateWhenCondition",
"description": "Reports repeated conditions used in case `when` expressions",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/DuplicatedRequire": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/DuplicatedRequire.html",
"title": "Lint/DuplicatedRequire",
"description": "Reports duplicated `require` statements",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.14.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/ElseNil": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/ElseNil.html",
"title": "Lint/ElseNil",
"description": "Disallows `else` blocks with `nil` as their body",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/EmptyEnsure": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/EmptyEnsure.html",
"title": "Lint/EmptyEnsure",
"description": "Disallows empty `ensure` statement",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.3.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/EmptyExpression": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/EmptyExpression.html",
"title": "Lint/EmptyExpression",
"description": "Disallows empty expressions",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.2.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/EmptyLoop": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/EmptyLoop.html",
"title": "Lint/EmptyLoop",
"description": "Disallows empty loops",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.12.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/EnumMemberNameConflict": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/EnumMemberNameConflict.html",
"title": "Lint/EnumMemberNameConflict",
"description": "Reports conflicting enum member names",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/Formatting": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/Formatting.html",
"title": "Lint/Formatting",
"description": "Reports not formatted sources",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.4.0"
},
"FailOnError": {
"type": "boolean",
"default": false
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/HashDuplicatedKey": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/HashDuplicatedKey.html",
"title": "Lint/HashDuplicatedKey",
"description": "Disallows duplicated keys in hash literals",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.3.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/LiteralAssignmentsInExpressions": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/LiteralAssignmentsInExpressions.html",
"title": "Lint/LiteralAssignmentsInExpressions",
"description": "Disallows assignments with literal values in control expressions",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.4.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/LiteralInCondition": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/LiteralInCondition.html",
"title": "Lint/LiteralInCondition",
"description": "Disallows useless conditional statements that contain a literal in place of a variable or predicate function",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.1.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/LiteralInInterpolation": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/LiteralInInterpolation.html",
"title": "Lint/LiteralInInterpolation",
"description": "Disallows useless string interpolations",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.1.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/LiteralsComparison": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/LiteralsComparison.html",
"title": "Lint/LiteralsComparison",
"description": "Identifies comparisons between literals",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.3.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/MissingBlockArgument": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/MissingBlockArgument.html",
"title": "Lint/MissingBlockArgument",
"description": "Disallows yielding method definitions without block argument",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.4.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/NonExistentRule": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/NonExistentRule.html",
"title": "Lint/NonExistentRule",
"description": "Reports non-existent rules in comment directives",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.13.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/NotNil": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/NotNil.html",
"title": "Lint/NotNil",
"description": "Identifies usage of `not_nil!` calls",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.3.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/NotNilAfterNoBang": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/NotNilAfterNoBang.html",
"title": "Lint/NotNilAfterNoBang",
"description": "Identifies usage of `index/rindex/find/match` calls followed by `not_nil!`",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.3.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/PercentArrays": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/PercentArrays.html",
"title": "Lint/PercentArrays",
"description": "Disallows some unwanted symbols in percent array literals",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.3.0"
},
"StringArrayUnwantedSymbols": {
"type": "string",
"default": ",\""
},
"SymbolArrayUnwantedSymbols": {
"type": "string",
"default": ",:"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/RandZero": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/RandZero.html",
"title": "Lint/RandZero",
"description": "Disallows `rand` zero calls",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.5.1"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/RedundantStringCoercion": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/RedundantStringCoercion.html",
"title": "Lint/RedundantStringCoercion",
"description": "Disallows redundant string conversions in interpolation",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.12.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/RedundantWithIndex": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/RedundantWithIndex.html",
"title": "Lint/RedundantWithIndex",
"description": "Disallows redundant `with_index` calls",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.11.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/RedundantWithObject": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/RedundantWithObject.html",
"title": "Lint/RedundantWithObject",
"description": "Disallows redundant `with_object` calls",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.11.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/RequireParentheses": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/RequireParentheses.html",
"title": "Lint/RequireParentheses",
"description": "Disallows method calls with no parentheses and a logical operator in the argument list",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/SelfInitializeDefinition": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/SelfInitializeDefinition.html",
"title": "Lint/SelfInitializeDefinition",
"description": "Reports `initialize` method definitions with a `self` receiver",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/ShadowedArgument": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/ShadowedArgument.html",
"title": "Lint/ShadowedArgument",
"description": "Disallows shadowed arguments",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/ShadowedException": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/ShadowedException.html",
"title": "Lint/ShadowedException",
"description": "Disallows rescued exception that get shadowed",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.3.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/ShadowingOuterLocalVar": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/ShadowingOuterLocalVar.html",
"title": "Lint/ShadowingOuterLocalVar",
"description": "Disallows the usage of the same name as outer local variables for block or proc arguments",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/SharedVarInFiber": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/SharedVarInFiber.html",
"title": "Lint/SharedVarInFiber",
"description": "Disallows shared variables in fibers",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.12.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/SignalTrap": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/SignalTrap.html",
"title": "Lint/SignalTrap",
"description": "Disallows `Signal::INT/HUP/TERM.trap` in favor of `Process.on_terminate`",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/SpecEqWithBoolOrNilLiteral": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/SpecEqWithBoolOrNilLiteral.html",
"title": "Lint/SpecEqWithBoolOrNilLiteral",
"description": "Reports `eq(true|false|nil)` expectations in specs",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/SpecFilename": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/SpecFilename.html",
"title": "Lint/SpecFilename",
"description": "Enforces spec filenames to have `_spec` suffix",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.6.0"
},
"IgnoredDirs": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"spec/support",
"spec/fixtures",
"spec/data"
]
},
"IgnoredFilenames": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"spec_helper"
]
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/SpecFocus": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/SpecFocus.html",
"title": "Lint/SpecFocus",
"description": "Reports focused spec items",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.14.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/Syntax": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/Syntax.html",
"title": "Lint/Syntax",
"description": "Reports invalid Crystal syntax",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.4.2"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Error"
}
}
},
"Lint/TopLevelOperatorDefinition": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/TopLevelOperatorDefinition.html",
"title": "Lint/TopLevelOperatorDefinition",
"description": "Disallows top level operator method definitions",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/TrailingRescueException": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/TrailingRescueException.html",
"title": "Lint/TrailingRescueException",
"description": "Disallows trailing `rescue` with a path",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/Typos": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/Typos.html",
"title": "Lint/Typos",
"description": "Reports typos found in source files",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.6.0"
},
"Enabled": {
"type": "boolean",
"default": false
},
"BinPath": {
"type": [
"string",
"null"
],
"default": null
},
"FailOnMissingBin": {
"type": "boolean",
"default": false
},
"FailOnError": {
"type": "boolean",
"default": true
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/UnneededDisableDirective": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/UnneededDisableDirective.html",
"title": "Lint/UnneededDisableDirective",
"description": "Reports unneeded disable directives in comments",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.5.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/UnreachableCode": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/UnreachableCode.html",
"title": "Lint/UnreachableCode",
"description": "Reports unreachable code",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.9.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/UnusedArgument": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/UnusedArgument.html",
"title": "Lint/UnusedArgument",
"description": "Disallows unused arguments",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.6.0"
},
"IgnoreDefs": {
"type": "boolean",
"default": true
},
"IgnoreBlocks": {
"type": "boolean",
"default": false
},
"IgnoreProcs": {
"type": "boolean",
"default": false
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/UnusedBlockArgument": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/UnusedBlockArgument.html",
"title": "Lint/UnusedBlockArgument",
"description": "Disallows unused block arguments",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.4.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/UnusedExpression": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/UnusedExpression.html",
"title": "Lint/UnusedExpression",
"description": "Disallows unused expressions",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/UnusedRescueVariable": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/UnusedRescueVariable.html",
"title": "Lint/UnusedRescueVariable",
"description": "Disallows unused `rescue` variables",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/UselessAssign": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/UselessAssign.html",
"title": "Lint/UselessAssign",
"description": "Disallows useless variable assignments",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.6.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/UselessConditionInWhen": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/UselessConditionInWhen.html",
"title": "Lint/UselessConditionInWhen",
"description": "Disallows useless conditions in `when`",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.3.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/UselessVisibilityModifier": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/UselessVisibilityModifier.html",
"title": "Lint/UselessVisibilityModifier",
"description": "Disallows top level `protected` method visibility modifier",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/VoidOutsideLib": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/VoidOutsideLib.html",
"title": "Lint/VoidOutsideLib",
"description": "Disallows use of `Void` outside C lib bindings and `Pointer(Void)`",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Lint/WhitespaceAroundMacroExpression": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/WhitespaceAroundMacroExpression.html",
"title": "Lint/WhitespaceAroundMacroExpression",
"description": "Reports missing spaces around macro expressions",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Metrics/CyclomaticComplexity": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Metrics/CyclomaticComplexity.html",
"title": "Metrics/CyclomaticComplexity",
"description": "Disallows methods with a cyclomatic complexity higher than `MaxComplexity`",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.9.1"
},
"MaxComplexity": {
"type": "number",
"default": 12
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Naming/AccessorMethodName": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/AccessorMethodName.html",
"title": "Naming/AccessorMethodName",
"description": "Makes sure that accessor methods are named properly",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.6.0"
}
}
},
"Naming/AsciiIdentifiers": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/AsciiIdentifiers.html",
"title": "Naming/AsciiIdentifiers",
"description": "Disallows non-ascii characters in identifiers",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.6.0"
},
"IgnoreSymbols": {
"type": "boolean",
"default": false
}
}
},
"Naming/BinaryOperatorParameterName": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/BinaryOperatorParameterName.html",
"title": "Naming/BinaryOperatorParameterName",
"description": "Enforces that certain binary operator methods have their sole parameter name standardized",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.6.0"
},
"ExcludedOperators": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"[]",
"[]?",
"[]=",
"<<",
">>",
"`",
"=~",
"!~"
]
},
"AllowedNames": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"other"
]
}
}
},
"Naming/BlockParameterName": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/BlockParameterName.html",
"title": "Naming/BlockParameterName",
"description": "Disallows non-descriptive block parameter names",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.6.0"
},
"MinNameLength": {
"type": "number",
"default": 3
},
"AllowNamesEndingInNumbers": {
"type": "boolean",
"default": true
},
"AllowedNames": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"a",
"b",
"e",
"i",
"j",
"k",
"v",
"x",
"y",
"k1",
"k2",
"v1",
"v2",
"db",
"ex",
"id",
"io",
"ip",
"op",
"tx",
"wg",
"ws"
]
},
"ForbiddenNames": {
"type": "array",
"items": {
"type": "string"
},
"default": []
}
}
},
"Naming/ConstantNames": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/ConstantNames.html",
"title": "Naming/ConstantNames",
"description": "Enforces constant names to be in screaming case",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.2.0"
}
}
},
"Naming/Filename": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/Filename.html",
"title": "Naming/Filename",
"description": "Enforces file names to be in underscored case",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.6.0"
}
}
},
"Naming/MethodNames": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/MethodNames.html",
"title": "Naming/MethodNames",
"description": "Enforces method names to be in underscored case",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.2.0"
}
}
},
"Naming/PredicateName": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/PredicateName.html",
"title": "Naming/PredicateName",
"description": "Disallows tautological predicate names",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.2.0"
}
}
},
"Naming/QueryBoolMethods": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/QueryBoolMethods.html",
"title": "Naming/QueryBoolMethods",
"description": "Reports boolean properties without the `?` suffix",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.4.0"
}
}
},
"Naming/RescuedExceptionsVariableName": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/RescuedExceptionsVariableName.html",
"title": "Naming/RescuedExceptionsVariableName",
"description": "Makes sure that rescued exceptions variables are named as expected",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.6.0"
},
"AllowedNames": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"e",
"ex",
"exception",
"err",
"error"
]
}
}
},
"Naming/TypeNames": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/TypeNames.html",
"title": "Naming/TypeNames",
"description": "Enforces type names in camelcase manner",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.2.0"
}
}
},
"Naming/VariableNames": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/VariableNames.html",
"title": "Naming/VariableNames",
"description": "Enforces variable names to be in underscored case",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.2.0"
}
}
},
"Performance/AnyAfterFilter": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/AnyAfterFilter.html",
"title": "Performance/AnyAfterFilter",
"description": "Identifies usage of `any?` calls that follow filters",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.8.1"
},
"FilterNames": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"select",
"reject"
]
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Performance/AnyInsteadOfPresent": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/AnyInsteadOfPresent.html",
"title": "Performance/AnyInsteadOfPresent",
"description": "Identifies usage of arg-less `any?` calls",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Performance/ChainedCallWithNoBang": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/ChainedCallWithNoBang.html",
"title": "Performance/ChainedCallWithNoBang",
"description": "Identifies usage of chained calls not utilizing the bang method variants",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.14.0"
},
"CallNames": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"uniq",
"unstable_sort",
"sort",
"sort_by",
"shuffle",
"reverse"
]
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Performance/CompactAfterMap": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/CompactAfterMap.html",
"title": "Performance/CompactAfterMap",
"description": "Identifies usage of `compact` calls that follow `map`",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.14.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Performance/ExcessiveAllocations": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/ExcessiveAllocations.html",
"title": "Performance/ExcessiveAllocations",
"description": "Identifies usage of excessive collection allocations",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.5.0"
},
"CallNames": {
"type": "object",
"properties": {
"codepoints": {
"type": "string",
"default": "each_codepoint"
},
"graphemes": {
"type": "string",
"default": "each_grapheme"
},
"chars": {
"type": "string",
"default": "each_char"
},
"lines": {
"type": "string",
"default": "each_line"
}
}
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Performance/FirstLastAfterFilter": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/FirstLastAfterFilter.html",
"title": "Performance/FirstLastAfterFilter",
"description": "Identifies usage of `first/last/first?/last?` calls that follow filters",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.8.1"
},
"FilterNames": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"select"
]
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Performance/FlattenAfterMap": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/FlattenAfterMap.html",
"title": "Performance/FlattenAfterMap",
"description": "Identifies usage of `flatten` calls that follow `map`",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.14.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Performance/MapInsteadOfBlock": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/MapInsteadOfBlock.html",
"title": "Performance/MapInsteadOfBlock",
"description": "Identifies usage of `sum/product` calls that follow `map`",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.14.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Performance/MinMaxAfterMap": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/MinMaxAfterMap.html",
"title": "Performance/MinMaxAfterMap",
"description": "Identifies usage of `min/max/minmax` calls that follow `map`",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.5.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Performance/SizeAfterFilter": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/SizeAfterFilter.html",
"title": "Performance/SizeAfterFilter",
"description": "Identifies usage of `size` calls that follow filter",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.8.1"
},
"FilterNames": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"select",
"reject"
]
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Performance/TimesMap": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/TimesMap.html",
"title": "Performance/TimesMap",
"description": "Identifies usage of `times.map { ... }.to_a` calls",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Severity": {
"$ref": "#/$defs/Severity",
"default": "Warning"
}
}
},
"Style/ArrayLiteralSyntax": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/ArrayLiteralSyntax.html",
"title": "Style/ArrayLiteralSyntax",
"description": "Encourages the use of `Array(T).new` over `[] of T`",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Enabled": {
"type": "boolean",
"default": false
}
}
},
"Style/CallParentheses": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/CallParentheses.html",
"title": "Style/CallParentheses",
"description": "Enforces usage of parentheses in method calls",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Enabled": {
"type": "boolean",
"default": false
},
"ExcludeTypeDeclarations": {
"type": "boolean",
"default": true
},
"ExcludeHeredocs": {
"type": "boolean",
"default": false
},
"ExcludedToplevelCallNames": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"spawn",
"raise",
"super",
"previous_def",
"exit",
"abort",
"sleep",
"print",
"printf",
"puts",
"p",
"p!",
"pp",
"pp!",
"record",
"class_getter",
"class_getter?",
"class_getter!",
"class_property",
"class_property?",
"class_property!",
"class_setter",
"getter",
"getter?",
"getter!",
"property",
"property?",
"property!",
"setter",
"def_equals_and_hash",
"def_equals",
"def_hash",
"delegate",
"forward_missing_to",
"describe",
"context",
"it",
"pending",
"fail",
"use_json_discriminator"
]
},
"ExcludedCallNames": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"should",
"should_not"
]
}
}
},
"Style/Elsif": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/Elsif.html",
"title": "Style/Elsif",
"description": "Encourages the use of `case/when` syntax over `if/elsif`",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Enabled": {
"type": "boolean",
"default": false
},
"IgnoreSuffix": {
"type": "boolean",
"default": true
},
"MaxBranches": {
"type": "number",
"default": 0
}
}
},
"Style/GuardClause": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/GuardClause.html",
"title": "Style/GuardClause",
"description": "Check for conditionals that can be replaced with guard clauses",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.0.0"
},
"Enabled": {
"type": "boolean",
"default": false
}
}
},
"Style/HashLiteralSyntax": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/HashLiteralSyntax.html",
"title": "Style/HashLiteralSyntax",
"description": "Encourages the use of `Hash(K, V).new` over `{} of K => V`",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Enabled": {
"type": "boolean",
"default": false
}
}
},
"Style/HeredocEscape": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/HeredocEscape.html",
"title": "Style/HeredocEscape",
"description": "Recommends using the heredoc variant that escapes interpolation or control chars in a heredoc body",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
}
}
},
"Style/HeredocIndent": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/HeredocIndent.html",
"title": "Style/HeredocIndent",
"description": "Recommends heredoc bodies are indented consistently",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"IndentBy": {
"type": "number",
"default": 2
},
"BodyAutoDedent": {
"type": "boolean",
"default": true
}
}
},
"Style/IsAFilter": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/IsAFilter.html",
"title": "Style/IsAFilter",
"description": "Identifies usage of `is_a?/nil?` calls within filters",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.14.0"
},
"FilterNames": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"select",
"reject",
"any?",
"all?",
"none?",
"one?"
]
}
}
},
"Style/IsANil": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/IsANil.html",
"title": "Style/IsANil",
"description": "Disallows calls to `is_a?(Nil)` in favor of `nil?`",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.13.0"
}
}
},
"Style/LargeNumbers": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/LargeNumbers.html",
"title": "Style/LargeNumbers",
"description": "Disallows usage of large numbers without underscore",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.2.0"
},
"Enabled": {
"type": "boolean",
"default": false
},
"IntMinDigits": {
"type": "number",
"default": 6
}
}
},
"Style/MultilineCurlyBlock": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/MultilineCurlyBlock.html",
"title": "Style/MultilineCurlyBlock",
"description": "Disallows multi-line blocks using curly block syntax",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
}
}
},
"Style/MultilineStringLiteral": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/MultilineStringLiteral.html",
"title": "Style/MultilineStringLiteral",
"description": "Disallows multiline string literals not using `<<-HEREDOC` markers",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"AllowBackslashSplitStrings": {
"type": "boolean",
"default": true
}
}
},
"Style/NegatedConditionsInUnless": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/NegatedConditionsInUnless.html",
"title": "Style/NegatedConditionsInUnless",
"description": "Disallows negated conditions in `unless`",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.2.0"
}
}
},
"Style/ParenthesesAroundCondition": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/ParenthesesAroundCondition.html",
"title": "Style/ParenthesesAroundCondition",
"description": "Disallows redundant parentheses around control expressions",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.4.0"
},
"ExcludeTernary": {
"type": "boolean",
"default": false
},
"ExcludeMultiline": {
"type": "boolean",
"default": false
},
"AllowSafeAssignment": {
"type": "boolean",
"default": false
}
}
},
"Style/PercentLiteralDelimiters": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/PercentLiteralDelimiters.html",
"title": "Style/PercentLiteralDelimiters",
"description": "Enforces the consistent usage of `%`-literal delimiters",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"DefaultDelimiters": {
"type": [
"string",
"null"
],
"default": "()"
},
"PreferredDelimiters": {
"type": "object",
"properties": {
"%w": {
"type": "string",
"default": "[]"
},
"%i": {
"type": "string",
"default": "[]"
},
"%r": {
"type": "string",
"default": "{}"
}
}
},
"IgnoreLiteralsContainingDelimiters": {
"type": "boolean",
"default": false
}
}
},
"Style/RedundantBegin": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/RedundantBegin.html",
"title": "Style/RedundantBegin",
"description": "Disallows redundant `begin` blocks",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.3.0"
}
}
},
"Style/RedundantNext": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/RedundantNext.html",
"title": "Style/RedundantNext",
"description": "Reports redundant `next` expressions",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.12.0"
},
"AllowMultiNext": {
"type": "boolean",
"default": true
},
"AllowEmptyNext": {
"type": "boolean",
"default": true
}
}
},
"Style/RedundantNilInControlExpression": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/RedundantNilInControlExpression.html",
"title": "Style/RedundantNilInControlExpression",
"description": "Disallows control expressions with `nil` argument",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
}
}
},
"Style/RedundantReturn": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/RedundantReturn.html",
"title": "Style/RedundantReturn",
"description": "Reports redundant `return` expressions",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.9.0"
},
"AllowMultiReturn": {
"type": "boolean",
"default": true
},
"AllowEmptyReturn": {
"type": "boolean",
"default": true
}
}
},
"Style/RedundantSelf": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/RedundantSelf.html",
"title": "Style/RedundantSelf",
"description": "Disallows redundant uses of `self`",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"AllowedMethodNames": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"in?",
"inspect",
"not_nil!"
]
}
}
},
"Style/UnlessElse": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/UnlessElse.html",
"title": "Style/UnlessElse",
"description": "Disallows the use of an `else` block with the `unless`",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.1.0"
}
}
},
"Style/VerboseBlock": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/VerboseBlock.html",
"title": "Style/VerboseBlock",
"description": "Identifies usage of collapsible single expression blocks",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.14.0"
},
"ExcludeMultipleLineBlocks": {
"type": "boolean",
"default": true
},
"ExcludeCallsWithBlock": {
"type": "boolean",
"default": true
},
"ExcludePrefixOperators": {
"type": "boolean",
"default": true
},
"ExcludeOperators": {
"type": "boolean",
"default": true
},
"ExcludeSetters": {
"type": "boolean",
"default": false
},
"MaxLineLength": {
"type": [
"number",
"null"
],
"default": null
},
"MaxLength": {
"type": [
"number",
"null"
],
"default": 50
}
}
},
"Style/VerboseNilType": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/VerboseNilType.html",
"title": "Style/VerboseNilType",
"description": "Enforces consistent naming of `Nil` in type unions",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"ExplicitNil": {
"type": "boolean",
"default": false
}
}
},
"Style/WhileTrue": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/WhileTrue.html",
"title": "Style/WhileTrue",
"description": "Disallows `while` statements with a `true` literal as condition",
"properties": {
"SinceVersion": {
"type": "string",
"default": "0.3.0"
}
}
},
"Typing/MacroCallArgumentTypeRestriction": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Typing/MacroCallArgumentTypeRestriction.html",
"title": "Typing/MacroCallArgumentTypeRestriction",
"description": "Recommends that call arguments to certain macros have type restrictions",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Enabled": {
"type": "boolean",
"default": false
},
"DefaultValue": {
"type": "boolean",
"default": false
},
"MacroNames": {
"type": "array",
"items": {
"type": "string"
},
"default": [
"getter",
"getter?",
"getter!",
"class_getter",
"class_getter?",
"class_getter!",
"setter",
"setter?",
"setter!",
"class_setter",
"class_setter?",
"class_setter!",
"property",
"property?",
"property!",
"class_property",
"class_property?",
"class_property!",
"record"
]
}
}
},
"Typing/MethodParameterTypeRestriction": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Typing/MethodParameterTypeRestriction.html",
"title": "Typing/MethodParameterTypeRestriction",
"description": "Recommends that method parameters have type restrictions",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Enabled": {
"type": "boolean",
"default": false
},
"DefaultValue": {
"type": "boolean",
"default": false
},
"BlockParameters": {
"type": "boolean",
"default": false
},
"PrivateMethods": {
"type": "boolean",
"default": false
},
"ProtectedMethods": {
"type": "boolean",
"default": false
},
"NodocMethods": {
"type": "boolean",
"default": false
}
}
},
"Typing/MethodReturnTypeRestriction": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Typing/MethodReturnTypeRestriction.html",
"title": "Typing/MethodReturnTypeRestriction",
"description": "Recommends that methods have a return type restriction",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Enabled": {
"type": "boolean",
"default": false
},
"PrivateMethods": {
"type": "boolean",
"default": false
},
"ProtectedMethods": {
"type": "boolean",
"default": false
},
"NodocMethods": {
"type": "boolean",
"default": false
}
}
},
"Typing/ProcLiteralReturnTypeRestriction": {
"$ref": "#/$defs/BaseRule",
"$comment": "https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Typing/ProcLiteralReturnTypeRestriction.html",
"title": "Typing/ProcLiteralReturnTypeRestriction",
"description": "Disallows proc literals without return type restriction",
"properties": {
"SinceVersion": {
"type": "string",
"default": "1.7.0"
},
"Enabled": {
"type": "boolean",
"default": false
}
}
}
}
}
================================================
FILE: .dockerignore
================================================
.*
!LICENSE
!Dockerfile
!Makefile
!shard.yml
!src
================================================
FILE: .editorconfig
================================================
[*.cr]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
================================================
FILE: .gitattributes
================================================
* text=auto eol=lf
## Generated files
.ameba.yml.schema.json linguist-generated
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
# Maintain dependencies for GitHub Actions
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily
================================================
FILE: .github/workflows/add-docs-version-to-json-index.yml
================================================
name: Add docs version to JSON index
on:
workflow_call:
inputs:
version:
required: true
type: string
git-branch:
type: string
default: gh-pages
permissions:
contents: write
jobs:
add-version-to-json-index:
concurrency: ci-${{ github.ref }}
runs-on: ubuntu-latest
steps:
- name: Download source
uses: actions/checkout@v6
with:
ref: ${{ inputs.git-branch }}
- name: Update version list
run: |
VERSION="${{ inputs.version }}"
if [[ $VERSION =~ ^[0-9] ]]; then
RELEASED="true"
else
RELEASED="false"
fi
JQ_EXPRESSION="
.versions |= (
if ( . | map(.name) | index(\"${VERSION}\") | not ) then
. += [ {\"name\": \"${VERSION}\", \"url\": \"/ameba/${VERSION}/\", \"released\": ${RELEASED}} ]
else
.
end
)"
cat versions.json | \
jq "$JQ_EXPRESSION" > versions.json.tmp && \
mv versions.json.tmp versions.json
- name: Check for changes
id: git-diff-changes
run: |
git diff --color
if [[ -n "$(git diff --exit-code)" ]]; then
echo "Changes detected."
echo "has-changes=true" >> $GITHUB_OUTPUT
else
echo "No changes detected."
echo "has-changes=false" >> $GITHUB_OUTPUT
fi
- name: Configure Git identity
if: steps.git-diff-changes.outputs.has-changes == 'true'
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Commit changes to Git
if: steps.git-diff-changes.outputs.has-changes == 'true'
run: |
git commit -am "Update versions.json"
git push -u origin HEAD
================================================
FILE: .github/workflows/build-and-deploy-docs.yml
================================================
name: Build and deploy docs
on:
workflow_dispatch:
inputs: &inputs
ref:
required: true
type: string
description: The tag or branch to deploy
version:
type: string
description: Version to deploy
git-branch:
type: string
description: Git branch to deploy to
default: gh-pages
workflow_call:
inputs: *inputs
permissions:
contents: write
jobs:
build-and-deploy:
concurrency: ci-${{ github.ref }}
runs-on: ubuntu-latest
steps:
- name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v5
- name: Install Crystal
uses: crystal-lang/install-crystal@v1
- name: Download source
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref }}
- name: Get ref SHA
id: ref-sha
run: |
REF_SHA="$(git rev-parse --short HEAD)"
echo "REF_SHA: ${REF_SHA}"
echo "sha=${REF_SHA}" >> $GITHUB_OUTPUT
- name: Install dependencies
run: shards install
- name: Build docs
run: |
crystal docs \
--project-version="${{ inputs.version || inputs.ref }}" \
--source-refname="${{ steps.ref-sha.outputs.sha }}" \
--json-config-url="/ameba/versions.json"
- name: Deploy docs 🚀
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: ${{ inputs.git-branch }}
folder: docs
target-folder: ${{ inputs.version || inputs.ref }}
clean: true
add-version-to-json-index:
uses: ./.github/workflows/add-docs-version-to-json-index.yml
with:
git-branch: ${{ inputs.git-branch }}
version: ${{ inputs.version || inputs.ref }}
needs:
- build-and-deploy
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
workflow_dispatch:
push:
branches: [master]
pull_request:
types: [opened, synchronize, reopened]
schedule:
- cron: "0 3 * * 1" # Every monday at 3 AM
permissions:
contents: read
jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
crystal: [latest, nightly]
runs-on: ${{ matrix.os }}
steps:
- name: Set timezone to UTC
uses: szenius/set-timezone@v2.0
- name: Install Crystal
uses: crystal-lang/install-crystal@v1
with:
crystal: ${{ matrix.crystal }}
- name: Download source
uses: actions/checkout@v6
- name: Install dependencies
run: shards install
- name: Install typos-cli
if: matrix.os == 'macos-latest'
run: brew install typos-cli
- name: Run specs
run: make spec
- name: Build ameba binary
run: make build
- name: Run ameba linter
run: make lint
================================================
FILE: .github/workflows/compare-json-schema.yml
================================================
name: Compare JSON Schema
on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- ".github/workflows/compare-json-schema.yml"
- "**/*.cr"
permissions:
contents: read
jobs:
check-for-changes:
runs-on: ubuntu-latest
steps:
- name: Set timezone to UTC
uses: szenius/set-timezone@v2.0
- name: Install Crystal
uses: crystal-lang/install-crystal@v1
- name: Download source
uses: actions/checkout@v6
- name: Install dependencies
run: shards install
- name: Run JSON Schema builder
run: shards run json-schema-builder
- name: Check for changes
run: git diff --color --exit-code
continue-on-error: true
================================================
FILE: .github/workflows/docker-image.yml
================================================
name: Docker image build and deploy
on:
workflow_dispatch:
push:
branches: [master]
release:
types: [published]
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v4
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into ${{ env.REGISTRY }} registry
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v6
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=tag
type=ref,event=branch
type=ref,event=pr
type=sha,format=long
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
# Build and push Docker image with Buildx
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v7
with:
context: .
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
CRFLAGS=-Dpreview_mt --release
platforms: |
linux/amd64
linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
================================================
FILE: .github/workflows/docs.yml
================================================
name: Docs
on:
push:
branches:
- master
tags:
- "v*.*.*"
permissions:
contents: write
jobs:
setup:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.variables.outputs.version }}
ref: ${{ steps.variables.outputs.ref }}
steps:
- name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v5
- name: Set output variables
id: variables
run: |
VERSION="${GITHUB_REF_NAME}"
if [[ $VERSION =~ ^v[0-9].* ]]; then
VERSION=${VERSION#"v"}
fi
echo "VERSION: ${VERSION}"
echo "version=${VERSION}" >> $GITHUB_OUTPUT
REF="${GITHUB_REF_POINT}"
echo "REF: ${REF}"
echo "ref=${REF}" >> $GITHUB_OUTPUT
build-and-deploy:
uses: ./.github/workflows/build-and-deploy-docs.yml
with:
version: ${{ needs.setup.outputs.version }}
ref: ${{ needs.setup.outputs.ref }}
needs:
- setup
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
push:
tags:
- "v*.*.*"
permissions:
contents: write
env:
GH_TOKEN: ${{ github.token }}
jobs:
draft:
runs-on: ubuntu-latest
steps:
- run: gh release -R ${{ github.repository }} create ${{ github.ref_name }} --draft --generate-notes
linux-musl:
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, ubuntu-24.04-arm]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- run: docker run --rm -v $PWD:/mnt -w /mnt crystallang/crystal:latest-alpine shards build ameba --static -Dpreview_mt --release
- run: tar zcf ameba-${{ github.ref_name }}-$(uname -m)-linux-musl.tar.gz -C bin ameba
- run: gh release -R ${{ github.repository }} upload ${{ github.ref_name }} ameba-${{ github.ref_name }}-$(uname -m)-linux-musl.tar.gz
darwin:
strategy:
fail-fast: false
matrix:
include:
- {os: macos-15, arch: aarch64}
- {os: macos-15-intel, arch: x86_64}
runs-on: ${{ matrix.os }}
steps:
- uses: crystal-lang/install-crystal@v1
- uses: actions/checkout@v6
- run: shards build ameba -Dpreview_mt --release
- run: tar zcf ameba-${{ github.ref_name }}-${{ matrix.arch }}-darwin.tar.gz -C bin ameba ameba.dwarf
- run: gh release -R ${{ github.repository }} upload ${{ github.ref_name }} ameba-${{ github.ref_name }}-${{ matrix.arch }}-darwin.tar.gz
x86_64-windows-msvc:
runs-on: windows-2025
steps:
- uses: crystal-lang/install-crystal@v1
- uses: actions/checkout@v6
- run: shards build ameba --static -Dpreview_mt --release
- run: cd bin; 7z a ../ameba-${{ github.ref_name }}-x86_64-windows-msvc.zip ameba.exe ameba.pdb
- run: gh release -R ${{ github.repository }} upload ${{ github.ref_name }} ameba-${{ github.ref_name }}-x86_64-windows-msvc.zip
================================================
FILE: .github/workflows/typos.yml
================================================
name: Spell checker
on:
push:
branches: [master]
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
jobs:
typos:
runs-on: ubuntu-latest
steps:
- name: Download source
uses: actions/checkout@v6
- name: Run `typos` spell checker
uses: crate-ci/typos@v1
================================================
FILE: .github/workflows/update-json-schema.yml
================================================
name: Update JSON Schema
on:
workflow_dispatch:
push:
branches: [master]
paths:
- ".github/workflows/update-json-schema.yml"
- "shard.yml"
- "**/*.cr"
permissions:
contents: write
jobs:
update-json-schema:
concurrency: ci-${{ github.ref }}
runs-on: ubuntu-latest
steps:
- name: Set timezone to UTC
uses: szenius/set-timezone@v2.0
- name: Install Crystal
uses: crystal-lang/install-crystal@v1
- name: Download source
uses: actions/checkout@v6
with:
ssh-key: ${{ secrets.MASTER_PUSHER_SSH_KEY }}
- name: Install dependencies
run: shards install
- name: Run JSON Schema builder
run: shards run json-schema-builder
- name: Check for changes
id: git-diff-changes
run: |
if [[ -n "$(git diff --exit-code)" ]]; then
echo "Changes detected."
echo "has-changes=true" >> $GITHUB_OUTPUT
else
echo "No changes detected."
echo "has-changes=false" >> $GITHUB_OUTPUT
fi
- name: Configure Git identity
if: steps.git-diff-changes.outputs.has-changes == 'true'
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Commit and push changes
if: steps.git-diff-changes.outputs.has-changes == 'true'
run: |
git commit -am "Update JSON Schema"
git push -u origin HEAD
================================================
FILE: .gitignore
================================================
# Documentation
/docs/
# Installed shards
/lib/
# Built binaries
/bin/**/*
!/bin/ameba.cr
# Libraries don't need dependency lock
# Dependencies will be locked in application that uses them
/shard.lock
# Workspace settings used by common text-editors
/.vscode
/.zed
================================================
FILE: .typos.toml
================================================
[default]
extend-ignore-re = [
# numeric literals
'0x[0-9a-fA-F_\.\+]+([fiu](8|16|32|64|128))?',
'\\u\{[0-9a-fA-F]+\}',
# fixed test values
'[Ff][Oo][Oo]+',
# words including a number are likely some kind of identifier
"[a-zA-Z]+[0-9]+",
]
[files]
extend-exclude = [
# git and dependencies
".git/**",
"lib/**",
# individual files to exclude
"spec/ameba/rule/lint/typos_spec.cr",
]
================================================
FILE: Dockerfile
================================================
FROM alpine:edge AS builder
ARG CRFLAGS="-Dpreview_mt"
RUN apk add --update crystal shards yaml-dev musl-dev make
RUN mkdir /ameba
WORKDIR /ameba
COPY . /ameba/
RUN crystal -v
RUN make clean && make CRFLAGS="$CRFLAGS"
FROM alpine:latest
RUN apk add --update yaml pcre2 gc libevent libgcc
RUN mkdir /src
WORKDIR /src
COPY --from=builder /ameba/bin/ameba /usr/bin/
RUN ameba -v
ENTRYPOINT [ "/usr/bin/ameba" ]
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2018-2020 Vitalii Elenhaupt
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: Makefile
================================================
.POSIX:
all:
# Recipes
## Build ameba
## $ make
##
## Run tests
## $ make test
##
## Install ameba
## $ sudo make install
-include Makefile.local # for optional local options
BUILD_TARGET := bin/ameba
DESTDIR ?= ## Install destination dir
PREFIX ?= /usr/local## Install path prefix
BINDIR ?= $(DESTDIR)$(PREFIX)/bin
# The crystal command to use
CRYSTAL_BIN ?= crystal
# The shards command to use
SHARDS_BIN ?= shards
# The install command to use
INSTALL_BIN ?= /usr/bin/install
CRFLAGS ?= -Dpreview_mt
SRC_SOURCES := $(shell find src -name '*.cr' 2>/dev/null)
.PHONY: all
all: build
.PHONY: build
build: ## Build the application binary
build: $(BUILD_TARGET)
$(BUILD_TARGET): $(SRC_SOURCES)
$(SHARDS_BIN) build ameba $(CRFLAGS)
.PHONY: docs
docs: ## Generate API docs
docs: $(SRC_SOURCES)
$(CRYSTAL_BIN) docs
.PHONY: spec
spec: ## Run the spec suite
spec:
$(CRYSTAL_BIN) spec
.PHONY: schema
schema: ## Build the latest schema
schema:
$(SHARDS_BIN) run schema
.PHONY: lint
lint: ## Run ameba on its own code base
lint: $(BUILD_TARGET)
$(BUILD_TARGET)
.PHONY: test
test: ## Run the spec suite and linter
test: spec lint
.PHONY: clean
clean: ## Remove application binary and API docs
clean:
@rm -f "$(BUILD_TARGET)" "$(BUILD_TARGET).dwarf"
@rm -rf docs
.PHONY: install
install: ## Install application binary into $DESTDIR
install: $(BUILD_TARGET)
mkdir -p "$(BINDIR)"
$(INSTALL_BIN) -m 0755 "$(BUILD_TARGET)" "$(BINDIR)/ameba"
.PHONY: help
help: ## Show this help
@printf '\033[34mtargets:\033[0m\n'
@grep -hE '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) |\
sort |\
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'
@echo
@printf '\033[34moptional variables:\033[0m\n'
@grep -hE '^[a-zA-Z_-]+ \?=.*?## .*$$' $(MAKEFILE_LIST) |\
sort |\
awk 'BEGIN {FS = " \\?=.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'
@echo
@printf '\033[34mrecipes:\033[0m\n'
@grep -hE '^##.*$$' $(MAKEFILE_LIST) |\
awk 'BEGIN {FS = "## "}; /^## [a-zA-Z_-]/ {printf " \033[36m%s\033[0m\n", $$2}; /^## / {printf " %s\n", $$2}'
================================================
FILE: README.md
================================================
<p align="center">
<img src="https://raw.githubusercontent.com/veelenga/bin/master/ameba/logo.png" width="800">
<h3 align="center">Ameba</h3>
<p align="center">Code style linter for Crystal<p>
<p align="center">
<sup>
<i>(a single-celled animal that catches food and moves about by extending fingerlike projections of protoplasm)</i>
</sup>
</p>
<p align="center">
<a href="https://github.com/crystal-ameba/ameba/actions/workflows/ci.yml"><img src="https://github.com/crystal-ameba/ameba/actions/workflows/ci.yml/badge.svg"></a>
<a href="https://github.com/crystal-ameba/ameba/releases"><img src="https://img.shields.io/github/release/crystal-ameba/ameba.svg?maxAge=360"></a>
<a href="https://github.com/crystal-ameba/ameba/blob/master/LICENSE"><img src="https://img.shields.io/github/license/crystal-ameba/ameba.svg"></a>
</p>
</p>
- [About](#about)
- [Usage](#usage)
- [Watch a tutorial](#watch-a-tutorial)
- [Autocorrection](#autocorrection)
- [Explain issues](#explain-issues)
- [Run in parallel](#run-in-parallel)
- [Installation](#installation)
- [As a project dependency:](#as-a-project-dependency)
- [OS X](#os-x)
- [Docker](#docker)
- [From sources](#from-sources)
- [Configuration](#configuration)
- [Sources](#sources)
- [Rules](#rules)
- [Inline disabling](#inline-disabling)
- [Editors \& integrations](#editors--integrations)
- [Credits \& inspirations](#credits--inspirations)
- [Contributors](#contributors)
## About
Ameba is a static code analysis tool for the Crystal language.
It enforces a consistent [Crystal code style](https://crystal-lang.org/reference/conventions/coding_style.html),
also catches code smells and wrong code constructions.
See also [Roadmap](https://github.com/crystal-ameba/ameba/wiki).
## Usage
Run `ameba` binary within your project directory to catch code issues:
```sh
$ ameba
Inspecting 107 files
...............F.....................FF....................................................................
src/ameba/formatter/flycheck_formatter.cr:6:37
[W] Lint/UnusedArgument: Unused argument `location`. If it's necessary, use `_` as an argument name to indicate that it won't be used.
> source.issues.each do |issue, location|
^
src/ameba/formatter/base_formatter.cr:16:14
[W] Lint/UselessAssign: Useless assignment to variable `s`
> return s += issues.size
^
src/ameba/formatter/base_formatter.cr:16:7 [Correctable]
[C] Style/RedundantReturn: Redundant `return` detected
> return s += issues.size
^---------------------^
Finished in 389.45 milliseconds
107 inspected, 3 failures
```
### Watch a tutorial
<a href="https://luckycasts.com/videos/ameba"><img src="https://i.imgur.com/uOETQlM.png" title="Write Better Crystal Code with the Ameba Shard" width="500" /></a>
[🎬 Watch the LuckyCast showing how to use Ameba](https://luckycasts.com/videos/ameba)
### Autocorrection
Rules that are marked as `[Correctable]` in the output can be automatically corrected using `--fix` flag:
```sh
$ ameba --fix
```
### Explain issues
Ameba allows you to dig deeper into an issue, by showing you details about the issue
and the reasoning by it being reported.
To be convenient, you can just copy-paste the `PATH:line:column` string from the
report and paste behind the `ameba` command to check it out.
```sh
$ ameba crystal/command/format.cr:26:83 # show explanation for the issue
$ ameba --explain crystal/command/format.cr:26:83 # same thing
```
### Run in parallel
Some quick benchmark results measured while running Ameba on Crystal repo:
```sh
$ CRYSTAL_WORKERS=1 ameba #=> 29.11 seconds
$ CRYSTAL_WORKERS=2 ameba #=> 19.49 seconds
$ CRYSTAL_WORKERS=4 ameba #=> 13.48 seconds
$ CRYSTAL_WORKERS=8 ameba #=> 10.14 seconds
```
## Installation
### As a project dependency
Add this to your application's `shard.yml`:
```yaml
development_dependencies:
ameba:
github: crystal-ameba/ameba
```
To prioritize runtime performance over compilation time, you can add `ameba`
target to the `shard.yml` file:
```yaml
targets:
ameba:
main: lib/ameba/bin/ameba.cr
```
And then run:
```sh
$ shards build ameba -Dpreview_mt
```
Alternatively, skip adding `ameba` target and use `crystal build` command directly:
```sh
$ crystal build -Dpreview_mt -o bin/ameba lib/ameba/bin/ameba.cr
```
Both of these will result in a compiled binary placed under `bin/ameba` path.
You can also just run the `lib/ameba/bin/ameba.cr` file, compiling it on the fly,
which is the slowest option:
```sh
$ lib/ameba/bin/ameba.cr
```
### OS X
```sh
$ brew tap crystal-ameba/ameba
$ brew install ameba
```
### Docker
Build the image:
```sh
$ docker build -t ghcr.io/crystal-ameba/ameba .
```
To use the resulting image on a local source folder, mount the current (or target) directory into `/src`:
```sh
$ docker run -v $(pwd):/src ghcr.io/crystal-ameba/ameba
```
Also available on GitHub: https://github.com/crystal-ameba/ameba/pkgs/container/ameba
### From sources
```sh
$ git clone https://github.com/crystal-ameba/ameba && cd ameba
$ make install
```
## Configuration
Default configuration file is `.ameba.yml`.
It allows to configure rule properties, disable specific rules and exclude sources from the rules.
Generate new file by running `ameba --gen-config`.
### Sources
**List of sources to run Ameba on can be configured globally via:**
- `Globs` section - an array of wildcards (or paths) to include to the
inspection. Defaults to `%w[**/*.cr **/*.ecr]`, meaning it includes all project
files with `*.cr` and `*.ecr` extensions.
- `Excluded` section - an array of wildcards (or paths) to exclude from the
source list defined by `Globs`. Defaults to `%w[lib]`, meaning it excludes the
`lib` folder.
In this example we define default globs and exclude `lib` and `src/compiler` folders:
``` yaml
Globs:
- "**/*.cr"
- "**/*.ecr"
Excluded:
- lib
- src/compiler
```
**Specific sources can be excluded at rule level**:
``` yaml
Style/RedundantBegin:
Excluded:
- src/server/processor.cr
- src/server/api.cr
```
### Rules
One or more rules, or a one or more group of rules can be included or excluded
via command line arguments:
```sh
$ ameba --only Lint/Syntax # runs only Lint/Syntax rule
$ ameba --only Style,Lint # runs only rules from Style and Lint groups
$ ameba --except Lint/Syntax # runs all rules except Lint/Syntax
$ ameba --except Style,Lint # runs all rules except rules in Style and Lint groups
```
Or through the configuration file:
``` yaml
Style/RedundantBegin:
Enabled: false
```
### Inline disabling
One or more rules or one or more group of rules can be disabled using inline directives:
```crystal
# ameba:disable Style/LargeNumbers
time = Time.epoch(1483859302)
time = Time.epoch(1483859302) # ameba:disable Style/LargeNumbers, Lint/UselessAssign
time = Time.epoch(1483859302) # ameba:disable Style, Lint
```
## Editors & integrations
- Vim: [vim-crystal](https://github.com/rhysd/vim-crystal), [Ale](https://github.com/w0rp/ale)
- Emacs: [ameba.el](https://github.com/crystal-ameba/ameba.el)
- Sublime Text: [Sublime Linter Ameba](https://github.com/epergo/SublimeLinter-contrib-ameba)
- VSCode: [vscode-crystal-ameba](https://github.com/crystal-ameba/vscode-crystal-ameba)
- Codacy: [codacy-ameba](https://github.com/codacy/codacy-ameba)
- GitHub Actions: [github-action](https://github.com/crystal-ameba/github-action)
## Credits & inspirations
- [Crystal Language](https://crystal-lang.org)
- [Rubocop](https://rubocop.org)
- [Credo](http://credo-ci.org)
- [Dogma](https://github.com/lpil/dogma)
## Contributors
- [veelenga](https://github.com/veelenga) Vitalii Elenhaupt - creator, maintainer
- [Sija](https://github.com/Sija) Sijawusz Pur Rahnama - contributor, maintainer
================================================
FILE: bench/check_sources.cr
================================================
require "../src/ameba"
require "benchmark"
private def get_files(n)
Dir["src/**/*.cr"].first(n)
end
puts "== Compare:"
Benchmark.ips do |x|
[
1,
3,
5,
10,
20,
30,
40,
].each do |n| # ameba:disable Naming/BlockParameterName
config = Ameba::Config.load
config.formatter = Ameba::Formatter::BaseFormatter.new
config.globs = get_files(n)
s = n == 1 ? "" : "s"
x.report("#{n} source#{s}") { Ameba.run config }
end
end
puts "== Measure:"
config = Ameba::Config.load
config.formatter = Ameba::Formatter::BaseFormatter.new
puts Benchmark.measure { Ameba.run config }
================================================
FILE: bin/ameba.cr
================================================
#!/usr/bin/env crystal
# Require ameba extensions here which are added as project dependencies.
# Example:
#
# require "ameba-performance"
# Require ameba cli which starts the inspection.
require "ameba/cli"
================================================
FILE: shard.yml
================================================
name: ameba
version: 1.7.0-dev
authors:
- Vitalii Elenhaupt <velenhaupt@gmail.com>
- Sijawusz Pur Rahnama <sija@sija.pl>
targets:
ameba:
main: src/cli.cr
json-schema-builder:
main: src/json-schema-builder.cr
crystal: ~> 1.19
license: MIT
================================================
FILE: spec/ameba/ast/flow_expression_spec.cr
================================================
require "../../spec_helper"
module Ameba::AST
describe FlowExpression do
describe "#initialize" do
it "creates a new flow expression" do
node = as_node("return 22")
flow_expression = FlowExpression.new node, false
flow_expression.node.should_not be_nil
flow_expression.in_loop?.should be_false
end
describe "#delegation" do
it "delegates to_s to @node" do
node = as_node("return 22")
flow_expression = FlowExpression.new node, false
flow_expression.to_s.should eq node.to_s
end
it "delegates locations to @node" do
node = as_node("break if true")
flow_expression = FlowExpression.new node, false
flow_expression.location.should eq node.location
flow_expression.end_location.should eq node.end_location
end
end
describe "#unreachable_nodes" do
it "returns unreachable nodes" do
nodes = as_nodes <<-CRYSTAL
def foobar
return
a = 1
a = 2
end
CRYSTAL
node = nodes.expressions_nodes.first
flow_expression = FlowExpression.new node, false
flow_expression.unreachable_nodes.should eq nodes.assign_nodes
end
it "returns nil if there is no unreachable node after loop" do
nodes = as_nodes <<-CRYSTAL
def run
idx = items.size - 1
while 0 <= idx
return
end
puts "foo"
end
CRYSTAL
node = nodes.expressions_nodes.first
flow_expression = FlowExpression.new node, false
flow_expression.unreachable_nodes.empty?.should be_true
end
it "returns nil if there is no unreachable node" do
nodes = as_nodes <<-CRYSTAL
def foobar
a = 1
return a
end
CRYSTAL
node = nodes.expressions_nodes.first
flow_expression = FlowExpression.new node, false
flow_expression.unreachable_nodes.empty?.should be_true
end
end
end
end
end
================================================
FILE: spec/ameba/ast/liveness_analyzer_spec.cr
================================================
require "../../spec_helper"
private def scopes_for(code)
rule = Ameba::ScopeRule.new
source = Ameba::Source.new(code)
Ameba::AST::ScopeVisitor.new(rule, source)
rule.scopes
end
private def def_scope(code)
scopes_for(code).find! { |scope| scope.node.is_a?(Crystal::Def) }
end
private def top_scope(code)
scopes_for(code).find! { |scope| scope.node.is_a?(Crystal::Expressions) }
end
private def block_scope(code)
scopes_for(code).find! { |scope| scope.node.is_a?(Crystal::Block) }
end
private def dead_store_names(scope)
Ameba::AST::LivenessAnalyzer.new(scope).dead_stores.map(&.variable.name)
end
module Ameba::AST
describe LivenessAnalyzer do
context "basic assignments" do
it "detects unused assignment as dead store" do
scope = def_scope <<-CRYSTAL
def foo
a = 1
end
CRYSTAL
dead_store_names(scope).should eq ["a"]
end
it "does not report assignment that is used" do
scope = def_scope <<-CRYSTAL
def foo
a = 1
a
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "detects first assignment as dead when overwritten before use" do
scope = def_scope <<-CRYSTAL
def foo
a = 1
a = 2
a
end
CRYSTAL
dead_store_names(scope).should eq ["a"]
end
it "detects all assignments as dead when none are used" do
scope = def_scope <<-CRYSTAL
def foo
a = 1
a = 2
end
CRYSTAL
dead_store_names(scope).should eq ["a", "a"]
end
it "does not report assignment used in a condition" do
scope = def_scope <<-CRYSTAL
def foo
a = 1
if a
nil
end
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "reports second assignment when value is not used" do
scope = def_scope <<-CRYSTAL
def foo
a = 1
a = a + 1
end
CRYSTAL
dead_store_names(scope).should eq ["a"]
end
it "does not report assignment used in another assignment" do
scope = def_scope <<-CRYSTAL
def foo
if f = get_something
@f = f
end
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "reports last assignment when not used after reassignment" do
scope = def_scope <<-CRYSTAL
def foo
a = 1
puts a
a = 2
end
CRYSTAL
dead_store_names(scope).should eq ["a"]
end
end
context "op assignments" do
it "does not report op-assign when result is used" do
scope = def_scope <<-CRYSTAL
def foo
a = 1
a += 1
a
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "reports op-assign when result is not used" do
scope = def_scope <<-CRYSTAL
def foo
a = 1
a += 1
end
CRYSTAL
dead_store_names(scope).should eq ["a"]
end
it "does not report chained op-assigns when result is used" do
scope = def_scope <<-CRYSTAL
def foo
a = 1
a += 1
a += 1
a = a + 1
a
end
CRYSTAL
dead_store_names(scope).should be_empty
end
end
context "if/unless branches" do
it "reports initial assignment as dead when overwritten in both branches" do
scope = def_scope <<-CRYSTAL
def foo
a = 0
if something
a = 1
else
a = 2
end
a
end
CRYSTAL
dead_store_names(scope).should eq ["a"]
end
it "does not report when assigned in one branch and used after" do
scope = def_scope <<-CRYSTAL
def foo
a = 0
if something
a = 1
end
a
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "does not report when assigned in one branch with else nil and used after" do
scope = def_scope <<-CRYSTAL
def foo
a = 0
if something
a = 1
else
nil
end
a
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "reports useless assignment in branch when not used after" do
scope = def_scope <<-CRYSTAL
def foo(a)
if a
a = 2
end
end
CRYSTAL
dead_store_names(scope).should eq ["a"]
end
it "reports first dead assignment in branch when overwritten" do
scope = def_scope <<-CRYSTAL
def foo(a)
a = 1
if a
a = 2
a = 3
end
a
end
CRYSTAL
dead_store_names(scope).should eq ["a"]
end
it "reports initial assignment as dead when overwritten in all branches" do
scope = def_scope <<-CRYSTAL
def foo
has_newline = false
if something
do_something unless false
has_newline = false
else
do_something if true
has_newline = true
end
has_newline
end
CRYSTAL
dead_store_names(scope).should eq ["has_newline"]
end
it "does not report unless with consumed branches" do
scope = def_scope <<-CRYSTAL
def foo
a = 0
unless something
a = 1
else
a = 2
end
a
end
CRYSTAL
dead_store_names(scope).should eq ["a"]
end
it "reports dead assignment in unless branch" do
scope = def_scope <<-CRYSTAL
def foo
a = 0
unless something
a = 1
a = 2
else
a = 2
end
a
end
CRYSTAL
dead_store_names(scope).should eq ["a", "a"]
end
it "does not report one-line if assignment used after" do
scope = def_scope <<-CRYSTAL
def foo
a = 0
a = 1 if something
a
end
CRYSTAL
dead_store_names(scope).should be_empty
end
end
context "while loops" do
it "does not report assignment used across iterations" do
scope = def_scope <<-CRYSTAL
def foo(a)
while a < 10
a = a + 1
end
a
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "reports assignment not used outside loop" do
scope = def_scope <<-CRYSTAL
def foo(a)
while a < 10
b = a
end
end
CRYSTAL
dead_store_names(scope).should eq ["b"]
end
it "does not report assignment used in loop with accumulator" do
scope = def_scope <<-CRYSTAL
def foo
a = 3
result = 0
while result < 10
result += a
a = a + 1
end
result
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "does not report parameter assignment used in loop" do
scope = def_scope <<-CRYSTAL
def foo(a)
result = 0
while result < 10
result += a
a = a + 1
end
result
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "does not report assignment in loop with inner branch" do
scope = def_scope <<-CRYSTAL
def foo(a)
result = 0
while result < 10
result += a
if result > 0
a = a + 1
else
a = 3
end
end
result
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "handles branch with blank node in loop" do
scope = def_scope <<-CRYSTAL
def foo
count = 0
while true
break if count == 1
case something
when :any
else
:anything_else
end
count += 1
end
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "does not report assignment used after break" do
scope = def_scope <<-CRYSTAL
def foo
found = false
while true
if something
found = true
break
end
end
found
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "does not report assignment before next used in subsequent iteration" do
scope = def_scope <<-CRYSTAL
def foo
atomic = parse_atomic
while true
if @token.instance_var?
atomic = parse_ivar(atomic)
next
end
break
end
atomic
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "does not report assignment used after break in nested loops" do
scope = def_scope <<-CRYSTAL
def foo
found = false
while outer_cond
while inner_cond
if something
found = true
break
end
end
end
found
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "does not report assignment inside conditional break" do
scope = def_scope <<-CRYSTAL
def foo
options = 0
while true
if done?
options = compute_options
break
end
process
end
options
end
CRYSTAL
dead_store_names(scope).should be_empty
end
end
context "until loops" do
it "does not report assignment used across until iterations" do
scope = def_scope <<-CRYSTAL
def foo(a)
until a > 10
a = a + 1
end
a
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "reports useless assignment in until loop" do
scope = def_scope <<-CRYSTAL
def foo(a)
until a > 10
b = a + 1
end
end
CRYSTAL
dead_store_names(scope).should eq ["b"]
end
end
context "exception handlers" do
it "does not report assignment used in rescue" do
scope = def_scope <<-CRYSTAL
def foo(a)
a = 2
rescue
a
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "does not report assignment used in ensure" do
scope = def_scope <<-CRYSTAL
def foo(a)
a = 2
ensure
a
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "does not report assignment used in else" do
scope = def_scope <<-CRYSTAL
def foo(a)
a = 2
rescue
else
a
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "reports useless assignment in rescue" do
scope = def_scope <<-CRYSTAL
def foo(a)
rescue
a = 2
end
CRYSTAL
dead_store_names(scope).should eq ["a"]
end
it "does not report assignment used in rescue when body has break" do
scope = block_scope <<-CRYSTAL
3.times do
start = 1
begin
perform_foo
break
rescue
start
end
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "does not report assignment used in rescue when body has return" do
scope = def_scope <<-CRYSTAL
def foo
start = 1
begin
perform_foo
return
rescue
start
end
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "does not report assignment used in rescue when body has next" do
scope = block_scope <<-CRYSTAL
3.times do
start = 1
begin
perform_foo
next
rescue
start
end
end
CRYSTAL
dead_store_names(scope).should be_empty
end
end
context "binary operators" do
it "does not report when both sides of && are used" do
scope = def_scope <<-CRYSTAL
def foo(a)
(a = 1) && (b = 1)
a + b
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "reports unused side of ||" do
scope = def_scope <<-CRYSTAL
def foo(a)
(a = 1) || (b = 1)
a
end
CRYSTAL
dead_store_names(scope).should eq ["b"]
end
end
context "case" do
it "does not report when used after case" do
scope = def_scope <<-CRYSTAL
def foo(a)
case a
when /foo/
a = 1
when /bar/
a = 2
end
puts a
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "reports when not used after case" do
scope = def_scope <<-CRYSTAL
def foo(a)
case a
when /foo/
a = 1
when /bar/
a = 2
end
end
CRYSTAL
dead_store_names(scope).should eq ["a", "a"]
end
it "does not report assignment used in case condition" do
scope = def_scope <<-CRYSTAL
def foo
a = 2
case a
when /foo/
end
end
CRYSTAL
dead_store_names(scope).should be_empty
end
context "when" do
it "does not report when assignment in when condition is used" do
scope = def_scope <<-CRYSTAL
def foo(a)
case
when a = foo_call
when a = bar_call
end
puts a
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "reports when assignment in when condition is not used" do
scope = def_scope <<-CRYSTAL
def foo(a)
case
when a = foo_call
when a = bar_call
end
end
CRYSTAL
dead_store_names(scope).should eq ["a", "a"]
end
end
end
context "multi assignments" do
it "does not report when all targets are used" do
scope = def_scope <<-CRYSTAL
def foo
a, b = {1, 2}
a + b
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "reports unused multi-assign target" do
scope = def_scope <<-CRYSTAL
def foo
a, b = {1, 2}
a
end
CRYSTAL
dead_store_names(scope).should eq ["b"]
end
it "reports all unused multi-assign targets" do
scope = def_scope <<-CRYSTAL
def foo
a, b = {1, 2}
end
CRYSTAL
dead_store_names(scope).should eq ["b", "a"]
end
it "reports reassigned multi-assign targets" do
scope = def_scope <<-CRYSTAL
def foo
a, b = {1, 2}
a, b = {3, 4}
end
CRYSTAL
dead_store_names(scope).should eq ["b", "a", "b", "a"]
end
it "reports multi-assign target overwritten at loop start" do
scope = def_scope <<-CRYSTAL
def foo
while true
word = get_word
if (word & 0xFF) == 0
word, success = compare_and_set(word, word + 1)
return if success
end
end
end
CRYSTAL
dead_store_names(scope).should eq ["word"]
end
it "does not report multi-assign target used in next loop iteration" do
scope = def_scope <<-CRYSTAL
def foo
while true
word = get_word
if (word & 0xFF) == 0
word, success = compare_and_set(word, word + 1)
if success
return
end
else
puts word
end
end
word
end
CRYSTAL
dead_store_names(scope).should be_empty
end
end
context "top level scope" do
it "detects dead stores at top level" do
scope = top_scope <<-CRYSTAL
a = 1
a = 2
CRYSTAL
dead_store_names(scope).should eq ["a", "a"]
end
it "does not report referenced top-level assignments" do
scope = top_scope <<-CRYSTAL
a = 1
a += 1
a
CRYSTAL
dead_store_names(scope).should be_empty
end
end
context "type declarations" do
it "does not report unused type declaration without value" do
scope = def_scope <<-CRYSTAL
def foo
a : String?
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "reports unused type declaration with value" do
scope = def_scope <<-CRYSTAL
def foo
a : String? = "foo"
end
CRYSTAL
dead_store_names(scope).should eq ["a"]
end
it "does not report used type declaration" do
scope = def_scope <<-CRYSTAL
def foo
a : String?
a
end
CRYSTAL
dead_store_names(scope).should be_empty
end
end
context "uninitialized" do
it "reports unused uninitialized assignment" do
scope = def_scope <<-CRYSTAL
def foo
a = uninitialized UInt8
end
CRYSTAL
dead_store_names(scope).should eq ["a"]
end
it "does not report used uninitialized assignment" do
scope = def_scope <<-CRYSTAL
def foo
a = uninitialized UInt8
a
end
CRYSTAL
dead_store_names(scope).should be_empty
end
end
context "super and previous_def" do
it "treats bare super as reading all arguments" do
scope = def_scope <<-CRYSTAL
def foo(a, b)
a = super
a
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "treats bare previous_def as reading all arguments" do
scope = def_scope <<-CRYSTAL
def foo(a)
a = previous_def
a
end
CRYSTAL
dead_store_names(scope).should be_empty
end
it "does not treat super() with parens as reading arguments" do
scope = def_scope <<-CRYSTAL
def foo(a)
b = super()
end
CRYSTAL
dead_store_names(scope).should eq ["b"]
end
it "does not treat super with explicit args as reading all arguments" do
scope = def_scope <<-CRYSTAL
def foo(a)
b = super(1)
end
CRYSTAL
dead_store_names(scope).should eq ["b"]
end
end
end
end
================================================
FILE: spec/ameba/ast/scope_spec.cr
================================================
require "../../spec_helper"
module Ameba::AST
describe Scope do
describe "#initialize" do
source = "a = 2"
it "assigns outer scope" do
root = Scope.new as_node(source)
child = Scope.new as_node(source), root
child.outer_scope.should_not be_nil
end
it "assigns node" do
scope = Scope.new as_node(source)
scope.node.should_not be_nil
end
end
end
describe "delegation" do
it "delegates to_s to node" do
node = as_node("def foo; end")
scope = Scope.new node
scope.to_s.should eq node.to_s
end
it "delegates locations to node" do
node = as_node("def foo; end")
scope = Scope.new node
scope.location.should eq node.location
scope.end_location.should eq node.end_location
end
end
describe "#references" do
it "can return an empty list of references" do
scope = Scope.new as_node("")
scope.references.should be_empty
end
it "allows to add variable references" do
scope = Scope.new as_node("")
nodes = as_nodes "a = 2"
scope.references << Reference.new(nodes.var_nodes.first, scope)
scope.references.size.should eq 1
end
end
describe "#references?" do
it "returns true if current scope references variable" do
nodes = as_nodes <<-CRYSTAL
def method
a = 2
block do
3.times { |i| a = a + i }
end
end
CRYSTAL
var_node = nodes.var_nodes.first
scope = Scope.new nodes.def_nodes.first
scope.add_variable(var_node)
scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope)
variable = Variable.new(var_node, scope)
variable.reference(nodes.var_nodes.first, scope.inner_scopes.first)
scope.references?(variable).should be_true
end
it "returns false if inner scopes are not checked" do
nodes = as_nodes <<-CRYSTAL
def method
a = 2
block do
3.times { |i| a = a + i }
end
end
CRYSTAL
var_node = nodes.var_nodes.first
scope = Scope.new nodes.def_nodes.first
scope.add_variable(var_node)
scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope)
variable = Variable.new(var_node, scope)
variable.reference(nodes.var_nodes.first, scope.inner_scopes.first)
scope.references?(variable, check_inner_scopes: false).should be_false
end
it "returns false if current scope does not reference variable" do
nodes = as_nodes <<-CRYSTAL
def method
a = 2
block do
b = 3
3.times { |i| b = b + i }
end
end
CRYSTAL
var_node = nodes.var_nodes.first
scope = Scope.new nodes.def_nodes.first
scope.add_variable(var_node)
scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope)
variable = Variable.new(var_node, scope)
scope.inner_scopes.first.references?(variable).should be_false
end
end
describe "#add_variable" do
it "adds a new variable to the scope" do
scope = Scope.new as_node("")
scope.add_variable(Crystal::Var.new "foo")
scope.variables.empty?.should be_false
end
end
describe "#find_variable" do
it "returns the variable in the scope by name" do
scope = Scope.new as_node("foo = 1")
scope.add_variable(Crystal::Var.new "foo")
scope.find_variable("foo").should_not be_nil
end
it "returns nil if variable not exist in this scope" do
scope = Scope.new as_node("foo = 1")
scope.find_variable("bar").should be_nil
end
end
describe "#assign_variable" do
it "creates a new assignment" do
scope = Scope.new as_node("foo = 1")
scope.add_variable(Crystal::Var.new "foo")
scope.assign_variable("foo", Crystal::Var.new "foo")
var = scope.find_variable("foo").should_not be_nil
var.assignments.size.should eq 1
end
it "does not create the assignment if variable is wrong" do
scope = Scope.new as_node("foo = 1")
scope.add_variable(Crystal::Var.new "foo")
scope.assign_variable("bar", Crystal::Var.new "bar")
var = scope.find_variable("foo").should_not be_nil
var.assignments.size.should eq 0
end
end
describe "#block?" do
it "returns true if Crystal::Block" do
nodes = as_nodes("3.times {}")
scope = Scope.new nodes.block_nodes.first
scope.block?.should be_true
end
it "returns false otherwise" do
scope = Scope.new as_node("a = 1")
scope.block?.should be_false
end
end
describe "#spawn_block?" do
it "returns true if a node is a spawn block" do
nodes = as_nodes("spawn {}")
scope = Scope.new nodes.block_nodes.first
scope.spawn_block?.should be_true
end
it "returns false otherwise" do
scope = Scope.new as_node("a = 1")
scope.spawn_block?.should be_false
end
end
describe "#def?" do
context "when check_outer_scopes: true" do
it "returns true if outer scope is Crystal::Def" do
nodes = as_nodes("def foo; 3.times {}; end")
outer_scope = Scope.new nodes.def_nodes.first
scope = Scope.new nodes.block_nodes.first, outer_scope
scope.def?(check_outer_scopes: true).should be_true
scope.def?.should be_false
end
end
it "returns true if Crystal::Def" do
nodes = as_nodes("def foo; end")
scope = Scope.new nodes.def_nodes.first
scope.def?.should be_true
end
it "returns false otherwise" do
scope = Scope.new as_node("a = 1")
scope.def?.should be_false
end
end
describe "#in_macro?" do
it "returns true if Crystal::Macro" do
nodes = as_nodes <<-CRYSTAL
macro included
end
CRYSTAL
scope = Scope.new nodes.macro_nodes.first
scope.in_macro?.should be_true
end
it "returns true if node is nested to Crystal::Macro" do
nodes = as_nodes <<-CRYSTAL
macro included
{{ @type.each do |type| a = type end }}
end
CRYSTAL
outer_scope = Scope.new nodes.macro_nodes.first
scope = Scope.new nodes.block_nodes.first, outer_scope
scope.in_macro?.should be_true
end
it "returns false otherwise" do
scope = Scope.new as_node("a = 1")
scope.in_macro?.should be_false
end
end
end
================================================
FILE: spec/ameba/ast/util_spec.cr
================================================
require "../../spec_helper"
module Ameba::AST
struct Test
include Util
end
describe Util do
subject = Test.new
describe "#literal?" do
[
Crystal::ArrayLiteral.new,
Crystal::BoolLiteral.new(false),
Crystal::CharLiteral.new('a'),
Crystal::HashLiteral.new,
Crystal::NamedTupleLiteral.new,
Crystal::NilLiteral.new,
Crystal::NumberLiteral.new(42),
Crystal::RegexLiteral.new(Crystal::StringLiteral.new("")),
Crystal::StringLiteral.new(""),
Crystal::SymbolLiteral.new(""),
Crystal::TupleLiteral.new([] of Crystal::ASTNode),
Crystal::RangeLiteral.new(
Crystal::NilLiteral.new,
Crystal::NilLiteral.new,
true),
].each do |literal|
it "returns true if node is #{literal}" do
subject.literal?(literal).should be_true
end
end
it "returns false if node is not a literal" do
subject.literal?(Crystal::Nop).should be_false
end
end
describe "#static/dynamic_literal?" do
[
Crystal::ArrayLiteral.new,
Crystal::ArrayLiteral.new([
Crystal::StringLiteral.new("foo"),
] of Crystal::ASTNode),
Crystal::BoolLiteral.new(false),
Crystal::CharLiteral.new('a'),
Crystal::HashLiteral.new,
Crystal::NamedTupleLiteral.new,
Crystal::NilLiteral.new,
Crystal::NumberLiteral.new(42),
Crystal::RegexLiteral.new(Crystal::StringLiteral.new("foo")),
Crystal::RegexLiteral.new(Crystal::StringInterpolation.new([
Crystal::StringLiteral.new("foo"),
] of Crystal::ASTNode)),
Crystal::StringLiteral.new("foo"),
Crystal::StringInterpolation.new([
Crystal::StringLiteral.new("foo"),
] of Crystal::ASTNode),
Crystal::SymbolLiteral.new("foo"),
Crystal::TupleLiteral.new([] of Crystal::ASTNode),
Crystal::TupleLiteral.new([
Crystal::StringLiteral.new("foo"),
] of Crystal::ASTNode),
Crystal::RangeLiteral.new(
Crystal::NumberLiteral.new(0),
Crystal::NumberLiteral.new(10),
true),
].each do |literal|
it "properly identifies static node #{literal}" do
subject.static_literal?(literal).should be_true
subject.dynamic_literal?(literal).should be_false
end
end
[
Crystal::StringInterpolation.new([Crystal::Path.new(%w[Foo])] of Crystal::ASTNode),
Crystal::ArrayLiteral.new([Crystal::Path.new(%w[Foo])] of Crystal::ASTNode),
Crystal::TupleLiteral.new([Crystal::Path.new(%w[Foo])] of Crystal::ASTNode),
Crystal::RegexLiteral.new(Crystal::StringInterpolation.new([
Crystal::StringLiteral.new("foo"),
Crystal::Path.new(%w[Foo]),
] of Crystal::ASTNode)),
Crystal::RangeLiteral.new(
Crystal::Path.new(%w[Foo]),
Crystal::NumberLiteral.new(10),
true),
Crystal::RangeLiteral.new(
Crystal::NumberLiteral.new(10),
Crystal::Path.new(%w[Foo]),
true),
].each do |literal|
it "properly identifies dynamic node #{literal}" do
subject.dynamic_literal?(literal).should be_true
subject.static_literal?(literal).should be_false
end
end
end
describe "#node_source" do
it "returns original source of the node" do
s = <<-CRYSTAL
a = 1
CRYSTAL
node = Crystal::Parser.new(s).parse
source = subject.node_source node, s.split("\n")
source.should eq "a = 1"
end
it "returns original source of multiline node" do
s = <<-CRYSTAL
if ()
:ok
end
CRYSTAL
node = Crystal::Parser.new(s).parse
source = subject.node_source node, s.split("\n")
source.should eq <<-CRYSTAL
if ()
:ok
end
CRYSTAL
end
it "does not report source of node which has incorrect location" do
s = <<-CRYSTAL
module MyModule
macro conditional_error_for_inline_callbacks
{%
raise ""
%}
end
macro before_save(x = nil)
end
end
CRYSTAL
node = as_nodes(s).nil_literal_nodes.first
source = subject.node_source node, s.split("\n")
source.should eq "nil"
end
end
describe "#flow_command?" do
it "returns true if this is return" do
node = as_node("return 22")
subject.flow_command?(node, false).should be_true
end
it "returns true if this is a break in a loop" do
node = as_node("break")
subject.flow_command?(node, true).should be_true
end
it "returns false if this is a break out of loop" do
node = as_node("break")
subject.flow_command?(node, false).should be_false
end
it "returns true if this is a next in a loop" do
node = as_node("next")
subject.flow_command?(node, true).should be_true
end
it "returns false if this is a next out of loop" do
node = as_node("next")
subject.flow_command?(node, false).should be_false
end
it "returns true if this is raise" do
node = as_node("raise e")
subject.flow_command?(node, false).should be_true
end
it "returns true if this is exit" do
node = as_node("exit")
subject.flow_command?(node, false).should be_true
end
it "returns true if this is abort" do
node = as_node("abort")
subject.flow_command?(node, false).should be_true
end
it "returns false otherwise" do
node = as_node("foobar")
subject.flow_command?(node, false).should be_false
end
end
describe "#flow_expression?" do
it "returns true if this is a flow command" do
node = as_node("return")
subject.flow_expression?(node, true).should be_true
end
it "returns true if this is if-else consumed by flow expressions" do
node = as_node <<-CRYSTAL
if foo
return :foo
else
return :bar
end
CRYSTAL
subject.flow_expression?(node, false).should be_true
end
it "returns true if this is unless-else consumed by flow expressions" do
node = as_node <<-CRYSTAL
unless foo
return :foo
else
return :bar
end
CRYSTAL
subject.flow_expression?(node).should be_true
end
it "returns true if this is case consumed by flow expressions" do
node = as_node <<-CRYSTAL
case
when 1
return 1
when 2
return 2
else
return 3
end
CRYSTAL
subject.flow_expression?(node).should be_true
end
it "returns true if this is select consumed by flow expressions" do
node = as_node <<-CRYSTAL
select
when a = foo
return 1
when a = bar
return 2
else
return 3
end
CRYSTAL
subject.flow_expression?(node).should be_true
end
it "returns true if this is exception handler consumed by flow expressions" do
node = as_node <<-CRYSTAL
begin
raise "exp"
rescue ex
return ex
end
CRYSTAL
subject.flow_expression?(node).should be_true
end
it "returns true if this while consumed by flow expressions" do
node = as_node <<-CRYSTAL
while true
return
end
CRYSTAL
subject.flow_expression?(node).should be_true
end
it "returns false if this while with break" do
node = as_node <<-CRYSTAL
while true
break
end
CRYSTAL
subject.flow_expression?(node).should be_false
end
it "returns true if this until consumed by flow expressions" do
node = as_node <<-CRYSTAL
until false
return
end
CRYSTAL
subject.flow_expression?(node).should be_true
end
it "returns false if this until with break" do
node = as_node <<-CRYSTAL
until false
break
end
CRYSTAL
subject.flow_expression?(node).should be_false
end
it "returns true if this expressions consumed by flow expressions" do
node = as_node <<-CRYSTAL
exp1
exp2
return
CRYSTAL
subject.flow_expression?(node).should be_true
end
it "returns false otherwise" do
node = as_node <<-CRYSTAL
exp1
exp2
CRYSTAL
subject.flow_expression?(node).should be_false
end
end
describe "#suffix?" do
it "returns true if the node is a suffix `if`" do
node = as_node("foo if bar")
subject.suffix?(node).should be_true
end
it "returns true if the node is a suffix `if` (2)" do
node = as_node("foo if bar.end")
subject.suffix?(node).should be_true
end
it "returns true if the node is a suffix `unless`" do
node = as_node("foo unless bar")
subject.suffix?(node).should be_true
end
it "returns true if the node is a suffix `rescue`" do
node = as_node("foo rescue bar")
subject.suffix?(node).should be_true
end
it "returns true if the node is a suffix `ensure`" do
node = as_node("foo ensure bar")
subject.suffix?(node).should be_true
end
it "returns false if the node is not a suffix `if` or `unless`" do
node = as_node("foo")
subject.suffix?(node).should be_false
end
it "returns false if the node is a ternary `if`" do
node = as_node("foo ? bar : baz")
subject.suffix?(node).should be_false
end
it "returns false if the node is a non-suffix `if`" do
node = as_node("if foo; bar; end")
subject.suffix?(node).should be_false
end
it "returns false if the node is a non-suffix `if` (2)" do
node = as_node("if foo;bar;end")
subject.suffix?(node).should be_false
end
it "returns false if the node is a non-suffix `if` (3)" do
node = as_node <<-CRYSTAL
if foo
bar
end
CRYSTAL
subject.suffix?(node).should be_false
end
it "returns false if the node is a non-suffix `unless`" do
node = as_node("unless foo; bar; end")
subject.suffix?(node).should be_false
end
it "returns false if the node is a non-suffix `rescue`" do
node = as_node("begin; foo; rescue; end")
subject.suffix?(node).should be_false
end
it "returns false if the node is a non-suffix `ensure`" do
node = as_node("begin; foo; ensure; end")
subject.suffix?(node).should be_false
end
end
describe "#has_short_block?" do
it "returns true if the node has a short block variant" do
source = Source.new "foo :bar, &.baz"
node = as_node(source.code)
subject.has_short_block?(node, source.lines).should be_true
end
it "returns false if the node has a one line block" do
source = Source.new "foo :bar { |x| x.baz? }"
node = as_node(source.code)
subject.has_short_block?(node, source.lines).should be_false
end
it "returns false if the node does not have a short block variant" do
source = Source.new "foo :bar { |x| x.baz? }"
node = as_node(source.code)
subject.has_short_block?(node, source.lines).should be_false
end
it "returns false if the node does not have a block" do
source = Source.new "foo :bar"
node = as_node(source.code)
subject.has_short_block?(node, source.lines).should be_false
end
end
describe "#has_block?" do
it "returns true if the node has a block" do
node = as_node("%w[foo bar].first { :baz }")
subject.has_block?(node).should be_true
end
it "returns true if the node has a block (shorthand)" do
node = as_node("%w[foo bar].find(&.empty?)")
subject.has_block?(node).should be_true
end
it "returns true if the node has a block (block argument)" do
node = as_node("%w[foo bar].find(&block)")
subject.has_block?(node).should be_true
end
it "returns true if the node has a block (forwarded block argument)" do
node = as_node("%w[foo bar].find(&->foo)")
subject.has_block?(node).should be_true
end
it "returns false if the node does not have a block" do
node = as_node("%w[foo bar].first")
subject.has_block?(node).should be_false
end
end
describe "#has_arguments?" do
it "returns false if the node has no arguments" do
node = as_node("foo.bar")
subject.has_arguments?(node).should be_false
end
it "returns true if the node has positional arguments" do
node = as_node("foo.bar(1)")
subject.has_arguments?(node).should be_true
end
it "returns true if the node has named arguments" do
node = as_node("foo.bar(baz: 1)")
subject.has_arguments?(node).should be_true
end
it "returns true if the node has splat arguments" do
node = as_node("foo.bar(*baz)")
subject.has_arguments?(node).should be_true
end
it "returns true if the node has double splat arguments" do
node = as_node("foo.bar(**baz)")
subject.has_arguments?(node).should be_true
end
end
describe "#takes_arguments?" do
it "returns false if the node takes no arguments" do
node = as_node("def foo; end")
subject.takes_arguments?(node).should be_false
end
it "returns true if the node takes positional arguments" do
node = as_node("def foo(bar); end")
subject.takes_arguments?(node).should be_true
end
it "returns true if the node takes named arguments" do
node = as_node("def foo(*, bar); end")
subject.takes_arguments?(node).should be_true
end
it "returns true if the node has splat index" do
node = as_node("def foo(*args); end")
subject.takes_arguments?(node).should be_true
end
it "returns true if the node has double splat" do
node = as_node("def foo(**kwargs); end")
subject.takes_arguments?(node).should be_true
end
end
describe "#raise?" do
it "returns true if this is a raise method call" do
node = as_node "raise e"
subject.raise?(node).should be_true
end
it "returns false if it has a receiver" do
node = as_node "obj.raise e"
subject.raise?(node).should be_false
end
it "returns false if size of the arguments doesn't match" do
node = as_node "raise"
subject.raise?(node).should be_false
end
end
describe "#exit?" do
it "returns true if this is a exit method call" do
node = as_node "exit"
subject.exit?(node).should be_true
end
it "returns true if this is a exit method call with one argument" do
node = as_node "exit 1"
subject.exit?(node).should be_true
end
it "returns false if it has a receiver" do
node = as_node "obj.exit"
subject.exit?(node).should be_false
end
it "returns false if size of the arguments doesn't match" do
node = as_node "exit 1, 1"
subject.exit?(node).should be_false
end
end
describe "#abort?" do
it "returns true if this is an abort method call" do
node = as_node "abort"
subject.abort?(node).should be_true
end
it "returns true if this is an abort method call with one argument" do
node = as_node "abort \"message\""
subject.abort?(node).should be_true
end
it "returns true if this is an abort method call with two arguments" do
node = as_node "abort \"message\", 1"
subject.abort?(node).should be_true
end
it "returns false if it has a receiver" do
node = as_node "obj.abort"
subject.abort?(node).should be_false
end
it "returns false if size of the arguments doesn't match" do
node = as_node "abort 1, 1, 1"
subject.abort?(node).should be_false
end
end
describe "#loop?" do
it "returns true if this is a loop method call" do
node = as_node "loop"
subject.loop?(node).should be_true
end
it "returns false if it has a receiver" do
node = as_node "obj.loop"
subject.loop?(node).should be_false
end
it "returns false if size of the arguments doesn't match" do
node = as_node "loop 1"
subject.loop?(node).should be_false
end
end
describe "#operator_method_name?" do
it "returns true for operator method names" do
%w[+ - * / == != ~= !~ <=>].each do |op|
subject.operator_method_name?(op).should be_true
end
end
it "returns false for non-operator method names" do
%w[foo? foo! ->].each do |op|
subject.operator_method_name?(op).should be_false
end
end
end
describe "#operator_method?" do
it "returns true for operator method definitions" do
node = as_node "def +(other); end"
subject.operator_method?(node).should be_true
end
it "returns false for other method definitions" do
node = as_node "def method; end"
subject.operator_method?(node).should be_false
end
it "returns true for operator method calls" do
node = as_node "obj + 1"
subject.operator_method?(node).should be_true
end
it "returns false for other method calls" do
node = as_node "obj.method"
subject.operator_method?(node).should be_false
end
it "returns false for procs" do
node = as_node "-> { nil }"
subject.operator_method?(node).should be_false
end
end
describe "#setter_method_name?" do
it "returns true for setter method names" do
%w[foo= []=].each do |op|
subject.setter_method_name?(op).should be_true
end
end
it "returns false for operator method names" do
%w[== !=].each do |op|
subject.setter_method_name?(op).should be_false
end
end
it "returns false for non-operator method names" do
%w[foo foo? foo!].each do |op|
subject.setter_method_name?(op).should be_false
end
end
end
describe "#setter_method?" do
it "returns true for setter method definitions" do
node = as_node "def foo=(@foo); end"
subject.setter_method?(node).should be_true
end
it "returns false for other method definitions" do
node = as_node "def foo; end"
subject.setter_method?(node).should be_false
end
it "returns true for setter method calls" do
node = as_node "foo.bar = 123"
subject.setter_method?(node).should be_true
end
it "returns false for regular method calls" do
node = as_node "obj.method"
subject.setter_method?(node).should be_false
end
it "returns false for procs" do
node = as_node "-> { nil }"
subject.setter_method?(node).should be_false
end
end
describe "#nodoc?" do
it "returns true if a node has a single `:nodoc:` annotation" do
node = as_node <<-CRYSTAL, wants_doc: true
# :nodoc:
def foo; end
CRYSTAL
subject.nodoc?(node).should be_true
end
it "returns true if a node has a `:nodoc:` annotation in the first line" do
node = as_node <<-CRYSTAL, wants_doc: true
# :nodoc:
#
# foo
def foo; end
CRYSTAL
subject.nodoc?(node).should be_true
end
it "returns false if a node has a `:nodoc:` annotation in the middle" do
node = as_node <<-CRYSTAL, wants_doc: true
# foo
# :nodoc:
# bar
def foo; end
CRYSTAL
subject.nodoc?(node).should be_false
end
end
describe "#heredoc?" do
it "returns true if a node is a heredoc string" do
source = Source.new <<-CRYSTAL
<<-FOO
This is a heredoc
FOO
CRYSTAL
subject.heredoc?(source.ast, source).should be_true
end
it "returns true if a node is a heredoc string interpolation" do
source = Source.new <<-'CRYSTAL'
<<-FOO
This is a heredoc #{1 + 2}
FOO
CRYSTAL
subject.heredoc?(source.ast, source).should be_true
end
it "returns false if a node is a regular string interpolation" do
source = Source.new <<-'CRYSTAL'
"This is not a heredoc #{1 + 2}"
CRYSTAL
subject.heredoc?(source.ast, source).should be_false
end
end
describe "#control_exp_code" do
it "returns the exp code of a control expression" do
s = "return 1"
node = as_node(s).as Crystal::ControlExpression
exp_code = subject.control_exp_code node, [s]
exp_code.should eq "1"
end
it "wraps implicit tuple literal with curly brackets" do
s = "return 1, 2"
node = as_node(s).as Crystal::ControlExpression
exp_code = subject.control_exp_code node, [s]
exp_code.should eq "{1, 2}"
end
it "accepts explicit tuple literal" do
s = "return {1, 2}"
node = as_node(s).as Crystal::ControlExpression
exp_code = subject.control_exp_code node, [s]
exp_code.should eq "{1, 2}"
end
end
describe "#name_location_or" do
it "adjusts location column number by a given value" do
node = as_node("def foo; end").as Crystal::Def
subject.name_location_or(node, adjust_location_column_number: 10).to_s
.should eq "{:1:15, :1:17}"
end
it "works on method call" do
node = as_node("def foo; end").as Crystal::Def
subject.name_location_or(node).to_s.should eq "{:1:5, :1:7}"
end
it "works on class definition" do
node = as_node("class Foo; end").as Crystal::ClassDef
subject.name_location_or(node).to_s.should eq "{:1:7, :1:9}"
end
it "works on module definition" do
node = as_node("module Foo; end").as Crystal::ModuleDef
subject.name_location_or(node).to_s.should eq "{:1:8, :1:10}"
end
end
describe "#name_end_location" do
it "works on method call" do
node = as_node("name(foo)").as Crystal::Call
subject.name_end_location(node).to_s.should eq ":1:4"
end
it "works on method definition" do
node = as_node("def name; end").as Crystal::Def
subject.name_end_location(node).to_s.should eq ":1:8"
end
it "works on macro definition" do
node = as_node("macro name; end").as Crystal::Macro
subject.name_end_location(node).to_s.should eq ":1:10"
end
it "works on class definition" do
node = as_node("class Name; end").as Crystal::ClassDef
subject.name_end_location(node).to_s.should eq ":1:10"
end
it "works on module definition" do
node = as_node("module Name; end").as Crystal::ModuleDef
subject.name_end_location(node).to_s.should eq ":1:11"
end
it "works on annotation definition" do
node = as_node("annotation Name; end").as Crystal::AnnotationDef
subject.name_end_location(node).to_s.should eq ":1:15"
end
it "works on enum definition" do
node = as_node("enum Name; end").as Crystal::EnumDef
subject.name_end_location(node).to_s.should eq ":1:9"
end
it "works on alias definition" do
node = as_node("alias Name = Foo").as Crystal::Alias
subject.name_end_location(node).to_s.should eq ":1:10"
end
it "works on generic" do
node = as_node("Name(Foo)").as Crystal::Generic
subject.name_end_location(node).to_s.should eq ":1:4"
end
it "works on include" do
node = as_node("include Name").as Crystal::Include
subject.name_end_location(node).to_s.should eq ":1:12"
end
it "works on extend" do
node = as_node("extend Name").as Crystal::Extend
subject.name_end_location(node).to_s.should eq ":1:11"
end
it "works on variable type declaration" do
node = as_node("name : Foo").as Crystal::TypeDeclaration
subject.name_end_location(node).to_s.should eq ":1:4"
end
it "works on uninitialized variable" do
node = as_node("name = uninitialized Foo").as Crystal::UninitializedVar
subject.name_end_location(node).to_s.should eq ":1:4"
end
it "works on lib definition" do
node = as_node("lib Name; end").as Crystal::LibDef
subject.name_end_location(node).to_s.should eq ":1:8"
end
it "works on lib type definition" do
node = as_node("lib Foo; type Name = Bar; end").as(Crystal::LibDef).body
node.class.should eq Crystal::TypeDef
subject.name_end_location(node).to_s.should eq ":1:18"
end
it "works on metaclass" do
node = as_node("foo : Name.class").as(Crystal::TypeDeclaration).declared_type
node.class.should eq Crystal::Metaclass
subject.name_end_location(node).to_s.should eq ":1:10"
end
end
end
end
================================================
FILE: spec/ameba/ast/variabling/argument_spec.cr
================================================
require "../../../spec_helper"
module Ameba::AST
describe Argument do
arg = Crystal::Arg.new "a"
scope = Scope.new as_node "foo = 1"
variable = Variable.new(Crystal::Var.new("foo"), scope)
describe "#initialize" do
it "creates a new argument" do
argument = Argument.new(arg, variable)
argument.node.should_not be_nil
end
end
describe "delegation" do
it "delegates locations to node" do
argument = Argument.new(arg, variable)
argument.location.should eq arg.location
argument.end_location.should eq arg.end_location
end
it "delegates to_s to node" do
argument = Argument.new(arg, variable)
argument.to_s.should eq arg.to_s
end
end
describe "#ignored?" do
it "is true if arg starts with _" do
argument = Argument.new(Crystal::Arg.new("_a"), variable)
argument.ignored?.should be_true
end
it "is false otherwise" do
argument = Argument.new(arg, variable)
argument.ignored?.should be_false
end
end
end
end
================================================
FILE: spec/ameba/ast/variabling/assignment_spec.cr
================================================
require "../../../spec_helper"
module Ameba::AST
describe Assignment do
node = Crystal::NilLiteral.new
scope = Scope.new as_node "foo = 1"
variable = Variable.new(Crystal::Var.new("foo"), scope)
describe "#initialize" do
it "creates a new assignment with node and var" do
assignment = Assignment.new(node, variable, scope)
assignment.node.should_not be_nil
end
end
describe "delegation" do
it "delegates locations" do
assignment = Assignment.new(node, variable, scope)
assignment.location.should eq node.location
assignment.end_location.should eq node.end_location
end
it "delegates to_s" do
assignment = Assignment.new(node, variable, scope)
assignment.to_s.should eq node.to_s
end
it "delegates scope" do
assignment = Assignment.new(node, variable, scope)
assignment.scope.should eq variable.scope
end
end
end
end
================================================
FILE: spec/ameba/ast/variabling/reference_spec.cr
================================================
require "../../../spec_helper"
module Ameba::AST
describe Reference do
it "is derived from a Variable" do
node = Crystal::Var.new "foo"
ref = Reference.new(node, Scope.new as_node "foo = 1")
ref.should be_a Variable
end
end
end
================================================
FILE: spec/ameba/ast/variabling/type_dec_variable_spec.cr
================================================
require "../../../spec_helper"
module Ameba::AST
describe TypeDecVariable do
var = Crystal::Var.new("foo")
declared_type = Crystal::Path.new("String")
type_dec = Crystal::TypeDeclaration.new(var, declared_type)
describe "#initialize" do
it "creates a new type dec variable" do
variable = TypeDecVariable.new(type_dec)
variable.node.should_not be_nil
end
end
describe "#name" do
it "returns var name" do
variable = TypeDecVariable.new(type_dec)
variable.name.should eq var.name
end
it "raises if type declaration is incorrect" do
type_dec = Crystal::TypeDeclaration.new(declared_type, declared_type)
expect_raises(Exception, "Unsupported var node type: Crystal::Path") do
TypeDecVariable.new(type_dec).name
end
end
end
end
end
================================================
FILE: spec/ameba/ast/variabling/variable_spec.cr
================================================
require "../../../spec_helper"
module Ameba::AST
describe Variable do
var_node = Crystal::Var.new("foo")
scope = Scope.new as_node "foo = 1"
describe "#initialize" do
it "creates a new variable" do
variable = Variable.new(var_node, scope)
variable.node.should_not be_nil
end
end
describe "delegation" do
it "delegates locations" do
variable = Variable.new(var_node, scope)
variable.location.should eq var_node.location
variable.end_location.should eq var_node.end_location
end
it "delegates name" do
variable = Variable.new(var_node, scope)
variable.name.should eq var_node.name
end
it "delegates to_s" do
variable = Variable.new(var_node, scope)
variable.to_s.should eq var_node.to_s
end
end
describe "#special?" do
it "returns truthy if it is a special `$?` var" do
variable = Variable.new Crystal::Var.new("$?"), scope
variable.special?.should be_truthy
end
it "returns falsey otherwise" do
variable = Variable.new Crystal::Var.new("a"), scope
variable.special?.should be_falsey
end
end
describe "#assign" do
assign_node = as_node("foo=1")
it "assigns the variable (creates a new assignment)" do
variable = Variable.new(var_node, scope)
variable.assign(assign_node, scope)
variable.assignments.empty?.should be_false
end
it "can create multiple assignments" do
variable = Variable.new(var_node, scope)
variable.assign(assign_node, scope)
variable.assign(assign_node, scope)
variable.assignments.size.should eq 2
end
end
describe "#reference" do
it "references the existed assignment" do
variable = Variable.new(var_node, scope)
variable.assign(as_node("foo=1"), scope)
variable.reference(var_node, scope)
variable.references.empty?.should be_false
end
it "adds a reference to the scope" do
scope = Scope.new as_node "foo = 1"
variable = Variable.new(var_node, scope)
variable.assign(as_node("foo=1"), scope)
variable.reference(var_node, scope)
scope.references.size.should eq 1
scope.references.first.node.to_s.should eq "foo"
end
end
describe "#captured_by_block?" do
it "returns truthy if the variable is captured by block" do
nodes = as_nodes <<-CRYSTAL
def method
a = 2
3.times { |i| a = a + i }
end
CRYSTAL
var_node = nodes.var_nodes.first
scope = Scope.new(nodes.def_nodes.first)
scope.add_variable(var_node)
scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope)
variable = Variable.new(var_node, scope)
variable.reference(nodes.var_nodes.last, scope.inner_scopes.last)
variable.captured_by_block?.should be_truthy
end
it "returns falsy if the variable is not captured by the block" do
scope = Scope.new as_node <<-CRYSTAL
def method
a = 1
end
CRYSTAL
scope.add_variable(Crystal::Var.new "a")
variable = scope.variables.first
variable.captured_by_block?.should be_falsey
end
end
describe "#target_of?" do
it "returns true if the variable is a target of Crystal::Assign node" do
assign_node = as_nodes("foo=1").assign_nodes.last
variable = Variable.new assign_node.target.as(Crystal::Var), scope
variable.target_of?(assign_node).should be_true
end
it "returns true if the variable is a target of Crystal::OpAssign node" do
assign_node = as_nodes("foo=1;foo+=1").op_assign_nodes.last
variable = Variable.new assign_node.target.as(Crystal::Var), scope
variable.target_of?(assign_node).should be_true
end
it "returns true if the variable is a target of Crystal::MultiAssign node" do
assign_node = as_nodes("a,b,c={1,2,3}").multi_assign_nodes.last
assign_node.targets.size.should_not eq 0
assign_node.targets.each do |target|
variable = Variable.new target.as(Crystal::Var), scope
variable.target_of?(assign_node).should be_true
end
end
it "returns false if the node is not assign" do
variable = Variable.new(Crystal::Var.new("v"), scope)
variable.target_of?(as_node "nil").should be_false
end
it "returns false if the variable is not a target of the assign" do
variable = Variable.new(Crystal::Var.new("foo"), scope)
variable.target_of?(as_node("bar = 1")).should be_false
end
end
describe "#ignored?" do
it "is true if variable is ignored" do
variable = Variable.new(Crystal::Var.new("_var"), scope)
variable.ignored?.should be_true
end
it "is false if variable is not ignored" do
variable = Variable.new(Crystal::Var.new("v_ar"), scope)
variable.ignored?.should be_false
end
it "is true if variable is a black hole" do
variable = Variable.new(Crystal::Var.new("_"), scope)
variable.ignored?.should be_true
end
end
describe "#eql?" do
var = Crystal::Var.new("foo").at(Crystal::Location.new(nil, 1, 2))
variable = Variable.new var, scope
it "is false if node is not a Crystal::Var" do
variable.eql?(as_node("nil")).should be_false
end
it "is false if node name is different" do
variable.eql?(Crystal::Var.new "bar").should be_false
end
it "is false if node has a different location" do
variable.eql?(Crystal::Var.new "foo").should be_false
end
it "is true otherwise" do
variable.eql?(variable.node).should be_true
end
end
describe "#declared_before?" do
it "is falsey if variable doesn't have location" do
var1 = Crystal::Var.new("foo")
var2 = Crystal::Var.new("bar").at(Crystal::Location.new(nil, 1, 2))
Variable.new(var1, scope).declared_before?(var2).should be_falsey
end
it "is falsey if node doesn't have location" do
var1 = Crystal::Var.new("foo").at(Crystal::Location.new(nil, 1, 2))
var2 = Crystal::Var.new("bar")
Variable.new(var1, scope).declared_before?(var2).should be_falsey
end
it "is true if var's line_number below the node" do
var1 = Crystal::Var.new("foo").at(Crystal::Location.new(nil, 1, 2))
var2 = Crystal::Var.new("bar").at(Crystal::Location.new(nil, 2, 2))
Variable.new(var1, scope).declared_before?(var2).should be_true
end
it "is true if var's column_number is after the node" do
var1 = Crystal::Var.new("foo").at(Crystal::Location.new(nil, 1, 2))
var2 = Crystal::Var.new("bar").at(Crystal::Location.new(nil, 1, 3))
Variable.new(var1, scope).declared_before?(var2).should be_true
end
it "is false if var's location is before the node" do
var1 = Crystal::Var.new("foo").at(Crystal::Location.new(nil, 2, 2))
var2 = Crystal::Var.new("bar").at(Crystal::Location.new(nil, 1, 3))
Variable.new(var1, scope).declared_before?(var2).should be_false
end
it "is false is the node is the same var" do
var = Crystal::Var.new("foo").at(Crystal::Location.new(nil, 2, 2))
Variable.new(var, scope).declared_before?(var).should be_false
end
end
end
end
================================================
FILE: spec/ameba/ast/visitors/counting_visitor_spec.cr
================================================
require "../../../spec_helper"
module Ameba::AST
describe CountingVisitor do
describe "#visit" do
it "allow to visit ASTNode" do
node = Crystal::Parser.new("").parse
visitor = CountingVisitor.new node
node.accept visitor
end
end
describe "#count" do
it "is 1 for an empty method" do
node = Crystal::Parser.new("def hello; end").parse
visitor = CountingVisitor.new node
visitor.count.should eq 1
end
it "is 1 if there is Macro::For" do
code = <<-CRYSTAL
def initialize
{% for c in ALL_NODES %}
true || false
{% end %}
end
CRYSTAL
node = Crystal::Parser.new(code).parse
visitor = CountingVisitor.new node
visitor.count.should eq 1
end
it "is 1 if there is Macro::If" do
code = <<-CRYSTAL
def initialize
{% if foo.bar? %}
true || false
{% end %}
end
CRYSTAL
node = Crystal::Parser.new(code).parse
visitor = CountingVisitor.new node
visitor.count.should eq 1
end
it "increases count for every exhaustive case" do
code = <<-CRYSTAL
def hello(a : Int32 | Int64 | Float32 | Float64)
case a
in Int32 then "int32"
in Int64 then "int64"
in Float32 then "float32"
in Float64 then "float64"
end
end
CRYSTAL
node = Crystal::Parser.new(code).parse
visitor = CountingVisitor.new node
visitor.count.should eq 2
end
{% for pair in [
{code: "if true; end", description: "conditional"},
{code: "while true; end", description: "while loop"},
{code: "until 1 < 2; end", description: "until loop"},
{code: "begin; rescue; end", description: "rescue"},
{code: "true || false", description: "or"},
{code: "true && false", description: "and"},
{
code: "a : String | Int32 = 1; case a when true; end",
description: "inexhaustive when",
},
{
code: "select; when a = foo; end",
description: "select",
},
] %}
it "increases count for every {{ pair[:description].id }}" do
node = Crystal::Parser.new("def hello; {{ pair[:code].id }} end").parse
visitor = CountingVisitor.new node
visitor.count.should eq 2
end
{% end %}
end
end
end
================================================
FILE: spec/ameba/ast/visitors/elseif_aware_node_visitor_spec.cr
================================================
require "../../../spec_helper"
module Ameba::AST
describe ElseIfAwareNodeVisitor do
rule = ElseIfRule.new
subject = ElseIfAwareNodeVisitor.new rule, Source.new <<-CRYSTAL
# rule.ifs[0]
foo ? bar : baz
def foo
# rule.ifs[2]
if :one
1
elsif :two
2
elsif :three
3
else
%w[].each do
# rule.ifs[1]
if true
'a'
elsif false
'b'
else
'c'
end
end
end
end
CRYSTAL
it "inherits a logic from `NodeVisitor`" do
subject.should be_a(NodeVisitor)
end
it "fires a callback for every `if` node, excluding `elsif` branches" do
rule.ifs.size.should eq 3
end
it "fires a callback with an array containing an `if` node without an `elsif` branches" do
if_node, ifs = rule.ifs[0]
if_node.to_s.should eq "foo ? bar : baz"
ifs.should be_nil
end
it "fires a callback with an array containing an `if` node with multiple `elsif` branches" do
if_node, ifs = rule.ifs[2]
if_node.cond.to_s.should eq ":one"
ifs = ifs.should_not be_nil
ifs.size.should eq 3
ifs.first.should be if_node
ifs.map(&.then.to_s).should eq %w[1 2 3]
end
it "fires a callback with an array containing an `if` node with the `else` branch as the last item" do
if_node, ifs = rule.ifs[1]
if_node.cond.to_s.should eq "true"
ifs = ifs.should_not be_nil
ifs.size.should eq 2
ifs.first.should be if_node
ifs.map(&.then.to_s).should eq %w['a' 'b']
ifs.last.else.to_s.should eq %('c')
end
end
end
================================================
FILE: spec/ameba/ast/visitors/flow_expression_visitor_spec.cr
================================================
require "../../../spec_helper"
module Ameba::AST
describe FlowExpressionVisitor do
it "creates an expression for return" do
rule = FlowExpressionRule.new
FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL
def foo
return :bar
end
CRYSTAL
rule.expressions.size.should eq 1
end
it "can create multiple expressions" do
rule = FlowExpressionRule.new
FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL
def foo
if bar
return :baz
else
return :foobar
end
end
CRYSTAL
rule.expressions.size.should eq 3
end
it "properly creates nested flow expressions" do
rule = FlowExpressionRule.new
FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL
def foo
return (
loop do
break if a > 1
return a
end
)
end
CRYSTAL
rule.expressions.size.should eq 4
end
it "creates an expression for break" do
rule = FlowExpressionRule.new
FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL
while true
break
end
CRYSTAL
rule.expressions.size.should eq 1
end
it "creates an expression for next" do
rule = FlowExpressionRule.new
FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL
while true
next if something
end
CRYSTAL
rule.expressions.size.should eq 1
end
end
end
================================================
FILE: spec/ameba/ast/visitors/implicit_return_visitor_spec.cr
================================================
require "../../../spec_helper"
private def implicit_return_visit(code)
Ameba::ImplicitReturnRule.new.tap do |rule|
Ameba::AST::ImplicitReturnVisitor.new rule, Ameba::Source.new(code)
end
end
private def has_unused_expression?(rule, str)
rule.unused_expressions.any?(&.to_s.== str)
end
private def has_unused_expression?(rule, node_type : Crystal::ASTNode.class)
rule.unused_expressions.any?(node_type)
end
private def has_unused_call?(rule, name)
rule.unused_expressions.any? do |node|
node.is_a?(Crystal::Call) && node.name == name
end
end
module Ameba::AST
describe ImplicitReturnVisitor do
context "Crystal::Expressions" do
it "reports all non-last expressions as unused" do
rule = implicit_return_visit(<<-CRYSTAL)
def method
foo
bar
baz
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_true
has_unused_expression?(rule, "bar").should be_true
has_unused_expression?(rule, "baz").should be_false
end
it "reports non-last expression even when parent captures result" do
rule = implicit_return_visit(<<-CRYSTAL)
x = begin
foo
bar
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_true
has_unused_expression?(rule, "bar").should be_false
has_unused_expression?(rule, "x").should be_false
end
it "stops processing after control expressions" do
rule = implicit_return_visit(<<-CRYSTAL)
foo
return bar
baz
CRYSTAL
has_unused_expression?(rule, "foo").should be_true
has_unused_expression?(rule, "bar").should be_false
has_unused_expression?(rule, "baz").should be_false
end
it "handles nested expressions correctly" do
rule = implicit_return_visit(<<-CRYSTAL)
begin
foo
begin
bar
baz
end
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_true
has_unused_expression?(rule, "bar").should be_true
end
end
context "assignments" do
it "marks assigned value as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
foo
x = bar
CRYSTAL
has_unused_expression?(rule, "foo").should be_true
has_unused_expression?(rule, "bar").should be_false
has_unused_expression?(rule, "x").should be_false
end
it "reports assignments when not captured" do
rule = implicit_return_visit(<<-CRYSTAL)
def method
x = 1
y = 2
end
CRYSTAL
assigns = rule.unused_expressions.select(Crystal::Assign)
assigns.size.should eq 1
assigns.first.to_s.should start_with "x ="
end
it "marks op-assigned values as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
x = 0
x += foo
bar
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
end
it "marks multi-assigned values as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
a, b = foo, bar
baz
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
has_unused_expression?(rule, "bar").should be_false
end
end
context "Crystal::Call" do
it "marks all call arguments as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
foo(bar, baz)
qux
CRYSTAL
has_unused_expression?(rule, "bar").should be_false
has_unused_expression?(rule, "baz").should be_false
end
it "reports the call itself when not captured" do
rule = implicit_return_visit(<<-CRYSTAL)
foo(1)
bar
CRYSTAL
has_unused_call?(rule, "foo").should be_true
has_unused_expression?(rule, "bar").should be_true
end
it "handles method calls with blocks" do
rule = implicit_return_visit(<<-CRYSTAL)
foo.map { |x| x + 1 }
bar
CRYSTAL
has_unused_call?(rule, "map").should be_true
has_unused_expression?(rule, "bar").should be_true
end
end
context "Crystal::If and Crystal::Unless" do
it "marks condition as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
if foo
bar
else
baz
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
has_unused_expression?(rule, "bar").should be_true
has_unused_expression?(rule, "baz").should be_true
end
it "reports if statement when not captured" do
rule = implicit_return_visit(<<-CRYSTAL)
if true
1
end
2
CRYSTAL
has_unused_expression?(rule, Crystal::If).should be_true
has_unused_expression?(rule, "1").should be_true
has_unused_expression?(rule, "2").should be_true
end
it "captures last line of branches when if is captured" do
rule = implicit_return_visit(<<-CRYSTAL)
x = if foo
bar
baz
else
qux
end
CRYSTAL
has_unused_expression?(rule, "bar").should be_true
has_unused_expression?(rule, "baz").should be_false
has_unused_expression?(rule, "qux").should be_false
end
end
context "Crystal::While and Crystal::Until" do
it "marks condition as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
while foo
bar
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
has_unused_expression?(rule, "bar").should be_true
end
it "does not capture loop body by default" do
rule = implicit_return_visit(<<-CRYSTAL)
while true
foo
bar
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_true
has_unused_expression?(rule, "bar").should be_true
end
end
context "Crystal::Case" do
it "marks case condition as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
case foo
when 1
bar
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
end
it "marks when conditions as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
case x
when foo, bar
baz
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
has_unused_expression?(rule, "bar").should be_false
end
it "inherits parent capture state for when bodies" do
rule = implicit_return_visit(<<-CRYSTAL)
result = case x
when 1
foo
bar
when 2
baz
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_true
end
end
context "Crystal::Def" do
it "marks method arguments as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
def method(x = foo)
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
end
it "captures method body last line by default" do
rule = implicit_return_visit(<<-CRYSTAL)
def method
foo
bar
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_true
has_unused_expression?(rule, "bar").should be_false
end
it "does not capture body when return type is Nil" do
rule = implicit_return_visit(<<-CRYSTAL)
def method : Nil
foo
bar
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_true
has_unused_expression?(rule, "bar").should be_true
end
it "does not capture body in initialize methods" do
rule = implicit_return_visit(<<-CRYSTAL)
class Foo
def initialize
foo
bar
end
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_true
has_unused_expression?(rule, "bar").should be_true
end
it "handles method with complex body" do
rule = implicit_return_visit(<<-CRYSTAL)
def outer
foo
if condition
bar
end
baz
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_true
has_unused_expression?(rule, "bar").should be_true
has_unused_expression?(rule, "baz").should be_false
end
end
context "literals" do
it "marks array elements as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
[foo, bar]
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
has_unused_expression?(rule, "bar").should be_false
end
it "marks hash values as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
{a: foo, b: bar}
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
has_unused_expression?(rule, "bar").should be_false
end
it "marks range bounds as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
foo..bar
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
has_unused_expression?(rule, "bar").should be_false
end
it "marks string interpolation expressions as captured" do
rule = implicit_return_visit(<<-'CRYSTAL')
"hello #{foo} world"
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
end
it "marks regex contents as captured" do
rule = implicit_return_visit(<<-'CRYSTAL')
/#{foo}/
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
end
end
context "Crystal::BinaryOp" do
it "marks left side as captured when right is a Call" do
rule = implicit_return_visit(<<-CRYSTAL)
foo + bar()
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
end
it "marks left side as captured when right is ControlExpression" do
rule = implicit_return_visit(<<-CRYSTAL)
foo || return bar
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
end
it "does not mark left side as captured for simple ops" do
rule = implicit_return_visit(<<-CRYSTAL)
foo + bar
CRYSTAL
has_unused_expression?(rule, "foo + bar").should be_true
end
end
context "type operations" do
it "marks casted object as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
foo.as(Bar)
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
end
it "marks tested object as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
foo.is_a?(Bar)
baz.responds_to?(:method)
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
has_unused_expression?(rule, "baz").should be_false
end
it "marks typeof argument as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
typeof(foo)
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
end
it "marks declared value as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
x : Int32 = foo
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
end
end
context "Crystal::ExceptionHandler" do
it "does not capture body last line when else is present" do
rule = implicit_return_visit(<<-CRYSTAL)
begin
foo
bar
rescue
baz
else
qux
end
CRYSTAL
has_unused_expression?(rule, "bar").should be_true
end
it "captures body last line when no else is present" do
rule = implicit_return_visit(<<-CRYSTAL)
x = begin
foo
bar
rescue
baz
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_true
has_unused_expression?(rule, "bar").should be_false
has_unused_expression?(rule, "baz").should be_false
end
it "does not capture ensure block" do
rule = implicit_return_visit(<<-CRYSTAL)
begin
foo
ensure
bar
baz
end
CRYSTAL
has_unused_expression?(rule, "bar").should be_true
has_unused_expression?(rule, "baz").should be_true
end
it "inherits capture state for rescue bodies" do
rule = implicit_return_visit(<<-CRYSTAL)
x = begin
foo
rescue
bar
baz
end
CRYSTAL
has_unused_expression?(rule, "bar").should be_true
has_unused_expression?(rule, "baz").should be_false
end
end
context "Crystal::Block" do
it "inherits parent capture state for block body" do
rule = implicit_return_visit(<<-CRYSTAL)
[1, 2].map do |x|
foo
x + 1
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_true
end
it "processes block when block itself is unused" do
rule = implicit_return_visit(<<-CRYSTAL)
3.times do
foo
bar
end
baz
CRYSTAL
has_unused_expression?(rule, "foo").should be_true
has_unused_expression?(rule, "bar").should be_false
has_unused_expression?(rule, "baz").should be_true
end
end
context "Crystal::ControlExpression" do
it "marks control expression arguments as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
return foo
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
end
it "reports the control expression itself" do
rule = implicit_return_visit(<<-CRYSTAL)
def method : Nil
return 1
end
CRYSTAL
has_unused_expression?(rule, Crystal::Return).should be_true
end
it "handles break with value" do
rule = implicit_return_visit(<<-CRYSTAL)
loop do
break foo if condition
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
end
it "handles next with value" do
rule = implicit_return_visit(<<-CRYSTAL)
[1, 2].each do |x|
next foo if x == 1
bar
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
end
end
context "macros" do
it "marks macro arguments as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
macro method(arg = foo)
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
end
it "captures macro body" do
rule = implicit_return_visit(<<-CRYSTAL)
macro method
foo
bar
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
has_unused_expression?(rule, "bar").should be_false
end
it "sets in_macro flag for macro body" do
rule = implicit_return_visit(<<-CRYSTAL)
macro method
{% foo %}
end
CRYSTAL
# ameba:disable Performance/AnyInsteadOfPresent
rule.macro_flags.any?.should be_true
has_unused_expression?(rule, "foo").should be_true
end
it "captures output macro expressions" do
rule = implicit_return_visit(<<-CRYSTAL)
{{ foo }}
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
end
it "does not capture non-output macro expressions" do
rule = implicit_return_visit(<<-CRYSTAL)
{% foo %}
CRYSTAL
has_unused_expression?(rule, "foo").should be_true
end
it "sets in_macro flag for macro expression" do
rule = implicit_return_visit(<<-CRYSTAL)
{% foo %}
CRYSTAL
# ameba:disable Performance/AnyInsteadOfPresent
rule.macro_flags.any?.should be_true
end
it "marks macro if condition as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
{% if foo %}
{% end %}
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
end
it "does not capture macro if branches" do
rule = implicit_return_visit(<<-CRYSTAL)
{% if true %}
foo
{% else %}
bar
{% end %}
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
has_unused_expression?(rule, "bar").should be_false
end
it "does not capture macro for body" do
rule = implicit_return_visit(<<-CRYSTAL)
{% for x in [1, 2] %}
foo
{% end %}
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
end
end
context "other node types" do
it "visits enum members" do
rule = implicit_return_visit(<<-CRYSTAL)
enum Color
Red
Green
Blue
end
CRYSTAL
has_unused_expression?(rule, "Red").should be_false
has_unused_expression?(rule, "Green").should be_false
has_unused_expression?(rule, "Blue").should be_false
end
it "visits class and module bodies" do
rule = implicit_return_visit(<<-CRYSTAL)
class Foo
foo
bar
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_true
has_unused_expression?(rule, "bar").should be_true
end
it "marks function body as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
fun foo : Int32
return 1
end
CRYSTAL
has_unused_expression?(rule, Crystal::FunDef).should be_true
end
it "marks unary operand as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
!foo
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
has_unused_expression?(rule, "!foo").should be_true
end
it "marks yielded values as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
yield foo, bar
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
has_unused_expression?(rule, "bar").should be_false
end
it "marks default argument value as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
def method(x = foo)
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
end
it "marks annotation arguments as captured" do
rule = implicit_return_visit(<<-CRYSTAL)
@[Foo(bar)]
def method
end
CRYSTAL
has_unused_expression?(rule, "bar").should be_false
end
it "visits select whens" do
rule = implicit_return_visit(<<-CRYSTAL)
select
when x = foo
bar
end
CRYSTAL
has_unused_expression?(rule, Crystal::Select).should be_true
has_unused_expression?(rule, "bar").should be_true
end
end
context "edge cases" do
it "handles deeply nested scopes" do
rule = implicit_return_visit(<<-CRYSTAL)
class Outer
class Inner
def method
if true
foo
bar
end
end
end
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_true
has_unused_expression?(rule, "bar").should be_false
end
it "handles control expressions in method body" do
rule = implicit_return_visit(<<-CRYSTAL)
def method
return foo if condition
bar
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
has_unused_expression?(rule, "bar").should be_false
end
it "handles multiple assignment targets" do
rule = implicit_return_visit(<<-CRYSTAL)
a, b, c = foo, bar, baz
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
has_unused_expression?(rule, "bar").should be_false
has_unused_expression?(rule, "baz").should be_false
end
it "handles nested exception handlers" do
rule = implicit_return_visit(<<-CRYSTAL)
begin
begin
foo
rescue
bar
end
rescue
baz
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_true
has_unused_expression?(rule, "bar").should be_true
has_unused_expression?(rule, "baz").should be_true
end
it "handles case with multiple when conditions" do
rule = implicit_return_visit(<<-CRYSTAL)
case x
when foo, bar, baz
qux
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_false
has_unused_expression?(rule, "bar").should be_false
has_unused_expression?(rule, "baz").should be_false
end
it "handles trailing control expressions" do
rule = implicit_return_visit(<<-CRYSTAL)
begin
foo
bar
return baz
end
CRYSTAL
has_unused_expression?(rule, "foo").should be_true
has_unused_expression?(rule, "bar").should be_true
end
end
context "integration scenarios" do
it "handles complex method with multiple features" do
rule = implicit_return_visit(<<-CRYSTAL)
def process(items)
return nil if items.empty?
result = items.map do |item|
if item.valid?
item.process
else
item.skip
end
end
log(result)
result
end
CRYSTAL
has_unused_call?(rule, "log").should be_true
end
it "handles initialize with side effects" do
rule = implicit_return_visit(<<-CRYSTAL)
class Foo
def initialize(@x : Int32)
validate!
setup_hooks
end
end
CRYSTAL
has_unused_call?(rule, "validate!").should be_true
has_unused_call?(rule, "setup_hooks").should be_true
end
end
end
end
================================================
FILE: spec/ameba/ast/visitors/node_visitor_spec.cr
================================================
require "../../../spec_helper"
module Ameba::AST
describe NodeVisitor do
describe "visit" do
it "allow to visit ASTNode" do
rule = DummyRule.new
visitor = NodeVisitor.new rule, Source.new
nodes = Crystal::Parser.new("").parse
nodes.accept visitor
end
end
end
end
================================================
FILE: spec/ameba/ast/visitors/redundant_control_expression_visitor_spec.cr
================================================
require "../../../spec_helper"
module Ameba::AST
describe RedundantControlExpressionVisitor do
source = Source.new
rule = RedundantControlExpressionRule.new
node = as_node <<-CRYSTAL
a = 1
b = 2
return a + b
CRYSTAL
subject = RedundantControlExpressionVisitor.new(rule, source, node)
it "assigns valid attributes" do
subject.rule.should eq rule
subject.source.should eq source
subject.node.should eq node
end
it "fires a callback with a valid node" do
rule.nodes.size.should eq 1
rule.nodes.first.to_s.should eq "return a + b"
end
end
end
================================================
FILE: spec/ameba/ast/visitors/scope_calls_with_self_receiver_visitor_spec.cr
================================================
require "../../../spec_helper"
module Ameba::AST
describe ScopeCallsWithSelfReceiverVisitor do
{% for type in %w[class module].map(&.id) %}
it "creates a scope for the {{ type }} def" do
rule = SelfCallsRule.new
visitor = ScopeCallsWithSelfReceiverVisitor.new rule, Source.new <<-CRYSTAL
{{ type }} Foo
self.foo
end
CRYSTAL
call_queue = visitor.scope_call_queue
call_queue.size.should eq 1
call_queue.should eq rule.call_queue
scope, calls = call_queue.first
node = scope.node.should be_a(Crystal::{{ type.capitalize }}Def)
node.name.should be_a(Crystal::Path)
node.name.to_s.should eq "Foo"
calls.size.should eq 1
calls.first.name.should eq "foo"
end
{% end %}
it "creates a scope for the def" do
rule = SelfCallsRule.new
visitor = ScopeCallsWithSelfReceiverVisitor.new rule, Source.new <<-CRYSTAL
class Foo
def method
self.foo :a, :b, :c
self.bar
baz :x, :y, :z
it_is_a.bat "country"
self
end
end
CRYSTAL
call_queue = visitor.scope_call_queue
call_queue.size.should eq 1
call_queue.should eq rule.call_queue
scope, calls = call_queue.first
node = scope.node.should be_a(Crystal::Def)
node.name.should eq "method"
calls.size.should eq 2
calls.first.name.should eq "foo"
calls.last.name.should eq "bar"
end
it "creates a scope for the proc" do
rule = SelfCallsRule.new
visitor = ScopeCallsWithSelfReceiverVisit
gitextract_e8xowmtu/
├── .ameba.yml
├── .ameba.yml.schema.json
├── .dockerignore
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ ├── add-docs-version-to-json-index.yml
│ ├── build-and-deploy-docs.yml
│ ├── ci.yml
│ ├── compare-json-schema.yml
│ ├── docker-image.yml
│ ├── docs.yml
│ ├── release.yml
│ ├── typos.yml
│ └── update-json-schema.yml
├── .gitignore
├── .typos.toml
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── bench/
│ └── check_sources.cr
├── bin/
│ └── ameba.cr
├── shard.yml
├── spec/
│ ├── ameba/
│ │ ├── ast/
│ │ │ ├── flow_expression_spec.cr
│ │ │ ├── liveness_analyzer_spec.cr
│ │ │ ├── scope_spec.cr
│ │ │ ├── util_spec.cr
│ │ │ ├── variabling/
│ │ │ │ ├── argument_spec.cr
│ │ │ │ ├── assignment_spec.cr
│ │ │ │ ├── reference_spec.cr
│ │ │ │ ├── type_dec_variable_spec.cr
│ │ │ │ └── variable_spec.cr
│ │ │ └── visitors/
│ │ │ ├── counting_visitor_spec.cr
│ │ │ ├── elseif_aware_node_visitor_spec.cr
│ │ │ ├── flow_expression_visitor_spec.cr
│ │ │ ├── implicit_return_visitor_spec.cr
│ │ │ ├── node_visitor_spec.cr
│ │ │ ├── redundant_control_expression_visitor_spec.cr
│ │ │ ├── scope_calls_with_self_receiver_visitor_spec.cr
│ │ │ ├── scope_visitor_spec.cr
│ │ │ └── top_level_nodes_visitor_spec.cr
│ │ ├── base_spec.cr
│ │ ├── cli/
│ │ │ └── cmd_spec.cr
│ │ ├── config_spec.cr
│ │ ├── ext/
│ │ │ └── location_spec.cr
│ │ ├── formatter/
│ │ │ ├── disabled_formatter_spec.cr
│ │ │ ├── dot_formatter_spec.cr
│ │ │ ├── explain_formatter_spec.cr
│ │ │ ├── flycheck_formatter_spec.cr
│ │ │ ├── github_actions_formatter_spec.cr
│ │ │ ├── json_formatter_spec.cr
│ │ │ ├── todo_formatter_spec.cr
│ │ │ └── util_spec.cr
│ │ ├── glob_utils_spec.cr
│ │ ├── inline_comments_spec.cr
│ │ ├── issue_spec.cr
│ │ ├── presenter/
│ │ │ ├── rule_collection_presenter_spec.cr
│ │ │ ├── rule_presenter_spec.cr
│ │ │ └── rule_versions_presenter_spec.cr
│ │ ├── reportable_spec.cr
│ │ ├── rule/
│ │ │ ├── base_spec.cr
│ │ │ ├── documentation/
│ │ │ │ ├── admonition_spec.cr
│ │ │ │ └── documentation_spec.cr
│ │ │ ├── layout/
│ │ │ │ ├── line_length_spec.cr
│ │ │ │ ├── trailing_blank_lines_spec.cr
│ │ │ │ └── trailing_whitespace_spec.cr
│ │ │ ├── lint/
│ │ │ │ ├── ambiguous_assignment_spec.cr
│ │ │ │ ├── assignment_in_call_argument_spec.cr
│ │ │ │ ├── bad_directive_spec.cr
│ │ │ │ ├── comparison_to_boolean_spec.cr
│ │ │ │ ├── debug_calls_spec.cr
│ │ │ │ ├── debugger_statement_spec.cr
│ │ │ │ ├── duplicate_branch_spec.cr
│ │ │ │ ├── duplicate_enum_value_spec.cr
│ │ │ │ ├── duplicate_method_signature_spec.cr
│ │ │ │ ├── duplicate_when_condition_spec.cr
│ │ │ │ ├── duplicated_require_spec.cr
│ │ │ │ ├── else_nil_spec.cr
│ │ │ │ ├── empty_ensure_spec.cr
│ │ │ │ ├── empty_expression_spec.cr
│ │ │ │ ├── empty_loop_spec.cr
│ │ │ │ ├── enum_member_name_conflict_spec.cr
│ │ │ │ ├── formatting_spec.cr
│ │ │ │ ├── hash_duplicated_key_spec.cr
│ │ │ │ ├── literal_assignments_in_expressions_spec.cr
│ │ │ │ ├── literal_in_condition_spec.cr
│ │ │ │ ├── literal_in_interpolation_spec.cr
│ │ │ │ ├── literals_comparison_spec.cr
│ │ │ │ ├── missing_block_argument_spec.cr
│ │ │ │ ├── non_existent_rule_spec.cr
│ │ │ │ ├── not_nil_after_no_bang_spec.cr
│ │ │ │ ├── not_nil_spec.cr
│ │ │ │ ├── percent_arrays_spec.cr
│ │ │ │ ├── rand_zero_spec.cr
│ │ │ │ ├── redundant_string_cercion_spec.cr
│ │ │ │ ├── redundant_with_index_spec.cr
│ │ │ │ ├── redundant_with_object_spec.cr
│ │ │ │ ├── require_parentheses_spec.cr
│ │ │ │ ├── self_initialize_definition_spec.cr
│ │ │ │ ├── shadowed_argument_spec.cr
│ │ │ │ ├── shadowed_exception_spec.cr
│ │ │ │ ├── shadowing_outer_local_var_spec.cr
│ │ │ │ ├── shared_var_in_fiber_spec.cr
│ │ │ │ ├── signal_trap_spec.cr
│ │ │ │ ├── spec_eq_with_bool_or_nil_literal_spec.cr
│ │ │ │ ├── spec_filename_spec.cr
│ │ │ │ ├── spec_focus_spec.cr
│ │ │ │ ├── syntax_spec.cr
│ │ │ │ ├── top_level_operator_definition_spec.cr
│ │ │ │ ├── trailing_rescue_exception_spec.cr
│ │ │ │ ├── typos_spec.cr
│ │ │ │ ├── unneeded_disable_directive_spec.cr
│ │ │ │ ├── unreachable_code_spec.cr
│ │ │ │ ├── unused_argument_spec.cr
│ │ │ │ ├── unused_block_argument_spec.cr
│ │ │ │ ├── unused_expression_spec.cr
│ │ │ │ ├── unused_rescue_variable_spec.cr
│ │ │ │ ├── useless_assign_spec.cr
│ │ │ │ ├── useless_condition_in_when_spec.cr
│ │ │ │ ├── useless_visibility_modifier_spec.cr
│ │ │ │ ├── void_outside_lib_spec.cr
│ │ │ │ └── whitespace_around_macro_expression_spec.cr
│ │ │ ├── metrics/
│ │ │ │ └── cyclomatic_complexity_spec.cr
│ │ │ ├── naming/
│ │ │ │ ├── accessor_method_name_spec.cr
│ │ │ │ ├── ascii_identifiers_spec.cr
│ │ │ │ ├── binary_operator_parameter_name_spec.cr
│ │ │ │ ├── block_parameter_name_spec.cr
│ │ │ │ ├── constant_names_spec.cr
│ │ │ │ ├── filename_spec.cr
│ │ │ │ ├── method_names_spec.cr
│ │ │ │ ├── predicate_name_spec.cr
│ │ │ │ ├── query_bool_methods_spec.cr
│ │ │ │ ├── rescued_exceptions_variable_name_spec.cr
│ │ │ │ ├── type_names_spec.cr
│ │ │ │ └── variable_names_spec.cr
│ │ │ ├── performance/
│ │ │ │ ├── any_after_filter_spec.cr
│ │ │ │ ├── any_instead_of_present_spec.cr
│ │ │ │ ├── base_spec.cr
│ │ │ │ ├── chained_call_with_no_bang_spec.cr
│ │ │ │ ├── compact_after_map_spec.cr
│ │ │ │ ├── excessive_allocations_spec.cr
│ │ │ │ ├── first_last_after_filter_spec.cr
│ │ │ │ ├── flatten_after_map_spec.cr
│ │ │ │ ├── map_instead_of_block_spec.cr
│ │ │ │ ├── minmax_after_map_spec.cr
│ │ │ │ ├── size_after_filter_spec.cr
│ │ │ │ └── times_map_spec.cr
│ │ │ ├── style/
│ │ │ │ ├── array_literal_syntax_spec.cr
│ │ │ │ ├── call_parentheses_spec.cr
│ │ │ │ ├── elsif_spec.cr
│ │ │ │ ├── guard_clause_spec.cr
│ │ │ │ ├── hash_literal_syntax_spec.cr
│ │ │ │ ├── heredoc_escape_spec.cr
│ │ │ │ ├── heredoc_indent_spec.cr
│ │ │ │ ├── is_a_filter_spec.cr
│ │ │ │ ├── is_a_nil_spec.cr
│ │ │ │ ├── large_numbers_spec.cr
│ │ │ │ ├── multiline_curly_block_spec.cr
│ │ │ │ ├── multiline_string_literal_spec.cr
│ │ │ │ ├── negated_conditions_in_unless_spec.cr
│ │ │ │ ├── parentheses_around_condition_spec.cr
│ │ │ │ ├── percent_literal_delimiters_spec.cr
│ │ │ │ ├── redundant_begin_spec.cr
│ │ │ │ ├── redundant_next_spec.cr
│ │ │ │ ├── redundant_nil_in_control_expression_spec.cr
│ │ │ │ ├── redundant_return_spec.cr
│ │ │ │ ├── redundant_self_spec.cr
│ │ │ │ ├── unless_else_spec.cr
│ │ │ │ ├── verbose_block_spec.cr
│ │ │ │ ├── verbose_nil_type_spec.cr
│ │ │ │ └── while_true_spec.cr
│ │ │ └── typing/
│ │ │ ├── macro_call_argument_type_restriction_spec.cr
│ │ │ ├── method_parameter_type_restriction_spec.cr
│ │ │ ├── method_return_type_restriction_spec.cr
│ │ │ └── proc_literal_return_type_restriction_spec.cr
│ │ ├── runner_spec.cr
│ │ ├── severity_spec.cr
│ │ ├── source/
│ │ │ └── rewriter_spec.cr
│ │ ├── source_spec.cr
│ │ ├── spec/
│ │ │ └── annotated_source_spec.cr
│ │ ├── tokenizer_spec.cr
│ │ └── version_spec.cr
│ ├── ameba_spec.cr
│ ├── fixtures/
│ │ └── .ameba.yml
│ └── spec_helper.cr
└── src/
├── ameba/
│ ├── ast/
│ │ ├── flow_expression.cr
│ │ ├── liveness_analyzer.cr
│ │ ├── scope.cr
│ │ ├── util.cr
│ │ ├── variabling/
│ │ │ ├── argument.cr
│ │ │ ├── assignment.cr
│ │ │ ├── ivariable.cr
│ │ │ ├── reference.cr
│ │ │ ├── type_dec_variable.cr
│ │ │ └── variable.cr
│ │ └── visitors/
│ │ ├── base_visitor.cr
│ │ ├── counting_visitor.cr
│ │ ├── elseif_aware_node_visitor.cr
│ │ ├── flow_expression_visitor.cr
│ │ ├── implicit_return_visitor.cr
│ │ ├── macro_reference_finder.cr
│ │ ├── node_visitor.cr
│ │ ├── redundant_control_expression_visitor.cr
│ │ ├── scope_calls_with_self_receiver_visitor.cr
│ │ ├── scope_visitor.cr
│ │ └── top_level_nodes_visitor.cr
│ ├── cli/
│ │ └── cmd.cr
│ ├── config/
│ │ ├── loader.cr
│ │ └── rule_config.cr
│ ├── config.cr
│ ├── ext/
│ │ └── location.cr
│ ├── formatter/
│ │ ├── base_formatter.cr
│ │ ├── disabled_formatter.cr
│ │ ├── dot_formatter.cr
│ │ ├── explain_formatter.cr
│ │ ├── flycheck_formatter.cr
│ │ ├── github_actions_formatter.cr
│ │ ├── json_formatter.cr
│ │ ├── todo_formatter.cr
│ │ └── util.cr
│ ├── glob_utils.cr
│ ├── inline_comments.cr
│ ├── issue.cr
│ ├── json_schema/
│ │ └── builder.cr
│ ├── presenter/
│ │ ├── base_presenter.cr
│ │ ├── rule_collection_presenter.cr
│ │ ├── rule_presenter.cr
│ │ └── rule_versions_presenter.cr
│ ├── reportable.cr
│ ├── rule/
│ │ ├── base.cr
│ │ ├── documentation/
│ │ │ ├── admonition.cr
│ │ │ └── documentation.cr
│ │ ├── layout/
│ │ │ ├── line_length.cr
│ │ │ ├── trailing_blank_lines.cr
│ │ │ └── trailing_whitespace.cr
│ │ ├── lint/
│ │ │ ├── ambiguous_assignment.cr
│ │ │ ├── assignment_in_call_argument.cr
│ │ │ ├── bad_directive.cr
│ │ │ ├── comparison_to_boolean.cr
│ │ │ ├── debug_calls.cr
│ │ │ ├── debugger_statement.cr
│ │ │ ├── duplicate_branch.cr
│ │ │ ├── duplicate_enum_value.cr
│ │ │ ├── duplicate_method_signature.cr
│ │ │ ├── duplicate_when_condition.cr
│ │ │ ├── duplicated_require.cr
│ │ │ ├── else_nil.cr
│ │ │ ├── empty_ensure.cr
│ │ │ ├── empty_expression.cr
│ │ │ ├── empty_loop.cr
│ │ │ ├── enum_member_name_conflict.cr
│ │ │ ├── formatting.cr
│ │ │ ├── hash_duplicated_key.cr
│ │ │ ├── literal_assignments_in_expressions.cr
│ │ │ ├── literal_in_condition.cr
│ │ │ ├── literal_in_interpolation.cr
│ │ │ ├── literals_comparison.cr
│ │ │ ├── missing_block_argument.cr
│ │ │ ├── non_existent_rule.cr
│ │ │ ├── not_nil.cr
│ │ │ ├── not_nil_after_no_bang.cr
│ │ │ ├── percent_arrays.cr
│ │ │ ├── rand_zero.cr
│ │ │ ├── redundant_string_coercion.cr
│ │ │ ├── redundant_with_index.cr
│ │ │ ├── redundant_with_object.cr
│ │ │ ├── require_parentheses.cr
│ │ │ ├── self_initialize_definition.cr
│ │ │ ├── shadowed_argument.cr
│ │ │ ├── shadowed_exception.cr
│ │ │ ├── shadowing_outer_local_var.cr
│ │ │ ├── shared_var_in_fiber.cr
│ │ │ ├── signal_trap.cr
│ │ │ ├── spec_eq_with_bool_or_nil_literal.cr
│ │ │ ├── spec_filename.cr
│ │ │ ├── spec_focus.cr
│ │ │ ├── syntax.cr
│ │ │ ├── top_level_operator_definition.cr
│ │ │ ├── trailing_rescue_exception.cr
│ │ │ ├── typos.cr
│ │ │ ├── unneeded_disable_directive.cr
│ │ │ ├── unreachable_code.cr
│ │ │ ├── unused_argument.cr
│ │ │ ├── unused_block_argument.cr
│ │ │ ├── unused_expression.cr
│ │ │ ├── unused_rescue_variable.cr
│ │ │ ├── useless_assign.cr
│ │ │ ├── useless_condition_in_when.cr
│ │ │ ├── useless_visibility_modifier.cr
│ │ │ ├── void_outside_lib.cr
│ │ │ └── whitespace_around_macro_expression.cr
│ │ ├── metrics/
│ │ │ └── cyclomatic_complexity.cr
│ │ ├── naming/
│ │ │ ├── accessor_method_name.cr
│ │ │ ├── ascii_identifiers.cr
│ │ │ ├── binary_operator_parameter_name.cr
│ │ │ ├── block_parameter_name.cr
│ │ │ ├── constant_names.cr
│ │ │ ├── filename.cr
│ │ │ ├── method_names.cr
│ │ │ ├── predicate_name.cr
│ │ │ ├── query_bool_methods.cr
│ │ │ ├── rescued_exceptions_variable_name.cr
│ │ │ ├── type_names.cr
│ │ │ └── variable_names.cr
│ │ ├── performance/
│ │ │ ├── any_after_filter.cr
│ │ │ ├── any_instead_of_present.cr
│ │ │ ├── base.cr
│ │ │ ├── chained_call_with_no_bang.cr
│ │ │ ├── compact_after_map.cr
│ │ │ ├── excessive_allocations.cr
│ │ │ ├── first_last_after_filter.cr
│ │ │ ├── flatten_after_map.cr
│ │ │ ├── map_instead_of_block.cr
│ │ │ ├── minmax_after_map.cr
│ │ │ ├── size_after_filter.cr
│ │ │ └── times_map.cr
│ │ ├── style/
│ │ │ ├── array_literal_syntax.cr
│ │ │ ├── call_parentheses.cr
│ │ │ ├── elsif.cr
│ │ │ ├── guard_clause.cr
│ │ │ ├── hash_literal_syntax.cr
│ │ │ ├── heredoc_escape.cr
│ │ │ ├── heredoc_indent.cr
│ │ │ ├── is_a_filter.cr
│ │ │ ├── is_a_nil.cr
│ │ │ ├── large_numbers.cr
│ │ │ ├── multiline_curly_block.cr
│ │ │ ├── multiline_string_literal.cr
│ │ │ ├── negated_conditions_in_unless.cr
│ │ │ ├── parentheses_around_condition.cr
│ │ │ ├── percent_literal_delimiters.cr
│ │ │ ├── redundant_begin.cr
│ │ │ ├── redundant_next.cr
│ │ │ ├── redundant_nil_in_control_expression.cr
│ │ │ ├── redundant_return.cr
│ │ │ ├── redundant_self.cr
│ │ │ ├── unless_else.cr
│ │ │ ├── verbose_block.cr
│ │ │ ├── verbose_nil_type.cr
│ │ │ └── while_true.cr
│ │ └── typing/
│ │ ├── macro_call_argument_type_restriction.cr
│ │ ├── method_parameter_type_restriction.cr
│ │ ├── method_return_type_restriction.cr
│ │ └── proc_literal_return_type_restriction.cr
│ ├── runner.cr
│ ├── severity.cr
│ ├── source/
│ │ ├── corrector.cr
│ │ ├── rewriter/
│ │ │ └── action.cr
│ │ └── rewriter.cr
│ ├── source.cr
│ ├── spec/
│ │ ├── annotated_source.cr
│ │ ├── be_valid.cr
│ │ ├── expect_issue.cr
│ │ ├── support.cr
│ │ └── util.cr
│ ├── tokenizer.cr
│ └── version.cr
├── ameba.cr
├── cli.cr
├── contrib/
│ └── read_type_doc.cr
└── json-schema-builder.cr
Condensed preview — 362 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,166K chars).
[
{
"path": ".ameba.yml",
"chars": 111,
"preview": "Lint/Typos:\n Enabled: true\n Excluded:\n - spec/ameba/rule/lint/typos_spec.cr\n\nStyle/Elsif:\n Enabled: true\n"
},
{
"path": ".ameba.yml.schema.json",
"chars": 70464,
"preview": "{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"$id\": \"https://crystal-ameba.github.io/.ameba.yml.sche"
},
{
"path": ".dockerignore",
"chars": 50,
"preview": ".*\n!LICENSE\n!Dockerfile\n!Makefile\n!shard.yml\n!src\n"
},
{
"path": ".editorconfig",
"chars": 137,
"preview": "[*.cr]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\nindent_size = 2\ntrim_trailing_w"
},
{
"path": ".gitattributes",
"chars": 82,
"preview": "* text=auto eol=lf\n\n## Generated files\n\n.ameba.yml.schema.json linguist-generated\n"
},
{
"path": ".github/dependabot.yml",
"chars": 156,
"preview": "version: 2\nupdates:\n # Maintain dependencies for GitHub Actions\n - package-ecosystem: github-actions\n directory: /\n"
},
{
"path": ".github/workflows/add-docs-version-to-json-index.yml",
"chars": 1973,
"preview": "name: Add docs version to JSON index\n\non:\n workflow_call:\n inputs:\n version:\n required: true\n typ"
},
{
"path": ".github/workflows/build-and-deploy-docs.yml",
"chars": 1814,
"preview": "name: Build and deploy docs\n\non:\n workflow_dispatch:\n inputs: &inputs\n ref:\n required: true\n type"
},
{
"path": ".github/workflows/ci.yml",
"chars": 1021,
"preview": "name: CI\n\non:\n workflow_dispatch:\n push:\n branches: [master]\n pull_request:\n types: [opened, synchronize, reope"
},
{
"path": ".github/workflows/compare-json-schema.yml",
"chars": 739,
"preview": "name: Compare JSON Schema\n\non:\n pull_request:\n types: [opened, synchronize, reopened]\n paths:\n - \".github/wo"
},
{
"path": ".github/workflows/docker-image.yml",
"chars": 2175,
"preview": "name: Docker image build and deploy\n\non:\n workflow_dispatch:\n push:\n branches: [master]\n release:\n types: [publ"
},
{
"path": ".github/workflows/docs.yml",
"chars": 984,
"preview": "name: Docs\n\non:\n push:\n branches:\n - master\n tags:\n - \"v*.*.*\"\n\npermissions:\n contents: write\n\njobs:\n "
},
{
"path": ".github/workflows/release.yml",
"chars": 1879,
"preview": "name: Release\n\non:\n push:\n tags:\n - \"v*.*.*\"\n\npermissions:\n contents: write\n\nenv:\n GH_TOKEN: ${{ github.token"
},
{
"path": ".github/workflows/typos.yml",
"chars": 338,
"preview": "name: Spell checker\n\non:\n push:\n branches: [master]\n pull_request:\n types: [opened, synchronize, reopened]\n\nperm"
},
{
"path": ".github/workflows/update-json-schema.yml",
"chars": 1574,
"preview": "name: Update JSON Schema\n\non:\n workflow_dispatch:\n push:\n branches: [master]\n paths:\n - \".github/workflows/"
},
{
"path": ".gitignore",
"chars": 269,
"preview": "# Documentation\n/docs/\n\n# Installed shards\n/lib/\n\n# Built binaries\n/bin/**/*\n!/bin/ameba.cr\n\n# Libraries don't need depe"
},
{
"path": ".typos.toml",
"chars": 406,
"preview": "[default]\nextend-ignore-re = [\n # numeric literals\n '0x[0-9a-fA-F_\\.\\+]+([fiu](8|16|32|64|128))?',\n '\\\\u\\{[0-9a-fA-F]"
},
{
"path": "Dockerfile",
"chars": 409,
"preview": "FROM alpine:edge AS builder\nARG CRFLAGS=\"-Dpreview_mt\"\nRUN apk add --update crystal shards yaml-dev musl-dev make\nRUN mk"
},
{
"path": "LICENSE",
"chars": 1089,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2018-2020 Vitalii Elenhaupt\n\nPermission is hereby granted, free of charge, to any p"
},
{
"path": "Makefile",
"chars": 2099,
"preview": ".POSIX:\nall:\n\n# Recipes\n\n## Build ameba\n## $ make\n##\n## Run tests\n## $ make test\n##\n## Install ameba\n## $ sudo mak"
},
{
"path": "README.md",
"chars": 7836,
"preview": "<p align=\"center\">\n <img src=\"https://raw.githubusercontent.com/veelenga/bin/master/ameba/logo.png\" width=\"800\">\n <h3 "
},
{
"path": "bench/check_sources.cr",
"chars": 618,
"preview": "require \"../src/ameba\"\nrequire \"benchmark\"\n\nprivate def get_files(n)\n Dir[\"src/**/*.cr\"].first(n)\nend\n\nputs \"== Compare"
},
{
"path": "bin/ameba.cr",
"chars": 210,
"preview": "#!/usr/bin/env crystal\n\n# Require ameba extensions here which are added as project dependencies.\n# Example:\n#\n# require "
},
{
"path": "shard.yml",
"chars": 258,
"preview": "name: ameba\nversion: 1.7.0-dev\n\nauthors:\n - Vitalii Elenhaupt <velenhaupt@gmail.com>\n - Sijawusz Pur Rahnama <sija@sij"
},
{
"path": "spec/ameba/ast/flow_expression_spec.cr",
"chars": 2202,
"preview": "require \"../../spec_helper\"\n\nmodule Ameba::AST\n describe FlowExpression do\n describe \"#initialize\" do\n it \"crea"
},
{
"path": "spec/ameba/ast/liveness_analyzer_spec.cr",
"chars": 20482,
"preview": "require \"../../spec_helper\"\n\nprivate def scopes_for(code)\n rule = Ameba::ScopeRule.new\n source = Ameba::Source.new(cod"
},
{
"path": "spec/ameba/ast/scope_spec.cr",
"chars": 6450,
"preview": "require \"../../spec_helper\"\n\nmodule Ameba::AST\n describe Scope do\n describe \"#initialize\" do\n source = \"a = 2\"\n"
},
{
"path": "spec/ameba/ast/util_spec.cr",
"chars": 26036,
"preview": "require \"../../spec_helper\"\n\nmodule Ameba::AST\n struct Test\n include Util\n end\n\n describe Util do\n subject = Te"
},
{
"path": "spec/ameba/ast/variabling/argument_spec.cr",
"chars": 1096,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n describe Argument do\n arg = Crystal::Arg.new \"a\"\n scope = Scop"
},
{
"path": "spec/ameba/ast/variabling/assignment_spec.cr",
"chars": 973,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n describe Assignment do\n node = Crystal::NilLiteral.new\n scope "
},
{
"path": "spec/ameba/ast/variabling/reference_spec.cr",
"chars": 259,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n describe Reference do\n it \"is derived from a Variable\" do\n n"
},
{
"path": "spec/ameba/ast/variabling/type_dec_variable_spec.cr",
"chars": 865,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n describe TypeDecVariable do\n var = Crystal::Var.new(\"foo\")\n de"
},
{
"path": "spec/ameba/ast/variabling/variable_spec.cr",
"chars": 7563,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n describe Variable do\n var_node = Crystal::Var.new(\"foo\")\n scop"
},
{
"path": "spec/ameba/ast/visitors/counting_visitor_spec.cr",
"chars": 2799,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n describe CountingVisitor do\n describe \"#visit\" do\n it \"allow"
},
{
"path": "spec/ameba/ast/visitors/elseif_aware_node_visitor_spec.cr",
"chars": 1730,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n describe ElseIfAwareNodeVisitor do\n rule = ElseIfRule.new\n sub"
},
{
"path": "spec/ameba/ast/visitors/flow_expression_visitor_spec.cr",
"chars": 1557,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n describe FlowExpressionVisitor do\n it \"creates an expression for "
},
{
"path": "spec/ameba/ast/visitors/implicit_return_visitor_spec.cr",
"chars": 23138,
"preview": "require \"../../../spec_helper\"\n\nprivate def implicit_return_visit(code)\n Ameba::ImplicitReturnRule.new.tap do |rule|\n "
},
{
"path": "spec/ameba/ast/visitors/node_visitor_spec.cr",
"chars": 321,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n describe NodeVisitor do\n describe \"visit\" do\n it \"allow to v"
},
{
"path": "spec/ameba/ast/visitors/redundant_control_expression_visitor_spec.cr",
"chars": 633,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n describe RedundantControlExpressionVisitor do\n source = Source.ne"
},
{
"path": "spec/ameba/ast/visitors/scope_calls_with_self_receiver_visitor_spec.cr",
"chars": 3262,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n describe ScopeCallsWithSelfReceiverVisitor do\n {% for type in %w["
},
{
"path": "spec/ameba/ast/visitors/scope_visitor_spec.cr",
"chars": 3258,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n describe ScopeVisitor do\n {% for type in %w[class module enum].ma"
},
{
"path": "spec/ameba/ast/visitors/top_level_nodes_visitor_spec.cr",
"chars": 466,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n describe TopLevelNodesVisitor do\n describe \"#require_nodes\" do\n "
},
{
"path": "spec/ameba/base_spec.cr",
"chars": 2796,
"preview": "require \"../spec_helper\"\n\nmodule Ameba::Rule\n root = Path[__DIR__, \"..\", \"..\"].expand\n\n describe Base do\n context \""
},
{
"path": "spec/ameba/cli/cmd_spec.cr",
"chars": 8028,
"preview": "require \"../../spec_helper\"\nrequire \"../../../src/ameba/cli/cmd\"\n\nmodule Ameba::CLI\n root = Path[__DIR__, \"..\", \"..\", \""
},
{
"path": "spec/ameba/config_spec.cr",
"chars": 7601,
"preview": "require \"../spec_helper\"\n\nmodule Ameba\n root = Path[__DIR__, \"..\", \"..\"].expand\n\n describe Config do\n config_sample"
},
{
"path": "spec/ameba/ext/location_spec.cr",
"chars": 1630,
"preview": "require \"../../spec_helper\"\n\ndescribe Crystal::Location do\n subject = Crystal::Location.new(nil, 2, 3)\n\n describe \"#wi"
},
{
"path": "spec/ameba/formatter/disabled_formatter_spec.cr",
"chars": 1328,
"preview": "require \"../../spec_helper\"\n\nmodule Ameba::Formatter\n describe DisabledFormatter do\n output = IO::Memory.new\n sub"
},
{
"path": "spec/ameba/formatter/dot_formatter_spec.cr",
"chars": 3273,
"preview": "require \"../../spec_helper\"\n\nmodule Ameba::Formatter\n describe DotFormatter do\n output = IO::Memory.new\n subject "
},
{
"path": "spec/ameba/formatter/explain_formatter_spec.cr",
"chars": 2013,
"preview": "require \"../../spec_helper\"\n\nprivate LOCATION = Crystal::Location.new(\"source.cr\", 1, 1)\n\nprivate def explanation(source"
},
{
"path": "spec/ameba/formatter/flycheck_formatter_spec.cr",
"chars": 1337,
"preview": "require \"../../spec_helper\"\n\nmodule Ameba::Formatter\n describe FlycheckFormatter do\n output = IO::Memory.new\n sub"
},
{
"path": "spec/ameba/formatter/github_actions_formatter_spec.cr",
"chars": 3942,
"preview": "require \"../../spec_helper\"\n\nmodule Ameba::Formatter\n describe GitHubActionsFormatter do\n output = IO::Memory.new\n "
},
{
"path": "spec/ameba/formatter/json_formatter_spec.cr",
"chars": 3118,
"preview": "require \"../../spec_helper\"\n\nprivate def get_result(sources = [Ameba::Source.new])\n output = IO::Memory.new\n formatter"
},
{
"path": "spec/ameba/formatter/todo_formatter_spec.cr",
"chars": 5141,
"preview": "require \"../../spec_helper\"\nrequire \"file_utils\"\n\nprivate CONFIG_PATH =\n Path[Dir.tempdir] / Ameba::Config::Loader::FIL"
},
{
"path": "spec/ameba/formatter/util_spec.cr",
"chars": 6514,
"preview": "require \"../../spec_helper\"\n\nmodule Ameba::Formatter\n class Subject\n include Util\n end\n\n describe Util do\n subj"
},
{
"path": "spec/ameba/glob_utils_spec.cr",
"chars": 2308,
"preview": "require \"../spec_helper\"\n\nmodule Ameba\n subject = GlobUtils\n root = Path[__DIR__, \"..\", \"..\"].expand\n\n current_file_p"
},
{
"path": "spec/ameba/inline_comments_spec.cr",
"chars": 5519,
"preview": "require \"../spec_helper\"\n\nmodule Ameba\n describe InlineComments do\n describe InlineComments::COMMENT_DIRECTIVE_REGEX"
},
{
"path": "spec/ameba/issue_spec.cr",
"chars": 1677,
"preview": "require \"../spec_helper\"\n\nmodule Ameba\n describe Issue do\n it \"accepts rule and message\" do\n issue = Issue.new "
},
{
"path": "spec/ameba/presenter/rule_collection_presenter_spec.cr",
"chars": 777,
"preview": "require \"../../spec_helper\"\n\nmodule Ameba\n private def with_rule_collection_presenter(&)\n rules = Config.load.rules\n"
},
{
"path": "spec/ameba/presenter/rule_presenter_spec.cr",
"chars": 616,
"preview": "require \"../../spec_helper\"\n\nmodule Ameba\n private def rule_presenter_each_rule(&)\n rules = Config.load.rules\n\n r"
},
{
"path": "spec/ameba/presenter/rule_versions_presenter_spec.cr",
"chars": 788,
"preview": "require \"../../spec_helper\"\n\nmodule Ameba\n private def with_rule_versions_presenter(&)\n rules = Config.load.rules\n\n "
},
{
"path": "spec/ameba/reportable_spec.cr",
"chars": 1149,
"preview": "require \"../spec_helper\"\n\nmodule Ameba\n describe Reportable do\n describe \"#add_issue\" do\n it \"adds a new issue "
},
{
"path": "spec/ameba/rule/base_spec.cr",
"chars": 689,
"preview": "require \"../../spec_helper\"\n\nmodule Ameba\n describe Rule::Base do\n describe \"#catch\" do\n it \"accepts and return"
},
{
"path": "spec/ameba/rule/documentation/admonition_spec.cr",
"chars": 4054,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Documentation\n describe Admonition do\n subject = Admonition.new\n"
},
{
"path": "spec/ameba/rule/documentation/documentation_spec.cr",
"chars": 3858,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Documentation\n describe Documentation do\n subject = Documentatio"
},
{
"path": "spec/ameba/rule/layout/line_length_spec.cr",
"chars": 1221,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Layout\n describe LineLength do\n subject = LineLength.new\n lon"
},
{
"path": "spec/ameba/rule/layout/trailing_blank_lines_spec.cr",
"chars": 1888,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Layout\n describe TrailingBlankLines do\n subject = TrailingBlankL"
},
{
"path": "spec/ameba/rule/layout/trailing_whitespace_spec.cr",
"chars": 1209,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Layout\n describe TrailingWhitespace do\n subject = TrailingWhites"
},
{
"path": "spec/ameba/rule/lint/ambiguous_assignment_spec.cr",
"chars": 3323,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe AmbiguousAssignment do\n subject = AmbiguousAssign"
},
{
"path": "spec/ameba/rule/lint/assignment_in_call_argument_spec.cr",
"chars": 3443,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe AssignmentInCallArgument do\n subject = Assignment"
},
{
"path": "spec/ameba/rule/lint/bad_directive_spec.cr",
"chars": 853,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe BadDirective do\n subject = BadDirective.new\n\n "
},
{
"path": "spec/ameba/rule/lint/comparison_to_boolean_spec.cr",
"chars": 3493,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe ComparisonToBoolean do\n subject = ComparisonToBoo"
},
{
"path": "spec/ameba/rule/lint/debug_calls_spec.cr",
"chars": 748,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe DebugCalls do\n subject = DebugCalls.new\n\n it \""
},
{
"path": "spec/ameba/rule/lint/debugger_statement_spec.cr",
"chars": 813,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe DebuggerStatement do\n subject = DebuggerStatement"
},
{
"path": "spec/ameba/rule/lint/duplicate_branch_spec.cr",
"chars": 7118,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe DuplicateBranch do\n subject = DuplicateBranch.new"
},
{
"path": "spec/ameba/rule/lint/duplicate_enum_value_spec.cr",
"chars": 1138,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe DuplicateEnumValue do\n subject = DuplicateEnumVal"
},
{
"path": "spec/ameba/rule/lint/duplicate_method_signature_spec.cr",
"chars": 3029,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe DuplicateMethodSignature do\n subject = DuplicateM"
},
{
"path": "spec/ameba/rule/lint/duplicate_when_condition_spec.cr",
"chars": 1557,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe DuplicateWhenCondition do\n subject = DuplicateWhe"
},
{
"path": "spec/ameba/rule/lint/duplicated_require_spec.cr",
"chars": 625,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe DuplicatedRequire do\n subject = DuplicatedRequire"
},
{
"path": "spec/ameba/rule/lint/else_nil_spec.cr",
"chars": 1657,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe ElseNil do\n subject = ElseNil.new\n\n {% for key"
},
{
"path": "spec/ameba/rule/lint/empty_ensure_spec.cr",
"chars": 1368,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe EmptyEnsure do\n subject = EmptyEnsure.new\n\n it"
},
{
"path": "spec/ameba/rule/lint/empty_expression_spec.cr",
"chars": 2246,
"preview": "require \"../../../spec_helper\"\n\nprivate def it_detects_empty_expression(code, *, file = __FILE__, line = __LINE__)\n it "
},
{
"path": "spec/ameba/rule/lint/empty_loop_spec.cr",
"chars": 1419,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe EmptyLoop do\n subject = EmptyLoop.new\n\n it \"do"
},
{
"path": "spec/ameba/rule/lint/enum_member_name_conflict_spec.cr",
"chars": 981,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe EnumMemberNameConflict do\n subject = EnumMemberNa"
},
{
"path": "spec/ameba/rule/lint/formatting_spec.cr",
"chars": 1284,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe Formatting do\n subject = Formatting.new\n\n it \""
},
{
"path": "spec/ameba/rule/lint/hash_duplicated_key_spec.cr",
"chars": 1276,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe HashDuplicatedKey do\n subject = HashDuplicatedKey"
},
{
"path": "spec/ameba/rule/lint/literal_assignments_in_expressions_spec.cr",
"chars": 1314,
"preview": "require \"../../../spec_helper\"\n\nLITERAL_SAMPLES = {\n nil, true, 42, 4.2, 'c', \"foo\", :foo, /foo/,\n 0..42, [1, 2, 3], {"
},
{
"path": "spec/ameba/rule/lint/literal_in_condition_spec.cr",
"chars": 5172,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe LiteralInCondition do\n subject = LiteralInConditi"
},
{
"path": "spec/ameba/rule/lint/literal_in_interpolation_spec.cr",
"chars": 1180,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe LiteralInInterpolation do\n subject = LiteralInInt"
},
{
"path": "spec/ameba/rule/lint/literals_comparison_spec.cr",
"chars": 2139,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe LiteralsComparison do\n subject = LiteralsComparis"
},
{
"path": "spec/ameba/rule/lint/missing_block_argument_spec.cr",
"chars": 1053,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe MissingBlockArgument do\n subject = MissingBlockAr"
},
{
"path": "spec/ameba/rule/lint/non_existent_rule_spec.cr",
"chars": 1053,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe NonExistentRule do\n subject = NonExistentRule.new"
},
{
"path": "spec/ameba/rule/lint/not_nil_after_no_bang_spec.cr",
"chars": 3242,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe NotNilAfterNoBang do\n subject = NotNilAfterNoBang"
},
{
"path": "spec/ameba/rule/lint/not_nil_spec.cr",
"chars": 909,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe NotNil do\n subject = NotNil.new\n\n it \"passes f"
},
{
"path": "spec/ameba/rule/lint/percent_arrays_spec.cr",
"chars": 1647,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe PercentArrays do\n subject = PercentArrays.new\n\n "
},
{
"path": "spec/ameba/rule/lint/rand_zero_spec.cr",
"chars": 623,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe RandZero do\n subject = RandZero.new\n\n it \"pass"
},
{
"path": "spec/ameba/rule/lint/redundant_string_cercion_spec.cr",
"chars": 1532,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe RedundantStringCoercion do\n subject = RedundantSt"
},
{
"path": "spec/ameba/rule/lint/redundant_with_index_spec.cr",
"chars": 3519,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe RedundantWithIndex do\n subject = RedundantWithInd"
},
{
"path": "spec/ameba/rule/lint/redundant_with_object_spec.cr",
"chars": 1742,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe RedundantWithObject do\n subject = RedundantWithOb"
},
{
"path": "spec/ameba/rule/lint/require_parentheses_spec.cr",
"chars": 1753,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe RequireParentheses do\n subject = RequireParenthes"
},
{
"path": "spec/ameba/rule/lint/self_initialize_definition_spec.cr",
"chars": 1656,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe SelfInitializeDefinition do\n subject = SelfInitia"
},
{
"path": "spec/ameba/rule/lint/shadowed_argument_spec.cr",
"chars": 5103,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe ShadowedArgument do\n subject = ShadowedArgument.n"
},
{
"path": "spec/ameba/rule/lint/shadowed_exception_spec.cr",
"chars": 5463,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe ShadowedException do\n subject = ShadowedException"
},
{
"path": "spec/ameba/rule/lint/shadowing_outer_local_var_spec.cr",
"chars": 7268,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe ShadowingOuterLocalVar do\n subject = ShadowingOut"
},
{
"path": "spec/ameba/rule/lint/shared_var_in_fiber_spec.cr",
"chars": 4954,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe SharedVarInFiber do\n subject = SharedVarInFiber.n"
},
{
"path": "spec/ameba/rule/lint/signal_trap_spec.cr",
"chars": 1244,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe SignalTrap do\n subject = SignalTrap.new\n\n it \""
},
{
"path": "spec/ameba/rule/lint/spec_eq_with_bool_or_nil_literal_spec.cr",
"chars": 2502,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe SpecEqWithBoolOrNilLiteral do\n subject = SpecEqWi"
},
{
"path": "spec/ameba/rule/lint/spec_filename_spec.cr",
"chars": 1545,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe SpecFilename do\n subject = SpecFilename.new\n\n "
},
{
"path": "spec/ameba/rule/lint/spec_focus_spec.cr",
"chars": 3509,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe SpecFocus do\n subject = SpecFocus.new\n\n it \"do"
},
{
"path": "spec/ameba/rule/lint/syntax_spec.cr",
"chars": 696,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe Syntax do\n subject = Syntax.new\n\n it \"passes i"
},
{
"path": "spec/ameba/rule/lint/top_level_operator_definition_spec.cr",
"chars": 1754,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe TopLevelOperatorDefinition do\n subject = TopLevel"
},
{
"path": "spec/ameba/rule/lint/trailing_rescue_exception_spec.cr",
"chars": 776,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe TrailingRescueException do\n subject = TrailingRes"
},
{
"path": "spec/ameba/rule/lint/typos_spec.cr",
"chars": 817,
"preview": "require \"../../../spec_helper\"\n\nprivate def check_typos_bin!\n unless Ameba::Rule::Lint::Typos::BIN_PATH\n pending! \"`"
},
{
"path": "spec/ameba/rule/lint/unneeded_disable_directive_spec.cr",
"chars": 2762,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe UnneededDisableDirective do\n subject = UnneededDi"
},
{
"path": "spec/ameba/rule/lint/unreachable_code_spec.cr",
"chars": 14357,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe UnreachableCode do\n subject = UnreachableCode.new"
},
{
"path": "spec/ameba/rule/lint/unused_argument_spec.cr",
"chars": 8022,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe UnusedArgument do\n subject = UnusedArgument.new\n "
},
{
"path": "spec/ameba/rule/lint/unused_block_argument_spec.cr",
"chars": 3277,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe UnusedBlockArgument do\n subject = UnusedBlockArgu"
},
{
"path": "spec/ameba/rule/lint/unused_expression_spec.cr",
"chars": 31720,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe UnusedExpression do\n subject = UnusedExpression.n"
},
{
"path": "spec/ameba/rule/lint/unused_rescue_variable_spec.cr",
"chars": 5116,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe UnusedRescueVariable do\n subject = UnusedRescueVa"
},
{
"path": "spec/ameba/rule/lint/useless_assign_spec.cr",
"chars": 37931,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe UselessAssign do\n subject = UselessAssign.new\n\n "
},
{
"path": "spec/ameba/rule/lint/useless_condition_in_when_spec.cr",
"chars": 670,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe UselessConditionInWhen do\n subject = UselessCondi"
},
{
"path": "spec/ameba/rule/lint/useless_visibility_modifier_spec.cr",
"chars": 1501,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe UselessVisibilityModifier do\n subject = UselessVi"
},
{
"path": "spec/ameba/rule/lint/void_outside_lib_spec.cr",
"chars": 2704,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe VoidOutsideLib do\n subject = VoidOutsideLib.new\n\n"
},
{
"path": "spec/ameba/rule/lint/whitespace_around_macro_expression_spec.cr",
"chars": 1692,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n describe WhitespaceAroundMacroExpression do\n subject = Whi"
},
{
"path": "spec/ameba/rule/metrics/cyclomatic_complexity_spec.cr",
"chars": 1206,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Metrics\n describe CyclomaticComplexity do\n subject = CyclomaticC"
},
{
"path": "spec/ameba/rule/naming/accessor_method_name_spec.cr",
"chars": 2999,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Naming\n describe AccessorMethodName do\n subject = AccessorMethod"
},
{
"path": "spec/ameba/rule/naming/ascii_identifiers_spec.cr",
"chars": 4958,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Naming\n describe AsciiIdentifiers do\n subject = AsciiIdentifiers"
},
{
"path": "spec/ameba/rule/naming/binary_operator_parameter_name_spec.cr",
"chars": 2301,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Naming\n describe BinaryOperatorParameterName do\n subject = Binar"
},
{
"path": "spec/ameba/rule/naming/block_parameter_name_spec.cr",
"chars": 3050,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Naming\n describe BlockParameterName do\n subject = BlockParameter"
},
{
"path": "spec/ameba/rule/naming/constant_names_spec.cr",
"chars": 1281,
"preview": "require \"../../../spec_helper\"\n\nprivate def it_reports_constant(name, value, expected, *, file = __FILE__, line = __LINE"
},
{
"path": "spec/ameba/rule/naming/filename_spec.cr",
"chars": 511,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Naming\n describe Filename do\n subject = Filename.new\n\n it \"pa"
},
{
"path": "spec/ameba/rule/naming/method_names_spec.cr",
"chars": 1049,
"preview": "require \"../../../spec_helper\"\n\nprivate def it_reports_method_name(name, expected, *, file = __FILE__, line = __LINE__)\n"
},
{
"path": "spec/ameba/rule/naming/predicate_name_spec.cr",
"chars": 1101,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Naming\n describe PredicateName do\n subject = PredicateName.new\n\n"
},
{
"path": "spec/ameba/rule/naming/query_bool_methods_spec.cr",
"chars": 2050,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Naming\n describe QueryBoolMethods do\n subject = QueryBoolMethods"
},
{
"path": "spec/ameba/rule/naming/rescued_exceptions_variable_name_spec.cr",
"chars": 1457,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Naming\n describe RescuedExceptionsVariableName do\n subject = Res"
},
{
"path": "spec/ameba/rule/naming/type_names_spec.cr",
"chars": 1476,
"preview": "require \"../../../spec_helper\"\n\nprivate def it_reports_name(type, name, expected, *, file = __FILE__, line = __LINE__)\n "
},
{
"path": "spec/ameba/rule/naming/variable_names_spec.cr",
"chars": 2172,
"preview": "require \"../../../spec_helper\"\n\nprivate def it_reports_var_name(name, value, expected, *, file = __FILE__, line = __LINE"
},
{
"path": "spec/ameba/rule/performance/any_after_filter_spec.cr",
"chars": 2203,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n describe AnyAfterFilter do\n subject = AnyAfterFilte"
},
{
"path": "spec/ameba/rule/performance/any_instead_of_present_spec.cr",
"chars": 1116,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n describe AnyInsteadOfPresent do\n subject = AnyInste"
},
{
"path": "spec/ameba/rule/performance/base_spec.cr",
"chars": 454,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n describe Base do\n subject = PerfRule.new\n\n descr"
},
{
"path": "spec/ameba/rule/performance/chained_call_with_no_bang_spec.cr",
"chars": 2046,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n describe ChainedCallWithNoBang do\n subject = Chaine"
},
{
"path": "spec/ameba/rule/performance/compact_after_map_spec.cr",
"chars": 1283,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n describe CompactAfterMap do\n subject = CompactAfter"
},
{
"path": "spec/ameba/rule/performance/excessive_allocations_spec.cr",
"chars": 1944,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n describe ExcessiveAllocations do\n subject = Excessi"
},
{
"path": "spec/ameba/rule/performance/first_last_after_filter_spec.cr",
"chars": 2791,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n describe FirstLastAfterFilter do\n subject = FirstLa"
},
{
"path": "spec/ameba/rule/performance/flatten_after_map_spec.cr",
"chars": 1117,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n describe FlattenAfterMap do\n subject = FlattenAfter"
},
{
"path": "spec/ameba/rule/performance/map_instead_of_block_spec.cr",
"chars": 1590,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n describe MapInsteadOfBlock do\n subject = MapInstead"
},
{
"path": "spec/ameba/rule/performance/minmax_after_map_spec.cr",
"chars": 1723,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n describe MinMaxAfterMap do\n subject = MinMaxAfterMa"
},
{
"path": "spec/ameba/rule/performance/size_after_filter_spec.cr",
"chars": 2082,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n describe SizeAfterFilter do\n subject = SizeAfterFil"
},
{
"path": "spec/ameba/rule/performance/times_map_spec.cr",
"chars": 1732,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n describe TimesMap do\n subject = TimesMap.new\n\n i"
},
{
"path": "spec/ameba/rule/style/array_literal_syntax_spec.cr",
"chars": 1434,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe ArrayLiteralSyntax do\n subject = ArrayLiteralSyn"
},
{
"path": "spec/ameba/rule/style/call_parentheses_spec.cr",
"chars": 8847,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe CallParentheses do\n subject = CallParentheses.ne"
},
{
"path": "spec/ameba/rule/style/elsif_spec.cr",
"chars": 2598,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe Elsif do\n subject = Elsif.new\n\n it \"does not "
},
{
"path": "spec/ameba/rule/style/guard_clause_spec.cr",
"chars": 10300,
"preview": "require \"../../../spec_helper\"\n\nprivate def it_reports_body(body, *, file = __FILE__, line = __LINE__)\n rule = Ameba::R"
},
{
"path": "spec/ameba/rule/style/hash_literal_syntax_spec.cr",
"chars": 1218,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe HashLiteralSyntax do\n subject = HashLiteralSynta"
},
{
"path": "spec/ameba/rule/style/heredoc_escape_spec.cr",
"chars": 3724,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe HeredocEscape do\n subject = HeredocEscape.new\n\n "
},
{
"path": "spec/ameba/rule/style/heredoc_indent_spec.cr",
"chars": 5249,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe HeredocIndent do\n subject = HeredocIndent.new\n\n "
},
{
"path": "spec/ameba/rule/style/is_a_filter_spec.cr",
"chars": 1755,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe IsAFilter do\n subject = IsAFilter.new\n\n it \"p"
},
{
"path": "spec/ameba/rule/style/is_a_nil_spec.cr",
"chars": 938,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe IsANil do\n subject = IsANil.new\n\n it \"doesn't"
},
{
"path": "spec/ameba/rule/style/large_numbers_spec.cr",
"chars": 3240,
"preview": "require \"../../../spec_helper\"\n\nprivate def it_transforms(number, expected, *, file = __FILE__, line = __LINE__)\n it \"t"
},
{
"path": "spec/ameba/rule/style/multiline_curly_block_spec.cr",
"chars": 1133,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe MultilineCurlyBlock do\n subject = MultilineCurly"
},
{
"path": "spec/ameba/rule/style/multiline_string_literal_spec.cr",
"chars": 3652,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe MultilineStringLiteral do\n subject = MultilineSt"
},
{
"path": "spec/ameba/rule/style/negated_conditions_in_unless_spec.cr",
"chars": 1366,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe NegatedConditionsInUnless do\n subject = NegatedC"
},
{
"path": "spec/ameba/rule/style/parentheses_around_condition_spec.cr",
"chars": 5805,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe ParenthesesAroundCondition do\n subject = Parenth"
},
{
"path": "spec/ameba/rule/style/percent_literal_delimiters_spec.cr",
"chars": 5080,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe PercentLiteralDelimiters do\n subject = PercentLi"
},
{
"path": "spec/ameba/rule/style/redundant_begin_spec.cr",
"chars": 6627,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe RedundantBegin do\n subject = RedundantBegin.new\n"
},
{
"path": "spec/ameba/rule/style/redundant_next_spec.cr",
"chars": 6000,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe RedundantNext do\n subject = RedundantNext.new\n\n "
},
{
"path": "spec/ameba/rule/style/redundant_nil_in_control_expression_spec.cr",
"chars": 2478,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe RedundantNilInControlExpression do\n subject = Re"
},
{
"path": "spec/ameba/rule/style/redundant_return_spec.cr",
"chars": 8249,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe RedundantReturn do\n subject = RedundantReturn.ne"
},
{
"path": "spec/ameba/rule/style/redundant_self_spec.cr",
"chars": 8721,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe RedundantSelf do\n subject = RedundantSelf.new\n\n "
},
{
"path": "spec/ameba/rule/style/unless_else_spec.cr",
"chars": 678,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe UnlessElse do\n subject = UnlessElse.new\n\n it "
},
{
"path": "spec/ameba/rule/style/verbose_block_spec.cr",
"chars": 10266,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe VerboseBlock do\n subject = VerboseBlock.new\n\n "
},
{
"path": "spec/ameba/rule/style/verbose_nil_type_spec.cr",
"chars": 4216,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe VerboseNilType do\n subject = VerboseNilType.new\n"
},
{
"path": "spec/ameba/rule/style/while_true_spec.cr",
"chars": 742,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n describe WhileTrue do\n subject = WhileTrue.new\n\n it \"p"
},
{
"path": "spec/ameba/rule/typing/macro_call_argument_type_restriction_spec.cr",
"chars": 2037,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Typing\n describe MacroCallArgumentTypeRestriction do\n subject = "
},
{
"path": "spec/ameba/rule/typing/method_parameter_type_restriction_spec.cr",
"chars": 6651,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Typing\n describe MethodParameterTypeRestriction do\n subject = Me"
},
{
"path": "spec/ameba/rule/typing/method_return_type_restriction_spec.cr",
"chars": 4682,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Typing\n describe MethodReturnTypeRestriction do\n subject = Metho"
},
{
"path": "spec/ameba/rule/typing/proc_literal_return_type_restriction_spec.cr",
"chars": 613,
"preview": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Typing\n describe ProcLiteralReturnTypeRestriction do\n subject = "
},
{
"path": "spec/ameba/runner_spec.cr",
"chars": 12182,
"preview": "require \"../spec_helper\"\n\nmodule Ameba\n private def runner(files = [__FILE__], formatter = DummyFormatter.new)\n conf"
},
{
"path": "spec/ameba/severity_spec.cr",
"chars": 3229,
"preview": "require \"../spec_helper\"\n\nmodule Ameba\n describe Severity do\n describe \"#symbol\" do\n it \"returns the symbol for"
},
{
"path": "spec/ameba/source/rewriter_spec.cr",
"chars": 3241,
"preview": "require \"../../spec_helper\"\n\nclass Ameba::Source\n describe Rewriter do\n code = \"puts(:hello, :world)\"\n hello = {5"
},
{
"path": "spec/ameba/source_spec.cr",
"chars": 3009,
"preview": "require \"../spec_helper\"\n\nmodule Ameba\n describe Source do\n describe \".new\" do\n it \"allows to create a source b"
},
{
"path": "spec/ameba/spec/annotated_source_spec.cr",
"chars": 6875,
"preview": "require \"../../spec_helper\"\n\nprivate def dummy_issue(code,\n message,\n posi"
},
{
"path": "spec/ameba/tokenizer_spec.cr",
"chars": 1449,
"preview": "require \"../spec_helper\"\n\nmodule Ameba\n private def it_tokenizes(str, expected, *, file = __FILE__, line = __LINE__)\n "
},
{
"path": "spec/ameba/version_spec.cr",
"chars": 2554,
"preview": "require \"../spec_helper\"\n\nprivate def build_ameba_version(string)\n Ameba::Version.new(SemanticVersion.parse(string))\nen"
},
{
"path": "spec/ameba_spec.cr",
"chars": 419,
"preview": "require \"./spec_helper\"\n\ndescribe Ameba do\n context \"VERSION\" do\n it \"contains a version-like string\" do\n Ameba"
},
{
"path": "spec/fixtures/.ameba.yml",
"chars": 89,
"preview": "Version: \"1.5.0\"\n\nFormatter:\n Name: flycheck\n\nLint/ComparisonToBoolean:\n Enabled: true\n"
},
{
"path": "spec/spec_helper.cr",
"chars": 8828,
"preview": "require \"spec\"\nrequire \"../src/ameba\"\nrequire \"../src/ameba/spec/support\"\n\nmodule Ameba\n # Dummy Rule which does nothin"
},
{
"path": "src/ameba/ast/flow_expression.cr",
"chars": 1642,
"preview": "require \"./util\"\n\nmodule Ameba::AST\n # Represents a flow expression in Crystal code.\n # For example,\n #\n # ```\n # d"
},
{
"path": "src/ameba/ast/liveness_analyzer.cr",
"chars": 14700,
"preview": "module Ameba::AST\n # Performs backward dataflow liveness analysis on a scope's AST to detect\n # dead stores (assignmen"
},
{
"path": "src/ameba/ast/scope.cr",
"chars": 6854,
"preview": "require \"./variabling/*\"\n\nmodule Ameba::AST\n # Represents a context of the local variable visibility.\n # This is where"
},
{
"path": "src/ameba/ast/util.cr",
"chars": 11528,
"preview": "# Utility module for Ameba's rules.\nmodule Ameba::AST::Util\n extend self\n\n # Returns tuple with two bool flags:\n #\n "
},
{
"path": "src/ameba/ast/variabling/argument.cr",
"chars": 1119,
"preview": "module Ameba::AST\n # Represents the argument of some node.\n # Holds the reference to the variable, thus to scope.\n #\n"
},
{
"path": "src/ameba/ast/variabling/assignment.cr",
"chars": 1254,
"preview": "require \"./reference\"\nrequire \"./variable\"\n\nmodule Ameba::AST\n # Represents the assignment to the variable.\n # Holds t"
},
{
"path": "src/ameba/ast/variabling/ivariable.cr",
"chars": 193,
"preview": "module Ameba::AST\n class InstanceVariable\n getter node : Crystal::InstanceVar\n\n delegate location, end_location, "
},
{
"path": "src/ameba/ast/variabling/reference.cr",
"chars": 246,
"preview": "require \"./variable\"\n\nmodule Ameba::AST\n # Represents a reference to the variable.\n # It behaves like a variable is us"
},
{
"path": "src/ameba/ast/variabling/type_dec_variable.cr",
"chars": 415,
"preview": "module Ameba::AST\n class TypeDecVariable\n getter node : Crystal::TypeDeclaration\n\n delegate location, end_locatio"
},
{
"path": "src/ameba/ast/variabling/variable.cr",
"chars": 4119,
"preview": "module Ameba::AST\n # Represents the existence of the local variable.\n # Holds the var node and variable assignments.\n "
},
{
"path": "src/ameba/ast/visitors/base_visitor.cr",
"chars": 780,
"preview": "require \"compiler/crystal/syntax/*\"\n\n# A module that helps to traverse Crystal AST using `Crystal::Visitor`.\nmodule Ameb"
},
{
"path": "src/ameba/ast/visitors/counting_visitor.cr",
"chars": 1568,
"preview": "module Ameba::AST\n # AST Visitor that counts occurrences of certain keywords\n class CountingVisitor < Crystal::Visitor"
},
{
"path": "src/ameba/ast/visitors/elseif_aware_node_visitor.cr",
"chars": 1841,
"preview": "require \"./node_visitor\"\n\nmodule Ameba::AST\n # A class that utilizes a logic inherited from `NodeVisitor` to traverse A"
},
{
"path": "src/ameba/ast/visitors/flow_expression_visitor.cr",
"chars": 1051,
"preview": "require \"../util\"\nrequire \"./base_visitor\"\n\nmodule Ameba::AST\n # AST Visitor that traverses all the flow expressions.\n "
}
]
// ... and 162 more files (download for full content)
About this extraction
This page contains the full source code of the veelenga/ameba GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 362 files (1.0 MB), approximately 289.8k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.