[
  {
    "path": ".ameba.yml",
    "content": "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",
    "content": "{\n  \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n  \"$id\": \"https://crystal-ameba.github.io/.ameba.yml.schema.json\",\n  \"title\": \".ameba.yml\",\n  \"description\": \"Configuration rules for the Crystal language Ameba linter\",\n  \"type\": \"object\",\n  \"additionalProperties\": false,\n  \"$defs\": {\n    \"Severity\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"Error\",\n        \"Warning\",\n        \"Convention\"\n      ]\n    },\n    \"Globs\": {\n      \"type\": \"array\",\n      \"title\": \"Globbed files and paths\",\n      \"description\": \"An array of wildcards (or paths) to include to the inspection\",\n      \"items\": {\n        \"type\": \"string\",\n        \"examples\": [\n          \"src/**/*.{cr,ecr}\",\n          \"!lib\"\n        ]\n      }\n    },\n    \"Excluded\": {\n      \"type\": \"array\",\n      \"title\": \"Excluded files and paths\",\n      \"description\": \"An array of wildcards (or paths) to exclude from the source list\",\n      \"items\": {\n        \"type\": \"string\",\n        \"examples\": [\n          \"spec/fixtures/**\",\n          \"spec/**/*.manual_spec.cr\"\n        ]\n      }\n    },\n    \"BaseRule\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\"\n        },\n        \"Enabled\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Convention\"\n        },\n        \"Excluded\": {\n          \"$ref\": \"#/$defs/Excluded\"\n        }\n      }\n    }\n  },\n  \"properties\": {\n    \"Version\": {\n      \"type\": \"string\",\n      \"description\": \"The version of Ameba to limit rules to\",\n      \"examples\": [\n        \"1.7.0\",\n        \"1.6.4\"\n      ]\n    },\n    \"Formatter\": {\n      \"type\": \"object\",\n      \"description\": \"The formatter to use for Ameba\",\n      \"properties\": {\n        \"Name\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"progress\",\n            \"todo\",\n            \"flycheck\",\n            \"silent\",\n            \"disabled\",\n            \"json\",\n            \"github-actions\"\n          ]\n        }\n      }\n    },\n    \"Globs\": {\n      \"$ref\": \"#/$defs/Globs\"\n    },\n    \"Excluded\": {\n      \"$ref\": \"#/$defs/Excluded\"\n    },\n    \"Documentation/Admonition\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Documentation/Admonition.html\",\n      \"title\": \"Documentation/Admonition\",\n      \"description\": \"Reports documentation admonitions\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.6.0\"\n        },\n        \"Enabled\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        },\n        \"Admonitions\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": [\n            \"TODO\",\n            \"FIXME\",\n            \"BUG\"\n          ]\n        },\n        \"Timezone\": {\n          \"type\": \"string\",\n          \"default\": \"UTC\"\n        }\n      }\n    },\n    \"Documentation/Documentation\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Documentation/Documentation.html\",\n      \"title\": \"Documentation/Documentation\",\n      \"description\": \"Enforces public types to be documented\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.5.0\"\n        },\n        \"Enabled\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"IgnoreClasses\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"IgnoreModules\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        },\n        \"IgnoreEnums\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"IgnoreDefs\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        },\n        \"IgnoreMacros\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"IgnoreMacroHooks\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        },\n        \"RequireExample\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        }\n      }\n    },\n    \"Layout/LineLength\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Layout/LineLength.html\",\n      \"title\": \"Layout/LineLength\",\n      \"description\": \"Disallows lines longer than `MaxLength` number of symbols\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.1.0\"\n        },\n        \"Enabled\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"MaxLength\": {\n          \"type\": \"number\",\n          \"default\": 140\n        }\n      }\n    },\n    \"Layout/TrailingBlankLines\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Layout/TrailingBlankLines.html\",\n      \"title\": \"Layout/TrailingBlankLines\",\n      \"description\": \"Disallows trailing blank lines\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.1.0\"\n        }\n      }\n    },\n    \"Layout/TrailingWhitespace\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Layout/TrailingWhitespace.html\",\n      \"title\": \"Layout/TrailingWhitespace\",\n      \"description\": \"Disallows trailing whitespace\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.1.0\"\n        }\n      }\n    },\n    \"Lint/AmbiguousAssignment\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/AmbiguousAssignment.html\",\n      \"title\": \"Lint/AmbiguousAssignment\",\n      \"description\": \"Disallows ambiguous `=-/=+/=!`\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.0.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/AssignmentInCallArgument\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/AssignmentInCallArgument.html\",\n      \"title\": \"Lint/AssignmentInCallArgument\",\n      \"description\": \"Disallows variable assignment in call arguments\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/BadDirective\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/BadDirective.html\",\n      \"title\": \"Lint/BadDirective\",\n      \"description\": \"Reports bad comment directives\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.13.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/ComparisonToBoolean\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/ComparisonToBoolean.html\",\n      \"title\": \"Lint/ComparisonToBoolean\",\n      \"description\": \"Disallows comparison to booleans\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.1.0\"\n        },\n        \"Enabled\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/DebugCalls\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/DebugCalls.html\",\n      \"title\": \"Lint/DebugCalls\",\n      \"description\": \"Disallows debug-related calls\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.0.0\"\n        },\n        \"MethodNames\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": [\n            \"p\",\n            \"p!\",\n            \"pp\",\n            \"pp!\"\n          ]\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/DebuggerStatement\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/DebuggerStatement.html\",\n      \"title\": \"Lint/DebuggerStatement\",\n      \"description\": \"Disallows calls to `debugger`\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.1.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/DuplicateBranch\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/DuplicateBranch.html\",\n      \"title\": \"Lint/DuplicateBranch\",\n      \"description\": \"Reports duplicated branch bodies\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Enabled\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"IgnoreLiteralBranches\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"IgnoreConstantBranches\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"IgnoreDuplicateElseBranch\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/DuplicateEnumValue\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/DuplicateEnumValue.html\",\n      \"title\": \"Lint/DuplicateEnumValue\",\n      \"description\": \"Reports duplicated `enum` member values\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/DuplicateMethodSignature\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/DuplicateMethodSignature.html\",\n      \"title\": \"Lint/DuplicateMethodSignature\",\n      \"description\": \"Reports repeated method signatures\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/DuplicateWhenCondition\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/DuplicateWhenCondition.html\",\n      \"title\": \"Lint/DuplicateWhenCondition\",\n      \"description\": \"Reports repeated conditions used in case `when` expressions\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/DuplicatedRequire\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/DuplicatedRequire.html\",\n      \"title\": \"Lint/DuplicatedRequire\",\n      \"description\": \"Reports duplicated `require` statements\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.14.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/ElseNil\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/ElseNil.html\",\n      \"title\": \"Lint/ElseNil\",\n      \"description\": \"Disallows `else` blocks with `nil` as their body\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/EmptyEnsure\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/EmptyEnsure.html\",\n      \"title\": \"Lint/EmptyEnsure\",\n      \"description\": \"Disallows empty `ensure` statement\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.3.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/EmptyExpression\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/EmptyExpression.html\",\n      \"title\": \"Lint/EmptyExpression\",\n      \"description\": \"Disallows empty expressions\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.2.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/EmptyLoop\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/EmptyLoop.html\",\n      \"title\": \"Lint/EmptyLoop\",\n      \"description\": \"Disallows empty loops\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.12.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/EnumMemberNameConflict\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/EnumMemberNameConflict.html\",\n      \"title\": \"Lint/EnumMemberNameConflict\",\n      \"description\": \"Reports conflicting enum member names\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/Formatting\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/Formatting.html\",\n      \"title\": \"Lint/Formatting\",\n      \"description\": \"Reports not formatted sources\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.4.0\"\n        },\n        \"FailOnError\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/HashDuplicatedKey\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/HashDuplicatedKey.html\",\n      \"title\": \"Lint/HashDuplicatedKey\",\n      \"description\": \"Disallows duplicated keys in hash literals\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.3.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/LiteralAssignmentsInExpressions\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/LiteralAssignmentsInExpressions.html\",\n      \"title\": \"Lint/LiteralAssignmentsInExpressions\",\n      \"description\": \"Disallows assignments with literal values in control expressions\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.4.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/LiteralInCondition\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/LiteralInCondition.html\",\n      \"title\": \"Lint/LiteralInCondition\",\n      \"description\": \"Disallows useless conditional statements that contain a literal in place of a variable or predicate function\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.1.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/LiteralInInterpolation\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/LiteralInInterpolation.html\",\n      \"title\": \"Lint/LiteralInInterpolation\",\n      \"description\": \"Disallows useless string interpolations\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.1.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/LiteralsComparison\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/LiteralsComparison.html\",\n      \"title\": \"Lint/LiteralsComparison\",\n      \"description\": \"Identifies comparisons between literals\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.3.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/MissingBlockArgument\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/MissingBlockArgument.html\",\n      \"title\": \"Lint/MissingBlockArgument\",\n      \"description\": \"Disallows yielding method definitions without block argument\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.4.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/NonExistentRule\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/NonExistentRule.html\",\n      \"title\": \"Lint/NonExistentRule\",\n      \"description\": \"Reports non-existent rules in comment directives\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.13.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/NotNil\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/NotNil.html\",\n      \"title\": \"Lint/NotNil\",\n      \"description\": \"Identifies usage of `not_nil!` calls\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.3.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/NotNilAfterNoBang\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/NotNilAfterNoBang.html\",\n      \"title\": \"Lint/NotNilAfterNoBang\",\n      \"description\": \"Identifies usage of `index/rindex/find/match` calls followed by `not_nil!`\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.3.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/PercentArrays\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/PercentArrays.html\",\n      \"title\": \"Lint/PercentArrays\",\n      \"description\": \"Disallows some unwanted symbols in percent array literals\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.3.0\"\n        },\n        \"StringArrayUnwantedSymbols\": {\n          \"type\": \"string\",\n          \"default\": \",\\\"\"\n        },\n        \"SymbolArrayUnwantedSymbols\": {\n          \"type\": \"string\",\n          \"default\": \",:\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/RandZero\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/RandZero.html\",\n      \"title\": \"Lint/RandZero\",\n      \"description\": \"Disallows `rand` zero calls\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.5.1\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/RedundantStringCoercion\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/RedundantStringCoercion.html\",\n      \"title\": \"Lint/RedundantStringCoercion\",\n      \"description\": \"Disallows redundant string conversions in interpolation\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.12.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/RedundantWithIndex\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/RedundantWithIndex.html\",\n      \"title\": \"Lint/RedundantWithIndex\",\n      \"description\": \"Disallows redundant `with_index` calls\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.11.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/RedundantWithObject\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/RedundantWithObject.html\",\n      \"title\": \"Lint/RedundantWithObject\",\n      \"description\": \"Disallows redundant `with_object` calls\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.11.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/RequireParentheses\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/RequireParentheses.html\",\n      \"title\": \"Lint/RequireParentheses\",\n      \"description\": \"Disallows method calls with no parentheses and a logical operator in the argument list\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/SelfInitializeDefinition\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/SelfInitializeDefinition.html\",\n      \"title\": \"Lint/SelfInitializeDefinition\",\n      \"description\": \"Reports `initialize` method definitions with a `self` receiver\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/ShadowedArgument\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/ShadowedArgument.html\",\n      \"title\": \"Lint/ShadowedArgument\",\n      \"description\": \"Disallows shadowed arguments\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/ShadowedException\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/ShadowedException.html\",\n      \"title\": \"Lint/ShadowedException\",\n      \"description\": \"Disallows rescued exception that get shadowed\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.3.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/ShadowingOuterLocalVar\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/ShadowingOuterLocalVar.html\",\n      \"title\": \"Lint/ShadowingOuterLocalVar\",\n      \"description\": \"Disallows the usage of the same name as outer local variables for block or proc arguments\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/SharedVarInFiber\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/SharedVarInFiber.html\",\n      \"title\": \"Lint/SharedVarInFiber\",\n      \"description\": \"Disallows shared variables in fibers\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.12.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/SignalTrap\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/SignalTrap.html\",\n      \"title\": \"Lint/SignalTrap\",\n      \"description\": \"Disallows `Signal::INT/HUP/TERM.trap` in favor of `Process.on_terminate`\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/SpecEqWithBoolOrNilLiteral\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/SpecEqWithBoolOrNilLiteral.html\",\n      \"title\": \"Lint/SpecEqWithBoolOrNilLiteral\",\n      \"description\": \"Reports `eq(true|false|nil)` expectations in specs\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/SpecFilename\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/SpecFilename.html\",\n      \"title\": \"Lint/SpecFilename\",\n      \"description\": \"Enforces spec filenames to have `_spec` suffix\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.6.0\"\n        },\n        \"IgnoredDirs\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": [\n            \"spec/support\",\n            \"spec/fixtures\",\n            \"spec/data\"\n          ]\n        },\n        \"IgnoredFilenames\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": [\n            \"spec_helper\"\n          ]\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/SpecFocus\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/SpecFocus.html\",\n      \"title\": \"Lint/SpecFocus\",\n      \"description\": \"Reports focused spec items\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.14.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/Syntax\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/Syntax.html\",\n      \"title\": \"Lint/Syntax\",\n      \"description\": \"Reports invalid Crystal syntax\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.4.2\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Error\"\n        }\n      }\n    },\n    \"Lint/TopLevelOperatorDefinition\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/TopLevelOperatorDefinition.html\",\n      \"title\": \"Lint/TopLevelOperatorDefinition\",\n      \"description\": \"Disallows top level operator method definitions\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/TrailingRescueException\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/TrailingRescueException.html\",\n      \"title\": \"Lint/TrailingRescueException\",\n      \"description\": \"Disallows trailing `rescue` with a path\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/Typos\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/Typos.html\",\n      \"title\": \"Lint/Typos\",\n      \"description\": \"Reports typos found in source files\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.6.0\"\n        },\n        \"Enabled\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"BinPath\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ],\n          \"default\": null\n        },\n        \"FailOnMissingBin\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"FailOnError\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/UnneededDisableDirective\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/UnneededDisableDirective.html\",\n      \"title\": \"Lint/UnneededDisableDirective\",\n      \"description\": \"Reports unneeded disable directives in comments\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.5.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/UnreachableCode\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/UnreachableCode.html\",\n      \"title\": \"Lint/UnreachableCode\",\n      \"description\": \"Reports unreachable code\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.9.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/UnusedArgument\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/UnusedArgument.html\",\n      \"title\": \"Lint/UnusedArgument\",\n      \"description\": \"Disallows unused arguments\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.6.0\"\n        },\n        \"IgnoreDefs\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        },\n        \"IgnoreBlocks\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"IgnoreProcs\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/UnusedBlockArgument\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/UnusedBlockArgument.html\",\n      \"title\": \"Lint/UnusedBlockArgument\",\n      \"description\": \"Disallows unused block arguments\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.4.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/UnusedExpression\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/UnusedExpression.html\",\n      \"title\": \"Lint/UnusedExpression\",\n      \"description\": \"Disallows unused expressions\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/UnusedRescueVariable\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/UnusedRescueVariable.html\",\n      \"title\": \"Lint/UnusedRescueVariable\",\n      \"description\": \"Disallows unused `rescue` variables\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/UselessAssign\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/UselessAssign.html\",\n      \"title\": \"Lint/UselessAssign\",\n      \"description\": \"Disallows useless variable assignments\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.6.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/UselessConditionInWhen\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/UselessConditionInWhen.html\",\n      \"title\": \"Lint/UselessConditionInWhen\",\n      \"description\": \"Disallows useless conditions in `when`\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.3.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/UselessVisibilityModifier\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/UselessVisibilityModifier.html\",\n      \"title\": \"Lint/UselessVisibilityModifier\",\n      \"description\": \"Disallows top level `protected` method visibility modifier\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/VoidOutsideLib\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/VoidOutsideLib.html\",\n      \"title\": \"Lint/VoidOutsideLib\",\n      \"description\": \"Disallows use of `Void` outside C lib bindings and `Pointer(Void)`\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Lint/WhitespaceAroundMacroExpression\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/WhitespaceAroundMacroExpression.html\",\n      \"title\": \"Lint/WhitespaceAroundMacroExpression\",\n      \"description\": \"Reports missing spaces around macro expressions\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Metrics/CyclomaticComplexity\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Metrics/CyclomaticComplexity.html\",\n      \"title\": \"Metrics/CyclomaticComplexity\",\n      \"description\": \"Disallows methods with a cyclomatic complexity higher than `MaxComplexity`\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.9.1\"\n        },\n        \"MaxComplexity\": {\n          \"type\": \"number\",\n          \"default\": 12\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Naming/AccessorMethodName\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/AccessorMethodName.html\",\n      \"title\": \"Naming/AccessorMethodName\",\n      \"description\": \"Makes sure that accessor methods are named properly\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.6.0\"\n        }\n      }\n    },\n    \"Naming/AsciiIdentifiers\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/AsciiIdentifiers.html\",\n      \"title\": \"Naming/AsciiIdentifiers\",\n      \"description\": \"Disallows non-ascii characters in identifiers\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.6.0\"\n        },\n        \"IgnoreSymbols\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        }\n      }\n    },\n    \"Naming/BinaryOperatorParameterName\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/BinaryOperatorParameterName.html\",\n      \"title\": \"Naming/BinaryOperatorParameterName\",\n      \"description\": \"Enforces that certain binary operator methods have their sole parameter name standardized\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.6.0\"\n        },\n        \"ExcludedOperators\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": [\n            \"[]\",\n            \"[]?\",\n            \"[]=\",\n            \"<<\",\n            \">>\",\n            \"`\",\n            \"=~\",\n            \"!~\"\n          ]\n        },\n        \"AllowedNames\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": [\n            \"other\"\n          ]\n        }\n      }\n    },\n    \"Naming/BlockParameterName\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/BlockParameterName.html\",\n      \"title\": \"Naming/BlockParameterName\",\n      \"description\": \"Disallows non-descriptive block parameter names\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.6.0\"\n        },\n        \"MinNameLength\": {\n          \"type\": \"number\",\n          \"default\": 3\n        },\n        \"AllowNamesEndingInNumbers\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        },\n        \"AllowedNames\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": [\n            \"a\",\n            \"b\",\n            \"e\",\n            \"i\",\n            \"j\",\n            \"k\",\n            \"v\",\n            \"x\",\n            \"y\",\n            \"k1\",\n            \"k2\",\n            \"v1\",\n            \"v2\",\n            \"db\",\n            \"ex\",\n            \"id\",\n            \"io\",\n            \"ip\",\n            \"op\",\n            \"tx\",\n            \"wg\",\n            \"ws\"\n          ]\n        },\n        \"ForbiddenNames\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": []\n        }\n      }\n    },\n    \"Naming/ConstantNames\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/ConstantNames.html\",\n      \"title\": \"Naming/ConstantNames\",\n      \"description\": \"Enforces constant names to be in screaming case\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.2.0\"\n        }\n      }\n    },\n    \"Naming/Filename\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/Filename.html\",\n      \"title\": \"Naming/Filename\",\n      \"description\": \"Enforces file names to be in underscored case\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.6.0\"\n        }\n      }\n    },\n    \"Naming/MethodNames\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/MethodNames.html\",\n      \"title\": \"Naming/MethodNames\",\n      \"description\": \"Enforces method names to be in underscored case\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.2.0\"\n        }\n      }\n    },\n    \"Naming/PredicateName\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/PredicateName.html\",\n      \"title\": \"Naming/PredicateName\",\n      \"description\": \"Disallows tautological predicate names\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.2.0\"\n        }\n      }\n    },\n    \"Naming/QueryBoolMethods\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/QueryBoolMethods.html\",\n      \"title\": \"Naming/QueryBoolMethods\",\n      \"description\": \"Reports boolean properties without the `?` suffix\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.4.0\"\n        }\n      }\n    },\n    \"Naming/RescuedExceptionsVariableName\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/RescuedExceptionsVariableName.html\",\n      \"title\": \"Naming/RescuedExceptionsVariableName\",\n      \"description\": \"Makes sure that rescued exceptions variables are named as expected\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.6.0\"\n        },\n        \"AllowedNames\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": [\n            \"e\",\n            \"ex\",\n            \"exception\",\n            \"err\",\n            \"error\"\n          ]\n        }\n      }\n    },\n    \"Naming/TypeNames\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/TypeNames.html\",\n      \"title\": \"Naming/TypeNames\",\n      \"description\": \"Enforces type names in camelcase manner\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.2.0\"\n        }\n      }\n    },\n    \"Naming/VariableNames\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Naming/VariableNames.html\",\n      \"title\": \"Naming/VariableNames\",\n      \"description\": \"Enforces variable names to be in underscored case\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.2.0\"\n        }\n      }\n    },\n    \"Performance/AnyAfterFilter\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/AnyAfterFilter.html\",\n      \"title\": \"Performance/AnyAfterFilter\",\n      \"description\": \"Identifies usage of `any?` calls that follow filters\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.8.1\"\n        },\n        \"FilterNames\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": [\n            \"select\",\n            \"reject\"\n          ]\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Performance/AnyInsteadOfPresent\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/AnyInsteadOfPresent.html\",\n      \"title\": \"Performance/AnyInsteadOfPresent\",\n      \"description\": \"Identifies usage of arg-less `any?` calls\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Performance/ChainedCallWithNoBang\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/ChainedCallWithNoBang.html\",\n      \"title\": \"Performance/ChainedCallWithNoBang\",\n      \"description\": \"Identifies usage of chained calls not utilizing the bang method variants\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.14.0\"\n        },\n        \"CallNames\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": [\n            \"uniq\",\n            \"unstable_sort\",\n            \"sort\",\n            \"sort_by\",\n            \"shuffle\",\n            \"reverse\"\n          ]\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Performance/CompactAfterMap\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/CompactAfterMap.html\",\n      \"title\": \"Performance/CompactAfterMap\",\n      \"description\": \"Identifies usage of `compact` calls that follow `map`\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.14.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Performance/ExcessiveAllocations\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/ExcessiveAllocations.html\",\n      \"title\": \"Performance/ExcessiveAllocations\",\n      \"description\": \"Identifies usage of excessive collection allocations\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.5.0\"\n        },\n        \"CallNames\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"codepoints\": {\n              \"type\": \"string\",\n              \"default\": \"each_codepoint\"\n            },\n            \"graphemes\": {\n              \"type\": \"string\",\n              \"default\": \"each_grapheme\"\n            },\n            \"chars\": {\n              \"type\": \"string\",\n              \"default\": \"each_char\"\n            },\n            \"lines\": {\n              \"type\": \"string\",\n              \"default\": \"each_line\"\n            }\n          }\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Performance/FirstLastAfterFilter\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/FirstLastAfterFilter.html\",\n      \"title\": \"Performance/FirstLastAfterFilter\",\n      \"description\": \"Identifies usage of `first/last/first?/last?` calls that follow filters\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.8.1\"\n        },\n        \"FilterNames\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": [\n            \"select\"\n          ]\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Performance/FlattenAfterMap\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/FlattenAfterMap.html\",\n      \"title\": \"Performance/FlattenAfterMap\",\n      \"description\": \"Identifies usage of `flatten` calls that follow `map`\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.14.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Performance/MapInsteadOfBlock\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/MapInsteadOfBlock.html\",\n      \"title\": \"Performance/MapInsteadOfBlock\",\n      \"description\": \"Identifies usage of `sum/product` calls that follow `map`\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.14.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Performance/MinMaxAfterMap\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/MinMaxAfterMap.html\",\n      \"title\": \"Performance/MinMaxAfterMap\",\n      \"description\": \"Identifies usage of `min/max/minmax` calls that follow `map`\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.5.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Performance/SizeAfterFilter\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/SizeAfterFilter.html\",\n      \"title\": \"Performance/SizeAfterFilter\",\n      \"description\": \"Identifies usage of `size` calls that follow filter\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.8.1\"\n        },\n        \"FilterNames\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": [\n            \"select\",\n            \"reject\"\n          ]\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Performance/TimesMap\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Performance/TimesMap.html\",\n      \"title\": \"Performance/TimesMap\",\n      \"description\": \"Identifies usage of `times.map { ... }.to_a` calls\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Severity\": {\n          \"$ref\": \"#/$defs/Severity\",\n          \"default\": \"Warning\"\n        }\n      }\n    },\n    \"Style/ArrayLiteralSyntax\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/ArrayLiteralSyntax.html\",\n      \"title\": \"Style/ArrayLiteralSyntax\",\n      \"description\": \"Encourages the use of `Array(T).new` over `[] of T`\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Enabled\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        }\n      }\n    },\n    \"Style/CallParentheses\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/CallParentheses.html\",\n      \"title\": \"Style/CallParentheses\",\n      \"description\": \"Enforces usage of parentheses in method calls\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Enabled\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"ExcludeTypeDeclarations\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        },\n        \"ExcludeHeredocs\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"ExcludedToplevelCallNames\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": [\n            \"spawn\",\n            \"raise\",\n            \"super\",\n            \"previous_def\",\n            \"exit\",\n            \"abort\",\n            \"sleep\",\n            \"print\",\n            \"printf\",\n            \"puts\",\n            \"p\",\n            \"p!\",\n            \"pp\",\n            \"pp!\",\n            \"record\",\n            \"class_getter\",\n            \"class_getter?\",\n            \"class_getter!\",\n            \"class_property\",\n            \"class_property?\",\n            \"class_property!\",\n            \"class_setter\",\n            \"getter\",\n            \"getter?\",\n            \"getter!\",\n            \"property\",\n            \"property?\",\n            \"property!\",\n            \"setter\",\n            \"def_equals_and_hash\",\n            \"def_equals\",\n            \"def_hash\",\n            \"delegate\",\n            \"forward_missing_to\",\n            \"describe\",\n            \"context\",\n            \"it\",\n            \"pending\",\n            \"fail\",\n            \"use_json_discriminator\"\n          ]\n        },\n        \"ExcludedCallNames\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": [\n            \"should\",\n            \"should_not\"\n          ]\n        }\n      }\n    },\n    \"Style/Elsif\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/Elsif.html\",\n      \"title\": \"Style/Elsif\",\n      \"description\": \"Encourages the use of `case/when` syntax over `if/elsif`\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Enabled\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"IgnoreSuffix\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        },\n        \"MaxBranches\": {\n          \"type\": \"number\",\n          \"default\": 0\n        }\n      }\n    },\n    \"Style/GuardClause\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/GuardClause.html\",\n      \"title\": \"Style/GuardClause\",\n      \"description\": \"Check for conditionals that can be replaced with guard clauses\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.0.0\"\n        },\n        \"Enabled\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        }\n      }\n    },\n    \"Style/HashLiteralSyntax\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/HashLiteralSyntax.html\",\n      \"title\": \"Style/HashLiteralSyntax\",\n      \"description\": \"Encourages the use of `Hash(K, V).new` over `{} of K => V`\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Enabled\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        }\n      }\n    },\n    \"Style/HeredocEscape\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/HeredocEscape.html\",\n      \"title\": \"Style/HeredocEscape\",\n      \"description\": \"Recommends using the heredoc variant that escapes interpolation or control chars in a heredoc body\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        }\n      }\n    },\n    \"Style/HeredocIndent\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/HeredocIndent.html\",\n      \"title\": \"Style/HeredocIndent\",\n      \"description\": \"Recommends heredoc bodies are indented consistently\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"IndentBy\": {\n          \"type\": \"number\",\n          \"default\": 2\n        },\n        \"BodyAutoDedent\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        }\n      }\n    },\n    \"Style/IsAFilter\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/IsAFilter.html\",\n      \"title\": \"Style/IsAFilter\",\n      \"description\": \"Identifies usage of `is_a?/nil?` calls within filters\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.14.0\"\n        },\n        \"FilterNames\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": [\n            \"select\",\n            \"reject\",\n            \"any?\",\n            \"all?\",\n            \"none?\",\n            \"one?\"\n          ]\n        }\n      }\n    },\n    \"Style/IsANil\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/IsANil.html\",\n      \"title\": \"Style/IsANil\",\n      \"description\": \"Disallows calls to `is_a?(Nil)` in favor of `nil?`\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.13.0\"\n        }\n      }\n    },\n    \"Style/LargeNumbers\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/LargeNumbers.html\",\n      \"title\": \"Style/LargeNumbers\",\n      \"description\": \"Disallows usage of large numbers without underscore\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.2.0\"\n        },\n        \"Enabled\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"IntMinDigits\": {\n          \"type\": \"number\",\n          \"default\": 6\n        }\n      }\n    },\n    \"Style/MultilineCurlyBlock\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/MultilineCurlyBlock.html\",\n      \"title\": \"Style/MultilineCurlyBlock\",\n      \"description\": \"Disallows multi-line blocks using curly block syntax\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        }\n      }\n    },\n    \"Style/MultilineStringLiteral\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/MultilineStringLiteral.html\",\n      \"title\": \"Style/MultilineStringLiteral\",\n      \"description\": \"Disallows multiline string literals not using `<<-HEREDOC` markers\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"AllowBackslashSplitStrings\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        }\n      }\n    },\n    \"Style/NegatedConditionsInUnless\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/NegatedConditionsInUnless.html\",\n      \"title\": \"Style/NegatedConditionsInUnless\",\n      \"description\": \"Disallows negated conditions in `unless`\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.2.0\"\n        }\n      }\n    },\n    \"Style/ParenthesesAroundCondition\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/ParenthesesAroundCondition.html\",\n      \"title\": \"Style/ParenthesesAroundCondition\",\n      \"description\": \"Disallows redundant parentheses around control expressions\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.4.0\"\n        },\n        \"ExcludeTernary\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"ExcludeMultiline\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"AllowSafeAssignment\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        }\n      }\n    },\n    \"Style/PercentLiteralDelimiters\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/PercentLiteralDelimiters.html\",\n      \"title\": \"Style/PercentLiteralDelimiters\",\n      \"description\": \"Enforces the consistent usage of `%`-literal delimiters\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"DefaultDelimiters\": {\n          \"type\": [\n            \"string\",\n            \"null\"\n          ],\n          \"default\": \"()\"\n        },\n        \"PreferredDelimiters\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"%w\": {\n              \"type\": \"string\",\n              \"default\": \"[]\"\n            },\n            \"%i\": {\n              \"type\": \"string\",\n              \"default\": \"[]\"\n            },\n            \"%r\": {\n              \"type\": \"string\",\n              \"default\": \"{}\"\n            }\n          }\n        },\n        \"IgnoreLiteralsContainingDelimiters\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        }\n      }\n    },\n    \"Style/RedundantBegin\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/RedundantBegin.html\",\n      \"title\": \"Style/RedundantBegin\",\n      \"description\": \"Disallows redundant `begin` blocks\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.3.0\"\n        }\n      }\n    },\n    \"Style/RedundantNext\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/RedundantNext.html\",\n      \"title\": \"Style/RedundantNext\",\n      \"description\": \"Reports redundant `next` expressions\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.12.0\"\n        },\n        \"AllowMultiNext\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        },\n        \"AllowEmptyNext\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        }\n      }\n    },\n    \"Style/RedundantNilInControlExpression\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/RedundantNilInControlExpression.html\",\n      \"title\": \"Style/RedundantNilInControlExpression\",\n      \"description\": \"Disallows control expressions with `nil` argument\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        }\n      }\n    },\n    \"Style/RedundantReturn\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/RedundantReturn.html\",\n      \"title\": \"Style/RedundantReturn\",\n      \"description\": \"Reports redundant `return` expressions\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.9.0\"\n        },\n        \"AllowMultiReturn\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        },\n        \"AllowEmptyReturn\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        }\n      }\n    },\n    \"Style/RedundantSelf\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/RedundantSelf.html\",\n      \"title\": \"Style/RedundantSelf\",\n      \"description\": \"Disallows redundant uses of `self`\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"AllowedMethodNames\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": [\n            \"in?\",\n            \"inspect\",\n            \"not_nil!\"\n          ]\n        }\n      }\n    },\n    \"Style/UnlessElse\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/UnlessElse.html\",\n      \"title\": \"Style/UnlessElse\",\n      \"description\": \"Disallows the use of an `else` block with the `unless`\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.1.0\"\n        }\n      }\n    },\n    \"Style/VerboseBlock\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/VerboseBlock.html\",\n      \"title\": \"Style/VerboseBlock\",\n      \"description\": \"Identifies usage of collapsible single expression blocks\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.14.0\"\n        },\n        \"ExcludeMultipleLineBlocks\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        },\n        \"ExcludeCallsWithBlock\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        },\n        \"ExcludePrefixOperators\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        },\n        \"ExcludeOperators\": {\n          \"type\": \"boolean\",\n          \"default\": true\n        },\n        \"ExcludeSetters\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"MaxLineLength\": {\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"default\": null\n        },\n        \"MaxLength\": {\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"default\": 50\n        }\n      }\n    },\n    \"Style/VerboseNilType\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/VerboseNilType.html\",\n      \"title\": \"Style/VerboseNilType\",\n      \"description\": \"Enforces consistent naming of `Nil` in type unions\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"ExplicitNil\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        }\n      }\n    },\n    \"Style/WhileTrue\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Style/WhileTrue.html\",\n      \"title\": \"Style/WhileTrue\",\n      \"description\": \"Disallows `while` statements with a `true` literal as condition\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"0.3.0\"\n        }\n      }\n    },\n    \"Typing/MacroCallArgumentTypeRestriction\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Typing/MacroCallArgumentTypeRestriction.html\",\n      \"title\": \"Typing/MacroCallArgumentTypeRestriction\",\n      \"description\": \"Recommends that call arguments to certain macros have type restrictions\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Enabled\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"DefaultValue\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"MacroNames\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": [\n            \"getter\",\n            \"getter?\",\n            \"getter!\",\n            \"class_getter\",\n            \"class_getter?\",\n            \"class_getter!\",\n            \"setter\",\n            \"setter?\",\n            \"setter!\",\n            \"class_setter\",\n            \"class_setter?\",\n            \"class_setter!\",\n            \"property\",\n            \"property?\",\n            \"property!\",\n            \"class_property\",\n            \"class_property?\",\n            \"class_property!\",\n            \"record\"\n          ]\n        }\n      }\n    },\n    \"Typing/MethodParameterTypeRestriction\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Typing/MethodParameterTypeRestriction.html\",\n      \"title\": \"Typing/MethodParameterTypeRestriction\",\n      \"description\": \"Recommends that method parameters have type restrictions\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Enabled\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"DefaultValue\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"BlockParameters\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"PrivateMethods\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"ProtectedMethods\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"NodocMethods\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        }\n      }\n    },\n    \"Typing/MethodReturnTypeRestriction\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Typing/MethodReturnTypeRestriction.html\",\n      \"title\": \"Typing/MethodReturnTypeRestriction\",\n      \"description\": \"Recommends that methods have a return type restriction\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Enabled\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"PrivateMethods\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"ProtectedMethods\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        },\n        \"NodocMethods\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        }\n      }\n    },\n    \"Typing/ProcLiteralReturnTypeRestriction\": {\n      \"$ref\": \"#/$defs/BaseRule\",\n      \"$comment\": \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Typing/ProcLiteralReturnTypeRestriction.html\",\n      \"title\": \"Typing/ProcLiteralReturnTypeRestriction\",\n      \"description\": \"Disallows proc literals without return type restriction\",\n      \"properties\": {\n        \"SinceVersion\": {\n          \"type\": \"string\",\n          \"default\": \"1.7.0\"\n        },\n        \"Enabled\": {\n          \"type\": \"boolean\",\n          \"default\": false\n        }\n      }\n    }\n  }\n}"
  },
  {
    "path": ".dockerignore",
    "content": ".*\n!LICENSE\n!Dockerfile\n!Makefile\n!shard.yml\n!src\n"
  },
  {
    "path": ".editorconfig",
    "content": "[*.cr]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto eol=lf\n\n## Generated files\n\n.ameba.yml.schema.json linguist-generated\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  # Maintain dependencies for GitHub Actions\n  - package-ecosystem: github-actions\n    directory: /\n    schedule:\n      interval: daily\n"
  },
  {
    "path": ".github/workflows/add-docs-version-to-json-index.yml",
    "content": "name: Add docs version to JSON index\n\non:\n  workflow_call:\n    inputs:\n      version:\n        required: true\n        type: string\n      git-branch:\n        type: string\n        default: gh-pages\n\npermissions:\n  contents: write\n\njobs:\n  add-version-to-json-index:\n    concurrency: ci-${{ github.ref }}\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Download source\n        uses: actions/checkout@v6\n        with:\n          ref: ${{ inputs.git-branch }}\n\n      - name: Update version list\n        run: |\n          VERSION=\"${{ inputs.version }}\"\n\n          if [[ $VERSION =~ ^[0-9] ]]; then\n            RELEASED=\"true\"\n          else\n            RELEASED=\"false\"\n          fi\n\n          JQ_EXPRESSION=\"\n            .versions |= (\n              if ( . | map(.name) | index(\\\"${VERSION}\\\") | not ) then\n                . += [ {\\\"name\\\": \\\"${VERSION}\\\", \\\"url\\\": \\\"/ameba/${VERSION}/\\\", \\\"released\\\": ${RELEASED}} ]\n              else\n                .\n              end\n            )\"\n\n          cat versions.json | \\\n            jq \"$JQ_EXPRESSION\" > versions.json.tmp && \\\n            mv versions.json.tmp versions.json\n\n      - name: Check for changes\n        id: git-diff-changes\n        run: |\n          git diff --color\n\n          if [[ -n \"$(git diff --exit-code)\" ]]; then\n            echo \"Changes detected.\"\n            echo \"has-changes=true\" >> $GITHUB_OUTPUT\n          else\n            echo \"No changes detected.\"\n            echo \"has-changes=false\" >> $GITHUB_OUTPUT\n          fi\n\n      - name: Configure Git identity\n        if: steps.git-diff-changes.outputs.has-changes == 'true'\n        run: |\n          git config --global user.name \"github-actions[bot]\"\n          git config --global user.email \"41898282+github-actions[bot]@users.noreply.github.com\"\n\n      - name: Commit changes to Git\n        if: steps.git-diff-changes.outputs.has-changes == 'true'\n        run: |\n          git commit -am \"Update versions.json\"\n          git push -u origin HEAD\n"
  },
  {
    "path": ".github/workflows/build-and-deploy-docs.yml",
    "content": "name: Build and deploy docs\n\non:\n  workflow_dispatch:\n    inputs: &inputs\n      ref:\n        required: true\n        type: string\n        description: The tag or branch to deploy\n      version:\n        type: string\n        description: Version to deploy\n      git-branch:\n        type: string\n        description: Git branch to deploy to\n        default: gh-pages\n  workflow_call:\n    inputs: *inputs\n\npermissions:\n  contents: write\n\njobs:\n  build-and-deploy:\n    concurrency: ci-${{ github.ref }}\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Inject slug/short variables\n        uses: rlespinasse/github-slug-action@v5\n\n      - name: Install Crystal\n        uses: crystal-lang/install-crystal@v1\n\n      - name: Download source\n        uses: actions/checkout@v6\n        with:\n          ref: ${{ inputs.ref }}\n\n      - name: Get ref SHA\n        id: ref-sha\n        run: |\n          REF_SHA=\"$(git rev-parse --short HEAD)\"\n          echo \"REF_SHA: ${REF_SHA}\"\n          echo \"sha=${REF_SHA}\" >> $GITHUB_OUTPUT\n\n      - name: Install dependencies\n        run: shards install\n\n      - name: Build docs\n        run: |\n          crystal docs \\\n            --project-version=\"${{ inputs.version || inputs.ref }}\" \\\n            --source-refname=\"${{ steps.ref-sha.outputs.sha }}\" \\\n            --json-config-url=\"/ameba/versions.json\"\n\n      - name: Deploy docs 🚀\n        uses: JamesIves/github-pages-deploy-action@v4\n        with:\n          branch: ${{ inputs.git-branch }}\n          folder: docs\n          target-folder: ${{ inputs.version || inputs.ref }}\n          clean: true\n\n  add-version-to-json-index:\n    uses: ./.github/workflows/add-docs-version-to-json-index.yml\n    with:\n      git-branch: ${{ inputs.git-branch }}\n      version: ${{ inputs.version || inputs.ref }}\n    needs:\n      - build-and-deploy\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  workflow_dispatch:\n  push:\n    branches: [master]\n  pull_request:\n    types: [opened, synchronize, reopened]\n  schedule:\n    - cron: \"0 3 * * 1\" # Every monday at 3 AM\n\npermissions:\n  contents: read\n\njobs:\n  test:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest, macos-latest, windows-latest]\n        crystal: [latest, nightly]\n    runs-on: ${{ matrix.os }}\n\n    steps:\n      - name: Set timezone to UTC\n        uses: szenius/set-timezone@v2.0\n\n      - name: Install Crystal\n        uses: crystal-lang/install-crystal@v1\n        with:\n          crystal: ${{ matrix.crystal }}\n\n      - name: Download source\n        uses: actions/checkout@v6\n\n      - name: Install dependencies\n        run: shards install\n\n      - name: Install typos-cli\n        if: matrix.os == 'macos-latest'\n        run: brew install typos-cli\n\n      - name: Run specs\n        run: make spec\n\n      - name: Build ameba binary\n        run: make build\n\n      - name: Run ameba linter\n        run: make lint\n"
  },
  {
    "path": ".github/workflows/compare-json-schema.yml",
    "content": "name: Compare JSON Schema\n\non:\n  pull_request:\n    types: [opened, synchronize, reopened]\n    paths:\n      - \".github/workflows/compare-json-schema.yml\"\n      - \"**/*.cr\"\n\npermissions:\n  contents: read\n\njobs:\n  check-for-changes:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Set timezone to UTC\n        uses: szenius/set-timezone@v2.0\n\n      - name: Install Crystal\n        uses: crystal-lang/install-crystal@v1\n\n      - name: Download source\n        uses: actions/checkout@v6\n\n      - name: Install dependencies\n        run: shards install\n\n      - name: Run JSON Schema builder\n        run: shards run json-schema-builder\n\n      - name: Check for changes\n        run: git diff --color --exit-code\n        continue-on-error: true\n"
  },
  {
    "path": ".github/workflows/docker-image.yml",
    "content": "name: Docker image build and deploy\n\non:\n  workflow_dispatch:\n  push:\n    branches: [master]\n  release:\n    types: [published]\n\nenv:\n  # Use docker.io for Docker Hub if empty\n  REGISTRY: ghcr.io\n  # github.repository as <account>/<repo>\n  IMAGE_NAME: ${{ github.repository }}\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      packages: write\n      # This is used to complete the identity challenge\n      # with sigstore/fulcio when running outside of PRs.\n      id-token: write\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v4\n\n      - name: Setup Docker Buildx\n        uses: docker/setup-buildx-action@v4\n\n      # Login against a Docker registry except on PR\n      # https://github.com/docker/login-action\n      - name: Log into ${{ env.REGISTRY }} registry\n        uses: docker/login-action@v4\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      # Extract metadata (tags, labels) for Docker\n      # https://github.com/docker/metadata-action\n      - name: Extract Docker metadata\n        id: meta\n        uses: docker/metadata-action@v6\n        with:\n          images: |\n            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\n          tags: |\n            type=ref,event=tag\n            type=ref,event=branch\n            type=ref,event=pr\n            type=sha,format=long\n            type=semver,pattern={{version}}\n            type=semver,pattern={{major}}.{{minor}}\n\n      # Build and push Docker image with Buildx\n      # https://github.com/docker/build-push-action\n      - name: Build and push Docker image\n        id: build-and-push\n        uses: docker/build-push-action@v7\n        with:\n          context: .\n          push: true\n          cache-from: type=gha\n          cache-to: type=gha,mode=max\n          build-args: |\n            CRFLAGS=-Dpreview_mt --release\n          platforms: |\n            linux/amd64\n            linux/arm64\n          tags: ${{ steps.meta.outputs.tags }}\n          labels: ${{ steps.meta.outputs.labels }}\n"
  },
  {
    "path": ".github/workflows/docs.yml",
    "content": "name: Docs\n\non:\n  push:\n    branches:\n      - master\n    tags:\n      - \"v*.*.*\"\n\npermissions:\n  contents: write\n\njobs:\n  setup:\n    runs-on: ubuntu-latest\n    outputs:\n      version: ${{ steps.variables.outputs.version }}\n      ref: ${{ steps.variables.outputs.ref }}\n\n    steps:\n      - name: Inject slug/short variables\n        uses: rlespinasse/github-slug-action@v5\n\n      - name: Set output variables\n        id: variables\n        run: |\n          VERSION=\"${GITHUB_REF_NAME}\"\n          if [[ $VERSION =~ ^v[0-9].* ]]; then\n            VERSION=${VERSION#\"v\"}\n          fi\n          echo \"VERSION: ${VERSION}\"\n          echo \"version=${VERSION}\" >> $GITHUB_OUTPUT\n\n          REF=\"${GITHUB_REF_POINT}\"\n          echo \"REF: ${REF}\"\n          echo \"ref=${REF}\" >> $GITHUB_OUTPUT\n\n  build-and-deploy:\n    uses: ./.github/workflows/build-and-deploy-docs.yml\n    with:\n      version: ${{ needs.setup.outputs.version }}\n      ref: ${{ needs.setup.outputs.ref }}\n    needs:\n      - setup\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    tags:\n      - \"v*.*.*\"\n\npermissions:\n  contents: write\n\nenv:\n  GH_TOKEN: ${{ github.token }}\n\njobs:\n  draft:\n    runs-on: ubuntu-latest\n    steps:\n      - run: gh release -R ${{ github.repository }} create ${{ github.ref_name }} --draft --generate-notes\n\n  linux-musl:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-24.04, ubuntu-24.04-arm]\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v6\n      - run: docker run --rm -v $PWD:/mnt -w /mnt crystallang/crystal:latest-alpine shards build ameba --static -Dpreview_mt --release\n      - run: tar zcf ameba-${{ github.ref_name }}-$(uname -m)-linux-musl.tar.gz -C bin ameba\n      - run: gh release -R ${{ github.repository }} upload ${{ github.ref_name }} ameba-${{ github.ref_name }}-$(uname -m)-linux-musl.tar.gz\n\n  darwin:\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - {os: macos-15, arch: aarch64}\n          - {os: macos-15-intel, arch: x86_64}\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: crystal-lang/install-crystal@v1\n      - uses: actions/checkout@v6\n      - run: shards build ameba -Dpreview_mt --release\n      - run: tar zcf ameba-${{ github.ref_name }}-${{ matrix.arch }}-darwin.tar.gz -C bin ameba ameba.dwarf\n      - run: gh release -R ${{ github.repository }} upload ${{ github.ref_name }} ameba-${{ github.ref_name }}-${{ matrix.arch }}-darwin.tar.gz\n\n  x86_64-windows-msvc:\n    runs-on: windows-2025\n    steps:\n      - uses: crystal-lang/install-crystal@v1\n      - uses: actions/checkout@v6\n      - run: shards build ameba --static -Dpreview_mt --release\n      - run: cd bin; 7z a ../ameba-${{ github.ref_name }}-x86_64-windows-msvc.zip ameba.exe ameba.pdb\n      - run: gh release -R ${{ github.repository }} upload ${{ github.ref_name }} ameba-${{ github.ref_name }}-x86_64-windows-msvc.zip\n\n"
  },
  {
    "path": ".github/workflows/typos.yml",
    "content": "name: Spell checker\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    types: [opened, synchronize, reopened]\n\npermissions:\n  contents: read\n\njobs:\n  typos:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Download source\n        uses: actions/checkout@v6\n\n      - name: Run `typos` spell checker\n        uses: crate-ci/typos@v1\n"
  },
  {
    "path": ".github/workflows/update-json-schema.yml",
    "content": "name: Update JSON Schema\n\non:\n  workflow_dispatch:\n  push:\n    branches: [master]\n    paths:\n      - \".github/workflows/update-json-schema.yml\"\n      - \"shard.yml\"\n      - \"**/*.cr\"\n\npermissions:\n  contents: write\n\njobs:\n  update-json-schema:\n    concurrency: ci-${{ github.ref }}\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Set timezone to UTC\n        uses: szenius/set-timezone@v2.0\n\n      - name: Install Crystal\n        uses: crystal-lang/install-crystal@v1\n\n      - name: Download source\n        uses: actions/checkout@v6\n        with:\n          ssh-key: ${{ secrets.MASTER_PUSHER_SSH_KEY }}\n\n      - name: Install dependencies\n        run: shards install\n\n      - name: Run JSON Schema builder\n        run: shards run json-schema-builder\n\n      - name: Check for changes\n        id: git-diff-changes\n        run: |\n          if [[ -n \"$(git diff --exit-code)\" ]]; then\n            echo \"Changes detected.\"\n            echo \"has-changes=true\" >> $GITHUB_OUTPUT\n          else\n            echo \"No changes detected.\"\n            echo \"has-changes=false\" >> $GITHUB_OUTPUT\n          fi\n\n      - name: Configure Git identity\n        if: steps.git-diff-changes.outputs.has-changes == 'true'\n        run: |\n          git config --global user.name \"github-actions[bot]\"\n          git config --global user.email \"41898282+github-actions[bot]@users.noreply.github.com\"\n\n      - name: Commit and push changes\n        if: steps.git-diff-changes.outputs.has-changes == 'true'\n        run: |\n          git commit -am \"Update JSON Schema\"\n          git push -u origin HEAD\n"
  },
  {
    "path": ".gitignore",
    "content": "# 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 dependency lock\n# Dependencies will be locked in application that uses them\n/shard.lock\n\n# Workspace settings used by common text-editors\n/.vscode\n/.zed\n"
  },
  {
    "path": ".typos.toml",
    "content": "[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]+\\}',\n  # fixed test values\n  '[Ff][Oo][Oo]+',\n  # words including a number are likely some kind of identifier\n  \"[a-zA-Z]+[0-9]+\",\n]\n\n[files]\nextend-exclude = [\n  # git and dependencies\n  \".git/**\",\n  \"lib/**\",\n  # individual files to exclude\n  \"spec/ameba/rule/lint/typos_spec.cr\",\n]\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM alpine:edge AS builder\nARG CRFLAGS=\"-Dpreview_mt\"\nRUN apk add --update crystal shards yaml-dev musl-dev make\nRUN mkdir /ameba\nWORKDIR /ameba\nCOPY . /ameba/\nRUN crystal -v\nRUN make clean && make CRFLAGS=\"$CRFLAGS\"\n\nFROM alpine:latest\nRUN apk add --update yaml pcre2 gc libevent libgcc\nRUN mkdir /src\nWORKDIR /src\nCOPY --from=builder /ameba/bin/ameba /usr/bin/\nRUN ameba -v\nENTRYPOINT [ \"/usr/bin/ameba\" ]\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2018-2020 Vitalii Elenhaupt\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": ".POSIX:\nall:\n\n# Recipes\n\n## Build ameba\n##   $ make\n##\n## Run tests\n##   $ make test\n##\n## Install ameba\n##   $ sudo make install\n\n-include Makefile.local # for optional local options\n\nBUILD_TARGET := bin/ameba\n\nDESTDIR ?=          ## Install destination dir\nPREFIX ?= /usr/local## Install path prefix\nBINDIR ?= $(DESTDIR)$(PREFIX)/bin\n\n# The crystal command to use\nCRYSTAL_BIN ?= crystal\n# The shards command to use\nSHARDS_BIN ?= shards\n# The install command to use\nINSTALL_BIN ?= /usr/bin/install\n\nCRFLAGS ?= -Dpreview_mt\n\nSRC_SOURCES := $(shell find src -name '*.cr' 2>/dev/null)\n\n.PHONY: all\nall: build\n\n.PHONY: build\nbuild: ## Build the application binary\nbuild: $(BUILD_TARGET)\n\n$(BUILD_TARGET): $(SRC_SOURCES)\n\t$(SHARDS_BIN) build ameba $(CRFLAGS)\n\n.PHONY: docs\ndocs: ## Generate API docs\ndocs: $(SRC_SOURCES)\n\t$(CRYSTAL_BIN) docs\n\n.PHONY: spec\nspec: ## Run the spec suite\nspec:\n\t$(CRYSTAL_BIN) spec\n\n.PHONY: schema\nschema: ## Build the latest schema\nschema:\n\t$(SHARDS_BIN) run schema\n\n.PHONY: lint\nlint: ## Run ameba on its own code base\nlint: $(BUILD_TARGET)\n\t$(BUILD_TARGET)\n\n.PHONY: test\ntest: ## Run the spec suite and linter\ntest: spec lint\n\n.PHONY: clean\nclean: ## Remove application binary and API docs\nclean:\n\t@rm -f \"$(BUILD_TARGET)\" \"$(BUILD_TARGET).dwarf\"\n\t@rm -rf docs\n\n.PHONY: install\ninstall: ## Install application binary into $DESTDIR\ninstall: $(BUILD_TARGET)\n\tmkdir -p \"$(BINDIR)\"\n\t$(INSTALL_BIN) -m 0755 \"$(BUILD_TARGET)\" \"$(BINDIR)/ameba\"\n\n.PHONY: help\nhelp: ## Show this help\n\t@printf '\\033[34mtargets:\\033[0m\\n'\n\t@grep -hE '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) |\\\n\t\tsort |\\\n\t\tawk 'BEGIN {FS = \":.*?## \"}; {printf \"  \\033[36m%-15s\\033[0m %s\\n\", $$1, $$2}'\n\t@echo\n\t@printf '\\033[34moptional variables:\\033[0m\\n'\n\t@grep -hE '^[a-zA-Z_-]+ \\?=.*?## .*$$' $(MAKEFILE_LIST) |\\\n\t\tsort |\\\n\t\tawk 'BEGIN {FS = \" \\\\?=.*?## \"}; {printf \"  \\033[36m%-15s\\033[0m %s\\n\", $$1, $$2}'\n\t@echo\n\t@printf '\\033[34mrecipes:\\033[0m\\n'\n\t@grep -hE '^##.*$$' $(MAKEFILE_LIST) |\\\n\t\tawk 'BEGIN {FS = \"## \"}; /^## [a-zA-Z_-]/ {printf \"  \\033[36m%s\\033[0m\\n\", $$2}; /^##  / {printf \"  %s\\n\", $$2}'\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/veelenga/bin/master/ameba/logo.png\" width=\"800\">\n  <h3 align=\"center\">Ameba</h3>\n  <p align=\"center\">Code style linter for Crystal<p>\n  <p align=\"center\">\n    <sup>\n      <i>(a single-celled animal that catches food and moves about by extending fingerlike projections of protoplasm)</i>\n    </sup>\n  </p>\n  <p align=\"center\">\n    <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>\n    <a href=\"https://github.com/crystal-ameba/ameba/releases\"><img src=\"https://img.shields.io/github/release/crystal-ameba/ameba.svg?maxAge=360\"></a>\n    <a href=\"https://github.com/crystal-ameba/ameba/blob/master/LICENSE\"><img src=\"https://img.shields.io/github/license/crystal-ameba/ameba.svg\"></a>\n  </p>\n</p>\n\n- [About](#about)\n- [Usage](#usage)\n  - [Watch a tutorial](#watch-a-tutorial)\n  - [Autocorrection](#autocorrection)\n  - [Explain issues](#explain-issues)\n  - [Run in parallel](#run-in-parallel)\n- [Installation](#installation)\n  - [As a project dependency:](#as-a-project-dependency)\n  - [OS X](#os-x)\n  - [Docker](#docker)\n  - [From sources](#from-sources)\n- [Configuration](#configuration)\n  - [Sources](#sources)\n  - [Rules](#rules)\n  - [Inline disabling](#inline-disabling)\n- [Editors \\& integrations](#editors--integrations)\n- [Credits \\& inspirations](#credits--inspirations)\n- [Contributors](#contributors)\n\n## About\n\nAmeba is a static code analysis tool for the Crystal language.\nIt enforces a consistent [Crystal code style](https://crystal-lang.org/reference/conventions/coding_style.html),\nalso catches code smells and wrong code constructions.\n\nSee also [Roadmap](https://github.com/crystal-ameba/ameba/wiki).\n\n## Usage\n\nRun `ameba` binary within your project directory to catch code issues:\n\n```sh\n$ ameba\nInspecting 107 files\n\n...............F.....................FF....................................................................\n\nsrc/ameba/formatter/flycheck_formatter.cr:6:37\n[W] Lint/UnusedArgument: Unused argument `location`. If it's necessary, use `_` as an argument name to indicate that it won't be used.\n> source.issues.each do |issue, location|\n                                ^\n\nsrc/ameba/formatter/base_formatter.cr:16:14\n[W] Lint/UselessAssign: Useless assignment to variable `s`\n> return s += issues.size\n         ^\n\nsrc/ameba/formatter/base_formatter.cr:16:7 [Correctable]\n[C] Style/RedundantReturn: Redundant `return` detected\n> return s += issues.size\n  ^---------------------^\n\nFinished in 389.45 milliseconds\n107 inspected, 3 failures\n```\n\n### Watch a tutorial\n\n<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>\n\n[🎬 Watch the LuckyCast showing how to use Ameba](https://luckycasts.com/videos/ameba)\n\n### Autocorrection\n\nRules that are marked as `[Correctable]` in the output can be automatically corrected using `--fix` flag:\n\n```sh\n$ ameba --fix\n```\n\n### Explain issues\n\nAmeba allows you to dig deeper into an issue, by showing you details about the issue\nand the reasoning by it being reported.\n\nTo be convenient, you can just copy-paste the `PATH:line:column` string from the\nreport and paste behind the `ameba` command to check it out.\n\n```sh\n$ ameba crystal/command/format.cr:26:83           # show explanation for the issue\n$ ameba --explain crystal/command/format.cr:26:83 # same thing\n```\n\n### Run in parallel\n\nSome quick benchmark results measured while running Ameba on Crystal repo:\n\n```sh\n$ CRYSTAL_WORKERS=1 ameba #=> 29.11 seconds\n$ CRYSTAL_WORKERS=2 ameba #=> 19.49 seconds\n$ CRYSTAL_WORKERS=4 ameba #=> 13.48 seconds\n$ CRYSTAL_WORKERS=8 ameba #=> 10.14 seconds\n```\n\n## Installation\n\n### As a project dependency\n\nAdd this to your application's `shard.yml`:\n\n```yaml\ndevelopment_dependencies:\n  ameba:\n    github: crystal-ameba/ameba\n```\n\nTo prioritize runtime performance over compilation time, you can add `ameba`\ntarget to the `shard.yml` file:\n\n```yaml\ntargets:\n  ameba:\n    main: lib/ameba/bin/ameba.cr\n```\n\nAnd then run:\n\n```sh\n$ shards build ameba -Dpreview_mt\n```\n\nAlternatively, skip adding `ameba` target and use `crystal build` command directly:\n\n```sh\n$ crystal build -Dpreview_mt -o bin/ameba lib/ameba/bin/ameba.cr\n```\n\nBoth of these will result in a compiled binary placed under `bin/ameba` path.\n\nYou can also just run the `lib/ameba/bin/ameba.cr` file, compiling it on the fly,\nwhich is the slowest option:\n\n```sh\n$ lib/ameba/bin/ameba.cr\n```\n\n### OS X\n\n```sh\n$ brew tap crystal-ameba/ameba\n$ brew install ameba\n```\n\n### Docker\n\nBuild the image:\n\n```sh\n$ docker build -t ghcr.io/crystal-ameba/ameba .\n```\n\nTo use the resulting image on a local source folder, mount the current (or target) directory into `/src`:\n\n```sh\n$ docker run -v $(pwd):/src ghcr.io/crystal-ameba/ameba\n```\n\nAlso available on GitHub: https://github.com/crystal-ameba/ameba/pkgs/container/ameba\n\n### From sources\n\n```sh\n$ git clone https://github.com/crystal-ameba/ameba && cd ameba\n$ make install\n```\n\n## Configuration\n\nDefault configuration file is `.ameba.yml`.\nIt allows to configure rule properties, disable specific rules and exclude sources from the rules.\n\nGenerate new file by running `ameba --gen-config`.\n\n### Sources\n\n**List of sources to run Ameba on can be configured globally via:**\n\n- `Globs` section - an array of wildcards (or paths) to include to the\n  inspection. Defaults to `%w[**/*.cr **/*.ecr]`, meaning it includes all project\n  files with `*.cr` and `*.ecr` extensions.\n- `Excluded` section - an array of wildcards (or paths) to exclude from the\n  source list defined by `Globs`. Defaults to `%w[lib]`, meaning it excludes the\n  `lib` folder.\n\nIn this example we define default globs and exclude `lib` and `src/compiler` folders:\n\n``` yaml\nGlobs:\n  - \"**/*.cr\"\n  - \"**/*.ecr\"\n\nExcluded:\n  - lib\n  - src/compiler\n```\n\n**Specific sources can be excluded at rule level**:\n\n``` yaml\nStyle/RedundantBegin:\n  Excluded:\n    - src/server/processor.cr\n    - src/server/api.cr\n```\n\n### Rules\n\nOne or more rules, or a one or more group of rules can be included or excluded\nvia command line arguments:\n\n```sh\n$ ameba --only   Lint/Syntax # runs only Lint/Syntax rule\n$ ameba --only   Style,Lint  # runs only rules from Style and Lint groups\n$ ameba --except Lint/Syntax # runs all rules except Lint/Syntax\n$ ameba --except Style,Lint  # runs all rules except rules in Style and Lint groups\n```\n\nOr through the configuration file:\n\n``` yaml\nStyle/RedundantBegin:\n  Enabled: false\n```\n\n### Inline disabling\n\nOne or more rules or one or more group of rules can be disabled using inline directives:\n\n```crystal\n# ameba:disable Style/LargeNumbers\ntime = Time.epoch(1483859302)\n\ntime = Time.epoch(1483859302) # ameba:disable Style/LargeNumbers, Lint/UselessAssign\ntime = Time.epoch(1483859302) # ameba:disable Style, Lint\n```\n\n## Editors & integrations\n\n- Vim: [vim-crystal](https://github.com/rhysd/vim-crystal), [Ale](https://github.com/w0rp/ale)\n- Emacs: [ameba.el](https://github.com/crystal-ameba/ameba.el)\n- Sublime Text: [Sublime Linter Ameba](https://github.com/epergo/SublimeLinter-contrib-ameba)\n- VSCode: [vscode-crystal-ameba](https://github.com/crystal-ameba/vscode-crystal-ameba)\n- Codacy: [codacy-ameba](https://github.com/codacy/codacy-ameba)\n- GitHub Actions: [github-action](https://github.com/crystal-ameba/github-action)\n\n## Credits & inspirations\n\n- [Crystal Language](https://crystal-lang.org)\n- [Rubocop](https://rubocop.org)\n- [Credo](http://credo-ci.org)\n- [Dogma](https://github.com/lpil/dogma)\n\n## Contributors\n\n- [veelenga](https://github.com/veelenga) Vitalii Elenhaupt - creator, maintainer\n- [Sija](https://github.com/Sija) Sijawusz Pur Rahnama - contributor, maintainer\n"
  },
  {
    "path": "bench/check_sources.cr",
    "content": "require \"../src/ameba\"\nrequire \"benchmark\"\n\nprivate def get_files(n)\n  Dir[\"src/**/*.cr\"].first(n)\nend\n\nputs \"== Compare:\"\nBenchmark.ips do |x|\n  [\n    1,\n    3,\n    5,\n    10,\n    20,\n    30,\n    40,\n  ].each do |n| # ameba:disable Naming/BlockParameterName\n    config = Ameba::Config.load\n    config.formatter = Ameba::Formatter::BaseFormatter.new\n    config.globs = get_files(n)\n    s = n == 1 ? \"\" : \"s\"\n    x.report(\"#{n} source#{s}\") { Ameba.run config }\n  end\nend\n\nputs \"== Measure:\"\nconfig = Ameba::Config.load\nconfig.formatter = Ameba::Formatter::BaseFormatter.new\nputs Benchmark.measure { Ameba.run config }\n"
  },
  {
    "path": "bin/ameba.cr",
    "content": "#!/usr/bin/env crystal\n\n# Require ameba extensions here which are added as project dependencies.\n# Example:\n#\n# require \"ameba-performance\"\n\n# Require ameba cli which starts the inspection.\nrequire \"ameba/cli\"\n"
  },
  {
    "path": "shard.yml",
    "content": "name: ameba\nversion: 1.7.0-dev\n\nauthors:\n  - Vitalii Elenhaupt <velenhaupt@gmail.com>\n  - Sijawusz Pur Rahnama <sija@sija.pl>\n\ntargets:\n  ameba:\n    main: src/cli.cr\n  json-schema-builder:\n    main: src/json-schema-builder.cr\n\ncrystal: ~> 1.19\n\nlicense: MIT\n"
  },
  {
    "path": "spec/ameba/ast/flow_expression_spec.cr",
    "content": "require \"../../spec_helper\"\n\nmodule Ameba::AST\n  describe FlowExpression do\n    describe \"#initialize\" do\n      it \"creates a new flow expression\" do\n        node = as_node(\"return 22\")\n        flow_expression = FlowExpression.new node, false\n        flow_expression.node.should_not be_nil\n        flow_expression.in_loop?.should be_false\n      end\n\n      describe \"#delegation\" do\n        it \"delegates to_s to @node\" do\n          node = as_node(\"return 22\")\n          flow_expression = FlowExpression.new node, false\n          flow_expression.to_s.should eq node.to_s\n        end\n\n        it \"delegates locations to @node\" do\n          node = as_node(\"break if true\")\n          flow_expression = FlowExpression.new node, false\n          flow_expression.location.should eq node.location\n          flow_expression.end_location.should eq node.end_location\n        end\n      end\n\n      describe \"#unreachable_nodes\" do\n        it \"returns unreachable nodes\" do\n          nodes = as_nodes <<-CRYSTAL\n            def foobar\n              return\n              a = 1\n              a = 2\n            end\n            CRYSTAL\n          node = nodes.expressions_nodes.first\n          flow_expression = FlowExpression.new node, false\n          flow_expression.unreachable_nodes.should eq nodes.assign_nodes\n        end\n\n        it \"returns nil if there is no unreachable node after loop\" do\n          nodes = as_nodes <<-CRYSTAL\n            def run\n              idx = items.size - 1\n              while 0 <= idx\n                return\n              end\n\n              puts \"foo\"\n            end\n            CRYSTAL\n          node = nodes.expressions_nodes.first\n          flow_expression = FlowExpression.new node, false\n          flow_expression.unreachable_nodes.empty?.should be_true\n        end\n\n        it \"returns nil if there is no unreachable node\" do\n          nodes = as_nodes <<-CRYSTAL\n            def foobar\n              a = 1\n              return a\n            end\n            CRYSTAL\n          node = nodes.expressions_nodes.first\n          flow_expression = FlowExpression.new node, false\n          flow_expression.unreachable_nodes.empty?.should be_true\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/ast/liveness_analyzer_spec.cr",
    "content": "require \"../../spec_helper\"\n\nprivate def scopes_for(code)\n  rule = Ameba::ScopeRule.new\n  source = Ameba::Source.new(code)\n  Ameba::AST::ScopeVisitor.new(rule, source)\n  rule.scopes\nend\n\nprivate def def_scope(code)\n  scopes_for(code).find! { |scope| scope.node.is_a?(Crystal::Def) }\nend\n\nprivate def top_scope(code)\n  scopes_for(code).find! { |scope| scope.node.is_a?(Crystal::Expressions) }\nend\n\nprivate def block_scope(code)\n  scopes_for(code).find! { |scope| scope.node.is_a?(Crystal::Block) }\nend\n\nprivate def dead_store_names(scope)\n  Ameba::AST::LivenessAnalyzer.new(scope).dead_stores.map(&.variable.name)\nend\n\nmodule Ameba::AST\n  describe LivenessAnalyzer do\n    context \"basic assignments\" do\n      it \"detects unused assignment as dead store\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = 1\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"a\"]\n      end\n\n      it \"does not report assignment that is used\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = 1\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"detects first assignment as dead when overwritten before use\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = 1\n            a = 2\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"a\"]\n      end\n\n      it \"detects all assignments as dead when none are used\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = 1\n            a = 2\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"a\", \"a\"]\n      end\n\n      it \"does not report assignment used in a condition\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = 1\n            if a\n              nil\n            end\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"reports second assignment when value is not used\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = 1\n            a = a + 1\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"a\"]\n      end\n\n      it \"does not report assignment used in another assignment\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            if f = get_something\n              @f = f\n            end\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"reports last assignment when not used after reassignment\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = 1\n            puts a\n            a = 2\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"a\"]\n      end\n    end\n\n    context \"op assignments\" do\n      it \"does not report op-assign when result is used\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = 1\n            a += 1\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"reports op-assign when result is not used\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = 1\n            a += 1\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"a\"]\n      end\n\n      it \"does not report chained op-assigns when result is used\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = 1\n            a += 1\n            a += 1\n            a = a + 1\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n    end\n\n    context \"if/unless branches\" do\n      it \"reports initial assignment as dead when overwritten in both branches\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = 0\n            if something\n              a = 1\n            else\n              a = 2\n            end\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"a\"]\n      end\n\n      it \"does not report when assigned in one branch and used after\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = 0\n            if something\n              a = 1\n            end\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"does not report when assigned in one branch with else nil and used after\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = 0\n            if something\n              a = 1\n            else\n              nil\n            end\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"reports useless assignment in branch when not used after\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a)\n            if a\n              a = 2\n            end\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"a\"]\n      end\n\n      it \"reports first dead assignment in branch when overwritten\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a)\n            a = 1\n            if a\n              a = 2\n              a = 3\n            end\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"a\"]\n      end\n\n      it \"reports initial assignment as dead when overwritten in all branches\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            has_newline = false\n\n            if something\n              do_something unless false\n              has_newline = false\n            else\n              do_something if true\n              has_newline = true\n            end\n\n            has_newline\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"has_newline\"]\n      end\n\n      it \"does not report unless with consumed branches\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = 0\n            unless something\n              a = 1\n            else\n              a = 2\n            end\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"a\"]\n      end\n\n      it \"reports dead assignment in unless branch\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = 0\n            unless something\n              a = 1\n              a = 2\n            else\n              a = 2\n            end\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"a\", \"a\"]\n      end\n\n      it \"does not report one-line if assignment used after\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = 0\n            a = 1 if something\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n    end\n\n    context \"while loops\" do\n      it \"does not report assignment used across iterations\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a)\n            while a < 10\n              a = a + 1\n            end\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"reports assignment not used outside loop\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a)\n            while a < 10\n              b = a\n            end\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"b\"]\n      end\n\n      it \"does not report assignment used in loop with accumulator\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = 3\n            result = 0\n\n            while result < 10\n              result += a\n              a = a + 1\n            end\n            result\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"does not report parameter assignment used in loop\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a)\n            result = 0\n\n            while result < 10\n              result += a\n              a = a + 1\n            end\n            result\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"does not report assignment in loop with inner branch\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a)\n            result = 0\n\n            while result < 10\n              result += a\n              if result > 0\n                a = a + 1\n              else\n                a = 3\n              end\n            end\n            result\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"handles branch with blank node in loop\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            count = 0\n            while true\n              break if count == 1\n              case something\n              when :any\n              else\n                :anything_else\n              end\n              count += 1\n            end\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"does not report assignment used after break\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            found = false\n            while true\n              if something\n                found = true\n                break\n              end\n            end\n            found\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"does not report assignment before next used in subsequent iteration\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            atomic = parse_atomic\n            while true\n              if @token.instance_var?\n                atomic = parse_ivar(atomic)\n                next\n              end\n              break\n            end\n            atomic\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"does not report assignment used after break in nested loops\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            found = false\n            while outer_cond\n              while inner_cond\n                if something\n                  found = true\n                  break\n                end\n              end\n            end\n            found\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"does not report assignment inside conditional break\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            options = 0\n            while true\n              if done?\n                options = compute_options\n                break\n              end\n              process\n            end\n            options\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n    end\n\n    context \"until loops\" do\n      it \"does not report assignment used across until iterations\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a)\n            until a > 10\n              a = a + 1\n            end\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"reports useless assignment in until loop\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a)\n            until a > 10\n              b = a + 1\n            end\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"b\"]\n      end\n    end\n\n    context \"exception handlers\" do\n      it \"does not report assignment used in rescue\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a)\n            a = 2\n          rescue\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"does not report assignment used in ensure\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a)\n            a = 2\n          ensure\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"does not report assignment used in else\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a)\n            a = 2\n          rescue\n          else\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"reports useless assignment in rescue\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a)\n          rescue\n            a = 2\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"a\"]\n      end\n\n      it \"does not report assignment used in rescue when body has break\" do\n        scope = block_scope <<-CRYSTAL\n          3.times do\n            start = 1\n            begin\n              perform_foo\n              break\n            rescue\n              start\n            end\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"does not report assignment used in rescue when body has return\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            start = 1\n            begin\n              perform_foo\n              return\n            rescue\n              start\n            end\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"does not report assignment used in rescue when body has next\" do\n        scope = block_scope <<-CRYSTAL\n          3.times do\n            start = 1\n            begin\n              perform_foo\n              next\n            rescue\n              start\n            end\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n    end\n\n    context \"binary operators\" do\n      it \"does not report when both sides of && are used\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a)\n            (a = 1) && (b = 1)\n            a + b\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"reports unused side of ||\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a)\n            (a = 1) || (b = 1)\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"b\"]\n      end\n    end\n\n    context \"case\" do\n      it \"does not report when used after case\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a)\n            case a\n            when /foo/\n              a = 1\n            when /bar/\n              a = 2\n            end\n            puts a\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"reports when not used after case\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a)\n            case a\n            when /foo/\n              a = 1\n            when /bar/\n              a = 2\n            end\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"a\", \"a\"]\n      end\n\n      it \"does not report assignment used in case condition\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = 2\n            case a\n            when /foo/\n            end\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      context \"when\" do\n        it \"does not report when assignment in when condition is used\" do\n          scope = def_scope <<-CRYSTAL\n            def foo(a)\n              case\n              when a = foo_call\n              when a = bar_call\n              end\n              puts a\n            end\n            CRYSTAL\n          dead_store_names(scope).should be_empty\n        end\n\n        it \"reports when assignment in when condition is not used\" do\n          scope = def_scope <<-CRYSTAL\n            def foo(a)\n              case\n              when a = foo_call\n              when a = bar_call\n              end\n            end\n            CRYSTAL\n          dead_store_names(scope).should eq [\"a\", \"a\"]\n        end\n      end\n    end\n\n    context \"multi assignments\" do\n      it \"does not report when all targets are used\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a, b = {1, 2}\n            a + b\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"reports unused multi-assign target\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a, b = {1, 2}\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"b\"]\n      end\n\n      it \"reports all unused multi-assign targets\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a, b = {1, 2}\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"b\", \"a\"]\n      end\n\n      it \"reports reassigned multi-assign targets\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a, b = {1, 2}\n            a, b = {3, 4}\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"b\", \"a\", \"b\", \"a\"]\n      end\n\n      it \"reports multi-assign target overwritten at loop start\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            while true\n              word = get_word\n              if (word & 0xFF) == 0\n                word, success = compare_and_set(word, word + 1)\n                return if success\n              end\n            end\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"word\"]\n      end\n\n      it \"does not report multi-assign target used in next loop iteration\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            while true\n              word = get_word\n              if (word & 0xFF) == 0\n                word, success = compare_and_set(word, word + 1)\n                if success\n                  return\n                end\n              else\n                puts word\n              end\n            end\n            word\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n    end\n\n    context \"top level scope\" do\n      it \"detects dead stores at top level\" do\n        scope = top_scope <<-CRYSTAL\n          a = 1\n          a = 2\n          CRYSTAL\n        dead_store_names(scope).should eq [\"a\", \"a\"]\n      end\n\n      it \"does not report referenced top-level assignments\" do\n        scope = top_scope <<-CRYSTAL\n          a = 1\n          a += 1\n          a\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n    end\n\n    context \"type declarations\" do\n      it \"does not report unused type declaration without value\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a : String?\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"reports unused type declaration with value\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a : String? = \"foo\"\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"a\"]\n      end\n\n      it \"does not report used type declaration\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a : String?\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n    end\n\n    context \"uninitialized\" do\n      it \"reports unused uninitialized assignment\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = uninitialized UInt8\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"a\"]\n      end\n\n      it \"does not report used uninitialized assignment\" do\n        scope = def_scope <<-CRYSTAL\n          def foo\n            a = uninitialized UInt8\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n    end\n    context \"super and previous_def\" do\n      it \"treats bare super as reading all arguments\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a, b)\n            a = super\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"treats bare previous_def as reading all arguments\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a)\n            a = previous_def\n            a\n          end\n          CRYSTAL\n        dead_store_names(scope).should be_empty\n      end\n\n      it \"does not treat super() with parens as reading arguments\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a)\n            b = super()\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"b\"]\n      end\n\n      it \"does not treat super with explicit args as reading all arguments\" do\n        scope = def_scope <<-CRYSTAL\n          def foo(a)\n            b = super(1)\n          end\n          CRYSTAL\n        dead_store_names(scope).should eq [\"b\"]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/ast/scope_spec.cr",
    "content": "require \"../../spec_helper\"\n\nmodule Ameba::AST\n  describe Scope do\n    describe \"#initialize\" do\n      source = \"a = 2\"\n\n      it \"assigns outer scope\" do\n        root = Scope.new as_node(source)\n        child = Scope.new as_node(source), root\n        child.outer_scope.should_not be_nil\n      end\n\n      it \"assigns node\" do\n        scope = Scope.new as_node(source)\n        scope.node.should_not be_nil\n      end\n    end\n  end\n\n  describe \"delegation\" do\n    it \"delegates to_s to node\" do\n      node = as_node(\"def foo; end\")\n      scope = Scope.new node\n      scope.to_s.should eq node.to_s\n    end\n\n    it \"delegates locations to node\" do\n      node = as_node(\"def foo; end\")\n      scope = Scope.new node\n      scope.location.should eq node.location\n      scope.end_location.should eq node.end_location\n    end\n  end\n\n  describe \"#references\" do\n    it \"can return an empty list of references\" do\n      scope = Scope.new as_node(\"\")\n      scope.references.should be_empty\n    end\n\n    it \"allows to add variable references\" do\n      scope = Scope.new as_node(\"\")\n      nodes = as_nodes \"a = 2\"\n      scope.references << Reference.new(nodes.var_nodes.first, scope)\n      scope.references.size.should eq 1\n    end\n  end\n\n  describe \"#references?\" do\n    it \"returns true if current scope references variable\" do\n      nodes = as_nodes <<-CRYSTAL\n        def method\n          a = 2\n          block do\n            3.times { |i| a = a + i }\n          end\n        end\n        CRYSTAL\n\n      var_node = nodes.var_nodes.first\n\n      scope = Scope.new nodes.def_nodes.first\n      scope.add_variable(var_node)\n      scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope)\n\n      variable = Variable.new(var_node, scope)\n      variable.reference(nodes.var_nodes.first, scope.inner_scopes.first)\n\n      scope.references?(variable).should be_true\n    end\n\n    it \"returns false if inner scopes are not checked\" do\n      nodes = as_nodes <<-CRYSTAL\n        def method\n          a = 2\n          block do\n            3.times { |i| a = a + i }\n          end\n        end\n        CRYSTAL\n\n      var_node = nodes.var_nodes.first\n\n      scope = Scope.new nodes.def_nodes.first\n      scope.add_variable(var_node)\n      scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope)\n\n      variable = Variable.new(var_node, scope)\n      variable.reference(nodes.var_nodes.first, scope.inner_scopes.first)\n\n      scope.references?(variable, check_inner_scopes: false).should be_false\n    end\n\n    it \"returns false if current scope does not reference variable\" do\n      nodes = as_nodes <<-CRYSTAL\n        def method\n          a = 2\n          block do\n            b = 3\n            3.times { |i| b = b + i }\n          end\n        end\n        CRYSTAL\n\n      var_node = nodes.var_nodes.first\n\n      scope = Scope.new nodes.def_nodes.first\n      scope.add_variable(var_node)\n      scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope)\n\n      variable = Variable.new(var_node, scope)\n\n      scope.inner_scopes.first.references?(variable).should be_false\n    end\n  end\n\n  describe \"#add_variable\" do\n    it \"adds a new variable to the scope\" do\n      scope = Scope.new as_node(\"\")\n      scope.add_variable(Crystal::Var.new \"foo\")\n      scope.variables.empty?.should be_false\n    end\n  end\n\n  describe \"#find_variable\" do\n    it \"returns the variable in the scope by name\" do\n      scope = Scope.new as_node(\"foo = 1\")\n      scope.add_variable(Crystal::Var.new \"foo\")\n      scope.find_variable(\"foo\").should_not be_nil\n    end\n\n    it \"returns nil if variable not exist in this scope\" do\n      scope = Scope.new as_node(\"foo = 1\")\n      scope.find_variable(\"bar\").should be_nil\n    end\n  end\n\n  describe \"#assign_variable\" do\n    it \"creates a new assignment\" do\n      scope = Scope.new as_node(\"foo = 1\")\n      scope.add_variable(Crystal::Var.new \"foo\")\n      scope.assign_variable(\"foo\", Crystal::Var.new \"foo\")\n      var = scope.find_variable(\"foo\").should_not be_nil\n      var.assignments.size.should eq 1\n    end\n\n    it \"does not create the assignment if variable is wrong\" do\n      scope = Scope.new as_node(\"foo = 1\")\n      scope.add_variable(Crystal::Var.new \"foo\")\n      scope.assign_variable(\"bar\", Crystal::Var.new \"bar\")\n      var = scope.find_variable(\"foo\").should_not be_nil\n      var.assignments.size.should eq 0\n    end\n  end\n\n  describe \"#block?\" do\n    it \"returns true if Crystal::Block\" do\n      nodes = as_nodes(\"3.times {}\")\n      scope = Scope.new nodes.block_nodes.first\n      scope.block?.should be_true\n    end\n\n    it \"returns false otherwise\" do\n      scope = Scope.new as_node(\"a = 1\")\n      scope.block?.should be_false\n    end\n  end\n\n  describe \"#spawn_block?\" do\n    it \"returns true if a node is a spawn block\" do\n      nodes = as_nodes(\"spawn {}\")\n      scope = Scope.new nodes.block_nodes.first\n      scope.spawn_block?.should be_true\n    end\n\n    it \"returns false otherwise\" do\n      scope = Scope.new as_node(\"a = 1\")\n      scope.spawn_block?.should be_false\n    end\n  end\n\n  describe \"#def?\" do\n    context \"when check_outer_scopes: true\" do\n      it \"returns true if outer scope is Crystal::Def\" do\n        nodes = as_nodes(\"def foo; 3.times {}; end\")\n        outer_scope = Scope.new nodes.def_nodes.first\n        scope = Scope.new nodes.block_nodes.first, outer_scope\n        scope.def?(check_outer_scopes: true).should be_true\n        scope.def?.should be_false\n      end\n    end\n\n    it \"returns true if Crystal::Def\" do\n      nodes = as_nodes(\"def foo; end\")\n      scope = Scope.new nodes.def_nodes.first\n      scope.def?.should be_true\n    end\n\n    it \"returns false otherwise\" do\n      scope = Scope.new as_node(\"a = 1\")\n      scope.def?.should be_false\n    end\n  end\n\n  describe \"#in_macro?\" do\n    it \"returns true if Crystal::Macro\" do\n      nodes = as_nodes <<-CRYSTAL\n        macro included\n        end\n        CRYSTAL\n      scope = Scope.new nodes.macro_nodes.first\n      scope.in_macro?.should be_true\n    end\n\n    it \"returns true if node is nested to Crystal::Macro\" do\n      nodes = as_nodes <<-CRYSTAL\n        macro included\n          {{ @type.each do |type| a = type end }}\n        end\n        CRYSTAL\n      outer_scope = Scope.new nodes.macro_nodes.first\n      scope = Scope.new nodes.block_nodes.first, outer_scope\n      scope.in_macro?.should be_true\n    end\n\n    it \"returns false otherwise\" do\n      scope = Scope.new as_node(\"a = 1\")\n      scope.in_macro?.should be_false\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/ast/util_spec.cr",
    "content": "require \"../../spec_helper\"\n\nmodule Ameba::AST\n  struct Test\n    include Util\n  end\n\n  describe Util do\n    subject = Test.new\n\n    describe \"#literal?\" do\n      [\n        Crystal::ArrayLiteral.new,\n        Crystal::BoolLiteral.new(false),\n        Crystal::CharLiteral.new('a'),\n        Crystal::HashLiteral.new,\n        Crystal::NamedTupleLiteral.new,\n        Crystal::NilLiteral.new,\n        Crystal::NumberLiteral.new(42),\n        Crystal::RegexLiteral.new(Crystal::StringLiteral.new(\"\")),\n        Crystal::StringLiteral.new(\"\"),\n        Crystal::SymbolLiteral.new(\"\"),\n        Crystal::TupleLiteral.new([] of Crystal::ASTNode),\n        Crystal::RangeLiteral.new(\n          Crystal::NilLiteral.new,\n          Crystal::NilLiteral.new,\n          true),\n      ].each do |literal|\n        it \"returns true if node is #{literal}\" do\n          subject.literal?(literal).should be_true\n        end\n      end\n\n      it \"returns false if node is not a literal\" do\n        subject.literal?(Crystal::Nop).should be_false\n      end\n    end\n\n    describe \"#static/dynamic_literal?\" do\n      [\n        Crystal::ArrayLiteral.new,\n        Crystal::ArrayLiteral.new([\n          Crystal::StringLiteral.new(\"foo\"),\n        ] of Crystal::ASTNode),\n        Crystal::BoolLiteral.new(false),\n        Crystal::CharLiteral.new('a'),\n        Crystal::HashLiteral.new,\n        Crystal::NamedTupleLiteral.new,\n        Crystal::NilLiteral.new,\n        Crystal::NumberLiteral.new(42),\n        Crystal::RegexLiteral.new(Crystal::StringLiteral.new(\"foo\")),\n        Crystal::RegexLiteral.new(Crystal::StringInterpolation.new([\n          Crystal::StringLiteral.new(\"foo\"),\n        ] of Crystal::ASTNode)),\n        Crystal::StringLiteral.new(\"foo\"),\n        Crystal::StringInterpolation.new([\n          Crystal::StringLiteral.new(\"foo\"),\n        ] of Crystal::ASTNode),\n        Crystal::SymbolLiteral.new(\"foo\"),\n        Crystal::TupleLiteral.new([] of Crystal::ASTNode),\n        Crystal::TupleLiteral.new([\n          Crystal::StringLiteral.new(\"foo\"),\n        ] of Crystal::ASTNode),\n        Crystal::RangeLiteral.new(\n          Crystal::NumberLiteral.new(0),\n          Crystal::NumberLiteral.new(10),\n          true),\n      ].each do |literal|\n        it \"properly identifies static node #{literal}\" do\n          subject.static_literal?(literal).should be_true\n          subject.dynamic_literal?(literal).should be_false\n        end\n      end\n\n      [\n        Crystal::StringInterpolation.new([Crystal::Path.new(%w[Foo])] of Crystal::ASTNode),\n        Crystal::ArrayLiteral.new([Crystal::Path.new(%w[Foo])] of Crystal::ASTNode),\n        Crystal::TupleLiteral.new([Crystal::Path.new(%w[Foo])] of Crystal::ASTNode),\n        Crystal::RegexLiteral.new(Crystal::StringInterpolation.new([\n          Crystal::StringLiteral.new(\"foo\"),\n          Crystal::Path.new(%w[Foo]),\n        ] of Crystal::ASTNode)),\n        Crystal::RangeLiteral.new(\n          Crystal::Path.new(%w[Foo]),\n          Crystal::NumberLiteral.new(10),\n          true),\n        Crystal::RangeLiteral.new(\n          Crystal::NumberLiteral.new(10),\n          Crystal::Path.new(%w[Foo]),\n          true),\n      ].each do |literal|\n        it \"properly identifies dynamic node #{literal}\" do\n          subject.dynamic_literal?(literal).should be_true\n          subject.static_literal?(literal).should be_false\n        end\n      end\n    end\n\n    describe \"#node_source\" do\n      it \"returns original source of the node\" do\n        s = <<-CRYSTAL\n          a = 1\n          CRYSTAL\n        node = Crystal::Parser.new(s).parse\n        source = subject.node_source node, s.split(\"\\n\")\n        source.should eq \"a = 1\"\n      end\n\n      it \"returns original source of multiline node\" do\n        s = <<-CRYSTAL\n          if ()\n            :ok\n          end\n          CRYSTAL\n        node = Crystal::Parser.new(s).parse\n        source = subject.node_source node, s.split(\"\\n\")\n        source.should eq <<-CRYSTAL\n          if ()\n            :ok\n          end\n          CRYSTAL\n      end\n\n      it \"does not report source of node which has incorrect location\" do\n        s = <<-CRYSTAL\n          module MyModule\n            macro conditional_error_for_inline_callbacks\n              {%\n                raise \"\"\n              %}\n            end\n\n            macro before_save(x = nil)\n            end\n          end\n          CRYSTAL\n        node = as_nodes(s).nil_literal_nodes.first\n        source = subject.node_source node, s.split(\"\\n\")\n        source.should eq \"nil\"\n      end\n    end\n\n    describe \"#flow_command?\" do\n      it \"returns true if this is return\" do\n        node = as_node(\"return 22\")\n        subject.flow_command?(node, false).should be_true\n      end\n\n      it \"returns true if this is a break in a loop\" do\n        node = as_node(\"break\")\n        subject.flow_command?(node, true).should be_true\n      end\n\n      it \"returns false if this is a break out of loop\" do\n        node = as_node(\"break\")\n        subject.flow_command?(node, false).should be_false\n      end\n\n      it \"returns true if this is a next in a loop\" do\n        node = as_node(\"next\")\n        subject.flow_command?(node, true).should be_true\n      end\n\n      it \"returns false if this is a next out of loop\" do\n        node = as_node(\"next\")\n        subject.flow_command?(node, false).should be_false\n      end\n\n      it \"returns true if this is raise\" do\n        node = as_node(\"raise e\")\n        subject.flow_command?(node, false).should be_true\n      end\n\n      it \"returns true if this is exit\" do\n        node = as_node(\"exit\")\n        subject.flow_command?(node, false).should be_true\n      end\n\n      it \"returns true if this is abort\" do\n        node = as_node(\"abort\")\n        subject.flow_command?(node, false).should be_true\n      end\n\n      it \"returns false otherwise\" do\n        node = as_node(\"foobar\")\n        subject.flow_command?(node, false).should be_false\n      end\n    end\n\n    describe \"#flow_expression?\" do\n      it \"returns true if this is a flow command\" do\n        node = as_node(\"return\")\n        subject.flow_expression?(node, true).should be_true\n      end\n\n      it \"returns true if this is if-else consumed by flow expressions\" do\n        node = as_node <<-CRYSTAL\n          if foo\n            return :foo\n          else\n            return :bar\n          end\n          CRYSTAL\n        subject.flow_expression?(node, false).should be_true\n      end\n\n      it \"returns true if this is unless-else consumed by flow expressions\" do\n        node = as_node <<-CRYSTAL\n          unless foo\n            return :foo\n          else\n            return :bar\n          end\n          CRYSTAL\n        subject.flow_expression?(node).should be_true\n      end\n\n      it \"returns true if this is case consumed by flow expressions\" do\n        node = as_node <<-CRYSTAL\n          case\n          when 1\n            return 1\n          when 2\n            return 2\n          else\n            return 3\n          end\n          CRYSTAL\n        subject.flow_expression?(node).should be_true\n      end\n\n      it \"returns true if this is select consumed by flow expressions\" do\n        node = as_node <<-CRYSTAL\n          select\n          when a = foo\n            return 1\n          when a = bar\n            return 2\n          else\n            return 3\n          end\n          CRYSTAL\n        subject.flow_expression?(node).should be_true\n      end\n\n      it \"returns true if this is exception handler consumed by flow expressions\" do\n        node = as_node <<-CRYSTAL\n          begin\n            raise \"exp\"\n          rescue ex\n            return ex\n          end\n          CRYSTAL\n        subject.flow_expression?(node).should be_true\n      end\n\n      it \"returns true if this while consumed by flow expressions\" do\n        node = as_node <<-CRYSTAL\n          while true\n            return\n          end\n          CRYSTAL\n        subject.flow_expression?(node).should be_true\n      end\n\n      it \"returns false if this while with break\" do\n        node = as_node <<-CRYSTAL\n          while true\n            break\n          end\n          CRYSTAL\n        subject.flow_expression?(node).should be_false\n      end\n\n      it \"returns true if this until consumed by flow expressions\" do\n        node = as_node <<-CRYSTAL\n          until false\n            return\n          end\n          CRYSTAL\n        subject.flow_expression?(node).should be_true\n      end\n\n      it \"returns false if this until with break\" do\n        node = as_node <<-CRYSTAL\n          until false\n            break\n          end\n          CRYSTAL\n        subject.flow_expression?(node).should be_false\n      end\n\n      it \"returns true if this expressions consumed by flow expressions\" do\n        node = as_node <<-CRYSTAL\n          exp1\n          exp2\n          return\n          CRYSTAL\n        subject.flow_expression?(node).should be_true\n      end\n\n      it \"returns false otherwise\" do\n        node = as_node <<-CRYSTAL\n          exp1\n          exp2\n          CRYSTAL\n        subject.flow_expression?(node).should be_false\n      end\n    end\n\n    describe \"#suffix?\" do\n      it \"returns true if the node is a suffix `if`\" do\n        node = as_node(\"foo if bar\")\n        subject.suffix?(node).should be_true\n      end\n\n      it \"returns true if the node is a suffix `if` (2)\" do\n        node = as_node(\"foo if bar.end\")\n        subject.suffix?(node).should be_true\n      end\n\n      it \"returns true if the node is a suffix `unless`\" do\n        node = as_node(\"foo unless bar\")\n        subject.suffix?(node).should be_true\n      end\n\n      it \"returns true if the node is a suffix `rescue`\" do\n        node = as_node(\"foo rescue bar\")\n        subject.suffix?(node).should be_true\n      end\n\n      it \"returns true if the node is a suffix `ensure`\" do\n        node = as_node(\"foo ensure bar\")\n        subject.suffix?(node).should be_true\n      end\n\n      it \"returns false if the node is not a suffix `if` or `unless`\" do\n        node = as_node(\"foo\")\n        subject.suffix?(node).should be_false\n      end\n\n      it \"returns false if the node is a ternary `if`\" do\n        node = as_node(\"foo ? bar : baz\")\n        subject.suffix?(node).should be_false\n      end\n\n      it \"returns false if the node is a non-suffix `if`\" do\n        node = as_node(\"if foo; bar; end\")\n        subject.suffix?(node).should be_false\n      end\n\n      it \"returns false if the node is a non-suffix `if` (2)\" do\n        node = as_node(\"if foo;bar;end\")\n        subject.suffix?(node).should be_false\n      end\n\n      it \"returns false if the node is a non-suffix `if` (3)\" do\n        node = as_node <<-CRYSTAL\n          if foo\n            bar\n          end\n          CRYSTAL\n        subject.suffix?(node).should be_false\n      end\n\n      it \"returns false if the node is a non-suffix `unless`\" do\n        node = as_node(\"unless foo; bar; end\")\n        subject.suffix?(node).should be_false\n      end\n\n      it \"returns false if the node is a non-suffix `rescue`\" do\n        node = as_node(\"begin; foo; rescue; end\")\n        subject.suffix?(node).should be_false\n      end\n\n      it \"returns false if the node is a non-suffix `ensure`\" do\n        node = as_node(\"begin; foo; ensure; end\")\n        subject.suffix?(node).should be_false\n      end\n    end\n\n    describe \"#has_short_block?\" do\n      it \"returns true if the node has a short block variant\" do\n        source = Source.new \"foo :bar, &.baz\"\n        node = as_node(source.code)\n        subject.has_short_block?(node, source.lines).should be_true\n      end\n\n      it \"returns false if the node has a one line block\" do\n        source = Source.new \"foo :bar { |x| x.baz? }\"\n        node = as_node(source.code)\n        subject.has_short_block?(node, source.lines).should be_false\n      end\n\n      it \"returns false if the node does not have a short block variant\" do\n        source = Source.new \"foo :bar { |x| x.baz? }\"\n        node = as_node(source.code)\n        subject.has_short_block?(node, source.lines).should be_false\n      end\n\n      it \"returns false if the node does not have a block\" do\n        source = Source.new \"foo :bar\"\n        node = as_node(source.code)\n        subject.has_short_block?(node, source.lines).should be_false\n      end\n    end\n\n    describe \"#has_block?\" do\n      it \"returns true if the node has a block\" do\n        node = as_node(\"%w[foo bar].first { :baz }\")\n        subject.has_block?(node).should be_true\n      end\n\n      it \"returns true if the node has a block (shorthand)\" do\n        node = as_node(\"%w[foo bar].find(&.empty?)\")\n        subject.has_block?(node).should be_true\n      end\n\n      it \"returns true if the node has a block (block argument)\" do\n        node = as_node(\"%w[foo bar].find(&block)\")\n        subject.has_block?(node).should be_true\n      end\n\n      it \"returns true if the node has a block (forwarded block argument)\" do\n        node = as_node(\"%w[foo bar].find(&->foo)\")\n        subject.has_block?(node).should be_true\n      end\n\n      it \"returns false if the node does not have a block\" do\n        node = as_node(\"%w[foo bar].first\")\n        subject.has_block?(node).should be_false\n      end\n    end\n\n    describe \"#has_arguments?\" do\n      it \"returns false if the node has no arguments\" do\n        node = as_node(\"foo.bar\")\n        subject.has_arguments?(node).should be_false\n      end\n\n      it \"returns true if the node has positional arguments\" do\n        node = as_node(\"foo.bar(1)\")\n        subject.has_arguments?(node).should be_true\n      end\n\n      it \"returns true if the node has named arguments\" do\n        node = as_node(\"foo.bar(baz: 1)\")\n        subject.has_arguments?(node).should be_true\n      end\n\n      it \"returns true if the node has splat arguments\" do\n        node = as_node(\"foo.bar(*baz)\")\n        subject.has_arguments?(node).should be_true\n      end\n\n      it \"returns true if the node has double splat arguments\" do\n        node = as_node(\"foo.bar(**baz)\")\n        subject.has_arguments?(node).should be_true\n      end\n    end\n\n    describe \"#takes_arguments?\" do\n      it \"returns false if the node takes no arguments\" do\n        node = as_node(\"def foo; end\")\n        subject.takes_arguments?(node).should be_false\n      end\n\n      it \"returns true if the node takes positional arguments\" do\n        node = as_node(\"def foo(bar); end\")\n        subject.takes_arguments?(node).should be_true\n      end\n\n      it \"returns true if the node takes named arguments\" do\n        node = as_node(\"def foo(*, bar); end\")\n        subject.takes_arguments?(node).should be_true\n      end\n\n      it \"returns true if the node has splat index\" do\n        node = as_node(\"def foo(*args); end\")\n        subject.takes_arguments?(node).should be_true\n      end\n\n      it \"returns true if the node has double splat\" do\n        node = as_node(\"def foo(**kwargs); end\")\n        subject.takes_arguments?(node).should be_true\n      end\n    end\n\n    describe \"#raise?\" do\n      it \"returns true if this is a raise method call\" do\n        node = as_node \"raise e\"\n        subject.raise?(node).should be_true\n      end\n\n      it \"returns false if it has a receiver\" do\n        node = as_node \"obj.raise e\"\n        subject.raise?(node).should be_false\n      end\n\n      it \"returns false if size of the arguments doesn't match\" do\n        node = as_node \"raise\"\n        subject.raise?(node).should be_false\n      end\n    end\n\n    describe \"#exit?\" do\n      it \"returns true if this is a exit method call\" do\n        node = as_node \"exit\"\n        subject.exit?(node).should be_true\n      end\n\n      it \"returns true if this is a exit method call with one argument\" do\n        node = as_node \"exit 1\"\n        subject.exit?(node).should be_true\n      end\n\n      it \"returns false if it has a receiver\" do\n        node = as_node \"obj.exit\"\n        subject.exit?(node).should be_false\n      end\n\n      it \"returns false if size of the arguments doesn't match\" do\n        node = as_node \"exit 1, 1\"\n        subject.exit?(node).should be_false\n      end\n    end\n\n    describe \"#abort?\" do\n      it \"returns true if this is an abort method call\" do\n        node = as_node \"abort\"\n        subject.abort?(node).should be_true\n      end\n\n      it \"returns true if this is an abort method call with one argument\" do\n        node = as_node \"abort \\\"message\\\"\"\n        subject.abort?(node).should be_true\n      end\n\n      it \"returns true if this is an abort method call with two arguments\" do\n        node = as_node \"abort \\\"message\\\", 1\"\n        subject.abort?(node).should be_true\n      end\n\n      it \"returns false if it has a receiver\" do\n        node = as_node \"obj.abort\"\n        subject.abort?(node).should be_false\n      end\n\n      it \"returns false if size of the arguments doesn't match\" do\n        node = as_node \"abort 1, 1, 1\"\n        subject.abort?(node).should be_false\n      end\n    end\n\n    describe \"#loop?\" do\n      it \"returns true if this is a loop method call\" do\n        node = as_node \"loop\"\n        subject.loop?(node).should be_true\n      end\n\n      it \"returns false if it has a receiver\" do\n        node = as_node \"obj.loop\"\n        subject.loop?(node).should be_false\n      end\n\n      it \"returns false if size of the arguments doesn't match\" do\n        node = as_node \"loop 1\"\n        subject.loop?(node).should be_false\n      end\n    end\n\n    describe \"#operator_method_name?\" do\n      it \"returns true for operator method names\" do\n        %w[+ - * / == != ~= !~ <=>].each do |op|\n          subject.operator_method_name?(op).should be_true\n        end\n      end\n\n      it \"returns false for non-operator method names\" do\n        %w[foo? foo! ->].each do |op|\n          subject.operator_method_name?(op).should be_false\n        end\n      end\n    end\n\n    describe \"#operator_method?\" do\n      it \"returns true for operator method definitions\" do\n        node = as_node \"def +(other); end\"\n        subject.operator_method?(node).should be_true\n      end\n\n      it \"returns false for other method definitions\" do\n        node = as_node \"def method; end\"\n        subject.operator_method?(node).should be_false\n      end\n\n      it \"returns true for operator method calls\" do\n        node = as_node \"obj + 1\"\n        subject.operator_method?(node).should be_true\n      end\n\n      it \"returns false for other method calls\" do\n        node = as_node \"obj.method\"\n        subject.operator_method?(node).should be_false\n      end\n\n      it \"returns false for procs\" do\n        node = as_node \"-> { nil }\"\n        subject.operator_method?(node).should be_false\n      end\n    end\n\n    describe \"#setter_method_name?\" do\n      it \"returns true for setter method names\" do\n        %w[foo= []=].each do |op|\n          subject.setter_method_name?(op).should be_true\n        end\n      end\n\n      it \"returns false for operator method names\" do\n        %w[== !=].each do |op|\n          subject.setter_method_name?(op).should be_false\n        end\n      end\n\n      it \"returns false for non-operator method names\" do\n        %w[foo foo? foo!].each do |op|\n          subject.setter_method_name?(op).should be_false\n        end\n      end\n    end\n\n    describe \"#setter_method?\" do\n      it \"returns true for setter method definitions\" do\n        node = as_node \"def foo=(@foo); end\"\n        subject.setter_method?(node).should be_true\n      end\n\n      it \"returns false for other method definitions\" do\n        node = as_node \"def foo; end\"\n        subject.setter_method?(node).should be_false\n      end\n\n      it \"returns true for setter method calls\" do\n        node = as_node \"foo.bar = 123\"\n        subject.setter_method?(node).should be_true\n      end\n\n      it \"returns false for regular method calls\" do\n        node = as_node \"obj.method\"\n        subject.setter_method?(node).should be_false\n      end\n\n      it \"returns false for procs\" do\n        node = as_node \"-> { nil }\"\n        subject.setter_method?(node).should be_false\n      end\n    end\n\n    describe \"#nodoc?\" do\n      it \"returns true if a node has a single `:nodoc:` annotation\" do\n        node = as_node <<-CRYSTAL, wants_doc: true\n          # :nodoc:\n          def foo; end\n          CRYSTAL\n\n        subject.nodoc?(node).should be_true\n      end\n\n      it \"returns true if a node has a `:nodoc:` annotation in the first line\" do\n        node = as_node <<-CRYSTAL, wants_doc: true\n          # :nodoc:\n          #\n          # foo\n          def foo; end\n          CRYSTAL\n\n        subject.nodoc?(node).should be_true\n      end\n\n      it \"returns false if a node has a `:nodoc:` annotation in the middle\" do\n        node = as_node <<-CRYSTAL, wants_doc: true\n          # foo\n          # :nodoc:\n          # bar\n          def foo; end\n          CRYSTAL\n\n        subject.nodoc?(node).should be_false\n      end\n    end\n\n    describe \"#heredoc?\" do\n      it \"returns true if a node is a heredoc string\" do\n        source = Source.new <<-CRYSTAL\n          <<-FOO\n            This is a heredoc\n            FOO\n          CRYSTAL\n\n        subject.heredoc?(source.ast, source).should be_true\n      end\n\n      it \"returns true if a node is a heredoc string interpolation\" do\n        source = Source.new <<-'CRYSTAL'\n          <<-FOO\n            This is a heredoc #{1 + 2}\n            FOO\n          CRYSTAL\n\n        subject.heredoc?(source.ast, source).should be_true\n      end\n\n      it \"returns false if a node is a regular string interpolation\" do\n        source = Source.new <<-'CRYSTAL'\n          \"This is not a heredoc #{1 + 2}\"\n          CRYSTAL\n\n        subject.heredoc?(source.ast, source).should be_false\n      end\n    end\n\n    describe \"#control_exp_code\" do\n      it \"returns the exp code of a control expression\" do\n        s = \"return 1\"\n        node = as_node(s).as Crystal::ControlExpression\n        exp_code = subject.control_exp_code node, [s]\n        exp_code.should eq \"1\"\n      end\n\n      it \"wraps implicit tuple literal with curly brackets\" do\n        s = \"return 1, 2\"\n        node = as_node(s).as Crystal::ControlExpression\n        exp_code = subject.control_exp_code node, [s]\n        exp_code.should eq \"{1, 2}\"\n      end\n\n      it \"accepts explicit tuple literal\" do\n        s = \"return {1, 2}\"\n        node = as_node(s).as Crystal::ControlExpression\n        exp_code = subject.control_exp_code node, [s]\n        exp_code.should eq \"{1, 2}\"\n      end\n    end\n\n    describe \"#name_location_or\" do\n      it \"adjusts location column number by a given value\" do\n        node = as_node(\"def foo; end\").as Crystal::Def\n        subject.name_location_or(node, adjust_location_column_number: 10).to_s\n          .should eq \"{:1:15, :1:17}\"\n      end\n\n      it \"works on method call\" do\n        node = as_node(\"def foo; end\").as Crystal::Def\n        subject.name_location_or(node).to_s.should eq \"{:1:5, :1:7}\"\n      end\n\n      it \"works on class definition\" do\n        node = as_node(\"class Foo; end\").as Crystal::ClassDef\n        subject.name_location_or(node).to_s.should eq \"{:1:7, :1:9}\"\n      end\n\n      it \"works on module definition\" do\n        node = as_node(\"module Foo; end\").as Crystal::ModuleDef\n        subject.name_location_or(node).to_s.should eq \"{:1:8, :1:10}\"\n      end\n    end\n\n    describe \"#name_end_location\" do\n      it \"works on method call\" do\n        node = as_node(\"name(foo)\").as Crystal::Call\n        subject.name_end_location(node).to_s.should eq \":1:4\"\n      end\n\n      it \"works on method definition\" do\n        node = as_node(\"def name; end\").as Crystal::Def\n        subject.name_end_location(node).to_s.should eq \":1:8\"\n      end\n\n      it \"works on macro definition\" do\n        node = as_node(\"macro name; end\").as Crystal::Macro\n        subject.name_end_location(node).to_s.should eq \":1:10\"\n      end\n\n      it \"works on class definition\" do\n        node = as_node(\"class Name; end\").as Crystal::ClassDef\n        subject.name_end_location(node).to_s.should eq \":1:10\"\n      end\n\n      it \"works on module definition\" do\n        node = as_node(\"module Name; end\").as Crystal::ModuleDef\n        subject.name_end_location(node).to_s.should eq \":1:11\"\n      end\n\n      it \"works on annotation definition\" do\n        node = as_node(\"annotation Name; end\").as Crystal::AnnotationDef\n        subject.name_end_location(node).to_s.should eq \":1:15\"\n      end\n\n      it \"works on enum definition\" do\n        node = as_node(\"enum Name; end\").as Crystal::EnumDef\n        subject.name_end_location(node).to_s.should eq \":1:9\"\n      end\n\n      it \"works on alias definition\" do\n        node = as_node(\"alias Name = Foo\").as Crystal::Alias\n        subject.name_end_location(node).to_s.should eq \":1:10\"\n      end\n\n      it \"works on generic\" do\n        node = as_node(\"Name(Foo)\").as Crystal::Generic\n        subject.name_end_location(node).to_s.should eq \":1:4\"\n      end\n\n      it \"works on include\" do\n        node = as_node(\"include Name\").as Crystal::Include\n        subject.name_end_location(node).to_s.should eq \":1:12\"\n      end\n\n      it \"works on extend\" do\n        node = as_node(\"extend Name\").as Crystal::Extend\n        subject.name_end_location(node).to_s.should eq \":1:11\"\n      end\n\n      it \"works on variable type declaration\" do\n        node = as_node(\"name : Foo\").as Crystal::TypeDeclaration\n        subject.name_end_location(node).to_s.should eq \":1:4\"\n      end\n\n      it \"works on uninitialized variable\" do\n        node = as_node(\"name = uninitialized Foo\").as Crystal::UninitializedVar\n        subject.name_end_location(node).to_s.should eq \":1:4\"\n      end\n\n      it \"works on lib definition\" do\n        node = as_node(\"lib Name; end\").as Crystal::LibDef\n        subject.name_end_location(node).to_s.should eq \":1:8\"\n      end\n\n      it \"works on lib type definition\" do\n        node = as_node(\"lib Foo; type Name = Bar; end\").as(Crystal::LibDef).body\n        node.class.should eq Crystal::TypeDef\n        subject.name_end_location(node).to_s.should eq \":1:18\"\n      end\n\n      it \"works on metaclass\" do\n        node = as_node(\"foo : Name.class\").as(Crystal::TypeDeclaration).declared_type\n        node.class.should eq Crystal::Metaclass\n        subject.name_end_location(node).to_s.should eq \":1:10\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/ast/variabling/argument_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n  describe Argument do\n    arg = Crystal::Arg.new \"a\"\n    scope = Scope.new as_node \"foo = 1\"\n    variable = Variable.new(Crystal::Var.new(\"foo\"), scope)\n\n    describe \"#initialize\" do\n      it \"creates a new argument\" do\n        argument = Argument.new(arg, variable)\n        argument.node.should_not be_nil\n      end\n    end\n\n    describe \"delegation\" do\n      it \"delegates locations to node\" do\n        argument = Argument.new(arg, variable)\n        argument.location.should eq arg.location\n        argument.end_location.should eq arg.end_location\n      end\n\n      it \"delegates to_s to node\" do\n        argument = Argument.new(arg, variable)\n        argument.to_s.should eq arg.to_s\n      end\n    end\n\n    describe \"#ignored?\" do\n      it \"is true if arg starts with _\" do\n        argument = Argument.new(Crystal::Arg.new(\"_a\"), variable)\n        argument.ignored?.should be_true\n      end\n\n      it \"is false otherwise\" do\n        argument = Argument.new(arg, variable)\n        argument.ignored?.should be_false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/ast/variabling/assignment_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n  describe Assignment do\n    node = Crystal::NilLiteral.new\n    scope = Scope.new as_node \"foo = 1\"\n    variable = Variable.new(Crystal::Var.new(\"foo\"), scope)\n\n    describe \"#initialize\" do\n      it \"creates a new assignment with node and var\" do\n        assignment = Assignment.new(node, variable, scope)\n        assignment.node.should_not be_nil\n      end\n    end\n\n    describe \"delegation\" do\n      it \"delegates locations\" do\n        assignment = Assignment.new(node, variable, scope)\n        assignment.location.should eq node.location\n        assignment.end_location.should eq node.end_location\n      end\n\n      it \"delegates to_s\" do\n        assignment = Assignment.new(node, variable, scope)\n        assignment.to_s.should eq node.to_s\n      end\n\n      it \"delegates scope\" do\n        assignment = Assignment.new(node, variable, scope)\n        assignment.scope.should eq variable.scope\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/ast/variabling/reference_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n  describe Reference do\n    it \"is derived from a Variable\" do\n      node = Crystal::Var.new \"foo\"\n      ref = Reference.new(node, Scope.new as_node \"foo = 1\")\n      ref.should be_a Variable\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/ast/variabling/type_dec_variable_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n  describe TypeDecVariable do\n    var = Crystal::Var.new(\"foo\")\n    declared_type = Crystal::Path.new(\"String\")\n    type_dec = Crystal::TypeDeclaration.new(var, declared_type)\n\n    describe \"#initialize\" do\n      it \"creates a new type dec variable\" do\n        variable = TypeDecVariable.new(type_dec)\n        variable.node.should_not be_nil\n      end\n    end\n\n    describe \"#name\" do\n      it \"returns var name\" do\n        variable = TypeDecVariable.new(type_dec)\n        variable.name.should eq var.name\n      end\n\n      it \"raises if type declaration is incorrect\" do\n        type_dec = Crystal::TypeDeclaration.new(declared_type, declared_type)\n\n        expect_raises(Exception, \"Unsupported var node type: Crystal::Path\") do\n          TypeDecVariable.new(type_dec).name\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/ast/variabling/variable_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n  describe Variable do\n    var_node = Crystal::Var.new(\"foo\")\n    scope = Scope.new as_node \"foo = 1\"\n\n    describe \"#initialize\" do\n      it \"creates a new variable\" do\n        variable = Variable.new(var_node, scope)\n        variable.node.should_not be_nil\n      end\n    end\n\n    describe \"delegation\" do\n      it \"delegates locations\" do\n        variable = Variable.new(var_node, scope)\n        variable.location.should eq var_node.location\n        variable.end_location.should eq var_node.end_location\n      end\n\n      it \"delegates name\" do\n        variable = Variable.new(var_node, scope)\n        variable.name.should eq var_node.name\n      end\n\n      it \"delegates to_s\" do\n        variable = Variable.new(var_node, scope)\n        variable.to_s.should eq var_node.to_s\n      end\n    end\n\n    describe \"#special?\" do\n      it \"returns truthy if it is a special `$?` var\" do\n        variable = Variable.new Crystal::Var.new(\"$?\"), scope\n        variable.special?.should be_truthy\n      end\n\n      it \"returns falsey otherwise\" do\n        variable = Variable.new Crystal::Var.new(\"a\"), scope\n        variable.special?.should be_falsey\n      end\n    end\n\n    describe \"#assign\" do\n      assign_node = as_node(\"foo=1\")\n\n      it \"assigns the variable (creates a new assignment)\" do\n        variable = Variable.new(var_node, scope)\n        variable.assign(assign_node, scope)\n        variable.assignments.empty?.should be_false\n      end\n\n      it \"can create multiple assignments\" do\n        variable = Variable.new(var_node, scope)\n        variable.assign(assign_node, scope)\n        variable.assign(assign_node, scope)\n        variable.assignments.size.should eq 2\n      end\n    end\n\n    describe \"#reference\" do\n      it \"references the existed assignment\" do\n        variable = Variable.new(var_node, scope)\n        variable.assign(as_node(\"foo=1\"), scope)\n        variable.reference(var_node, scope)\n        variable.references.empty?.should be_false\n      end\n\n      it \"adds a reference to the scope\" do\n        scope = Scope.new as_node \"foo = 1\"\n        variable = Variable.new(var_node, scope)\n        variable.assign(as_node(\"foo=1\"), scope)\n        variable.reference(var_node, scope)\n        scope.references.size.should eq 1\n        scope.references.first.node.to_s.should eq \"foo\"\n      end\n    end\n\n    describe \"#captured_by_block?\" do\n      it \"returns truthy if the variable is captured by block\" do\n        nodes = as_nodes <<-CRYSTAL\n          def method\n            a = 2\n            3.times { |i| a = a + i }\n          end\n          CRYSTAL\n\n        var_node = nodes.var_nodes.first\n\n        scope = Scope.new(nodes.def_nodes.first)\n        scope.add_variable(var_node)\n        scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope)\n\n        variable = Variable.new(var_node, scope)\n        variable.reference(nodes.var_nodes.last, scope.inner_scopes.last)\n\n        variable.captured_by_block?.should be_truthy\n      end\n\n      it \"returns falsy if the variable is not captured by the block\" do\n        scope = Scope.new as_node <<-CRYSTAL\n          def method\n            a = 1\n          end\n          CRYSTAL\n\n        scope.add_variable(Crystal::Var.new \"a\")\n        variable = scope.variables.first\n\n        variable.captured_by_block?.should be_falsey\n      end\n    end\n\n    describe \"#target_of?\" do\n      it \"returns true if the variable is a target of Crystal::Assign node\" do\n        assign_node = as_nodes(\"foo=1\").assign_nodes.last\n        variable = Variable.new assign_node.target.as(Crystal::Var), scope\n        variable.target_of?(assign_node).should be_true\n      end\n\n      it \"returns true if the variable is a target of Crystal::OpAssign node\" do\n        assign_node = as_nodes(\"foo=1;foo+=1\").op_assign_nodes.last\n        variable = Variable.new assign_node.target.as(Crystal::Var), scope\n        variable.target_of?(assign_node).should be_true\n      end\n\n      it \"returns true if the variable is a target of Crystal::MultiAssign node\" do\n        assign_node = as_nodes(\"a,b,c={1,2,3}\").multi_assign_nodes.last\n        assign_node.targets.size.should_not eq 0\n        assign_node.targets.each do |target|\n          variable = Variable.new target.as(Crystal::Var), scope\n          variable.target_of?(assign_node).should be_true\n        end\n      end\n\n      it \"returns false if the node is not assign\" do\n        variable = Variable.new(Crystal::Var.new(\"v\"), scope)\n        variable.target_of?(as_node \"nil\").should be_false\n      end\n\n      it \"returns false if the variable is not a target of the assign\" do\n        variable = Variable.new(Crystal::Var.new(\"foo\"), scope)\n        variable.target_of?(as_node(\"bar = 1\")).should be_false\n      end\n    end\n\n    describe \"#ignored?\" do\n      it \"is true if variable is ignored\" do\n        variable = Variable.new(Crystal::Var.new(\"_var\"), scope)\n        variable.ignored?.should be_true\n      end\n\n      it \"is false if variable is not ignored\" do\n        variable = Variable.new(Crystal::Var.new(\"v_ar\"), scope)\n        variable.ignored?.should be_false\n      end\n\n      it \"is true if variable is a black hole\" do\n        variable = Variable.new(Crystal::Var.new(\"_\"), scope)\n        variable.ignored?.should be_true\n      end\n    end\n\n    describe \"#eql?\" do\n      var = Crystal::Var.new(\"foo\").at(Crystal::Location.new(nil, 1, 2))\n      variable = Variable.new var, scope\n\n      it \"is false if node is not a Crystal::Var\" do\n        variable.eql?(as_node(\"nil\")).should be_false\n      end\n\n      it \"is false if node name is different\" do\n        variable.eql?(Crystal::Var.new \"bar\").should be_false\n      end\n\n      it \"is false if node has a different location\" do\n        variable.eql?(Crystal::Var.new \"foo\").should be_false\n      end\n\n      it \"is true otherwise\" do\n        variable.eql?(variable.node).should be_true\n      end\n    end\n\n    describe \"#declared_before?\" do\n      it \"is falsey if variable doesn't have location\" do\n        var1 = Crystal::Var.new(\"foo\")\n        var2 = Crystal::Var.new(\"bar\").at(Crystal::Location.new(nil, 1, 2))\n        Variable.new(var1, scope).declared_before?(var2).should be_falsey\n      end\n\n      it \"is falsey if node doesn't have location\" do\n        var1 = Crystal::Var.new(\"foo\").at(Crystal::Location.new(nil, 1, 2))\n        var2 = Crystal::Var.new(\"bar\")\n        Variable.new(var1, scope).declared_before?(var2).should be_falsey\n      end\n\n      it \"is true if var's line_number below the node\" do\n        var1 = Crystal::Var.new(\"foo\").at(Crystal::Location.new(nil, 1, 2))\n        var2 = Crystal::Var.new(\"bar\").at(Crystal::Location.new(nil, 2, 2))\n        Variable.new(var1, scope).declared_before?(var2).should be_true\n      end\n\n      it \"is true if var's column_number is after the node\" do\n        var1 = Crystal::Var.new(\"foo\").at(Crystal::Location.new(nil, 1, 2))\n        var2 = Crystal::Var.new(\"bar\").at(Crystal::Location.new(nil, 1, 3))\n        Variable.new(var1, scope).declared_before?(var2).should be_true\n      end\n\n      it \"is false if var's location is before the node\" do\n        var1 = Crystal::Var.new(\"foo\").at(Crystal::Location.new(nil, 2, 2))\n        var2 = Crystal::Var.new(\"bar\").at(Crystal::Location.new(nil, 1, 3))\n        Variable.new(var1, scope).declared_before?(var2).should be_false\n      end\n\n      it \"is false is the node is the same var\" do\n        var = Crystal::Var.new(\"foo\").at(Crystal::Location.new(nil, 2, 2))\n        Variable.new(var, scope).declared_before?(var).should be_false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/ast/visitors/counting_visitor_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n  describe CountingVisitor do\n    describe \"#visit\" do\n      it \"allow to visit ASTNode\" do\n        node = Crystal::Parser.new(\"\").parse\n        visitor = CountingVisitor.new node\n        node.accept visitor\n      end\n    end\n\n    describe \"#count\" do\n      it \"is 1 for an empty method\" do\n        node = Crystal::Parser.new(\"def hello; end\").parse\n        visitor = CountingVisitor.new node\n\n        visitor.count.should eq 1\n      end\n\n      it \"is 1 if there is Macro::For\" do\n        code = <<-CRYSTAL\n          def initialize\n            {% for c in ALL_NODES %}\n              true || false\n            {% end %}\n          end\n          CRYSTAL\n        node = Crystal::Parser.new(code).parse\n        visitor = CountingVisitor.new node\n        visitor.count.should eq 1\n      end\n\n      it \"is 1 if there is Macro::If\" do\n        code = <<-CRYSTAL\n          def initialize\n            {% if foo.bar? %}\n              true || false\n            {% end %}\n          end\n          CRYSTAL\n        node = Crystal::Parser.new(code).parse\n        visitor = CountingVisitor.new node\n        visitor.count.should eq 1\n      end\n\n      it \"increases count for every exhaustive case\" do\n        code = <<-CRYSTAL\n          def hello(a : Int32 | Int64 | Float32 | Float64)\n            case a\n            in Int32   then \"int32\"\n            in Int64   then \"int64\"\n            in Float32 then \"float32\"\n            in Float64 then \"float64\"\n            end\n          end\n          CRYSTAL\n        node = Crystal::Parser.new(code).parse\n        visitor = CountingVisitor.new node\n        visitor.count.should eq 2\n      end\n\n      {% for pair in [\n                       {code: \"if true; end\", description: \"conditional\"},\n                       {code: \"while true; end\", description: \"while loop\"},\n                       {code: \"until 1 < 2; end\", description: \"until loop\"},\n                       {code: \"begin; rescue; end\", description: \"rescue\"},\n                       {code: \"true || false\", description: \"or\"},\n                       {code: \"true && false\", description: \"and\"},\n                       {\n                         code:        \"a : String | Int32 = 1; case a when true; end\",\n                         description: \"inexhaustive when\",\n                       },\n                       {\n                         code:        \"select; when a = foo; end\",\n                         description: \"select\",\n                       },\n                     ] %}\n        it \"increases count for every {{ pair[:description].id }}\" do\n          node = Crystal::Parser.new(\"def hello; {{ pair[:code].id }} end\").parse\n          visitor = CountingVisitor.new node\n\n          visitor.count.should eq 2\n        end\n      {% end %}\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/ast/visitors/elseif_aware_node_visitor_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n  describe ElseIfAwareNodeVisitor do\n    rule = ElseIfRule.new\n    subject = ElseIfAwareNodeVisitor.new rule, Source.new <<-CRYSTAL\n      # rule.ifs[0]\n      foo ? bar : baz\n\n      def foo\n        # rule.ifs[2]\n        if :one\n          1\n        elsif :two\n          2\n        elsif :three\n          3\n        else\n          %w[].each do\n            # rule.ifs[1]\n            if true\n              'a'\n            elsif false\n              'b'\n            else\n              'c'\n            end\n          end\n        end\n      end\n      CRYSTAL\n\n    it \"inherits a logic from `NodeVisitor`\" do\n      subject.should be_a(NodeVisitor)\n    end\n\n    it \"fires a callback for every `if` node, excluding `elsif` branches\" do\n      rule.ifs.size.should eq 3\n    end\n\n    it \"fires a callback with an array containing an `if` node without an `elsif` branches\" do\n      if_node, ifs = rule.ifs[0]\n      if_node.to_s.should eq \"foo ? bar : baz\"\n\n      ifs.should be_nil\n    end\n\n    it \"fires a callback with an array containing an `if` node with multiple `elsif` branches\" do\n      if_node, ifs = rule.ifs[2]\n      if_node.cond.to_s.should eq \":one\"\n\n      ifs = ifs.should_not be_nil\n      ifs.size.should eq 3\n      ifs.first.should be if_node\n      ifs.map(&.then.to_s).should eq %w[1 2 3]\n    end\n\n    it \"fires a callback with an array containing an `if` node with the `else` branch as the last item\" do\n      if_node, ifs = rule.ifs[1]\n      if_node.cond.to_s.should eq \"true\"\n\n      ifs = ifs.should_not be_nil\n      ifs.size.should eq 2\n      ifs.first.should be if_node\n      ifs.map(&.then.to_s).should eq %w['a' 'b']\n      ifs.last.else.to_s.should eq %('c')\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/ast/visitors/flow_expression_visitor_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n  describe FlowExpressionVisitor do\n    it \"creates an expression for return\" do\n      rule = FlowExpressionRule.new\n      FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL\n        def foo\n          return :bar\n        end\n        CRYSTAL\n      rule.expressions.size.should eq 1\n    end\n\n    it \"can create multiple expressions\" do\n      rule = FlowExpressionRule.new\n      FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL\n        def foo\n          if bar\n            return :baz\n          else\n            return :foobar\n          end\n        end\n        CRYSTAL\n      rule.expressions.size.should eq 3\n    end\n\n    it \"properly creates nested flow expressions\" do\n      rule = FlowExpressionRule.new\n      FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL\n        def foo\n          return (\n            loop do\n              break if a > 1\n              return a\n            end\n          )\n        end\n        CRYSTAL\n      rule.expressions.size.should eq 4\n    end\n\n    it \"creates an expression for break\" do\n      rule = FlowExpressionRule.new\n      FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL\n        while true\n          break\n        end\n        CRYSTAL\n      rule.expressions.size.should eq 1\n    end\n\n    it \"creates an expression for next\" do\n      rule = FlowExpressionRule.new\n      FlowExpressionVisitor.new rule, Source.new <<-CRYSTAL\n        while true\n          next if something\n        end\n        CRYSTAL\n      rule.expressions.size.should eq 1\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/ast/visitors/implicit_return_visitor_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nprivate def implicit_return_visit(code)\n  Ameba::ImplicitReturnRule.new.tap do |rule|\n    Ameba::AST::ImplicitReturnVisitor.new rule, Ameba::Source.new(code)\n  end\nend\n\nprivate def has_unused_expression?(rule, str)\n  rule.unused_expressions.any?(&.to_s.== str)\nend\n\nprivate def has_unused_expression?(rule, node_type : Crystal::ASTNode.class)\n  rule.unused_expressions.any?(node_type)\nend\n\nprivate def has_unused_call?(rule, name)\n  rule.unused_expressions.any? do |node|\n    node.is_a?(Crystal::Call) && node.name == name\n  end\nend\n\nmodule Ameba::AST\n  describe ImplicitReturnVisitor do\n    context \"Crystal::Expressions\" do\n      it \"reports all non-last expressions as unused\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          def method\n            foo\n            bar\n            baz\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_true\n        has_unused_expression?(rule, \"bar\").should be_true\n        has_unused_expression?(rule, \"baz\").should be_false\n      end\n\n      it \"reports non-last expression even when parent captures result\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          x = begin\n            foo\n            bar\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_true\n        has_unused_expression?(rule, \"bar\").should be_false\n        has_unused_expression?(rule, \"x\").should be_false\n      end\n\n      it \"stops processing after control expressions\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          foo\n          return bar\n          baz\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_true\n        has_unused_expression?(rule, \"bar\").should be_false\n        has_unused_expression?(rule, \"baz\").should be_false\n      end\n\n      it \"handles nested expressions correctly\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          begin\n            foo\n            begin\n              bar\n              baz\n            end\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_true\n        has_unused_expression?(rule, \"bar\").should be_true\n      end\n    end\n\n    context \"assignments\" do\n      it \"marks assigned value as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          foo\n          x = bar\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_true\n        has_unused_expression?(rule, \"bar\").should be_false\n        has_unused_expression?(rule, \"x\").should be_false\n      end\n\n      it \"reports assignments when not captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          def method\n            x = 1\n            y = 2\n          end\n          CRYSTAL\n\n        assigns = rule.unused_expressions.select(Crystal::Assign)\n        assigns.size.should eq 1\n        assigns.first.to_s.should start_with \"x =\"\n      end\n\n      it \"marks op-assigned values as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          x = 0\n          x += foo\n          bar\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n      end\n\n      it \"marks multi-assigned values as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          a, b = foo, bar\n          baz\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n        has_unused_expression?(rule, \"bar\").should be_false\n      end\n    end\n\n    context \"Crystal::Call\" do\n      it \"marks all call arguments as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          foo(bar, baz)\n          qux\n          CRYSTAL\n\n        has_unused_expression?(rule, \"bar\").should be_false\n        has_unused_expression?(rule, \"baz\").should be_false\n      end\n\n      it \"reports the call itself when not captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          foo(1)\n          bar\n          CRYSTAL\n\n        has_unused_call?(rule, \"foo\").should be_true\n        has_unused_expression?(rule, \"bar\").should be_true\n      end\n\n      it \"handles method calls with blocks\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          foo.map { |x| x + 1 }\n          bar\n          CRYSTAL\n\n        has_unused_call?(rule, \"map\").should be_true\n        has_unused_expression?(rule, \"bar\").should be_true\n      end\n    end\n\n    context \"Crystal::If and Crystal::Unless\" do\n      it \"marks condition as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          if foo\n            bar\n          else\n            baz\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n        has_unused_expression?(rule, \"bar\").should be_true\n        has_unused_expression?(rule, \"baz\").should be_true\n      end\n\n      it \"reports if statement when not captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          if true\n            1\n          end\n          2\n          CRYSTAL\n\n        has_unused_expression?(rule, Crystal::If).should be_true\n        has_unused_expression?(rule, \"1\").should be_true\n        has_unused_expression?(rule, \"2\").should be_true\n      end\n\n      it \"captures last line of branches when if is captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          x = if foo\n            bar\n            baz\n          else\n            qux\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"bar\").should be_true\n        has_unused_expression?(rule, \"baz\").should be_false\n        has_unused_expression?(rule, \"qux\").should be_false\n      end\n    end\n\n    context \"Crystal::While and Crystal::Until\" do\n      it \"marks condition as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          while foo\n            bar\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n        has_unused_expression?(rule, \"bar\").should be_true\n      end\n\n      it \"does not capture loop body by default\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          while true\n            foo\n            bar\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_true\n        has_unused_expression?(rule, \"bar\").should be_true\n      end\n    end\n\n    context \"Crystal::Case\" do\n      it \"marks case condition as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          case foo\n          when 1\n            bar\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n      end\n\n      it \"marks when conditions as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          case x\n          when foo, bar\n            baz\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n        has_unused_expression?(rule, \"bar\").should be_false\n      end\n\n      it \"inherits parent capture state for when bodies\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          result = case x\n          when 1\n            foo\n            bar\n          when 2\n            baz\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_true\n      end\n    end\n\n    context \"Crystal::Def\" do\n      it \"marks method arguments as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          def method(x = foo)\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n      end\n\n      it \"captures method body last line by default\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          def method\n            foo\n            bar\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_true\n        has_unused_expression?(rule, \"bar\").should be_false\n      end\n\n      it \"does not capture body when return type is Nil\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          def method : Nil\n            foo\n            bar\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_true\n        has_unused_expression?(rule, \"bar\").should be_true\n      end\n\n      it \"does not capture body in initialize methods\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          class Foo\n            def initialize\n              foo\n              bar\n            end\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_true\n        has_unused_expression?(rule, \"bar\").should be_true\n      end\n\n      it \"handles method with complex body\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          def outer\n            foo\n            if condition\n              bar\n            end\n            baz\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_true\n        has_unused_expression?(rule, \"bar\").should be_true\n        has_unused_expression?(rule, \"baz\").should be_false\n      end\n    end\n\n    context \"literals\" do\n      it \"marks array elements as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          [foo, bar]\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n        has_unused_expression?(rule, \"bar\").should be_false\n      end\n\n      it \"marks hash values as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          {a: foo, b: bar}\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n        has_unused_expression?(rule, \"bar\").should be_false\n      end\n\n      it \"marks range bounds as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          foo..bar\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n        has_unused_expression?(rule, \"bar\").should be_false\n      end\n\n      it \"marks string interpolation expressions as captured\" do\n        rule = implicit_return_visit(<<-'CRYSTAL')\n          \"hello #{foo} world\"\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n      end\n\n      it \"marks regex contents as captured\" do\n        rule = implicit_return_visit(<<-'CRYSTAL')\n          /#{foo}/\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n      end\n    end\n\n    context \"Crystal::BinaryOp\" do\n      it \"marks left side as captured when right is a Call\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          foo + bar()\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n      end\n\n      it \"marks left side as captured when right is ControlExpression\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          foo || return bar\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n      end\n\n      it \"does not mark left side as captured for simple ops\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          foo + bar\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo + bar\").should be_true\n      end\n    end\n\n    context \"type operations\" do\n      it \"marks casted object as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          foo.as(Bar)\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n      end\n\n      it \"marks tested object as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          foo.is_a?(Bar)\n          baz.responds_to?(:method)\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n        has_unused_expression?(rule, \"baz\").should be_false\n      end\n\n      it \"marks typeof argument as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          typeof(foo)\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n      end\n\n      it \"marks declared value as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          x : Int32 = foo\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n      end\n    end\n\n    context \"Crystal::ExceptionHandler\" do\n      it \"does not capture body last line when else is present\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          begin\n            foo\n            bar\n          rescue\n            baz\n          else\n            qux\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"bar\").should be_true\n      end\n\n      it \"captures body last line when no else is present\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          x = begin\n            foo\n            bar\n          rescue\n            baz\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_true\n        has_unused_expression?(rule, \"bar\").should be_false\n        has_unused_expression?(rule, \"baz\").should be_false\n      end\n\n      it \"does not capture ensure block\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          begin\n            foo\n          ensure\n            bar\n            baz\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"bar\").should be_true\n        has_unused_expression?(rule, \"baz\").should be_true\n      end\n\n      it \"inherits capture state for rescue bodies\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          x = begin\n            foo\n          rescue\n            bar\n            baz\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"bar\").should be_true\n        has_unused_expression?(rule, \"baz\").should be_false\n      end\n    end\n\n    context \"Crystal::Block\" do\n      it \"inherits parent capture state for block body\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          [1, 2].map do |x|\n            foo\n            x + 1\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_true\n      end\n\n      it \"processes block when block itself is unused\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          3.times do\n            foo\n            bar\n          end\n          baz\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_true\n        has_unused_expression?(rule, \"bar\").should be_false\n        has_unused_expression?(rule, \"baz\").should be_true\n      end\n    end\n\n    context \"Crystal::ControlExpression\" do\n      it \"marks control expression arguments as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          return foo\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n      end\n\n      it \"reports the control expression itself\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          def method : Nil\n            return 1\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, Crystal::Return).should be_true\n      end\n\n      it \"handles break with value\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          loop do\n            break foo if condition\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n      end\n\n      it \"handles next with value\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          [1, 2].each do |x|\n            next foo if x == 1\n            bar\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n      end\n    end\n\n    context \"macros\" do\n      it \"marks macro arguments as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          macro method(arg = foo)\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n      end\n\n      it \"captures macro body\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          macro method\n            foo\n            bar\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n        has_unused_expression?(rule, \"bar\").should be_false\n      end\n\n      it \"sets in_macro flag for macro body\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          macro method\n            {% foo %}\n          end\n          CRYSTAL\n\n        # ameba:disable Performance/AnyInsteadOfPresent\n        rule.macro_flags.any?.should be_true\n        has_unused_expression?(rule, \"foo\").should be_true\n      end\n\n      it \"captures output macro expressions\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          {{ foo }}\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n      end\n\n      it \"does not capture non-output macro expressions\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          {% foo %}\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_true\n      end\n\n      it \"sets in_macro flag for macro expression\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          {% foo %}\n          CRYSTAL\n\n        # ameba:disable Performance/AnyInsteadOfPresent\n        rule.macro_flags.any?.should be_true\n      end\n\n      it \"marks macro if condition as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          {% if foo %}\n          {% end %}\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n      end\n\n      it \"does not capture macro if branches\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          {% if true %}\n            foo\n          {% else %}\n            bar\n          {% end %}\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n        has_unused_expression?(rule, \"bar\").should be_false\n      end\n\n      it \"does not capture macro for body\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          {% for x in [1, 2] %}\n            foo\n          {% end %}\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n      end\n    end\n\n    context \"other node types\" do\n      it \"visits enum members\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          enum Color\n            Red\n            Green\n            Blue\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"Red\").should be_false\n        has_unused_expression?(rule, \"Green\").should be_false\n        has_unused_expression?(rule, \"Blue\").should be_false\n      end\n\n      it \"visits class and module bodies\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          class Foo\n            foo\n            bar\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_true\n        has_unused_expression?(rule, \"bar\").should be_true\n      end\n\n      it \"marks function body as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          fun foo : Int32\n            return 1\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, Crystal::FunDef).should be_true\n      end\n\n      it \"marks unary operand as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          !foo\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n        has_unused_expression?(rule, \"!foo\").should be_true\n      end\n\n      it \"marks yielded values as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          yield foo, bar\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n        has_unused_expression?(rule, \"bar\").should be_false\n      end\n\n      it \"marks default argument value as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          def method(x = foo)\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n      end\n\n      it \"marks annotation arguments as captured\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          @[Foo(bar)]\n          def method\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"bar\").should be_false\n      end\n\n      it \"visits select whens\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          select\n          when x = foo\n            bar\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, Crystal::Select).should be_true\n        has_unused_expression?(rule, \"bar\").should be_true\n      end\n    end\n\n    context \"edge cases\" do\n      it \"handles deeply nested scopes\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          class Outer\n            class Inner\n              def method\n                if true\n                  foo\n                  bar\n                end\n              end\n            end\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_true\n        has_unused_expression?(rule, \"bar\").should be_false\n      end\n\n      it \"handles control expressions in method body\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          def method\n            return foo if condition\n            bar\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n        has_unused_expression?(rule, \"bar\").should be_false\n      end\n\n      it \"handles multiple assignment targets\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          a, b, c = foo, bar, baz\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n        has_unused_expression?(rule, \"bar\").should be_false\n        has_unused_expression?(rule, \"baz\").should be_false\n      end\n\n      it \"handles nested exception handlers\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          begin\n            begin\n              foo\n            rescue\n              bar\n            end\n          rescue\n            baz\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_true\n        has_unused_expression?(rule, \"bar\").should be_true\n        has_unused_expression?(rule, \"baz\").should be_true\n      end\n\n      it \"handles case with multiple when conditions\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          case x\n          when foo, bar, baz\n            qux\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_false\n        has_unused_expression?(rule, \"bar\").should be_false\n        has_unused_expression?(rule, \"baz\").should be_false\n      end\n\n      it \"handles trailing control expressions\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          begin\n            foo\n            bar\n            return baz\n          end\n          CRYSTAL\n\n        has_unused_expression?(rule, \"foo\").should be_true\n        has_unused_expression?(rule, \"bar\").should be_true\n      end\n    end\n\n    context \"integration scenarios\" do\n      it \"handles complex method with multiple features\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          def process(items)\n            return nil if items.empty?\n\n            result = items.map do |item|\n              if item.valid?\n                item.process\n              else\n                item.skip\n              end\n            end\n\n            log(result)\n            result\n          end\n          CRYSTAL\n\n        has_unused_call?(rule, \"log\").should be_true\n      end\n\n      it \"handles initialize with side effects\" do\n        rule = implicit_return_visit(<<-CRYSTAL)\n          class Foo\n            def initialize(@x : Int32)\n              validate!\n              setup_hooks\n            end\n          end\n          CRYSTAL\n\n        has_unused_call?(rule, \"validate!\").should be_true\n        has_unused_call?(rule, \"setup_hooks\").should be_true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/ast/visitors/node_visitor_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n  describe NodeVisitor do\n    describe \"visit\" do\n      it \"allow to visit ASTNode\" do\n        rule = DummyRule.new\n        visitor = NodeVisitor.new rule, Source.new\n\n        nodes = Crystal::Parser.new(\"\").parse\n        nodes.accept visitor\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/ast/visitors/redundant_control_expression_visitor_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n  describe RedundantControlExpressionVisitor do\n    source = Source.new\n    rule = RedundantControlExpressionRule.new\n\n    node = as_node <<-CRYSTAL\n      a = 1\n      b = 2\n      return a + b\n      CRYSTAL\n    subject = RedundantControlExpressionVisitor.new(rule, source, node)\n\n    it \"assigns valid attributes\" do\n      subject.rule.should eq rule\n      subject.source.should eq source\n      subject.node.should eq node\n    end\n\n    it \"fires a callback with a valid node\" do\n      rule.nodes.size.should eq 1\n      rule.nodes.first.to_s.should eq \"return a + b\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/ast/visitors/scope_calls_with_self_receiver_visitor_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n  describe ScopeCallsWithSelfReceiverVisitor do\n    {% for type in %w[class module].map(&.id) %}\n      it \"creates a scope for the {{ type }} def\" do\n        rule = SelfCallsRule.new\n        visitor = ScopeCallsWithSelfReceiverVisitor.new rule, Source.new <<-CRYSTAL\n          {{ type }} Foo\n            self.foo\n          end\n          CRYSTAL\n\n        call_queue = visitor.scope_call_queue\n        call_queue.size.should eq 1\n        call_queue.should eq rule.call_queue\n\n        scope, calls = call_queue.first\n\n        node = scope.node.should be_a(Crystal::{{ type.capitalize }}Def)\n        node.name.should be_a(Crystal::Path)\n        node.name.to_s.should eq \"Foo\"\n\n        calls.size.should eq 1\n        calls.first.name.should eq \"foo\"\n      end\n    {% end %}\n\n    it \"creates a scope for the def\" do\n      rule = SelfCallsRule.new\n      visitor = ScopeCallsWithSelfReceiverVisitor.new rule, Source.new <<-CRYSTAL\n        class Foo\n          def method\n            self.foo :a, :b, :c\n            self.bar\n            baz :x, :y, :z\n            it_is_a.bat \"country\"\n            self\n          end\n        end\n        CRYSTAL\n\n      call_queue = visitor.scope_call_queue\n      call_queue.size.should eq 1\n      call_queue.should eq rule.call_queue\n\n      scope, calls = call_queue.first\n\n      node = scope.node.should be_a(Crystal::Def)\n      node.name.should eq \"method\"\n\n      calls.size.should eq 2\n      calls.first.name.should eq \"foo\"\n      calls.last.name.should eq \"bar\"\n    end\n\n    it \"creates a scope for the proc\" do\n      rule = SelfCallsRule.new\n      visitor = ScopeCallsWithSelfReceiverVisitor.new rule, Source.new <<-CRYSTAL\n        -> { self.foo }\n        CRYSTAL\n\n      call_queue = visitor.scope_call_queue\n      call_queue.size.should eq 1\n      call_queue.should eq rule.call_queue\n    end\n\n    it \"creates a scope for the block\" do\n      rule = SelfCallsRule.new\n      visitor = ScopeCallsWithSelfReceiverVisitor.new rule, Source.new <<-CRYSTAL\n        3.times { self.foo }\n        CRYSTAL\n\n      call_queue = visitor.scope_call_queue\n      call_queue.size.should eq 1\n      call_queue.should eq rule.call_queue\n    end\n\n    context \"inner scopes\" do\n      it \"creates scope for block inside def\" do\n        rule = SelfCallsRule.new\n        visitor = ScopeCallsWithSelfReceiverVisitor.new rule, Source.new <<-CRYSTAL\n          def method\n            self.foo\n            3.times { self.bar }\n          end\n          CRYSTAL\n\n        call_queue = visitor.scope_call_queue\n        call_queue.size.should eq 2\n        call_queue.should eq rule.call_queue\n        call_queue.last_key.outer_scope.should eq call_queue.first_key\n      end\n\n      it \"creates scope for block inside block\" do\n        rule = SelfCallsRule.new\n        visitor = ScopeCallsWithSelfReceiverVisitor.new rule, Source.new <<-CRYSTAL\n          def method\n            3.times do\n              self.bar\n              2.times { self.baz }\n            end\n          end\n          CRYSTAL\n\n        call_queue = visitor.scope_call_queue\n        call_queue.size.should eq 2\n        call_queue.should eq rule.call_queue\n        call_queue.last_key.outer_scope.should eq call_queue.first_key\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/ast/visitors/scope_visitor_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n  describe ScopeVisitor do\n    {% for type in %w[class module enum].map(&.id) %}\n      it \"creates a scope for the {{ type }} def\" do\n        rule = ScopeRule.new\n        ScopeVisitor.new rule, Source.new <<-CRYSTAL\n          {{ type }} Foo\n          end\n          CRYSTAL\n        rule.scopes.size.should eq 1\n      end\n    {% end %}\n\n    it \"creates a scope for the def\" do\n      rule = ScopeRule.new\n      ScopeVisitor.new rule, Source.new <<-CRYSTAL\n        def method\n        end\n        CRYSTAL\n      rule.scopes.size.should eq 1\n    end\n\n    it \"creates a scope for the proc\" do\n      rule = ScopeRule.new\n      ScopeVisitor.new rule, Source.new <<-CRYSTAL\n        -> {}\n        CRYSTAL\n      rule.scopes.size.should eq 1\n    end\n\n    it \"creates a scope for the block\" do\n      rule = ScopeRule.new\n      ScopeVisitor.new rule, Source.new <<-CRYSTAL\n        3.times {}\n        CRYSTAL\n      rule.scopes.size.should eq 2\n    end\n\n    it \"correctly resets location-less node scope\" do\n      rule = ScopeRule.new\n      ScopeVisitor.new rule, Source.new <<-CRYSTAL\n        class Foo       # Scope #1\n          def foo?      # Scope #2\n            {% begin %} # Scope #3\n              # `Crystal::MacroFor` doesn't have location information\n              {% for method in @type.ancestors %} # Scope #4\n              {% end %}\n            {% end %}\n          end\n        end\n        CRYSTAL\n      rule.scopes.size.should eq 4\n    end\n\n    context \"inner scopes\" do\n      it \"creates scope for block inside def\" do\n        rule = ScopeRule.new\n        ScopeVisitor.new rule, Source.new <<-CRYSTAL\n          def method\n            3.times {}\n          end\n          CRYSTAL\n        rule.scopes.size.should eq 2\n        rule.scopes.last.outer_scope.should_not be_nil\n        rule.scopes.first.outer_scope.should eq rule.scopes.last\n      end\n\n      it \"creates scope for block inside block\" do\n        rule = ScopeRule.new\n        ScopeVisitor.new rule, Source.new <<-CRYSTAL\n          3.times do\n            2.times {}\n          end\n          CRYSTAL\n        rule.scopes.size.should eq 3\n        inner_block = rule.scopes.first\n        outer_block = rule.scopes.last\n        inner_block.outer_scope.should_not eq outer_block\n        outer_block.outer_scope.should be_nil\n      end\n    end\n\n    context \"#visibility\" do\n      it \"is being properly set\" do\n        rule = ScopeRule.new\n        ScopeVisitor.new rule, Source.new <<-CRYSTAL\n          private class Foo\n          end\n          CRYSTAL\n        rule.scopes.size.should eq 1\n        rule.scopes.first.visibility.should eq Crystal::Visibility::Private\n      end\n\n      it \"is being inherited from the outer scope(s)\" do\n        rule = ScopeRule.new\n        ScopeVisitor.new rule, Source.new <<-CRYSTAL\n          private class Foo\n            class Bar\n              def baz\n              end\n            end\n          end\n          CRYSTAL\n        rule.scopes.size.should eq 3\n        rule.scopes.each &.visibility.should eq Crystal::Visibility::Private\n        rule.scopes.last.node.visibility.should eq Crystal::Visibility::Private\n        rule.scopes[0...-1].each &.node.visibility.should eq Crystal::Visibility::Public\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/ast/visitors/top_level_nodes_visitor_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::AST\n  describe TopLevelNodesVisitor do\n    describe \"#require_nodes\" do\n      it \"returns require node\" do\n        source = Source.new <<-CRYSTAL\n          require \"foo\"\n\n          def bar\n          end\n          CRYSTAL\n        visitor = TopLevelNodesVisitor.new(source.ast)\n        visitor.require_nodes.size.should eq 1\n        visitor.require_nodes.first.to_s.should eq %q(require \"foo\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/base_spec.cr",
    "content": "require \"../spec_helper\"\n\nmodule Ameba::Rule\n  root = Path[__DIR__, \"..\", \"..\"].expand\n\n  describe Base do\n    context \".rules\" do\n      it \"returns a list of all rules\" do\n        rules = Rule.rules\n        rules.should_not be_nil\n        rules.should contain DummyRule\n      end\n\n      it \"contains rules across all the available groups\" do\n        Rule.rules.map(&.group_name).uniq!.reject!(&.empty?).sort.should eq %w[\n          Ameba\n          Documentation\n          Layout\n          Lint\n          Metrics\n          Naming\n          Performance\n          Style\n          Typing\n        ]\n      end\n    end\n\n    context \"properties\" do\n      subject = DummyRule.new\n\n      it \"is enabled by default\" do\n        subject.enabled?.should be_true\n      end\n\n      it \"has a description property\" do\n        subject.description.should_not be_nil\n      end\n\n      it \"has a dummy? property\" do\n        subject.dummy?.should be_true\n      end\n\n      it \"has excluded property\" do\n        subject.excluded.should be_nil\n      end\n    end\n\n    describe \"#excluded?\" do\n      it \"returns false if a rule does no have a list of excluded source\" do\n        DummyRule.new.excluded?(Source.new path: \"source.cr\").should_not be_true\n      end\n\n      it \"returns false if source is not excluded from this rule\" do\n        rule = DummyRule.new\n        rule.excluded = Set{\"some_source.cr\"}\n        rule.excluded?(Source.new path: \"another_source.cr\").should_not be_true\n      end\n\n      it \"returns true if source is excluded from this rule\" do\n        rule = DummyRule.new\n        rule.excluded = Set{\"source.cr\"}\n        rule.excluded?(Source.new path: \"source.cr\").should be_true\n      end\n\n      it \"returns true if source matches the wildcard\" do\n        rule = DummyRule.new\n        rule.excluded = Set{\"**/*.cr\"}\n        rule.excluded?(Source.new(path: __FILE__), root).should be_true\n      end\n\n      it \"returns false if source does not match the wildcard\" do\n        rule = DummyRule.new\n        rule.excluded = Set{\"*_spec.cr\"}\n        rule.excluded?(Source.new path: \"source.cr\").should be_false\n      end\n    end\n\n    describe \".documentation_url\" do\n      it \"returns the parsed rule documentation URL\" do\n        Lint::Syntax.documentation_url.should eq \\\n          \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/Syntax.html\"\n      end\n    end\n\n    describe \".parsed_doc\" do\n      it \"returns the parsed rule doc\" do\n        DummyRule.parsed_doc.should eq \"Dummy Rule which does nothing.\"\n      end\n    end\n\n    describe \"#==\" do\n      it \"returns true if rule has the same name\" do\n        DummyRule.new.should eq(DummyRule.new)\n      end\n\n      it \"returns false if rule has a different name\" do\n        DummyRule.new.should_not eq(ScopeRule.new)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/cli/cmd_spec.cr",
    "content": "require \"../../spec_helper\"\nrequire \"../../../src/ameba/cli/cmd\"\n\nmodule Ameba::CLI\n  root = Path[__DIR__, \"..\", \"..\", \"fixtures\"].expand\n\n  describe \"Cmd\" do\n    describe \".run\" do\n      it \"runs ameba\" do\n        r = CLI.run %w[-f silent file.cr]\n        r.should be_true\n      end\n    end\n\n    describe \".parse_args\" do\n      %w[-s --silent].each do |flag|\n        it \"accepts #{flag} flag\" do\n          opts = CLI.parse_args [flag]\n          opts.formatter.should eq :silent\n        end\n      end\n\n      %w[-c --config].each do |flag|\n        it \"accepts #{flag} flag\" do\n          opts = CLI.parse_args [flag, \"config.yml\"]\n          opts.config.should eq Path[\"config.yml\"]\n        end\n      end\n\n      %w[-f --format].each do |flag|\n        it \"accepts #{flag} flag\" do\n          opts = CLI.parse_args [flag, \"my-formatter\"]\n          opts.formatter.should eq \"my-formatter\"\n        end\n      end\n\n      %w[-u --up-to-version].each do |flag|\n        it \"accepts #{flag} flag\" do\n          opts = CLI.parse_args [flag, \"1.5.0\"]\n          opts.version.should eq \"1.5.0\"\n        end\n      end\n\n      it \"accepts --stdin-filename flag\" do\n        opts = CLI.parse_args %w[--stdin-filename foo.cr]\n        opts.stdin_filename.should eq \"foo.cr\"\n      end\n\n      it \"accepts --only flag\" do\n        opts = CLI.parse_args [\"--only\", \"RULE1,RULE2\"]\n        opts.only.should eq Set{\"RULE1\", \"RULE2\"}\n      end\n\n      it \"accepts --except flag\" do\n        opts = CLI.parse_args [\"--except\", \"RULE1,RULE2\"]\n        opts.except.should eq Set{\"RULE1\", \"RULE2\"}\n      end\n\n      it \"defaults rules? flag to false\" do\n        opts = CLI.parse_args %w[file.cr]\n        opts.rules?.should be_false\n      end\n\n      it \"defaults skip_reading_config? flag to false\" do\n        opts = CLI.parse_args %w[file.cr]\n        opts.skip_reading_config?.should be_false\n      end\n\n      it \"accepts --rules flag\" do\n        opts = CLI.parse_args %w[--rules]\n        opts.rules?.should be_true\n      end\n\n      it \"defaults all? flag to false\" do\n        opts = CLI.parse_args %w[file.cr]\n        opts.all?.should be_false\n      end\n\n      it \"accepts --all flag\" do\n        opts = CLI.parse_args %w[--all]\n        opts.all?.should be_true\n      end\n\n      it \"accepts --gen-config flag\" do\n        opts = CLI.parse_args %w[--gen-config]\n        opts.formatter.should eq :todo\n      end\n\n      it \"accepts --no-color flag\" do\n        opts = CLI.parse_args %w[--no-color]\n        opts.colors?.should be_false\n      end\n\n      it \"accepts --without-affected-code flag\" do\n        opts = CLI.parse_args %w[--without-affected-code]\n        opts.without_affected_code?.should be_true\n      end\n\n      it \"doesn't disable colors by default\" do\n        opts = CLI.parse_args %w[--all]\n        opts.colors?.should be_true\n      end\n\n      it \"ignores --config if --gen-config flag passed\" do\n        opts = CLI.parse_args %w[--gen-config --config my_config.yml]\n        opts.formatter.should eq :todo\n        opts.skip_reading_config?.should be_true\n      end\n\n      describe \"-e/--explain\" do\n        it \"configures file/line/column\" do\n          opts = CLI.parse_args %w[--explain src/file.cr:3:5]\n\n          location_to_explain = opts.location_to_explain.should_not be_nil\n          location_to_explain.filename.should eq \"src/file.cr\"\n          location_to_explain.line_number.should eq 3\n          location_to_explain.column_number.should eq 5\n        end\n\n        it \"raises an error if location is not valid\" do\n          expect_raises(Exception, \"location should have PATH:line:column\") do\n            CLI.parse_args %w[--explain src/file.cr:3]\n          end\n        end\n\n        it \"raises an error if line number is not valid\" do\n          expect_raises(Exception, \"location should have PATH:line:column\") do\n            CLI.parse_args %w[--explain src/file.cr:a:3]\n          end\n        end\n\n        it \"raises an error if column number is not valid\" do\n          expect_raises(Exception, \"location should have PATH:line:column\") do\n            CLI.parse_args %w[--explain src/file.cr:3:&]\n          end\n        end\n\n        it \"raises an error if line/column are missing\" do\n          expect_raises(Exception, \"location should have PATH:line:column\") do\n            CLI.parse_args %w[--explain src/file.cr]\n          end\n        end\n      end\n\n      context \"--min-severity\" do\n        it \"configures fail level Convention\" do\n          opts = CLI.parse_args %w[--min-severity convention]\n          opts.severity.should eq Severity::Convention\n        end\n\n        it \"configures fail level Warning\" do\n          opts = CLI.parse_args %w[--min-severity Warning]\n          opts.severity.should eq Severity::Warning\n        end\n\n        it \"configures fail level Error\" do\n          opts = CLI.parse_args %w[--min-severity error]\n          opts.severity.should eq Severity::Error\n        end\n\n        it \"raises if fail level is incorrect\" do\n          expect_raises(Exception, \"Incorrect severity name JohnDoe\") do\n            CLI.parse_args %w[--min-severity JohnDoe]\n          end\n        end\n      end\n\n      it \"sets #root to the first directory passed as an argument if it's a project directory\" do\n        opts = CLI.parse_args [root.to_s]\n        opts.root.should eq root\n      end\n\n      it \"sets #root to the current directory if the given directory is not a project directory\" do\n        opts = CLI.parse_args [Dir.tempdir]\n        opts.root.should eq Path[Dir.current]\n      end\n\n      it \"sets #root to the current directory if no project directory is passed\" do\n        opts = CLI.parse_args %w[]\n        opts.root.should eq Path[Dir.current]\n      end\n\n      it \"accepts unknown args as globs\" do\n        opts = CLI.parse_args %w[source1.cr source2.cr]\n        opts.globs.should eq Set{\n          Path[\"source1.cr\"].expand.to_posix.to_s,\n          Path[\"source2.cr\"].expand.to_posix.to_s,\n        }\n      end\n\n      it \"leaves the absolute paths intact\" do\n        opts = CLI.parse_args [\n          Path[Dir.tempdir, \"foo.cr\"].to_s,\n          Path[Dir.tempdir, \"bar.cr\"].to_s,\n          Path[Dir.tempdir, \"baz*.cr\"].to_s,\n        ]\n        opts.root.should eq Path[Dir.current]\n        opts.globs.should eq Set{\n          Path[Dir.tempdir, \"foo.cr\"].to_posix.to_s,\n          Path[Dir.tempdir, \"bar.cr\"].to_posix.to_s,\n          Path[Dir.tempdir, \"baz*.cr\"].to_posix.to_s,\n        }\n      end\n\n      it \"expands relative globs using current directory as base\" do\n        opts = CLI.parse_args [\n          Path[Dir.tempdir, \"foo.cr\"].to_s,\n          \"**/bar.cr\",\n        ]\n        opts.root.should eq Path[Dir.current]\n        opts.globs.should eq Set{\n          Path[Dir.tempdir, \"foo.cr\"].to_posix.to_s,\n          Path[Dir.current, \"**\", \"bar.cr\"].to_posix.to_s,\n        }\n      end\n\n      it \"expands relative globs using project root directory as base\" do\n        opts = CLI.parse_args [\n          root.to_s,\n          Path[Dir.tempdir, \"foo.cr\"].to_s,\n          \"**/bar.cr\",\n        ]\n        opts.globs.should eq Set{\n          root.to_posix.to_s,\n          Path[Dir.tempdir, \"foo.cr\"].to_posix.to_s,\n          Path[Dir.current, \"**\", \"bar.cr\"].to_posix.to_s,\n        }\n      end\n\n      it \"accepts single '-' argument as STDIN\" do\n        opts = CLI.parse_args %w[-]\n        opts.stdin_filename.should eq \"-\"\n      end\n\n      it \"accepts one unknown arg as explain location if it has correct format\" do\n        opts = CLI.parse_args %w[source.cr:3:22]\n\n        location_to_explain = opts.location_to_explain.should_not be_nil\n        location_to_explain.filename.should eq \"source.cr\"\n        location_to_explain.line_number.should eq 3\n        location_to_explain.column_number.should eq 22\n      end\n\n      it \"allows args to be blank\" do\n        opts = CLI.parse_args [] of String\n        opts.root.should eq Path[Dir.current]\n        opts.formatter.should be_nil\n        opts.globs.should be_nil\n        opts.only.should be_nil\n        opts.except.should be_nil\n        opts.config.should be_nil\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/config_spec.cr",
    "content": "require \"../spec_helper\"\n\nmodule Ameba\n  root = Path[__DIR__, \"..\", \"..\"].expand\n\n  describe Config do\n    config_sample = Path[__DIR__, \"..\", \"fixtures\", \".ameba.yml\"].to_s\n\n    it \"should have a list of available formatters\" do\n      Config::AVAILABLE_FORMATTERS.should_not be_nil\n    end\n\n    describe \".new\" do\n      it \"raises when the config is not a Hash\" do\n        yaml = YAML.parse \"[]\"\n        expect_raises(Exception, \"Invalid config file format\") do\n          Config.from_yaml(yaml)\n        end\n      end\n\n      it \"loads default globs when config is empty\" do\n        yaml = YAML.parse \"{}\"\n        config = Config.from_yaml(yaml)\n        config.globs.should eq Config::DEFAULT_GLOBS\n      end\n\n      it \"loads default globs when config has no value\" do\n        yaml = YAML.parse <<-YAML\n          # Empty config with comment\n          YAML\n        config = Config.from_yaml(yaml)\n        config.globs.should eq Config::DEFAULT_GLOBS\n      end\n\n      it \"initializes globs as string\" do\n        yaml = YAML.parse <<-YAML\n          ---\n          Globs: src/*.cr\n          YAML\n        config = Config.from_yaml(yaml)\n        config.globs.should eq Set{\"src/*.cr\"}\n      end\n\n      it \"initializes globs as array\" do\n        yaml = YAML.parse <<-YAML\n          ---\n          Globs:\n            - \"src/*.cr\"\n            - \"!spec\"\n          YAML\n        config = Config.from_yaml(yaml)\n        config.globs.should eq Set{\"src/*.cr\", \"!spec\"}\n      end\n\n      it \"raises if Globs has a wrong type\" do\n        yaml = YAML.parse <<-YAML\n          ---\n          Globs: 100\n          YAML\n        expect_raises(Exception, \"Incorrect `Globs` section in a config file\") do\n          Config.from_yaml(yaml)\n        end\n      end\n\n      it \"initializes excluded as string\" do\n        yaml = YAML.parse <<-YAML\n          ---\n          Excluded: spec\n          YAML\n        config = Config.from_yaml(yaml)\n        config.excluded.should eq Set{\"spec\"}\n      end\n\n      it \"initializes excluded as array\" do\n        yaml = YAML.parse <<-YAML\n          ---\n          Excluded:\n            - spec\n            - lib/*.cr\n          YAML\n        config = Config.from_yaml(yaml)\n        config.excluded.should eq Set{\"spec\", \"lib/*.cr\"}\n      end\n\n      it \"raises if Excluded has a wrong type\" do\n        yaml = YAML.parse <<-YAML\n          ---\n          Excluded: true\n          YAML\n        expect_raises(Exception, \"Incorrect `Excluded` section in a config file\") do\n          Config.from_yaml(yaml)\n        end\n      end\n    end\n\n    describe \".load\" do\n      it \"loads custom config\" do\n        config = Config.load config_sample\n        config.should_not be_nil\n        config.version.should_not be_nil\n        config.globs.should_not be_nil\n        config.formatter.should be_a Formatter::FlycheckFormatter\n      end\n\n      it \"raises when custom config file doesn't exist\" do\n        expect_raises(Exception, %(Unable to load config file: Config file \"foo.yml\" does not exist)) do\n          Config.load \"foo.yml\"\n        end\n      end\n\n      it \"loads default config\" do\n        config = Config.load\n        config.should_not be_nil\n        config.version.should be_nil\n        config.globs.should_not be_nil\n        config.formatter.should_not be_nil\n      end\n    end\n\n    describe \"#globs, #globs=\" do\n      config = Config.load config_sample\n\n      it \"holds source globs\" do\n        config.globs.should eq Config::DEFAULT_GLOBS\n      end\n\n      it \"allows to set globs\" do\n        config.globs = Set{\"src/**/*.cr\"}\n        config.globs.should eq Set{\"src/**/*.cr\"}\n      end\n    end\n\n    describe \"#excluded, #excluded=\" do\n      config = Config.load config_sample\n\n      it \"defaults to `lib`\" do\n        config.excluded.should eq Set{\"lib\"}\n      end\n\n      it \"allows to set excluded\" do\n        config.excluded = Set{\"spec\"}\n        config.excluded.should eq Set{\"spec\"}\n      end\n    end\n\n    describe \"#sources\" do\n      config = Config.load config_sample, root: root\n\n      it \"returns list of sources\" do\n        config.sources.size.should be > 0\n        config.sources.first.should be_a Source\n        config.sources.any?(&.fullpath.==(__FILE__)).should be_true\n      end\n\n      it \"returns a list of sources matching globs\" do\n        config.globs = Set{\"**/config_spec.cr\"}\n        config.sources.size.should eq(1)\n      end\n\n      it \"returns a list of sources excluding 'Excluded'\" do\n        config.excluded = Set{\"**/config_spec.cr\"}\n        config.sources.any?(&.fullpath.==(__FILE__)).should be_false\n      end\n    end\n\n    describe \"#formatter, formatter=\" do\n      config = Config.load config_sample\n      formatter = DummyFormatter.new\n\n      it \"contains default formatter\" do\n        config.formatter.should_not be_nil\n      end\n\n      it \"allows to set formatter\" do\n        config.formatter = formatter\n        config.formatter.should eq formatter\n      end\n\n      it \"allows to set formatter using a name\" do\n        config.formatter = :progress\n        config.formatter.should_not be_nil\n      end\n\n      it \"raises an error if not available formatter is set\" do\n        expect_raises(Exception) do\n          config.formatter = :no_such_formatter\n        end\n      end\n    end\n\n    describe \"#version, version=\" do\n      config = Config.load config_sample\n      version = SemanticVersion.parse(\"1.5.0\")\n\n      it \"contains default version\" do\n        config.version.should_not be_nil\n      end\n\n      it \"allows to set version\" do\n        config.version = version\n        config.version.should eq version\n      end\n\n      it \"allows to set version using a string\" do\n        config.version = version.to_s\n        config.version.should eq version\n      end\n\n      it \"raises an error if version is not valid\" do\n        expect_raises(Exception) do\n          config.version = \"foo\"\n        end\n      end\n    end\n\n    describe \"#update_rule\" do\n      config = Config.load config_sample\n\n      it \"updates enabled property\" do\n        name = DummyRule.rule_name\n        config.update_rule name, enabled: false\n        rule = config.rules.find!(&.name.== name)\n        rule.enabled?.should be_false\n      end\n\n      it \"updates excluded property\" do\n        name = DummyRule.rule_name\n        excluded = Set{\"spec/source.cr\"}\n        config.update_rule name, excluded: excluded\n        rule = config.rules.find!(&.name.== name)\n        rule.excluded.should eq excluded\n      end\n    end\n\n    describe \"#update_rules\" do\n      config = Config.load config_sample\n\n      it \"updates multiple rules by enabled property\" do\n        name = DummyRule.rule_name\n        config.update_rules [name], enabled: false\n        rule = config.rules.find!(&.name.== name)\n        rule.enabled?.should be_false\n      end\n\n      it \"updates multiple rules by excluded property\" do\n        name = DummyRule.rule_name\n        excluded = Set{\"spec/source.cr\"}\n        config.update_rules [name], excluded: excluded\n        rule = config.rules.find!(&.name.== name)\n        rule.excluded.should eq excluded\n      end\n\n      it \"updates a group of rules by enabled property\" do\n        group = DummyRule.group_name\n        config.update_rules [group], enabled: false\n        rule = config.rules.find!(&.name.== DummyRule.rule_name)\n        rule.enabled?.should be_false\n      end\n\n      it \"updates a group by excluded property\" do\n        name = DummyRule.group_name\n        excluded = Set{\"spec/source.cr\"}\n        config.update_rules [name], excluded: excluded\n        rule = config.rules.find!(&.name.== DummyRule.rule_name)\n        rule.excluded.should eq excluded\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/ext/location_spec.cr",
    "content": "require \"../../spec_helper\"\n\ndescribe Crystal::Location do\n  subject = Crystal::Location.new(nil, 2, 3)\n\n  describe \"#with\" do\n    it \"changes line number\" do\n      subject.with(line_number: 1).to_s.should eq \":1:3\"\n    end\n\n    it \"changes column number\" do\n      subject.with(column_number: 1).to_s.should eq \":2:1\"\n    end\n\n    it \"changes line and column numbers\" do\n      subject.with(line_number: 1, column_number: 2).to_s.should eq \":1:2\"\n    end\n  end\n\n  describe \"#adjust\" do\n    it \"adjusts line number\" do\n      subject.adjust(line_number: 1).to_s.should eq \":3:3\"\n    end\n\n    it \"adjusts column number\" do\n      subject.adjust(column_number: 1).to_s.should eq \":2:4\"\n    end\n\n    it \"adjusts line and column numbers\" do\n      subject.adjust(line_number: 1, column_number: 2).to_s.should eq \":3:5\"\n    end\n  end\n\n  describe \"#seek\" do\n    it \"adjusts column number if line offset is 1\" do\n      subject.seek(Crystal::Location.new(nil, 1, 2)).to_s.should eq \":2:4\"\n    end\n\n    it \"adjusts line number and changes column number if line offset is greater than 1\" do\n      subject.seek(Crystal::Location.new(nil, 2, 1)).to_s.should eq \":3:1\"\n    end\n\n    it \"adjusts line number and changes column number if line offset is less than 1\" do\n      subject.seek(Crystal::Location.new(nil, 0, 1)).to_s.should eq \":1:1\"\n    end\n\n    it \"raises exception if filenames don't match\" do\n      expect_raises(ArgumentError, \"Mismatching filenames:\\n  source.cr\\n  source2.cr\") do\n        location = Crystal::Location.new(\"source.cr\", 1, 1)\n        location.seek(Crystal::Location.new(\"source2.cr\", 1, 1))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/formatter/disabled_formatter_spec.cr",
    "content": "require \"../../spec_helper\"\n\nmodule Ameba::Formatter\n  describe DisabledFormatter do\n    output = IO::Memory.new\n    subject = DisabledFormatter.new output\n\n    before_each do\n      output.clear\n    end\n\n    describe \"#finished\" do\n      it \"writes a final message\" do\n        subject.finished [Source.new]\n        output.to_s.should contain \"Disabled rules using inline directives:\"\n      end\n\n      it \"writes disabled rules if any\" do\n        path = \"source.cr\"\n\n        source = Source.new(path: path)\n        source.add_issue(ErrorRule.new, {1, 2}, message: \"ErrorRule\", status: :disabled)\n        source.add_issue(NamedRule.new, location: {2, 2}, message: \"NamedRule\", status: :disabled)\n\n        subject.finished [source]\n        log = output.to_s\n        log = Util.deansify(log).should_not be_nil\n        log.should contain \"#{path}:1 #{ErrorRule.rule_name}\"\n        log.should contain \"#{path}:2 #{NamedRule.rule_name}\"\n      end\n\n      it \"does not write not-disabled rules\" do\n        source = Source.new(path: \"source.cr\")\n        source.add_issue(ErrorRule.new, {1, 2}, \"ErrorRule\")\n        source.add_issue(NamedRule.new, location: {2, 2},\n          message: \"NamedRule\", status: :disabled)\n\n        subject.finished [source]\n        output.to_s.should_not contain ErrorRule.rule_name\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/formatter/dot_formatter_spec.cr",
    "content": "require \"../../spec_helper\"\n\nmodule Ameba::Formatter\n  describe DotFormatter do\n    output = IO::Memory.new\n    subject = DotFormatter.new output\n\n    before_each do\n      output.clear\n    end\n\n    describe \"#started\" do\n      it \"writes started message\" do\n        subject.started [Source.new]\n        output.to_s.should eq \"Inspecting 1 file\\n\\n\"\n      end\n    end\n\n    describe \"#source_finished\" do\n      it \"writes valid source\" do\n        subject.source_finished Source.new\n        output.to_s.should contain \".\"\n      end\n\n      it \"writes invalid source\" do\n        source = Source.new\n        source.add_issue DummyRule.new, Crystal::Nop.new, \"message\"\n\n        subject.source_finished source\n        output.to_s.should contain \"F\"\n      end\n    end\n\n    describe \"#finished\" do\n      it \"writes a final message\" do\n        subject.finished [Source.new]\n        output.to_s.should contain \"1 inspected, 0 failures\"\n      end\n\n      it \"writes the elapsed time\" do\n        subject.finished [Source.new]\n        output.to_s.should contain \"Finished in\"\n      end\n\n      context \"when issues found\" do\n        it \"writes each issue\" do\n          source = Source.new\n          source.add_issue(DummyRule.new, {1, 1}, \"DummyRuleError\")\n          source.add_issue(NamedRule.new, {1, 2}, \"NamedRuleError\")\n\n          subject.finished [source]\n          log = output.to_s\n          log.should contain \"1 inspected, 2 failures\"\n          log.should contain \"DummyRuleError\"\n          log.should contain \"NamedRuleError\"\n        end\n\n        it \"writes affected code by default\" do\n          source = Source.new <<-CRYSTAL\n            a = 22\n            puts a\n            CRYSTAL\n          source.add_issue(DummyRule.new, {1, 5}, \"DummyRuleError\")\n\n          subject.finished [source]\n          log = output.to_s\n          log = subject.deansify(log).should_not be_nil\n          log.should contain \"> a = 22\"\n          log.should contain \"      ^\"\n        end\n\n        it \"writes severity\" do\n          source = Source.new <<-CRYSTAL\n            a = 22\n            puts a\n            CRYSTAL\n          source.add_issue(DummyRule.new, {1, 5}, \"DummyRuleError\")\n\n          subject.finished [source]\n          log = output.to_s\n          log.should contain \"[C]\"\n        end\n\n        it \"doesn't write affected code if it is disabled\" do\n          source = Source.new <<-CRYSTAL\n            a = 22\n            puts a\n            CRYSTAL\n          source.add_issue(DummyRule.new, {1, 5}, \"DummyRuleError\")\n\n          formatter = DotFormatter.new output\n          formatter.config[:without_affected_code] = true\n          formatter.finished [source]\n\n          log = output.to_s\n          log = formatter.deansify(log).should_not be_nil\n          log.should_not contain \"> a = 22\"\n          log.should_not contain \"      ^\"\n        end\n\n        it \"does not write disabled issues\" do\n          source = Source.new\n          source.add_issue(DummyRule.new, {1, 1}, \"DummyRuleError\", status: :disabled)\n          source.add_issue(NamedRule.new, {1, 2}, \"NamedRuleError\")\n\n          subject.finished [source]\n          log = output.to_s\n          log.should_not contain \"DummyRuleError\"\n          log.should contain \"1 inspected, 1 failure\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/formatter/explain_formatter_spec.cr",
    "content": "require \"../../spec_helper\"\n\nprivate LOCATION = Crystal::Location.new(\"source.cr\", 1, 1)\n\nprivate def explanation(source)\n  Ameba::ErrorRule.new.catch(source)\n\n  output = IO::Memory.new\n  Ameba::Formatter::ExplainFormatter.new(output, LOCATION).finished([source])\n  output.to_s\nend\n\nmodule Ameba::Formatter\n  describe ExplainFormatter do\n    describe \"#location\" do\n      it \"returns crystal location\" do\n        location = ExplainFormatter\n          .new(STDOUT, LOCATION)\n          .location\n\n        location.should eq LOCATION\n      end\n    end\n\n    describe \"#output\" do\n      it \"returns io\" do\n        output = ExplainFormatter\n          .new(STDOUT, LOCATION)\n          .output\n\n        output.should eq STDOUT\n      end\n    end\n\n    describe \"#finished\" do\n      it \"writes issue info\" do\n        source = Source.new \"a = 42\", \"source.cr\"\n        output = explanation(source)\n        output.should contain \"ISSUE INFO\"\n        output.should contain \"This rule always adds an error\"\n        output.should contain \"source.cr:1:1\"\n      end\n\n      it \"writes affected code\" do\n        source = Source.new \"a = 42\", \"source.cr\"\n        output = explanation(source)\n        output.should contain \"AFFECTED CODE\"\n        output.should contain \"a = 42\"\n      end\n\n      it \"writes rule info\" do\n        source = Source.new \"a = 42\", \"source.cr\"\n        output = explanation(source)\n        output.should contain \"RULE INFO\"\n        output.should contain \"Convention\"\n        output.should contain \"Ameba/ErrorRule\"\n        output.should contain \"Always adds an error\"\n      end\n\n      it \"writes detailed description\" do\n        source = Source.new \"a = 42\", \"source.cr\"\n        output = explanation(source)\n        output.should contain \"DETAILED DESCRIPTION\"\n        output.should contain \"Rule extended description\"\n      end\n\n      it \"writes nothing if location not found\" do\n        source = Source.new \"a = 42\", \"another_source.cr\"\n        explanation(source).should be_empty\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/formatter/flycheck_formatter_spec.cr",
    "content": "require \"../../spec_helper\"\n\nmodule Ameba::Formatter\n  describe FlycheckFormatter do\n    output = IO::Memory.new\n    subject = FlycheckFormatter.new output\n\n    before_each do\n      output.clear\n    end\n\n    context \"problems not found\" do\n      it \"reports nothing\" do\n        subject.source_finished Source.new\n        subject.output.to_s.empty?.should be_true\n      end\n    end\n\n    context \"when problems found\" do\n      it \"reports an issue\" do\n        source = Source.new \"a = 1\", \"source.cr\"\n        source.add_issue DummyRule.new, {1, 2}, \"message\"\n\n        subject.source_finished(source)\n        subject.output.to_s.should eq(\n          \"source.cr:1:2: C: [#{DummyRule.rule_name}] message\\n\"\n        )\n      end\n\n      it \"properly reports multi-line message\" do\n        source = Source.new \"a = 1\", \"source.cr\"\n        source.add_issue DummyRule.new, {1, 2}, \"multi\\nline\"\n\n        subject.source_finished(source)\n        subject.output.to_s.should eq(\n          \"source.cr:1:2: C: [#{DummyRule.rule_name}] multi line\\n\"\n        )\n      end\n\n      it \"reports nothing if location was not set\" do\n        source = Source.new \"a = 1\", \"source.cr\"\n        source.add_issue DummyRule.new, Crystal::Nop.new, \"message\"\n\n        subject.source_finished(source)\n        subject.output.to_s.should be_empty\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/formatter/github_actions_formatter_spec.cr",
    "content": "require \"../../spec_helper\"\n\nmodule Ameba::Formatter\n  describe GitHubActionsFormatter do\n    output = IO::Memory.new\n    subject = GitHubActionsFormatter.new(output)\n\n    before_each do\n      output.clear\n    end\n\n    describe \"#source_finished\" do\n      it \"writes valid source\" do\n        source = Source.new path: \"/path/to/file.cr\"\n\n        subject.source_finished(source)\n        output.to_s.should be_empty\n      end\n\n      it \"writes invalid source\" do\n        source = Source.new path: \"/path/to/file.cr\"\n        location = Crystal::Location.new(\"/path/to/file.cr\", 1, 2)\n\n        source.add_issue DummyRule.new, location, location, \"message\\n2nd line\"\n\n        subject.source_finished(source)\n        output.to_s.should eq(\n          \"::notice file=/path/to/file.cr,line=1,col=2,endLine=1,endColumn=2,\" \\\n          \"title=Ameba/DummyRule::message%0A2nd line\\n\"\n        )\n      end\n    end\n\n    describe \"#finished\" do\n      it \"doesn't do anything if 'GITHUB_STEP_SUMMARY' ENV var is not set\" do\n        subject.finished [Source.new]\n        output.to_s.should be_empty\n      end\n\n      it \"writes a Markdown summary to a filename given in 'GITHUB_STEP_SUMMARY' ENV var\" do\n        prev_summary = ENV[\"GITHUB_STEP_SUMMARY\"]?\n        ENV[\"GITHUB_STEP_SUMMARY\"] = summary_filename = File.tempname\n        begin\n          sources = [Source.new]\n\n          subject.started(sources)\n          subject.finished(sources)\n\n          File.exists?(summary_filename).should be_true\n\n          summary = File.read(summary_filename)\n          summary.should contain \"## Ameba Results :green_heart:\"\n          summary.should contain \"Finished in\"\n          summary.should contain \"**1** sources inspected, **0** failures.\"\n          summary.should contain \"> Ameba version: **#{Ameba.version}**\"\n        ensure\n          ENV[\"GITHUB_STEP_SUMMARY\"] = prev_summary\n          File.delete(summary_filename) rescue nil\n        end\n      end\n\n      context \"when issues found\" do\n        it \"writes each issue\" do\n          prev_summary = ENV[\"GITHUB_STEP_SUMMARY\"]?\n          ENV[\"GITHUB_STEP_SUMMARY\"] = summary_filename = File.tempname\n\n          repo = ENV[\"GITHUB_REPOSITORY\"]?\n          sha = ENV[\"GITHUB_SHA\"]?\n          begin\n            source = Source.new path: \"src/source.cr\"\n            source.add_issue(DummyRule.new, {1, 1}, {2, 1}, \"DummyRuleError\")\n            source.add_issue(DummyRule.new, {1, 1}, \"DummyRuleError 2|3\")\n            source.add_issue(NamedRule.new, {1, 2}, \"NamedRuleError\", status: :disabled)\n\n            subject.finished([source])\n\n            File.exists?(summary_filename).should be_true\n\n            summary = File.read(summary_filename)\n            summary.should contain \"## Ameba Results :bug:\"\n            summary.should contain \"### Issues found\"\n            summary.should contain \"#### `src/source.cr` (**2** issues)\"\n\n            linked_name =\n              \"[#{DummyRule.rule_name}](#{DummyRule.documentation_url})\"\n\n            if repo && sha\n              summary.should contain(\n                \"| [1-2](https://github.com/#{repo}/blob/#{sha}/src/source.cr#L1-L2) \" \\\n                \"| Convention | #{linked_name} | DummyRuleError |\"\n              )\n              summary.should contain(\n                \"| [1](https://github.com/#{repo}/blob/#{sha}/src/source.cr#L1) \" \\\n                \"| Convention | #{linked_name} | DummyRuleError 2\\\\|3 |\"\n              )\n            else\n              summary.should contain \"| 1-2 | Convention | #{linked_name} | DummyRuleError |\"\n              summary.should contain \"| 1 | Convention | #{linked_name} | DummyRuleError 2\\\\|3 |\"\n            end\n            summary.should_not contain \"NamedRuleError\"\n            summary.should contain \"**1** sources inspected, **2** failures.\"\n          ensure\n            ENV[\"GITHUB_STEP_SUMMARY\"] = prev_summary\n            File.delete(summary_filename) rescue nil\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/formatter/json_formatter_spec.cr",
    "content": "require \"../../spec_helper\"\n\nprivate def get_result(sources = [Ameba::Source.new])\n  output = IO::Memory.new\n  formatter = Ameba::Formatter::JSONFormatter.new output\n\n  formatter.started sources\n  sources.each { |source| formatter.source_finished source }\n  formatter.finished sources\n\n  JSON.parse(output.to_s)\nend\n\nmodule Ameba::Formatter\n  describe JSONFormatter do\n    context \"metadata\" do\n      it \"shows ameba version\" do\n        get_result[\"metadata\"][\"ameba_version\"].should eq Ameba.version.to_s\n      end\n\n      it \"shows crystal version\" do\n        get_result[\"metadata\"][\"crystal_version\"].should eq Crystal::VERSION\n      end\n    end\n\n    context \"sources\" do\n      it \"doesn't add empty sources\" do\n        result = get_result [Source.new path: \"source.cr\"]\n        result[\"sources\"].as_a.should be_empty\n      end\n\n      it \"shows path to the source\" do\n        source = Source.new path: \"source.cr\"\n        source.add_issue DummyRule.new, {1, 2}, \"message\"\n\n        result = get_result [source]\n        result[\"sources\"][0][\"path\"].should eq \"source.cr\"\n      end\n\n      it \"shows rule name\" do\n        source = Source.new\n        source.add_issue DummyRule.new, {1, 2}, \"message\"\n\n        result = get_result [source]\n        result[\"sources\"][0][\"issues\"][0][\"rule_name\"].should eq DummyRule.rule_name\n      end\n\n      it \"shows severity\" do\n        source = Source.new\n        source.add_issue DummyRule.new, {1, 2}, \"message\"\n\n        result = get_result [source]\n        result[\"sources\"][0][\"issues\"][0][\"severity\"].should eq \"Convention\"\n      end\n\n      it \"shows a message\" do\n        source = Source.new\n        source.add_issue DummyRule.new, {1, 2}, \"message\"\n\n        result = get_result [source]\n        result[\"sources\"][0][\"issues\"][0][\"message\"].should eq \"message\"\n      end\n\n      it \"shows issue location\" do\n        source = Source.new\n        source.add_issue DummyRule.new, {1, 2}, \"message\"\n\n        result = get_result [source]\n        location = result[\"sources\"][0][\"issues\"][0][\"location\"]\n        location[\"line\"].should eq 1\n        location[\"column\"].should eq 2\n      end\n\n      it \"shows issue end_location\" do\n        source = Source.new\n        source.add_issue DummyRule.new,\n          Crystal::Location.new(\"path\", 3, 3),\n          Crystal::Location.new(\"path\", 5, 4),\n          \"message\"\n\n        result = get_result [source]\n        end_location = result[\"sources\"][0][\"issues\"][0][\"end_location\"]\n        end_location[\"line\"].should eq 5\n        end_location[\"column\"].should eq 4\n      end\n    end\n\n    context \"summary\" do\n      it \"shows a target sources count\" do\n        result = get_result [Source.new, Source.new]\n        result[\"summary\"][\"target_sources_count\"].should eq 2\n      end\n\n      it \"shows issues count\" do\n        s1 = Source.new\n        s1.add_issue DummyRule.new, {1, 2}, \"message1\"\n        s1.add_issue DummyRule.new, {1, 2}, \"message2\"\n\n        s2 = Source.new\n        s2.add_issue DummyRule.new, {1, 2}, \"message3\"\n\n        result = get_result [s1, s2]\n        result[\"summary\"][\"issues_count\"].should eq 3\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/formatter/todo_formatter_spec.cr",
    "content": "require \"../../spec_helper\"\nrequire \"file_utils\"\n\nprivate CONFIG_PATH =\n  Path[Dir.tempdir] / Ameba::Config::Loader::FILENAME\n\nmodule Ameba\n  private def with_formatter(&)\n    io = IO::Memory.new\n    formatter = Formatter::TODOFormatter.new(io, CONFIG_PATH)\n\n    yield formatter, io\n  end\n\n  private def create_todo\n    with_formatter do |formatter|\n      source = Source.new \"a = 1\", \"source.cr\"\n      source.add_issue DummyRule.new, {1, 2}, \"message\"\n\n      formatter.finished([source])\n\n      File.exists?(CONFIG_PATH) ? File.read(CONFIG_PATH) : \"\"\n    end\n  end\n\n  describe Formatter::TODOFormatter do\n    ::Spec.after_each do\n      FileUtils.rm_rf(CONFIG_PATH)\n    end\n\n    context \"problems not found\" do\n      it \"does not create file\" do\n        with_formatter do |formatter|\n          file = formatter.finished [Source.new]\n          file.should be_nil\n        end\n      end\n\n      it \"reports a message saying file is not created\" do\n        with_formatter do |formatter, io|\n          formatter.finished [Source.new]\n          io.to_s.should contain \"No issues found. File is not generated\"\n        end\n      end\n    end\n\n    context \"problems found\" do\n      it \"prints a message saying file is created\" do\n        with_formatter do |formatter, io|\n          source = Source.new \"a = 1\", \"source.cr\"\n          source.add_issue DummyRule.new, {1, 2}, \"message\"\n          formatter.finished([source])\n          io.to_s.should contain \"Created #{CONFIG_PATH}\"\n        end\n      end\n\n      it \"creates a valid YAML document\" do\n        YAML.parse(create_todo).should_not be_nil\n      end\n\n      it \"creates a todo with header\" do\n        create_todo.should contain \"# This configuration file was generated by\"\n      end\n\n      it \"creates a todo with UTC time\" do\n        create_todo.should match /\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2} UTC/\n      end\n\n      it \"creates a todo with version\" do\n        create_todo.should contain \"Ameba version #{Ameba.version}\"\n      end\n\n      it \"creates a todo with a rule name\" do\n        create_todo.should contain \"DummyRule\"\n      end\n\n      it \"excludes source from this rule\" do\n        create_todo.should contain \"Excluded:\\n  - source.cr\"\n      end\n\n      context \"with multiple issues\" do\n        it \"does generate todo file\" do\n          with_formatter do |formatter|\n            s1 = Source.new \"a = 1\", \"source1.cr\"\n            s2 = Source.new \"a = 1\", \"source2.cr\"\n            s1.add_issue DummyRule.new, {1, 2}, \"message1\"\n            s1.add_issue NamedRule.new, {1, 2}, \"message1\"\n            s1.add_issue DummyRule.new, {2, 2}, \"message1\"\n            s2.add_issue DummyRule.new, {2, 2}, \"message2\"\n\n            formatter.finished([s1, s2])\n\n            content = File.read(CONFIG_PATH)\n            content.should contain <<-YAML\n              Ameba/DummyRule:\n                Excluded:\n                - source1.cr\n                - source2.cr\n              YAML\n          end\n        end\n      end\n\n      it \"output format\" do\n        with_formatter do |formatter|\n          s1 = Source.new(path: \"source1.cr\")\n          s2 = Source.new(path: \"source2.cr\")\n          s1.add_issue NamedRule.new, {1, 1}, \"\"\n          s2.add_issue DummyRule.new, {2, 2}, \"\"\n          s1.add_issue DummyRule.new, {3, 3}, \"\"\n\n          formatter.finished([s1, s2])\n\n          content = File.read(CONFIG_PATH)\n          content.should contain <<-YAML\n            Ameba/DummyRule:\n              Excluded:\n              - source1.cr\n              - source2.cr\n\n            BreakingRule:\n              Excluded:\n              - source1.cr\n            YAML\n        end\n      end\n\n      it \"converts paths to posix variant\" do\n        with_formatter do |formatter|\n          s1 = Source.new(path: Path[\"foo\", \"bar\", \"source1.cr\"].to_s)\n          s2 = Source.new(path: Path[\"foo\", \"bar\", \"source2.cr\"].to_s)\n          s1.add_issue NamedRule.new, {1, 1}, \"\"\n          s2.add_issue DummyRule.new, {2, 2}, \"\"\n          s1.add_issue DummyRule.new, {3, 3}, \"\"\n\n          formatter.finished([s1, s2])\n\n          content = File.read(CONFIG_PATH)\n          content.should contain <<-YAML\n            Ameba/DummyRule:\n              Excluded:\n              - foo/bar/source1.cr\n              - foo/bar/source2.cr\n\n            BreakingRule:\n              Excluded:\n              - foo/bar/source1.cr\n            YAML\n        end\n      end\n\n      context \"when invalid syntax\" do\n        it \"does generate todo file\" do\n          with_formatter do |formatter|\n            source = Source.new \"def invalid_syntax\"\n            source.add_issue Rule::Lint::Syntax.new, {1, 2}, \"message\"\n\n            file = formatter.finished [source]\n            file.should be_nil\n          end\n        end\n\n        it \"prints an error message\" do\n          with_formatter do |formatter, io|\n            source = Source.new \"def invalid_syntax\"\n            source.add_issue Rule::Lint::Syntax.new, {1, 2}, \"message\"\n\n            formatter.finished [source]\n            io.to_s.should contain \"Unable to generate TODO file\"\n            io.to_s.should contain \"Please fix syntax issues\"\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/formatter/util_spec.cr",
    "content": "require \"../../spec_helper\"\n\nmodule Ameba::Formatter\n  class Subject\n    include Util\n  end\n\n  describe Util do\n    subject = Subject.new\n\n    describe \"#colorize_text_styles\" do\n      it \"underlines headings\" do\n        1.upto(6) do |i|\n          string = \"#{(\"#\" * i)} foo\"\n\n          subject.colorize_text_styles(string)\n            .should eq string.colorize.underline.to_s\n        end\n      end\n\n      it \"applies italic\" do\n        %w[* _].each do |char|\n          string = \"%1$s|foo|%1$s\" % char\n\n          subject.colorize_text_styles(string)\n            .should eq string.colorize.italic.to_s\n        end\n      end\n\n      it \"applies bold\" do\n        %w[* _].each do |char|\n          string = \"%1$s|foo|%1$s\" % (char * 2)\n\n          subject.colorize_text_styles(string)\n            .should eq string.colorize.bold.to_s\n        end\n      end\n\n      it \"applies strikethrough\" do\n        string = \"~~foo~~\"\n\n        subject.colorize_text_styles(string)\n          .should eq string.colorize.strikethrough.to_s\n      end\n\n      it \"combines styles\" do\n        subject.colorize_text_styles(\"~~*__foo__*~~\")\n          .should eq \"~~#{\"*#{\"__foo__\".colorize.bold}*\".colorize.italic}~~\"\n            .colorize.strikethrough.to_s\n      end\n    end\n\n    describe \"#colorize_code_fences\" do\n      it \"highlights multiline code blocks\" do\n        code_string = \"```\\nfoo\\nbar\\nbaz\\n```\"\n        string = \"foo\\n\\n%s\\n\\nbar\"\n\n        subject.colorize_code_fences(string % code_string, :red)\n          .should eq (string % code_string.colorize.red).to_s\n      end\n\n      it \"highlights inline code blocks\" do\n        code_string = \"`foo bar baz`\"\n        string = \"foo %s bar\"\n\n        subject.colorize_code_fences(string % code_string, :red)\n          .should eq (string % code_string.colorize.red).to_s\n      end\n    end\n\n    describe \"#deansify\" do\n      it \"returns given string without ANSI codes\" do\n        str = String.build do |io|\n          io << \"foo\".colorize.green.underline\n          io << '-'\n          io << \"bar\".colorize.red.underline\n        end\n        subject.deansify(\"foo-bar\").should eq \"foo-bar\"\n        subject.deansify(str).should eq \"foo-bar\"\n      end\n    end\n\n    describe \"#trim\" do\n      it \"trims string longer than :max_length\" do\n        subject.trim((\"+\" * 300), 1).should eq \"+\"\n        subject.trim((\"+\" * 300), 3).should eq \"+++\"\n        subject.trim((\"+\" * 300), 5).should eq \"+ ...\"\n        subject.trim((\"+\" * 300), 7).should eq \"+++ ...\"\n      end\n\n      it \"leaves intact string shorter than :max_length\" do\n        subject.trim((\"+\" * 3), 100).should eq \"+++\"\n      end\n\n      it \"allows to use custom ellipsis\" do\n        subject.trim((\"+\" * 300), 3, \"…\").should eq \"++…\"\n      end\n    end\n\n    describe \"#context\" do\n      it \"returns correct pre/post context lines\" do\n        source = Source.new <<-CRYSTAL\n          # pre:1\n            # pre:2\n              # pre:3\n                # pre:4\n                  # pre:5\n          a = 1\n                  # post:1\n                # post:2\n              # post:3\n            # post:4\n          # post:5\n          CRYSTAL\n\n        subject.context(source.lines, lineno: 6, context_lines: 3)\n          .should eq({<<-PRE.lines, <<-POST.lines\n                # pre:3\n                  # pre:4\n                    # pre:5\n            PRE\n                    # post:1\n                  # post:2\n                # post:3\n            POST\n          })\n      end\n    end\n\n    describe \"#affected_code\" do\n      it \"returns nil if there is no such a line number\" do\n        code = <<-CRYSTAL\n          a = 1\n          CRYSTAL\n        location = Crystal::Location.new(\"filename\", 2, 1)\n        subject.affected_code(code, location).should be_nil\n      end\n\n      it \"works with file-wide location (1, 1) + indented code\" do\n        code = <<-CRYSTAL\n                  a = 1\n          CRYSTAL\n        location = Crystal::Location.new(\"filename\", 1, 1)\n        subject.deansify(subject.affected_code(code, location))\n          .should eq \"> a = 1\\n  ^\\n\"\n      end\n\n      context \"trimming\" do\n        max_length = 33\n\n        code = <<-CRYSTAL\n          FOO = \"#{\"foo\" * 111}\"\n          CRYSTAL\n\n        it \"trims the affected line to `max_length` if location is within the trimmed line\" do\n          location = Crystal::Location.new(\"filename\", 1, max_length - 10)\n          end_location = Crystal::Location.new(\"filename\", 1, max_length + 10)\n\n          affected_code = subject.affected_code(code, location, end_location, max_length: max_length)\n          affected_code = subject.deansify(affected_code).should_not be_nil\n\n          affected_code.should start_with \"> %s\\n\" % subject.trim(code, max_length)\n          affected_code.should end_with \"^%s^\\n\" % {\n            \"-\" * (max_length - location.column_number - 1),\n          }\n        end\n\n        it \"does not trim the affected line if location is not within the `max_length`\" do\n          location = Crystal::Location.new(\"filename\", 1, max_length + 10)\n          end_location = Crystal::Location.new(\"filename\", 1, max_length + 30)\n\n          affected_code = subject.affected_code(code, location, end_location, max_length: max_length)\n          affected_code = subject.deansify(affected_code).should_not be_nil\n\n          affected_code.should start_with \"> %s\\n\" % code\n          affected_code.should end_with \"^%s^\\n\" % {\n            \"-\" * (end_location.column_number - location.column_number - 1),\n          }\n        end\n      end\n\n      it \"returns correct line if it is found\" do\n        code = <<-CRYSTAL\n          a = 1\n          CRYSTAL\n        location = Crystal::Location.new(\"filename\", 1, 1)\n        subject.deansify(subject.affected_code(code, location))\n          .should eq \"> a = 1\\n  ^\\n\"\n      end\n\n      it \"returns correct line if it is found (2)\" do\n        code = <<-CRYSTAL\n          # pre:1\n            # pre:2\n              # pre:3\n                # pre:4\n                  # pre:5\n          a = 1\n                  # post:1\n                # post:2\n              # post:3\n            # post:4\n          # post:5\n          CRYSTAL\n\n        location = Crystal::Location.new(\"filename\", 6, 1)\n        subject.deansify(subject.affected_code(code, location, context_lines: 3))\n          .should eq <<-STR\n            >     # pre:3\n            >       # pre:4\n            >         # pre:5\n            > a = 1\n              ^\n            >         # post:1\n            >       # post:2\n            >     # post:3\n\n            STR\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/glob_utils_spec.cr",
    "content": "require \"../spec_helper\"\n\nmodule Ameba\n  subject = GlobUtils\n  root = Path[__DIR__, \"..\", \"..\"].expand\n\n  current_file_path = __FILE__\n  current_file_basename = File.basename(current_file_path)\n  current_file_relative_path =\n    Path[current_file_path].relative_to(Dir.current).to_s\n\n  describe GlobUtils do\n    describe \"#find_files_by_globs\" do\n      it \"returns files by directory\" do\n        subject.find_files_by_globs([__DIR__], root: root)\n          .should contain current_file_relative_path\n      end\n\n      it \"returns a file by filepath\" do\n        subject.find_files_by_globs([__FILE__], root: root)\n          .should eq [current_file_relative_path]\n      end\n\n      it \"returns a file by globs\" do\n        subject.find_files_by_globs([\"**/#{current_file_basename}\"], root: root)\n          .should eq [current_file_relative_path]\n      end\n\n      it \"returns files by globs\" do\n        subject.find_files_by_globs([\"**/*_spec.cr\"], root: root)\n          .should contain current_file_relative_path\n      end\n\n      it \"doesn't return rejected globs\" do\n        subject\n          .find_files_by_globs([\"**/*_spec.cr\", \"!**/#{current_file_basename}\"], root: root)\n          .should_not contain current_file_relative_path\n      end\n\n      it \"doesn't return rejected folders\" do\n        subject\n          .find_files_by_globs([\"**/*_spec.cr\", \"!spec\"], root: root)\n          .should be_empty\n      end\n\n      it \"doesn't return duplicated globs\" do\n        subject\n          .find_files_by_globs([\"**/*_spec.cr\", \"**/*_spec.cr\"], root: root)\n          .count(current_file_relative_path)\n          .should eq 1\n      end\n    end\n\n    describe \"#expand\" do\n      it \"expands globs\" do\n        subject.expand([\"**/#{current_file_basename}\"], root: root)\n          .should eq [current_file_relative_path]\n      end\n\n      it \"does not list duplicated files\" do\n        subject.expand([\"**/#{current_file_basename}\"] * 3, root: root)\n          .should eq [current_file_relative_path]\n      end\n\n      it \"list only files\" do\n        subject.expand([\"**/*\"], root: root).each do |path|\n          fail \"#{path.inspect} should be a file\" unless File.file?(path)\n        end\n      end\n\n      it \"expands folders\" do\n        subject.expand([\"spec\"], root: root).should_not be_empty\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/inline_comments_spec.cr",
    "content": "require \"../spec_helper\"\n\nmodule Ameba\n  describe InlineComments do\n    describe InlineComments::COMMENT_DIRECTIVE_REGEX do\n      subject = InlineComments::COMMENT_DIRECTIVE_REGEX\n\n      it \"allows to parse action and rule name\" do\n        result = subject.match(\"# ameba:enable Group/RuleName\")\n        result = result.should_not be_nil\n        result[\"action\"].should eq \"enable\"\n        result[\"rules\"].should eq \"Group/RuleName\"\n      end\n\n      it \"parses multiple rules\" do\n        result = subject.match(\"# ameba:enable Group/RuleName, OtherRule, Foo/Bar\")\n        result = result.should_not be_nil\n        result[\"action\"].should eq \"enable\"\n        result[\"rules\"].should eq \"Group/RuleName, OtherRule, Foo/Bar\"\n      end\n\n      it \"fails to parse directives with spaces\" do\n        result = subject.match(\"# ameba  :  enable     Group/RuleName\")\n        result.should be_nil\n      end\n    end\n\n    it \"disables a rule with a comment directive\" do\n      source = Source.new <<-CRYSTAL\n        # ameba:disable #{NamedRule.name}\n        Time.epoch(1483859302)\n        CRYSTAL\n      source.add_issue(NamedRule.new, location: {1, 12}, message: \"Error!\")\n      source.should be_valid\n    end\n\n    it \"disables a rule with a line that ends with a comment directive\" do\n      source = Source.new <<-CRYSTAL\n        Time.epoch(1483859302) # ameba:disable #{NamedRule.name}\n        CRYSTAL\n      source.add_issue(NamedRule.new, location: {1, 12}, message: \"Error!\")\n      source.should be_valid\n    end\n\n    it \"does not disable a rule of a different name\" do\n      source = Source.new <<-CRYSTAL\n        # ameba:disable WrongName\n        Time.epoch(1483859302)\n        CRYSTAL\n      source.add_issue(NamedRule.new, location: {2, 12}, message: \"Error!\")\n      source.should_not be_valid\n    end\n\n    it \"disables a rule if multiple rule names provided\" do\n      source = Source.new <<-CRYSTAL\n        # ameba:disable SomeRule LargeNumbers #{NamedRule.name} SomeOtherRule\n        Time.epoch(1483859302)\n        CRYSTAL\n      source.add_issue(NamedRule.new, location: {2, 12}, message: \"\")\n      source.should be_valid\n    end\n\n    it \"disables a rule if multiple rule names are separated by comma\" do\n      source = Source.new <<-CRYSTAL\n        # ameba:disable SomeRule, LargeNumbers, #{NamedRule.name}, SomeOtherRule\n        Time.epoch(1483859302)\n        CRYSTAL\n      source.add_issue(NamedRule.new, location: {2, 12}, message: \"\")\n      source.should be_valid\n    end\n\n    it \"does not disable if multiple rule names used without required one\" do\n      source = Source.new <<-CRYSTAL\n        # ameba:disable SomeRule, SomeOtherRule LargeNumbers\n        Time.epoch(1483859302)\n        CRYSTAL\n      source.add_issue(NamedRule.new, location: {2, 12}, message: \"\")\n      source.should_not be_valid\n    end\n\n    it \"does not disable if comment directive has wrong place\" do\n      source = Source.new <<-CRYSTAL\n        # ameba:disable #{NamedRule.name}\n        #\n        Time.epoch(1483859302)\n        CRYSTAL\n      source.add_issue(NamedRule.new, location: {3, 12}, message: \"\")\n      source.should_not be_valid\n    end\n\n    it \"does not disable if comment directive added to the wrong line\" do\n      source = Source.new <<-CRYSTAL\n        if use_epoch? # ameba:disable #{NamedRule.name}\n          Time.epoch(1483859302)\n        end\n        CRYSTAL\n      source.add_issue(NamedRule.new, location: {3, 12}, message: \"\")\n      source.should_not be_valid\n    end\n\n    it \"does not disable if that is not a comment directive\" do\n      source = Source.new <<-CRYSTAL\n        \"ameba:disable #{NamedRule.name}\"\n        Time.epoch(1483859302)\n        CRYSTAL\n      source.add_issue(NamedRule.new, location: {3, 12}, message: \"\")\n      source.should_not be_valid\n    end\n\n    it \"does not disable if that is a commented out directive\" do\n      source = Source.new <<-CRYSTAL\n        # # ameba:disable #{NamedRule.name}\n        Time.epoch(1483859302)\n        CRYSTAL\n      source.add_issue(NamedRule.new, location: {3, 12}, message: \"\")\n      source.should_not be_valid\n    end\n\n    it \"does not disable if that is an inline commented out directive\" do\n      source = Source.new <<-CRYSTAL\n        a = 1 # Disable it: # ameba:disable #{NamedRule.name}\n        CRYSTAL\n      source.add_issue(NamedRule.new, location: {2, 12}, message: \"\")\n      source.should_not be_valid\n    end\n\n    context \"with group name\" do\n      it \"disables one rule with a group\" do\n        source = Source.new <<-CRYSTAL\n          a = 1 # ameba:disable #{DummyRule.rule_name}\n          CRYSTAL\n        source.add_issue(DummyRule.new, location: {1, 12}, message: \"\")\n        source.should be_valid\n      end\n\n      it \"doesn't disable others rules\" do\n        source = Source.new <<-CRYSTAL\n          a = 1 # ameba:disable #{DummyRule.rule_name}\n          CRYSTAL\n        source.add_issue(NamedRule.new, location: {2, 12}, message: \"\")\n        source.should_not be_valid\n      end\n\n      it \"disables a hole group of rules\" do\n        source = Source.new <<-CRYSTAL\n          a = 1 # ameba:disable #{DummyRule.group_name}\n          CRYSTAL\n        source.add_issue(DummyRule.new, location: {1, 12}, message: \"\")\n        source.should be_valid\n      end\n\n      it \"does not disable rules which do not belong to the group\" do\n        source = Source.new <<-CRYSTAL\n          a = 1 # ameba:disable Lint\n          CRYSTAL\n        source.add_issue(DummyRule.new, location: {2, 12}, message: \"\")\n        source.should_not be_valid\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/issue_spec.cr",
    "content": "require \"../spec_helper\"\n\nmodule Ameba\n  describe Issue do\n    it \"accepts rule and message\" do\n      issue = Issue.new code: \"\",\n        rule: DummyRule.new,\n        location: nil,\n        end_location: nil,\n        message: \"Blah\",\n        status: nil\n\n      issue.rule.should_not be_nil\n      issue.message.should eq \"Blah\"\n    end\n\n    it \"accepts location\" do\n      location = Crystal::Location.new(\"path\", 3, 2)\n      issue = Issue.new code: \"\",\n        rule: DummyRule.new,\n        location: location,\n        end_location: nil,\n        message: \"Blah\",\n        status: nil\n\n      issue.location.to_s.should eq location.to_s\n      issue.end_location.should be_nil\n    end\n\n    it \"accepts end_location\" do\n      location = Crystal::Location.new(\"path\", 3, 2)\n      issue = Issue.new code: \"\",\n        rule: DummyRule.new,\n        location: nil,\n        end_location: location,\n        message: \"Blah\",\n        status: nil\n\n      issue.location.should be_nil\n      issue.end_location.to_s.should eq location.to_s\n    end\n\n    it \"accepts status\" do\n      issue = Issue.new code: \"\",\n        rule: DummyRule.new,\n        location: nil,\n        end_location: nil,\n        message: \"\",\n        status: :disabled\n\n      issue.status.should eq Issue::Status::Disabled\n      issue.disabled?.should be_true\n      issue.enabled?.should be_false\n    end\n\n    it \"sets status to :enabled by default\" do\n      issue = Issue.new code: \"\",\n        rule: DummyRule.new,\n        location: nil,\n        end_location: nil,\n        message: \"\"\n\n      issue.status.should eq Issue::Status::Enabled\n      issue.enabled?.should be_true\n      issue.disabled?.should be_false\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/presenter/rule_collection_presenter_spec.cr",
    "content": "require \"../../spec_helper\"\n\nmodule Ameba\n  private def with_rule_collection_presenter(&)\n    rules = Config.load.rules\n\n    with_presenter(Presenter::RuleCollectionPresenter, rules) do |presenter, output|\n      yield rules, output, presenter\n    end\n  end\n\n  describe Presenter::RuleCollectionPresenter do\n    it \"outputs rule collection details\" do\n      with_rule_collection_presenter do |rules, output|\n        rules.each do |rule|\n          output.should contain rule.name\n          output.should contain rule.severity.symbol\n\n          if description = rule.description\n            output.should contain description\n          end\n        end\n        output.should contain \"Total rules: #{rules.size}\"\n        output.should match /\\d+ enabled/\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/presenter/rule_presenter_spec.cr",
    "content": "require \"../../spec_helper\"\n\nmodule Ameba\n  private def rule_presenter_each_rule(&)\n    rules = Config.load.rules\n\n    rules.each do |rule|\n      with_presenter(Presenter::RulePresenter, rule) do |presenter, output|\n        yield rule, output, presenter\n      end\n    end\n  end\n\n  describe Presenter::RulePresenter do\n    it \"outputs rule details\" do\n      rule_presenter_each_rule do |rule, output|\n        output.should contain rule.name\n        output.should contain rule.severity.to_s\n\n        if description = rule.description\n          output.should contain description\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/presenter/rule_versions_presenter_spec.cr",
    "content": "require \"../../spec_helper\"\n\nmodule Ameba\n  private def with_rule_versions_presenter(&)\n    rules = Config.load.rules\n\n    with_presenter(Presenter::RuleVersionsPresenter, rules) do |presenter, output|\n      yield rules, output, presenter\n    end\n  end\n\n  describe Presenter::RuleVersionsPresenter do\n    it \"outputs rule versions\" do\n      with_rule_versions_presenter do |_rules, output|\n        output.should contain <<-TEXT\n          - 0.1.0\n            - Layout/LineLength\n            - Layout/TrailingBlankLines\n            - Layout/TrailingWhitespace\n            - Lint/ComparisonToBoolean\n            - Lint/DebuggerStatement\n            - Lint/LiteralInCondition\n            - Lint/LiteralInInterpolation\n            - Style/UnlessElse\n          TEXT\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/reportable_spec.cr",
    "content": "require \"../spec_helper\"\n\nmodule Ameba\n  describe Reportable do\n    describe \"#add_issue\" do\n      it \"adds a new issue for node\" do\n        source = Source.new path: \"source.cr\"\n        source.add_issue(DummyRule.new, Crystal::Nop.new, \"Error!\")\n\n        issue = source.issues.first\n        issue.rule.should_not be_nil\n        issue.location.to_s.should be_empty\n        issue.message.should eq \"Error!\"\n      end\n\n      it \"adds a new issue by line and column number\" do\n        source = Source.new path: \"source.cr\"\n        source.add_issue(DummyRule.new, {23, 2}, \"Error!\")\n\n        issue = source.issues.first\n        issue.rule.should_not be_nil\n        issue.location.to_s.should eq \"source.cr:23:2\"\n        issue.message.should eq \"Error!\"\n      end\n    end\n\n    describe \"#valid?\" do\n      it \"returns true if no issues added\" do\n        source = Source.new path: \"source.cr\"\n        source.should be_valid\n      end\n\n      it \"returns false if there are issues added\" do\n        source = Source.new path: \"source.cr\"\n        source.add_issue DummyRule.new, {22, 2}, \"ERROR!\"\n        source.should_not be_valid\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/base_spec.cr",
    "content": "require \"../../spec_helper\"\n\nmodule Ameba\n  describe Rule::Base do\n    describe \"#catch\" do\n      it \"accepts and returns source\" do\n        source = Source.new\n        DummyRule.new.catch(source).should eq source\n      end\n    end\n\n    describe \"#name\" do\n      it \"returns name of the rule\" do\n        DummyRule.new.name.should eq \"Ameba/DummyRule\"\n      end\n    end\n\n    describe \"#group\" do\n      it \"returns a group rule belongs to\" do\n        DummyRule.new.group.should eq \"Ameba\"\n      end\n    end\n  end\n\n  describe Rule do\n    describe \".rules\" do\n      it \"returns a list of all defined rules\" do\n        Rule.rules.includes?(DummyRule).should be_true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/documentation/admonition_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Documentation\n  describe Admonition do\n    subject = Admonition.new\n\n    it \"passes for comments with admonition mid-word/sentence\" do\n      subject.admonitions.each do |admonition|\n        expect_no_issues subject, <<-CRYSTAL\n          # Mentioning #{admonition} mid-sentence\n          # x#{admonition}x\n          # x#{admonition}\n          # #{admonition}x\n          CRYSTAL\n      end\n    end\n\n    it \"fails for comments with admonition\" do\n      subject.admonitions.each do |admonition|\n        expect_issue subject, <<-CRYSTAL, admonition: admonition\n          def foo\n            # %{admonition}: Single-line comment\n            # ^{admonition} error: Found a %{admonition} admonition in a comment\n          end\n          CRYSTAL\n\n        expect_issue subject, <<-CRYSTAL, admonition: admonition\n          def foo\n            # Text before ...\n            # %{admonition}(some context): Part of multi-line comment\n            # ^{admonition} error: Found a %{admonition} admonition in a comment\n            # Text after ...\n          end\n          CRYSTAL\n\n        expect_issue subject, <<-CRYSTAL, admonition: admonition\n          def foo\n            # %{admonition}\n            # ^{admonition} error: Found a %{admonition} admonition in a comment\n            if rand > 0.5\n            end\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"with date\" do\n      it \"passes for admonitions with future date\" do\n        subject.admonitions.each do |admonition|\n          future_date = (Time.utc + 21.days).to_s(format: \"%F\")\n          expect_no_issues subject, <<-CRYSTAL\n            # #{admonition}(#{future_date}): sth in the future\n            CRYSTAL\n        end\n      end\n\n      it \"fails for admonitions with past date\" do\n        subject.admonitions.each do |admonition|\n          past_date = (Time.utc - 21.days).to_s(format: \"%F\")\n          expect_issue subject, <<-CRYSTAL, admonition: admonition\n            # %{admonition}(#{past_date}): sth in the past\n            # ^{admonition} error: Found a %{admonition} admonition in a comment (21 days past)\n            CRYSTAL\n        end\n      end\n\n      it \"fails for admonitions with yesterday's date\" do\n        subject.admonitions.each do |admonition|\n          yesterday_date = (Time.utc - 1.day).to_s(format: \"%F\")\n          expect_issue subject, <<-CRYSTAL, admonition: admonition\n            # %{admonition}(#{yesterday_date}): sth in the past\n            # ^{admonition} error: Found a %{admonition} admonition in a comment (1 day past)\n            CRYSTAL\n        end\n      end\n\n      it \"fails for admonitions with today's date\" do\n        subject.admonitions.each do |admonition|\n          today_date = Time.utc.to_s(format: \"%F\")\n          expect_issue subject, <<-CRYSTAL, admonition: admonition\n            # %{admonition}(#{today_date}): sth in the past\n            # ^{admonition} error: Found a %{admonition} admonition in a comment (today is the day!)\n            CRYSTAL\n        end\n      end\n\n      it \"fails for admonitions with invalid date\" do\n        subject.admonitions.each do |admonition|\n          expect_issue subject, <<-CRYSTAL, admonition: admonition\n            # %{admonition}(0000-00-00): sth wrong\n            # ^{admonition} error: %{admonition} admonition error: Invalid time: \"0000-00-00\"\n            CRYSTAL\n        end\n      end\n    end\n\n    context \"properties\" do\n      describe \"#admonitions\" do\n        it \"lets setting custom admonitions\" do\n          rule = Admonition.new\n          rule.admonitions = %w[FOO BAR]\n\n          rule.admonitions.each do |admonition|\n            expect_issue rule, <<-CRYSTAL, admonition: admonition\n              # %{admonition}\n              # ^{admonition} error: Found a %{admonition} admonition in a comment\n              CRYSTAL\n          end\n\n          subject.admonitions.each do |admonition|\n            expect_no_issues rule, <<-CRYSTAL\n              # #{admonition}\n              CRYSTAL\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/documentation/documentation_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Documentation\n  describe Documentation do\n    subject = Documentation.new\n    subject.ignore_classes = false\n    subject.ignore_modules = false\n    subject.ignore_enums = false\n    subject.ignore_defs = false\n    subject.ignore_macros = false\n\n    it \"passes for undocumented private types\" do\n      expect_no_issues subject, <<-CRYSTAL\n        private class Foo\n          def foo\n          end\n        end\n\n        private module Bar\n          def bar\n          end\n        end\n\n        private enum Baz\n        end\n\n        private def bat\n        end\n\n        private macro bag\n        end\n        CRYSTAL\n    end\n\n    it \"passes for documented public types\" do\n      expect_no_issues subject, <<-CRYSTAL\n        # Foo\n        class Foo\n          # foo\n          def foo\n          end\n        end\n\n        # Bar\n        module Bar\n          # bar\n          def bar\n          end\n        end\n\n        # Baz\n        enum Baz\n        end\n\n        # bat\n        def bat\n        end\n\n        # bag\n        macro bag\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is an undocumented public type\" do\n      expect_issue subject, <<-CRYSTAL\n        class Foo\n        # ^^^^^^^ error: Missing documentation\n        end\n\n        module Bar\n        # ^^^^^^^^ error: Missing documentation\n        end\n\n        enum Baz\n        # ^^^^^^ error: Missing documentation\n        end\n\n        def bat\n        # ^^^^^ error: Missing documentation\n        end\n\n        macro bag\n        # ^^^^^^^ error: Missing documentation\n        end\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      describe \"#ignore_classes\" do\n        it \"lets the rule to ignore method definitions if true\" do\n          rule = Documentation.new\n          rule.ignore_classes = true\n\n          expect_no_issues rule, <<-CRYSTAL\n            class Foo\n            end\n            CRYSTAL\n        end\n      end\n\n      describe \"#ignore_modules\" do\n        it \"lets the rule to ignore method definitions if true\" do\n          rule = Documentation.new\n          rule.ignore_modules = true\n\n          expect_no_issues rule, <<-CRYSTAL\n            module Bar\n            end\n            CRYSTAL\n        end\n      end\n\n      describe \"#ignore_enums\" do\n        it \"lets the rule to ignore method definitions if true\" do\n          rule = Documentation.new\n          rule.ignore_enums = true\n\n          expect_no_issues rule, <<-CRYSTAL\n            enum Baz\n            end\n            CRYSTAL\n        end\n      end\n\n      describe \"#ignore_defs\" do\n        it \"lets the rule to ignore method definitions if true\" do\n          rule = Documentation.new\n          rule.ignore_defs = true\n\n          expect_no_issues rule, <<-CRYSTAL\n            def bat\n            end\n            CRYSTAL\n        end\n      end\n\n      describe \"#ignore_macros\" do\n        it \"lets the rule to ignore macros if true\" do\n          rule = Documentation.new\n          rule.ignore_macros = true\n\n          expect_no_issues rule, <<-CRYSTAL\n            macro bag\n            end\n            CRYSTAL\n        end\n      end\n\n      describe \"#require_example\" do\n        rule = Documentation.new\n        rule.require_example = true\n\n        it \"fails if there is a documented public type without example\" do\n          expect_issue rule, <<-CRYSTAL\n            # Foo documentation\n            class Foo\n            # ^^^^^^^ error: Missing documentation example\n            end\n            CRYSTAL\n        end\n\n        it \"passes if there is a documented public type with example\" do\n          expect_no_issues rule, <<-CRYSTAL\n            # Foo documentation\n            #\n            # ```\n            # foo = Foo.new\n            # foo.bar # => :baz\n            # ```\n            class Foo\n            end\n            CRYSTAL\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/layout/line_length_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Layout\n  describe LineLength do\n    subject = LineLength.new\n    long_line = \"*\" * (subject.max_length + 1)\n\n    it \"passes if all lines are shorter than MaxLength symbols\" do\n      expect_no_issues subject, <<-CRYSTAL\n        short line\n        CRYSTAL\n    end\n\n    it \"passes if line consists of MaxLength symbols\" do\n      expect_no_issues subject, <<-CRYSTAL\n        #{\"*\" * subject.max_length}\n        CRYSTAL\n    end\n\n    it \"fails if there is at least one line longer than MaxLength symbols\" do\n      source = Source.new long_line\n      subject.catch(source).should_not be_valid\n    end\n\n    it \"reports rule, pos and message\" do\n      source = Source.new long_line, \"source.cr\"\n      subject.catch(source).should_not be_valid\n\n      issue = source.issues.first\n      issue.rule.should eq subject\n      issue.location.to_s.should eq \"source.cr:1:#{subject.max_length + 1}\"\n      issue.end_location.should be_nil\n      issue.message.should eq \"Line too long\"\n    end\n\n    context \"properties\" do\n      it \"#max_length\" do\n        rule = LineLength.new\n        rule.max_length = long_line.size\n\n        expect_no_issues rule, long_line\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/layout/trailing_blank_lines_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Layout\n  describe TrailingBlankLines do\n    subject = TrailingBlankLines.new\n\n    it \"passes if there is a blank line at the end of a source\" do\n      expect_no_issues subject, \"a = 1\\n\"\n    end\n\n    it \"passes if source is empty\" do\n      expect_no_issues subject, \"\"\n    end\n\n    it \"fails if there is no blank lines at the end\" do\n      source = expect_issue subject, \"no-blankline # error: Trailing newline missing\"\n      expect_correction source, \"no-blankline\\n\"\n    end\n\n    it \"fails if there more then one blank line at the end of a source\" do\n      source = expect_issue subject, \"a = 1\\n \\n # error: Excessive trailing newline detected\"\n      expect_no_corrections source\n    end\n\n    it \"fails if last line is not blank\" do\n      source = expect_issue subject, \"\\n\\n\\n puts 22 # error: Trailing newline missing\"\n      expect_correction source, \"\\n\\n\\n puts 22\\n\"\n    end\n\n    context \"when unnecessary blank line has been detected\" do\n      it \"reports rule, pos and message\" do\n        source = Source.new \"a = 1\\n\\n\", \"source.cr\", normalize: false\n        subject.catch(source).should_not be_valid\n\n        issue = source.issues.first\n        issue.rule.should_not be_nil\n        issue.location.to_s.should eq \"source.cr:3:1\"\n        issue.end_location.should be_nil\n        issue.message.should eq \"Excessive trailing newline detected\"\n      end\n    end\n\n    context \"when final line has been missed\" do\n      it \"reports rule, pos and message\" do\n        source = Source.new \"a = 1\", \"source.cr\", normalize: false\n        subject.catch(source).should_not be_valid\n\n        issue = source.issues.first\n        issue.rule.should_not be_nil\n        issue.location.to_s.should eq \"source.cr:1:1\"\n        issue.end_location.should be_nil\n        issue.message.should eq \"Trailing newline missing\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/layout/trailing_whitespace_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Layout\n  describe TrailingWhitespace do\n    subject = TrailingWhitespace.new\n\n    it \"passes if all lines do not have trailing whitespace\" do\n      expect_no_issues subject, \"no-whitespace\"\n    end\n\n    it \"passes a line ends with trailing CRLF sequence\" do\n      expect_no_issues subject, \"no-whitespace\\r\\n\"\n    end\n\n    it \"fails if a line ends with trailing \\\\r character\" do\n      source = expect_issue subject, <<-TEXT\n        carriage return at the end\\r\n                                # ^ error: Trailing whitespace detected\n        TEXT\n\n      expect_correction source, \"carriage return at the end\"\n    end\n\n    it \"fails if there is a line with trailing tab\" do\n      source = expect_issue subject, <<-TEXT\n        tab at the end\\t\n                    # ^ error: Trailing whitespace detected\n        TEXT\n\n      expect_correction source, \"tab at the end\"\n    end\n\n    it \"fails if there is a line with trailing whitespace\" do\n      source = expect_issue subject,\n        \"whitespace at the end  \\n\" \\\n        \"                   # ^^ error: Trailing whitespace detected\"\n\n      expect_correction source, \"whitespace at the end\"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/ambiguous_assignment_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe AmbiguousAssignment do\n    subject = AmbiguousAssignment.new\n\n    context \"when using `-`\" do\n      it \"registers an offense with `x`\" do\n        expect_issue subject, <<-CRYSTAL\n          x =- y\n          # ^^ error: Suspicious assignment detected. Did you mean `-=`?\n          CRYSTAL\n      end\n\n      it \"registers an offense with `@x`\" do\n        expect_issue subject, <<-CRYSTAL\n          @x =- y\n           # ^^ error: Suspicious assignment detected. Did you mean `-=`?\n          CRYSTAL\n      end\n\n      it \"registers an offense with `@@x`\" do\n        expect_issue subject, <<-CRYSTAL\n          @@x =- y\n            # ^^ error: Suspicious assignment detected. Did you mean `-=`?\n          CRYSTAL\n      end\n\n      it \"registers an offense with `X`\" do\n        expect_issue subject, <<-CRYSTAL\n          X =- y\n          # ^^ error: Suspicious assignment detected. Did you mean `-=`?\n          CRYSTAL\n      end\n\n      it \"does not register an offense when no mistype assignments\" do\n        expect_no_issues subject, <<-CRYSTAL\n          x = 1\n          x -= y\n          x = -y\n          CRYSTAL\n      end\n    end\n\n    context \"when using `+`\" do\n      it \"registers an offense with `x`\" do\n        expect_issue subject, <<-CRYSTAL\n          x =+ y\n          # ^^ error: Suspicious assignment detected. Did you mean `+=`?\n          CRYSTAL\n      end\n\n      it \"registers an offense with `@x`\" do\n        expect_issue subject, <<-CRYSTAL\n          @x =+ y\n           # ^^ error: Suspicious assignment detected. Did you mean `+=`?\n          CRYSTAL\n      end\n\n      it \"registers an offense with `@@x`\" do\n        expect_issue subject, <<-CRYSTAL\n          @@x =+ y\n            # ^^ error: Suspicious assignment detected. Did you mean `+=`?\n          CRYSTAL\n      end\n\n      it \"registers an offense with `X`\" do\n        expect_issue subject, <<-CRYSTAL\n          X =+ y\n          # ^^ error: Suspicious assignment detected. Did you mean `+=`?\n          CRYSTAL\n      end\n\n      it \"does not register an offense when no mistype assignments\" do\n        expect_no_issues subject, <<-CRYSTAL\n          x = 1\n          x += y\n          x = +y\n          CRYSTAL\n      end\n    end\n\n    context \"when using `!`\" do\n      it \"registers an offense with `x`\" do\n        expect_issue subject, <<-CRYSTAL\n          x =! y\n          # ^^ error: Suspicious assignment detected. Did you mean `!=`?\n          CRYSTAL\n      end\n\n      it \"registers an offense with `@x`\" do\n        expect_issue subject, <<-CRYSTAL\n          @x =! y\n           # ^^ error: Suspicious assignment detected. Did you mean `!=`?\n          CRYSTAL\n      end\n\n      it \"registers an offense with `@@x`\" do\n        expect_issue subject, <<-CRYSTAL\n          @@x =! y\n            # ^^ error: Suspicious assignment detected. Did you mean `!=`?\n          CRYSTAL\n      end\n\n      it \"registers an offense with `X`\" do\n        expect_issue subject, <<-CRYSTAL\n          X =! y\n          # ^^ error: Suspicious assignment detected. Did you mean `!=`?\n          CRYSTAL\n      end\n\n      it \"does not register an offense when no mistype assignments\" do\n        expect_no_issues subject, <<-CRYSTAL\n          x = false\n          x != y\n          x = !y\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/assignment_in_call_argument_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe AssignmentInCallArgument do\n    subject = AssignmentInCallArgument.new\n\n    context \"outside of a method call arguments\" do\n      it \"ignores const assignments\" do\n        expect_no_issues subject, <<-CRYSTAL\n          FOO = 1\n          CRYSTAL\n      end\n\n      it \"ignores bare assignments\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo = 1\n          CRYSTAL\n      end\n\n      it \"ignores assignments within blocks\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo do\n            foo = 1\n          end\n          CRYSTAL\n      end\n\n      it \"ignores assignments within methods\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            foo = 1\n          end\n          CRYSTAL\n      end\n\n      it \"ignores assignments within classes\" do\n        expect_no_issues subject, <<-CRYSTAL\n          class Foo\n            foo = 1\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"in stdlib macros\" do\n      it \"ignores assignments within accessor macros\" do\n        expect_no_issues subject, <<-CRYSTAL\n          class Foo\n            class_getter foo = 1\n            getter bar = 2\n            property baz = 3\n          end\n          CRYSTAL\n      end\n\n      it \"ignores assignments within `record` macro\" do\n        expect_no_issues subject, <<-CRYSTAL\n          record Foo, foo = 1\n          CRYSTAL\n      end\n    end\n\n    it \"ignores assignments within assignments\" do\n      expect_no_issues subject, <<-CRYSTAL\n        self.foo = foo = 1\n        CRYSTAL\n    end\n\n    it \"ignores assignments within operator assignments\" do\n      expect_no_issues subject, <<-CRYSTAL\n        self.foo += foo = 1\n        CRYSTAL\n    end\n\n    it \"ignores assignments within operator calls\" do\n      expect_no_issues subject, <<-CRYSTAL\n        self.foo + (foo = 1)\n        CRYSTAL\n    end\n\n    it \"ignores assignment within a proc passed as a method call argument\" do\n      expect_no_issues subject, <<-CRYSTAL\n        foo -> {\n          bar = 1\n        }\n        CRYSTAL\n    end\n\n    it \"ignores assignment within a proc passed as a method call named argument\" do\n      expect_no_issues subject, <<-CRYSTAL\n        foo bar: -> {\n          baz = 1\n        }\n        CRYSTAL\n    end\n\n    it \"ignores assignment within a nested call\" do\n      expect_no_issues subject, <<-CRYSTAL\n        foo(bar do\n          baz = 1\n        end)\n        CRYSTAL\n    end\n\n    it \"reports assignment within a method call argument\" do\n      expect_issue subject, <<-CRYSTAL\n        foo a = 1\n          # ^^^^^ error: Assignment within a call argument detected\n        CRYSTAL\n    end\n\n    it \"reports multiple assignments within a method call arguments\" do\n      expect_issue subject, <<-CRYSTAL\n        foo a = 1, b = 2\n          # ^^^^^ error: Assignment within a call argument detected\n                 # ^^^^^ error: Assignment within a call argument detected\n        CRYSTAL\n    end\n\n    it \"reports operator assignment within a method call argument\" do\n      expect_issue subject, <<-CRYSTAL\n        a = 0\n        foo a += 1\n          # ^^^^^^ error: Assignment within a call argument detected\n        CRYSTAL\n    end\n\n    it \"reports assignment within a method call named argument\" do\n      expect_issue subject, <<-CRYSTAL\n        foo a: a = 1\n             # ^^^^^ error: Assignment within a call argument detected\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/bad_directive_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe BadDirective do\n    subject = BadDirective.new\n\n    it \"does not report if action is correct\" do\n      expect_no_issues subject, <<-CRYSTAL\n        # ameba:disable Lint/BadDirective\n        CRYSTAL\n    end\n\n    it \"reports if there is incorrect action\" do\n      expect_issue subject, <<-CRYSTAL\n        # ameba:foo Lint/BadDirective\n              # ^^^ error: Bad action in comment directive: `foo`. Possible values: `disable`, `enable`\n        CRYSTAL\n    end\n\n    it \"does not report if there no action and rules at all\" do\n      expect_no_issues subject, <<-CRYSTAL\n        # ameba:\n        CRYSTAL\n    end\n\n    it \"does not report if there are no rules\" do\n      expect_no_issues subject, <<-CRYSTAL\n        # ameba:enable\n        # ameba:disable\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/comparison_to_boolean_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe ComparisonToBoolean do\n    subject = ComparisonToBoolean.new\n\n    it \"passes if there is no comparison to boolean\" do\n      expect_no_issues subject, <<-CRYSTAL\n        a = true\n\n        if a\n          :ok\n        end\n\n        if true\n          :ok\n        end\n\n        unless s.empty?\n          :ok\n        end\n\n        :ok if a\n        :ok if a != 1\n        :ok if a == \"true\"\n\n        case a\n        when true\n          :ok\n        when false\n          :not_ok\n        end\n        CRYSTAL\n    end\n\n    context \"boolean on the right\" do\n      it \"fails if there is == comparison to boolean\" do\n        source = expect_issue subject, <<-CRYSTAL\n          if s.empty? == true\n           # ^^^^^^^^^^^^^^^^ error: Comparison to a boolean is pointless\n            :ok\n          end\n\n          if s.empty? == false\n           # ^^^^^^^^^^^^^^^^^ error: Comparison to a boolean is pointless\n            :ok\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          if s.empty?\n            :ok\n          end\n\n          if !s.empty?\n            :ok\n          end\n          CRYSTAL\n      end\n\n      it \"fails if there is != comparison to boolean\" do\n        source = expect_issue subject, <<-CRYSTAL\n          if a != false\n           # ^^^^^^^^^^ error: Comparison to a boolean is pointless\n            :ok\n          end\n\n          if a != true\n           # ^^^^^^^^^ error: Comparison to a boolean is pointless\n            :ok\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          if a\n            :ok\n          end\n\n          if !a\n            :ok\n          end\n          CRYSTAL\n      end\n\n      it \"fails if there is case comparison to boolean\" do\n        source = expect_issue subject, <<-CRYSTAL\n          a === true\n          # ^^^^^^^^ error: Comparison to a boolean is pointless\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          a\n          CRYSTAL\n      end\n    end\n\n    context \"boolean on the left\" do\n      it \"fails if there is == comparison to boolean\" do\n        source = expect_issue subject, <<-CRYSTAL\n          if true == s.empty?\n           # ^^^^^^^^^^^^^^^^ error: Comparison to a boolean is pointless\n            :ok\n          end\n\n          if false == s.empty?\n           # ^^^^^^^^^^^^^^^^^ error: Comparison to a boolean is pointless\n            :ok\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          if s.empty?\n            :ok\n          end\n\n          if !s.empty?\n            :ok\n          end\n          CRYSTAL\n      end\n\n      it \"fails if there is != comparison to boolean\" do\n        source = expect_issue subject, <<-CRYSTAL\n          if false != a\n           # ^^^^^^^^^^ error: Comparison to a boolean is pointless\n            :ok\n          end\n\n          if true != a\n           # ^^^^^^^^^ error: Comparison to a boolean is pointless\n            :ok\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          if a\n            :ok\n          end\n\n          if !a\n            :ok\n          end\n          CRYSTAL\n      end\n\n      it \"fails if there is case comparison to boolean\" do\n        source = expect_issue subject, <<-CRYSTAL\n          true === a\n          # ^^^^^^^^ error: Comparison to a boolean is pointless\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          a\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/debug_calls_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe DebugCalls do\n    subject = DebugCalls.new\n\n    it \"fails if there is a debug call\" do\n      subject.method_names.each do |name|\n        source = expect_issue subject, <<-CRYSTAL, name: name\n          a = 2\n          %{name} a\n          # ^{name} error: Possibly forgotten debug-related `%{name}` call detected\n          a = a + 1\n          CRYSTAL\n\n        expect_no_corrections source\n      end\n    end\n\n    it \"passes if there is no debug call\" do\n      subject.method_names.each do |name|\n        expect_no_issues subject, <<-CRYSTAL\n          class A\n            def #{name}\n            end\n          end\n          A.new.#{name}\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/debugger_statement_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe DebuggerStatement do\n    subject = DebuggerStatement.new\n\n    it \"passes if there is no debugger statement\" do\n      expect_no_issues subject, <<-CRYSTAL\n        \"this is not a debugger statement\"\n        s = \"debugger\"\n\n        def debugger(program)\n        end\n        debugger \"\"\n\n        class A\n          def debugger\n          end\n        end\n        A.new.debugger\n        CRYSTAL\n    end\n\n    it \"fails if there is a debugger statement\" do\n      source = expect_issue subject, <<-CRYSTAL\n        a = 2\n        debugger\n        # ^^^^^^ error: Possible forgotten `debugger` statement detected\n        a = a + 1\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        a = 2\n\n        a = a + 1\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/duplicate_branch_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe DuplicateBranch do\n    subject = DuplicateBranch.new\n\n    it \"does not report different `if` and `else` branch bodies\" do\n      expect_no_issues subject, <<-CRYSTAL\n        if :foo\n          1\n        elsif :foo\n          2\n        elsif :foo\n          3\n        else\n          nil\n        end\n        CRYSTAL\n    end\n\n    it \"reports duplicated `if` and `else` branch bodies\" do\n      expect_issue subject, <<-CRYSTAL\n        if 1\n          :foo\n        elsif 2\n          :foo\n        # ^^^^ error: Duplicate branch body detected\n        elsif 3\n          :foo\n        # ^^^^ error: Duplicate branch body detected\n        else\n          :foo\n        # ^^^^ error: Duplicate branch body detected\n        end\n        CRYSTAL\n    end\n\n    it \"reports duplicated `if` branch bodies\" do\n      expect_issue subject, <<-CRYSTAL\n        if true\n          :foo\n        elsif false\n          :foo\n        # ^^^^ error: Duplicate branch body detected\n        end\n        CRYSTAL\n    end\n\n    it \"reports duplicated `else` branch bodies\" do\n      expect_issue subject, <<-CRYSTAL\n        if true\n          :foo\n        else\n          :foo\n        # ^^^^ error: Duplicate branch body detected\n        end\n        CRYSTAL\n    end\n\n    it \"reports duplicated `else` branch body within `unless`\" do\n      expect_issue subject, <<-CRYSTAL\n        unless true\n          :foo\n        else\n          :foo\n        # ^^^^ error: Duplicate branch body detected\n        end\n        CRYSTAL\n    end\n\n    it \"reports duplicated `if` / `else` branch bodies nested within `if`\" do\n      expect_issue subject, <<-CRYSTAL\n        if true\n          :foo\n        elsif false\n          %w[foo bar].each do\n            if 1\n              :abc\n            elsif 2\n              :abc\n            # ^^^^ error: Duplicate branch body detected\n            else\n              :abc\n            # ^^^^ error: Duplicate branch body detected\n            end\n          end\n          :foo\n        end\n        CRYSTAL\n    end\n\n    it \"reports duplicated `if` / `else` branch bodies nested within `else`\" do\n      expect_issue subject, <<-CRYSTAL\n        if true\n          :foo\n        else\n          %w[foo bar].each do\n            if 1\n              :abc\n            elsif 2\n              :abc\n            # ^^^^ error: Duplicate branch body detected\n            else\n              :abc\n            # ^^^^ error: Duplicate branch body detected\n            end\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"reports duplicated `else` branch bodies within a ternary `if`\" do\n      expect_issue subject, <<-CRYSTAL\n        true ? :foo : :foo\n                    # ^^^^ error: Duplicate branch body detected\n        CRYSTAL\n    end\n\n    it \"reports duplicated `case` branch bodies\" do\n      expect_issue subject, <<-CRYSTAL\n        case\n        when true\n          :foo\n        when false\n          :foo\n        # ^^^^ error: Duplicate branch body detected\n        end\n        CRYSTAL\n    end\n\n    it \"reports duplicated exception handler branch bodies\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n          :foo\n        rescue ArgumentError\n          :foo\n        rescue OverflowError\n          :foo\n        # ^^^^ error: Duplicate branch body detected\n        else\n          :foo\n        # ^^^^ error: Duplicate branch body detected\n        end\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      context \"#ignore_literal_branches\" do\n        it \"when disabled reports duplicated (static) literal branch bodies\" do\n          rule = DuplicateBranch.new\n          rule.ignore_literal_branches = false\n\n          expect_issue rule, <<-CRYSTAL\n            true ? :foo : :foo\n                        # ^^^^ error: Duplicate branch body detected\n            true ? \"foo\" : \"foo\"\n                         # ^^^^^ error: Duplicate branch body detected\n            true ? 123 : 123\n                       # ^^^ error: Duplicate branch body detected\n            true ? [1, 2, 3] : [1, 2, 3]\n                             # ^^^^^^^^^ error: Duplicate branch body detected\n            true ? [foo, bar, baz] : [foo, bar, baz]\n                                   # ^^^^^^^^^^^^^^^ error: Duplicate branch body detected\n            CRYSTAL\n        end\n\n        it \"when enabled does not report duplicated (static) literal branch bodies\" do\n          rule = DuplicateBranch.new\n          rule.ignore_literal_branches = true\n\n          # static literals\n          expect_no_issues rule, <<-CRYSTAL\n            true ? :foo : :foo\n            true ? \"foo\" : \"foo\"\n            true ? 123 : 123\n            true ? [1, 2, 3] : [1, 2, 3]\n            true ? {foo: \"bar\"} : {foo: \"bar\"}\n            CRYSTAL\n\n          # dynamic literals\n          expect_issue rule, <<-CRYSTAL\n            true ? [foo, bar, baz] : [foo, bar, baz]\n                                   # ^^^^^^^^^^^^^^^ error: Duplicate branch body detected\n            CRYSTAL\n        end\n      end\n\n      context \"#ignore_constant_branches\" do\n        it \"when disabled reports constant branch bodies\" do\n          rule = DuplicateBranch.new\n          rule.ignore_constant_branches = false\n\n          expect_issue rule, <<-CRYSTAL\n            true ? FOO : FOO\n                       # ^^^ error: Duplicate branch body detected\n            true ? Foo::Bar : Foo::Bar\n                            # ^^^^^^^^ error: Duplicate branch body detected\n            CRYSTAL\n        end\n\n        it \"when enabled does not report constant branch bodies\" do\n          rule = DuplicateBranch.new\n          rule.ignore_constant_branches = true\n\n          expect_no_issues rule, <<-CRYSTAL\n            true ? FOO : FOO\n            true ? Foo::Bar : Foo::Bar\n            CRYSTAL\n        end\n      end\n\n      context \"#ignore_duplicate_else_branch\" do\n        rule = DuplicateBranch.new\n        rule.ignore_duplicate_else_branch = true\n\n        context \"when enabled does not report duplicated `else` branch bodies\" do\n          it \"in `if`\" do\n            expect_no_issues rule, <<-CRYSTAL\n              if true\n                :foo\n              else\n                :foo\n              end\n              CRYSTAL\n          end\n\n          it \"in ternary `if`\" do\n            expect_no_issues rule, <<-CRYSTAL\n              true ? :foo : :foo\n              CRYSTAL\n          end\n\n          it \"in `unless`\" do\n            expect_no_issues rule, <<-CRYSTAL\n              unless true\n                :foo\n              else\n                :foo\n              end\n              CRYSTAL\n          end\n\n          it \"in `case`\" do\n            expect_no_issues rule, <<-CRYSTAL\n              case\n              when true\n                :foo\n              else\n                :foo\n              end\n              CRYSTAL\n          end\n\n          it \"in exception handler\" do\n            expect_no_issues rule, <<-CRYSTAL\n              begin\n                :foo\n              rescue ArgumentError\n                :foo\n              else\n                :foo\n              end\n              CRYSTAL\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/duplicate_enum_value_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe DuplicateEnumValue do\n    subject = DuplicateEnumValue.new\n\n    it \"passes if there are no enum members with duplicate values\" do\n      expect_no_issues subject, <<-CRYSTAL\n        enum Foo\n          Foo\n          Bar\n          Baz\n        end\n        CRYSTAL\n    end\n\n    it \"passes if there are no duplicate enum member values\" do\n      expect_no_issues subject, <<-CRYSTAL\n        enum Foo\n          Foo = 1\n          Bar = 2\n          Baz = 3\n        end\n        CRYSTAL\n    end\n\n    it \"passes if there are aliased enum member values\" do\n      expect_no_issues subject, <<-CRYSTAL\n        enum Foo\n          Foo = 1\n          Bar = 2\n          Baz = Bar\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there are a duplicate enum member values\" do\n      expect_issue subject, <<-CRYSTAL\n        enum Foo\n          Foo = 111\n          Bar = 222\n          Baz = 222\n              # ^^^ error: Duplicate enum member value detected\n          Bat = 222\n              # ^^^ error: Duplicate enum member value detected\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/duplicate_method_signature_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe DuplicateMethodSignature do\n    subject = DuplicateMethodSignature.new\n\n    it \"passes if there are no duplicate methods\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def foo; end\n          def foo(&); end\n          def foo?; end\n          def foo!; end\n          def foo(\n            bar : Bar,\n          )\n          end\n          def foo(\n            baz : Baz,\n          )\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"passes if there are duplicate methods with `previous_def`\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def foo\n            42\n          end\n\n          def foo\n            previous_def if rand >= 0.42\n            24\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there are duplicate methods without `previous_def`\" do\n      expect_issue subject, <<-CRYSTAL\n        class Foo\n          def foo\n            42\n          end\n\n          def foo\n        # ^^^^^^^ error: Duplicate method signature detected\n            previous_definition if rand >= 0.42\n            24\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there are multiple duplicate methods\" do\n      expect_issue subject, <<-CRYSTAL\n        class Foo\n          def foo; end\n          def bar; end\n          def foo; end\n        # ^^^^^^^^^^^^ error: Duplicate method signature detected\n          def foo; end\n        # ^^^^^^^^^^^^ error: Duplicate method signature detected\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there are duplicate methods (with block)\" do\n      expect_issue subject, <<-CRYSTAL\n        class Foo\n          def foo(&); end\n          def bar(&); end\n          def foo(&); end\n        # ^^^^^^^^^^^^^^^ error: Duplicate method signature detected\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there are duplicate methods (with visibility modifier)\" do\n      expect_issue subject, <<-CRYSTAL\n        class Foo\n          def foo; end\n          private def foo; end\n                # ^^^^^^^^^^^^ error: Duplicate method signature detected\n          protected def foo; end\n                  # ^^^^^^^^^^^^ error: Duplicate method signature detected\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there are duplicate methods (with arguments)\" do\n      expect_issue subject, <<-CRYSTAL\n        class Foo\n          def foo(a, b, c = 3, &); end\n          def foo(a, b, c = 3); end\n          def foo(a, b, c = 3); end\n        # ^^^^^^^^^^^^^^^^^^^^^^^^^ error: Duplicate method signature detected\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there are duplicate methods with different bodies\" do\n      expect_issue subject, <<-CRYSTAL\n        class Foo\n          def foo(a, b, c = 3)\n            puts :foo\n          end\n\n          def foo(a, b, c = 3)\n        # ^^^^^^^^^^^^^^^^^^^^ error: Duplicate method signature detected\n            puts :bar\n          end\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/duplicate_when_condition_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe DuplicateWhenCondition do\n    subject = DuplicateWhenCondition.new\n\n    it \"passes if there are no duplicated `when` conditions\" do\n      expect_no_issues subject, <<-CRYSTAL\n        case x\n        when .nil?\n          do_something\n        when Symbol\n          do_something_else\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there are a duplicated `when` conditions in `case` expression\" do\n      expect_issue subject, <<-CRYSTAL\n        case x\n        when .foo?, .nil?\n          do_something\n        when .nil?\n           # ^^^^^ error: Duplicate `when` condition detected\n          do_something_else\n        end\n        CRYSTAL\n\n      expect_issue subject, <<-CRYSTAL\n        case\n        when foo?\n          :foo\n        when foo?, bar?\n           # ^^^^ error: Duplicate `when` condition detected\n           :foobar\n        when Time.utc.year == 1996\n          :yo\n        when Time.utc.year == 1996\n           # ^^^^^^^^^^^^^^^^^^^^^ error: Duplicate `when` condition detected\n          :yo\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there are a duplicated `when` conditions in `select` expression\" do\n      expect_issue subject, <<-CRYSTAL\n        select\n        when foo = foo_channel.receive\n          puts foo\n        when foo = foo_channel.receive\n           # ^^^^^^^^^^^^^^^^^^^^^^^^^ error: Duplicate `when` condition detected\n          puts foo\n        when bar = bar_channel.receive?\n          puts bar\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/duplicated_require_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe DuplicatedRequire do\n    subject = DuplicatedRequire.new\n\n    it \"passes if there are no duplicated requires\" do\n      expect_no_issues subject, <<-CRYSTAL\n        require \"math\"\n        require \"big\"\n        require \"big/big_decimal\"\n        CRYSTAL\n    end\n\n    it \"reports if there are a duplicated requires\" do\n      source = expect_issue subject, <<-CRYSTAL\n        require \"big\"\n        require \"math\"\n        require \"big\"\n        # ^^^^^^^^^^^ error: Duplicated require of `big`\n        CRYSTAL\n\n      expect_no_corrections source\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/else_nil_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe ElseNil do\n    subject = ElseNil.new\n\n    {% for keyword in %w[if unless].map(&.id) %}\n      it \"does not report if `else` block of an `{{ keyword }}` has a non-nil body\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {{ keyword }} foo\n            do_foo\n          else\n            do_bar\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there is an `else` block of an `{{ keyword }}` with a `nil` body\" do\n        source = expect_issue subject, <<-CRYSTAL\n          {{ keyword }} foo\n            do_foo\n          else\n            nil\n          # ^^^ error: Avoid `else` blocks with `nil` as their body\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          {{ keyword }} foo\n            do_foo\n          end\n          CRYSTAL\n      end\n    {% end %}\n\n    it \"does not report if there is an `else` block of an ternary `if` with a `nil` body\" do\n      expect_no_issues subject, <<-CRYSTAL\n        foo ? do_foo : nil\n        CRYSTAL\n    end\n\n    it \"does not report if `else` block of a `case` has a non-nil body\" do\n      expect_no_issues subject, <<-CRYSTAL\n        case foo\n        when :foo\n          do_foo\n        else\n          do_bar\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there is an `else` block of a `case` with a `nil` body\" do\n      source = expect_issue subject, <<-CRYSTAL\n        case foo\n        when :foo\n          do_foo\n        else\n          nil\n        # ^^^ error: Avoid `else` blocks with `nil` as their body\n        end\n        CRYSTAL\n\n      expect_no_corrections source\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/empty_ensure_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe EmptyEnsure do\n    subject = EmptyEnsure.new\n\n    it \"passes if there is no empty ensure blocks\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def some_method\n          do_some_stuff\n        ensure\n          do_something_else\n        end\n\n        begin\n          do_some_stuff\n        ensure\n          do_something_else\n        end\n\n        def method_with_rescue\n        rescue\n        ensure\n          nil\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is an empty ensure in method\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def method\n          do_some_stuff\n        ensure\n        # ^^^^ error: Empty `ensure` block detected\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def method\n          do_some_stuff\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is an empty ensure in a block\" do\n      source = expect_issue subject, <<-CRYSTAL\n        begin\n          do_some_stuff\n        rescue\n          do_some_other_stuff\n        ensure\n        # ^^^^ error: Empty `ensure` block detected\n          # nothing here\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        begin\n          do_some_stuff\n        rescue\n          do_some_other_stuff\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/empty_expression_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nprivate def it_detects_empty_expression(code, *, file = __FILE__, line = __LINE__)\n  it \"detects empty expression #{code.inspect}\", file, line do\n    source = Ameba::Source.new code\n    rule = Ameba::Rule::Lint::EmptyExpression.new\n    rule.catch(source).should_not be_valid, file: file, line: line\n  end\nend\n\nmodule Ameba::Rule::Lint\n  describe EmptyExpression do\n    subject = EmptyExpression.new\n\n    it \"passes if there is no empty expression\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method()\n        end\n\n        method()\n        method(1, 2, 3)\n        method(nil)\n\n        a = nil\n        a = \"\"\n        a = 0\n\n        nil\n        :any.nil?\n\n        begin \"\" end\n        [nil] << nil\n        CRYSTAL\n    end\n\n    it_detects_empty_expression %(())\n    it_detects_empty_expression %(((())))\n    it_detects_empty_expression %(a = ())\n    it_detects_empty_expression %((();()))\n    it_detects_empty_expression %(if (); end)\n\n    it_detects_empty_expression <<-CRYSTAL\n      if foo\n        1\n      elsif ()\n        2\n      end\n      CRYSTAL\n\n    it_detects_empty_expression <<-CRYSTAL\n      case foo\n      when :foo then ()\n      end\n      CRYSTAL\n\n    it_detects_empty_expression <<-CRYSTAL\n      case foo\n      when :foo then 1\n      else\n        ()\n      end\n      CRYSTAL\n\n    it_detects_empty_expression <<-CRYSTAL\n      case foo\n      when () then 1\n      end\n      CRYSTAL\n\n    it_detects_empty_expression <<-CRYSTAL\n      def method\n        a = 1\n        ()\n      end\n      CRYSTAL\n\n    it_detects_empty_expression <<-CRYSTAL\n      def method\n      rescue\n        ()\n      end\n      CRYSTAL\n\n    it_detects_empty_expression <<-CRYSTAL\n      def method\n        begin\n        end\n      end\n      CRYSTAL\n\n    it_detects_empty_expression <<-CRYSTAL\n      begin; end\n      CRYSTAL\n\n    it_detects_empty_expression <<-CRYSTAL\n      begin\n        ()\n      end\n      CRYSTAL\n\n    it \"does not report empty expression in macro\" do\n      expect_no_issues subject, <<-CRYSTAL\n        module MyModule\n          macro conditional_error_for_inline_callbacks\n            \\\\{% raise \"\" %}\n          end\n\n          macro before_save(x = nil)\n          end\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/empty_loop_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe EmptyLoop do\n    subject = EmptyLoop.new\n\n    it \"does not report if there are not empty loops\" do\n      expect_no_issues subject, <<-CRYSTAL\n        a = 1\n\n        while a < 10\n          a += 1\n        end\n\n        until a == 10\n         a += 1\n        end\n\n        loop do\n          a += 1\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there is an empty while loop\" do\n      expect_issue subject, <<-CRYSTAL\n        a = 1\n        while true\n        # ^^^^^^^^ error: Empty loop detected\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if while loop has non-literals in cond block\" do\n      expect_no_issues subject, <<-CRYSTAL\n        a = 1\n        while a = gets.to_s\n          # nothing here\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there is an empty until loop\" do\n      expect_issue subject, <<-CRYSTAL\n        do_something\n        until false\n        # ^^^^^^^^^ error: Empty loop detected\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if until loop has non-literals in cond block\" do\n      expect_no_issues subject, <<-CRYSTAL\n        until socket_open?\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there an empty loop\" do\n      expect_issue subject, <<-CRYSTAL\n        a = 1\n        loop do\n        # ^^^^^ error: Empty loop detected\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/enum_member_name_conflict_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe EnumMemberNameConflict do\n    subject = EnumMemberNameConflict.new\n\n    it \"passes if there are no enum members with duplicate names\" do\n      expect_no_issues subject, <<-CRYSTAL\n        enum Foo\n          Foo\n          Bar\n          Baz\n        end\n        CRYSTAL\n    end\n\n    it \"reports if enum members have their values assigned\" do\n      expect_issue subject, <<-CRYSTAL\n        enum Foo\n          Foo = 1\n          FOO = 2\n        # ^^^ error: Enum member name conflict detected\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there are a duplicate enum member names\" do\n      expect_issue subject, <<-CRYSTAL\n        enum Foo\n          Foo\n          FOo\n        # ^^^ error: Enum member name conflict detected\n          FoO\n        # ^^^ error: Enum member name conflict detected\n          FOO\n        # ^^^ error: Enum member name conflict detected\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/formatting_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe Formatting do\n    subject = Formatting.new\n\n    it \"passes if source is formatted\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method(a, b)\n          a + b\n        end\n\n        CRYSTAL\n    end\n\n    it \"reports if source is not formatted\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def method(a,b,c=0)\n        # ^{} error: Use built-in formatter to format this source\n          a+b+c\n        end\n\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def method(a, b, c = 0)\n          a + b + c\n        end\n\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      context \"#fail_on_error\" do\n        it \"passes on formatter errors by default\" do\n          rule = Formatting.new\n\n          expect_no_issues rule, <<-CRYSTAL\n            def method(a, b)\n              a + b\n            CRYSTAL\n        end\n\n        it \"reports on formatter errors when enabled\" do\n          rule = Formatting.new\n          rule.fail_on_error = true\n\n          expect_issue rule, <<-CRYSTAL\n            def method(a, b)\n              a + b\n                 # ^ error: Error while formatting: expecting identifier 'end', not 'EOF'\n            CRYSTAL\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/hash_duplicated_key_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe HashDuplicatedKey do\n    subject = HashDuplicatedKey.new\n\n    it \"passes if there is no duplicated keys in a hash literals\" do\n      expect_no_issues subject, <<-CRYSTAL\n        h = {\"a\" => 1, :a => 2, \"b\" => 3}\n        h = {\"a\" => 1, \"b\" => 2, \"c\" => {\"a\" => 3, \"b\" => 4}}\n        h = {} of String => String\n        CRYSTAL\n    end\n\n    it \"fails if there is a duplicated key in a hash literal\" do\n      expect_issue subject, <<-CRYSTAL\n        h = {\"a\" => 1, \"b\" => 2, \"a\" => 3}\n          # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Duplicated keys in hash literal: `\"a\"`\n        CRYSTAL\n    end\n\n    it \"fails if there is a duplicated key in the inner hash literal\" do\n      expect_issue subject, <<-CRYSTAL\n        h = {\"a\" => 1, \"b\" => {\"a\" => 3, \"b\" => 4, \"a\" => 5}}\n                            # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Duplicated keys in hash literal: `\"a\"`\n        CRYSTAL\n    end\n\n    it \"reports multiple duplicated keys\" do\n      expect_issue subject, <<-CRYSTAL\n        h = {\"key1\" => 1, \"key1\" => 2, \"key2\" => 3, \"key2\" => 4}\n          # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Duplicated keys in hash literal: `\"key1\"`, `\"key2\"`\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/literal_assignments_in_expressions_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nLITERAL_SAMPLES = {\n  nil, true, 42, 4.2, 'c', \"foo\", :foo, /foo/,\n  0..42, [1, 2, 3], {1, 2, 3},\n  {foo: :bar}, {:foo => :bar},\n}\n\nmodule Ameba::Rule::Lint\n  describe LiteralAssignmentsInExpressions do\n    subject = LiteralAssignmentsInExpressions.new\n\n    it \"passes if the assignment value is not a literal\" do\n      expect_no_issues subject, <<-CRYSTAL\n        if a = b\n          :ok\n        end\n\n        unless a = b.presence\n          :ok\n        end\n\n        :ok if a = b\n        :ok unless a = b\n\n        case {a, b}\n        when {0, 1} then :gt\n        when {1, 0} then :lt\n        end\n        CRYSTAL\n    end\n\n    {% for literal in LITERAL_SAMPLES %}\n      it %(reports if the assignment value is a {{ literal }} literal) do\n        expect_issue subject, <<-CRYSTAL, literal: {{ literal.stringify }}\n          raise \"boo!\" if foo = {{ literal }}\n                        # ^{literal}^^^^^^ error: Detected assignment with a literal value in control expression\n          CRYSTAL\n\n        expect_issue subject, <<-CRYSTAL, literal: {{ literal.stringify }}\n          raise \"boo!\" unless foo = {{ literal }}\n                            # ^{literal}^^^^^^ error: Detected assignment with a literal value in control expression\n          CRYSTAL\n      end\n    {% end %}\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/literal_in_condition_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe LiteralInCondition do\n    subject = LiteralInCondition.new\n\n    it \"passes if there is not literals in conditional\" do\n      expect_no_issues subject, <<-CRYSTAL\n        if a == 2\n          :ok\n        end\n\n        :ok unless b\n\n        case string\n        when \"a\"\n          :ok\n        when \"b\"\n          :ok\n        end\n\n        unless a.nil?\n          :ok\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a predicate with non-literals\" do\n      expect_issue subject, <<-CRYSTAL\n        :ok if     [foo, bar]\n                 # ^^^^^^^^^^ error: Literal value found in conditional\n        :ok unless [foo, bar]\n                 # ^^^^^^^^^^ error: Literal value found in conditional\n\n        while [foo, bar]\n            # ^^^^^^^^^^ error: Literal value found in conditional\n          :ok\n        end\n\n        until [foo, bar]\n            # ^^^^^^^^^^ error: Literal value found in conditional\n          :ok\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a predicate in `if` conditional\" do\n      expect_issue subject, <<-CRYSTAL\n        if \"string\"\n         # ^^^^^^^^ error: Literal value found in conditional\n          :ok\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a predicate in `unless` conditional\" do\n      expect_issue subject, <<-CRYSTAL\n        unless true\n             # ^^^^ error: Literal value found in conditional\n          :ok\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a predicate in `while` conditional\" do\n      expect_issue subject, <<-CRYSTAL\n        while 1\n            # ^ error: Literal value found in conditional\n          :ok\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a `false` predicate in `while` conditional\" do\n      expect_issue subject, <<-CRYSTAL\n        while false\n            # ^^^^^ error: Literal value found in conditional\n          :ok\n        end\n        CRYSTAL\n    end\n\n    it \"passes if there is a `true` predicate in `while` conditional\" do\n      expect_no_issues subject, <<-CRYSTAL\n        while true\n          :ok\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a predicate in `until` conditional\" do\n      expect_issue subject, <<-CRYSTAL\n        until true\n            # ^^^^ error: Literal value found in conditional\n          :foo\n        end\n        CRYSTAL\n    end\n\n    describe \"range\" do\n      it \"reports range with literals\" do\n        expect_issue subject, <<-CRYSTAL\n          case 1..2\n             # ^^^^ error: Literal value found in conditional\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report range with non-literals\" do\n        expect_no_issues subject, <<-CRYSTAL\n          case (1..a)\n          end\n          CRYSTAL\n      end\n    end\n\n    describe \"array\" do\n      it \"reports array with literals\" do\n        expect_issue subject, <<-CRYSTAL\n          case [1, 2, 3]\n             # ^^^^^^^^^ error: Literal value found in conditional\n          when :array\n            :ok\n          when :not_array\n            :also_ok\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report array with non-literals\" do\n        expect_no_issues subject, <<-CRYSTAL\n          a, b = 1, 2\n          case [1, 2, a]\n          when :array\n            :ok\n          when :not_array\n            :also_ok\n          end\n          CRYSTAL\n      end\n    end\n\n    describe \"hash\" do\n      it \"reports hash with literals\" do\n        expect_issue subject, <<-CRYSTAL\n          case { \"name\" => 1, 33 => 'b' }\n             # ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Literal value found in conditional\n          when :hash\n            :ok\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report hash with non-literals in keys\" do\n        expect_no_issues subject, <<-CRYSTAL\n          case { a => 1, 33 => 'b' }\n          when :hash\n            :ok\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report hash with non-literals in values\" do\n        expect_no_issues subject, <<-CRYSTAL\n          case { \"name\" => a, 33 => 'b' }\n          when :hash\n            :ok\n          end\n          CRYSTAL\n      end\n    end\n\n    describe \"tuple\" do\n      it \"reports tuple with literals\" do\n        expect_issue subject, <<-CRYSTAL\n          case {1, false}\n             # ^^^^^^^^^^ error: Literal value found in conditional\n          when {1, _}\n            :ok\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report tuple with non-literals\" do\n        expect_no_issues subject, <<-CRYSTAL\n          a, b = 1, 2\n          case {1, b}\n          when {1, 2}\n            :ok\n          end\n          CRYSTAL\n      end\n    end\n\n    describe \"named tuple\" do\n      it \"reports named tuple with literals\" do\n        expect_issue subject, <<-CRYSTAL\n          case { name: 1, foo: :bar}\n             # ^^^^^^^^^^^^^^^^^^^^^ error: Literal value found in conditional\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report named tuple with non-literals\" do\n        expect_no_issues subject, <<-CRYSTAL\n          case { name: a, foo: :bar}\n          end\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/literal_in_interpolation_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe LiteralInInterpolation do\n    subject = LiteralInInterpolation.new\n\n    it \"passes with good interpolation examples\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        \"Hello, #{name}\"\n        \"#{name}\"\n        \"Name size: #{name.size}\"\n        \"#{foo..}\"\n        CRYSTAL\n    end\n\n    it \"fails if there is useless interpolation\" do\n      [\n        %q(\"#{:Ary}\"),\n        %q(\"#{[1, 2, 3]}\"),\n        %q(\"#{true}\"),\n        %q(\"#{false}\"),\n        %q(\"here are #{4} cats\"),\n      ].each do |str|\n        subject.catch(Source.new str).should_not be_valid\n      end\n    end\n\n    it \"works with magic constants (#593)\" do\n      expect_no_issues subject, <<-'CRYSTAL', \"/home/foo/source.cr\"\n        \"Hello from #{__FILE__} at line #{__LINE__} in #{__DIR__}\"\n        CRYSTAL\n    end\n\n    it \"reports if there is a literal in interpolation\" do\n      expect_issue subject, <<-'CRYSTAL'\n        \"Hello, #{:world} from #{:ameba}\"\n                # ^^^^^^ error: Literal value found in interpolation\n                               # ^^^^^^ error: Literal value found in interpolation\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/literals_comparison_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe LiteralsComparison do\n    subject = LiteralsComparison.new\n\n    it \"passes for valid cases\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        \"foo\" == foo\n        \"foo\" != foo\n        \"foo\" == FOO\n        FOO == \"foo\"\n        foo == \"foo\"\n        foo != \"foo\"\n\n        {start.year, start.month} == {stop.year, stop.month}\n        /foo/ =~ \"foo#{bar}\"\n        /foo/ !~ \"foo#{bar}\"\n        [\"foo\"] === [bar]\n        [foo] === [\"bar\"]\n        [foo] === [bar]\n        [foo] == [bar]\n        [foo] == [foo]\n        CRYSTAL\n    end\n\n    it \"reports if there is a static comparison evaluating to the same\" do\n      expect_issue subject, <<-CRYSTAL\n        \"foo\" === \"bar\"\n        # ^^^^^^^^^^^^^ error: Comparison always evaluates to the same\n        /foo/ =~ \"bar\"\n        # ^^^^^^^^^^^^ error: Comparison always evaluates to the same\n        \"foo\" <=> \"bar\"\n        # ^^^^^^^^^^^^^ error: Comparison always evaluates to the same\n        CRYSTAL\n    end\n\n    it \"reports if there is a static comparison evaluating to true\" do\n      expect_issue subject, <<-CRYSTAL\n        \"foo\" == \"foo\"\n        # ^^^^^^^^^^^^ error: Comparison always evaluates to `true`\n        \"foo\" != \"bar\"\n        # ^^^^^^^^^^^^ error: Comparison always evaluates to `true`\n        CRYSTAL\n    end\n\n    it \"reports if there is a static comparison evaluating to false\" do\n      expect_issue subject, <<-CRYSTAL\n        \"foo\" == \"bar\"\n        # ^^^^^^^^^^^^ error: Comparison always evaluates to `false`\n        \"foo\" != \"foo\"\n        # ^^^^^^^^^^^^ error: Comparison always evaluates to `false`\n        CRYSTAL\n    end\n\n    context \"macro\" do\n      it \"reports in macro scope\" do\n        expect_issue subject, <<-CRYSTAL\n          {{ \"foo\" == \"bar\" }}\n           # ^^^^^^^^^^^^^^ error: Comparison always evaluates to `false`\n          CRYSTAL\n      end\n\n      it \"passes for valid cases\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {{ \"foo\" == foo }}\n          {{ \"foo\" != foo }}\n          {% foo == \"foo\" %}\n          {% foo != \"foo\" %}\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/missing_block_argument_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe MissingBlockArgument do\n    subject = MissingBlockArgument.new\n\n    it \"passes if the block argument is defined\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(&)\n          yield 42\n        end\n\n        def bar(&block)\n          yield 24\n        end\n\n        def baz(a, b, c, &block)\n          yield a, b, c\n        end\n        CRYSTAL\n    end\n\n    it \"reports if the block argument is missing\" do\n      expect_issue subject, <<-CRYSTAL\n        def foo\n          # ^^^ error: Missing anonymous block argument. Use `&` as an argument name to indicate yielding method.\n          yield 42\n        end\n\n        def bar\n          # ^^^ error: Missing anonymous block argument. Use `&` as an argument name to indicate yielding method.\n          yield 24\n        end\n\n        def baz(a, b, c)\n          # ^^^ error: Missing anonymous block argument. Use `&` as an argument name to indicate yielding method.\n          yield a, b, c\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/non_existent_rule_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe NonExistentRule do\n    subject = NonExistentRule.new\n\n    it \"does not report if the rule name is correct\" do\n      expect_no_issues subject, <<-CRYSTAL\n        # ameba:disable Lint/NonExistentRule\n        CRYSTAL\n    end\n\n    it \"reports if there are incorrect rule names\" do\n      expect_issue subject, <<-CRYSTAL\n        # ameba:disable BadRule1, BadRule2\n                      # ^^^^^^^^^^^^^^^^^^ error: Such rules do not exist: `BadRule1`, `BadRule2`\n        CRYSTAL\n    end\n\n    it \"does not report if there no action and rules at all\" do\n      expect_no_issues subject, <<-CRYSTAL\n        # ameba:\n        CRYSTAL\n    end\n\n    it \"does not report if there are no rules\" do\n      expect_no_issues subject, <<-CRYSTAL\n        # ameba:enable\n        # ameba:disable\n        CRYSTAL\n    end\n\n    it \"does not report if there are group names in the directive\" do\n      expect_no_issues subject, <<-CRYSTAL\n        # ameba:disable Style Performance\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/not_nil_after_no_bang_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe NotNilAfterNoBang do\n    subject = NotNilAfterNoBang.new\n\n    it \"passes for valid cases\" do\n      expect_no_issues subject, <<-CRYSTAL\n        (1..3).index(1).not_nil!(:foo)\n        (1..3).rindex(1).not_nil!(:foo)\n        (1..3).index { |i| i > 2 }.not_nil!(:foo)\n        (1..3).rindex { |i| i > 2 }.not_nil!(:foo)\n        (1..3).find { |i| i > 2 }.not_nil!(:foo)\n        /(.)(.)(.)/.match(\"abc\", &.itself).not_nil!\n        /(.)(.)(.)/.match(\"abc\", &foo).not_nil!\n        CRYSTAL\n    end\n\n    it \"reports if there is an `index` call followed by `not_nil!`\" do\n      source = expect_issue subject, <<-CRYSTAL\n        (1..3).index(1).not_nil!\n             # ^^^^^^^^^^^^^^^^^ error: Use `index! {...}` instead of `index {...}.not_nil!`\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        (1..3).index!(1)\n        CRYSTAL\n    end\n\n    it \"reports if there is an `rindex` call followed by `not_nil!`\" do\n      source = expect_issue subject, <<-CRYSTAL\n        (1..3).rindex(1).not_nil!\n             # ^^^^^^^^^^^^^^^^^^ error: Use `rindex! {...}` instead of `rindex {...}.not_nil!`\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        (1..3).rindex!(1)\n        CRYSTAL\n    end\n\n    it \"reports if there is an `match` call followed by `not_nil!`\" do\n      source = expect_issue subject, <<-CRYSTAL\n        /(.)(.)(.)/.match(\"abc\").not_nil![2]\n                  # ^^^^^^^^^^^^^^^^^^^^^ error: Use `match! {...}` instead of `match {...}.not_nil!`\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        /(.)(.)(.)/.match!(\"abc\")[2]\n        CRYSTAL\n    end\n\n    it \"reports if there is an `index` call with block followed by `not_nil!`\" do\n      source = expect_issue subject, <<-CRYSTAL\n        (1..3).index { |i| i > 2 }.not_nil!\n             # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `index! {...}` instead of `index {...}.not_nil!`\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        (1..3).index! { |i| i > 2 }\n        CRYSTAL\n    end\n\n    it \"reports if there is an `rindex` call with block followed by `not_nil!`\" do\n      source = expect_issue subject, <<-CRYSTAL\n        (1..3).rindex { |i| i > 2 }.not_nil!\n             # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `rindex! {...}` instead of `rindex {...}.not_nil!`\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        (1..3).rindex! { |i| i > 2 }\n        CRYSTAL\n    end\n\n    it \"reports if there is a `find` call with block followed by `not_nil!`\" do\n      source = expect_issue subject, <<-CRYSTAL\n        (1..3).find { |i| i > 2 }.not_nil!\n             # ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `find! {...}` instead of `find {...}.not_nil!`\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        (1..3).find! { |i| i > 2 }\n        CRYSTAL\n    end\n\n    it \"passes if there is a `find` call without block followed by `not_nil!`\" do\n      expect_no_issues subject, <<-CRYSTAL\n        (1..3).find(1).not_nil!\n        CRYSTAL\n    end\n\n    context \"macro\" do\n      it \"doesn't report in macro scope\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {{ [1, 2, 3].index(1).not_nil! }}\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/not_nil_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe NotNil do\n    subject = NotNil.new\n\n    it \"passes for valid cases\" do\n      expect_no_issues subject, <<-CRYSTAL\n        (1..3).first?.not_nil!(:foo)\n        not_nil!\n        CRYSTAL\n    end\n\n    it \"reports if there is a `not_nil!` call\" do\n      expect_issue subject, <<-CRYSTAL\n        (1..3).first?.not_nil!\n                    # ^^^^^^^^ error: Avoid using `not_nil!`\n        CRYSTAL\n    end\n\n    it \"reports if there is a `not_nil!` call in the middle of the call-chain\" do\n      expect_issue subject, <<-CRYSTAL\n        (1..3).first?.not_nil!.to_s\n                    # ^^^^^^^^ error: Avoid using `not_nil!`\n        CRYSTAL\n    end\n\n    context \"macro\" do\n      it \"doesn't report in macro scope\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {{ [1, 2, 3].first.not_nil! }}\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/percent_arrays_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe PercentArrays do\n    subject = PercentArrays.new\n\n    it \"passes if percent arrays are written correctly\" do\n      expect_no_issues subject, <<-CRYSTAL\n        %w[one two three]\n        %w[1 2 3]\n        %w[]\n\n        %i[one two three]\n        %i[1 2 3]\n        %i[]\n        CRYSTAL\n    end\n\n    it \"fails if string percent array has commas\" do\n      expect_issue subject, <<-CRYSTAL\n        puts %w[one, two]\n           # ^^ error: Symbols `,\"` may be unwanted in `%w` array literals\n        CRYSTAL\n    end\n\n    it \"fails if string percent array has quotes\" do\n      expect_issue subject, <<-CRYSTAL\n        puts %w[\"one\" \"two\"]\n           # ^^ error: Symbols `,\"` may be unwanted in `%w` array literals\n        CRYSTAL\n    end\n\n    it \"fails if symbols percent array has commas\" do\n      expect_issue subject, <<-CRYSTAL\n        puts %i[one, two]\n           # ^^ error: Symbols `,:` may be unwanted in `%i` array literals\n        CRYSTAL\n    end\n\n    it \"fails if symbols percent array has a colon\" do\n      expect_issue subject, <<-CRYSTAL\n        puts %i[:one :two]\n           # ^^ error: Symbols `,:` may be unwanted in `%i` array literals\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      it \"#string_array_unwanted_symbols\" do\n        rule = PercentArrays.new\n        rule.string_array_unwanted_symbols = \",\"\n\n        expect_no_issues rule, %(%w[one])\n      end\n\n      it \"#symbol_array_unwanted_symbols\" do\n        rule = PercentArrays.new\n        rule.symbol_array_unwanted_symbols = \",\"\n\n        expect_no_issues rule, %(%i[:one])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/rand_zero_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe RandZero do\n    subject = RandZero.new\n\n    it \"passes if it is not rand(1) or rand(0)\" do\n      expect_no_issues subject, <<-CRYSTAL\n        rand(1.0)\n        rand(0.11)\n        rand(2)\n        CRYSTAL\n    end\n\n    it \"fails if it is rand(0)\" do\n      expect_issue subject, <<-CRYSTAL\n        rand(0)\n        # ^^^^^ error: `rand(0)` always returns `0`\n        CRYSTAL\n    end\n\n    it \"fails if it is rand(1)\" do\n      expect_issue subject, <<-CRYSTAL\n        rand(1)\n        # ^^^^^ error: `rand(1)` always returns `0`\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/redundant_string_cercion_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe RedundantStringCoercion do\n    subject = RedundantStringCoercion.new\n\n    it \"does not report if there is no redundant string coercion\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        \"Hello, #{name}\"\n        CRYSTAL\n    end\n\n    it \"does not report if coercion is used in binary op\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        \"Hello, #{3.to_s + 's'}\"\n        CRYSTAL\n    end\n\n    {% for v in %w[name :symbol 42 false 't'] %}\n      it \"reports if there is a redundant string coercion ({{ v.id }})\" do\n        expect_issue subject, <<-'CRYSTAL', v: {{ v }}\n          \"Hello, #{%{v}.to_s}\"\n                  _{v} # ^^^^ error: Redundant use of `Object#to_s` in interpolation\n          CRYSTAL\n      end\n    {% end %}\n\n    it \"reports redundant coercion in regex\" do\n      expect_issue subject, <<-'CRYSTAL'\n        /\\w #{name.to_s}/\n                 # ^^^^ error: Redundant use of `Object#to_s` in interpolation\n        CRYSTAL\n    end\n\n    it \"doesn't report if Object#to_s is called with arguments\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        /\\w #{name.to_s(io)}/\n        CRYSTAL\n    end\n\n    it \"doesn't report if Object#to_s is called without receiver\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        /\\w #{to_s}/\n        CRYSTAL\n    end\n\n    it \"doesn't report if Object#to_s is called with named args\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        \"0x#{250.to_s(base: 16)}\"\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/redundant_with_index_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe RedundantWithIndex do\n    subject = RedundantWithIndex.new\n\n    context \"with_index\" do\n      it \"does not report if there is index argument\" do\n        expect_no_issues subject, <<-CRYSTAL\n          collection.each.with_index do |e, i|\n            e += i\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there is no index argument\" do\n        expect_issue subject, <<-CRYSTAL\n          collection.each.with_index do |e|\n                        # ^^^^^^^^^^ error: Remove redundant `with_index`\n            e += 1\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there is an underscored index argument\" do\n        expect_issue subject, <<-CRYSTAL\n          collection.each.with_index do |e, _|\n                        # ^^^^^^^^^^ error: Remove redundant `with_index`\n            e += 1\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there is no args\" do\n        expect_issue subject, <<-CRYSTAL\n          collection.each.with_index do\n                        # ^^^^^^^^^^ error: Remove redundant `with_index`\n            puts :nothing\n          end\n          CRYSTAL\n      end\n\n      it \"does not report if there is no block\" do\n        expect_no_issues subject, <<-CRYSTAL\n          collection.each.with_index\n          CRYSTAL\n      end\n\n      it \"does not report if first argument is underscored\" do\n        expect_no_issues subject, <<-CRYSTAL\n          collection.each.with_index do |_, i|\n            puts i\n          end\n          CRYSTAL\n      end\n\n      it \"does not report if there are more than 2 args\" do\n        expect_no_issues subject, <<-CRYSTAL\n          tup.each.with_index do |key, value, index|\n            puts i\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"each_with_index\" do\n      it \"does not report if there is index argument\" do\n        expect_no_issues subject, <<-CRYSTAL\n          collection.each_with_index do |e, i|\n            e += i\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there is not index argument\" do\n        expect_issue subject, <<-CRYSTAL\n          collection.each_with_index do |e|\n                   # ^^^^^^^^^^^^^^^ error: Use `each` instead of `each_with_index`\n            e += 1\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there is underscored index argument\" do\n        expect_issue subject, <<-CRYSTAL\n          collection.each_with_index do |e, _|\n                   # ^^^^^^^^^^^^^^^ error: Use `each` instead of `each_with_index`\n            e += 1\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there is no args\" do\n        expect_issue subject, <<-CRYSTAL\n          collection.each_with_index do\n                   # ^^^^^^^^^^^^^^^ error: Use `each` instead of `each_with_index`\n            puts :nothing\n          end\n          CRYSTAL\n      end\n\n      it \"does not report if there is no block\" do\n        expect_no_issues subject, <<-CRYSTAL\n          collection.each_with_index(1)\n          CRYSTAL\n      end\n\n      it \"does not report if first argument is underscored\" do\n        expect_no_issues subject, <<-CRYSTAL\n          collection.each_with_index do |_, i|\n            puts i\n          end\n          CRYSTAL\n      end\n\n      it \"does not report if there are more than 2 args\" do\n        expect_no_issues subject, <<-CRYSTAL\n          tup.each_with_index do |key, value, index|\n            puts i\n          end\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/redundant_with_object_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe RedundantWithObject do\n    subject = RedundantWithObject.new\n\n    it \"does not report if there is index argument\" do\n      expect_no_issues subject, <<-CRYSTAL\n        collection.each_with_object(0) do |e, obj|\n          obj += i\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there is not index argument\" do\n      expect_issue subject, <<-CRYSTAL\n        collection.each_with_object(0) do |e|\n                 # ^^^^^^^^^^^^^^^^ error: Use `each` instead of `each_with_object`\n          e += 1\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there is underscored index argument\" do\n      expect_issue subject, <<-CRYSTAL\n        collection.each_with_object(0) do |e, _|\n                 # ^^^^^^^^^^^^^^^^ error: Use `each` instead of `each_with_object`\n          e += 1\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there is no args\" do\n      expect_issue subject, <<-CRYSTAL\n        collection.each_with_object(0) do\n                 # ^^^^^^^^^^^^^^^^ error: Use `each` instead of `each_with_object`\n          puts :nothing\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if there is no block\" do\n      expect_no_issues subject, <<-CRYSTAL\n        collection.each_with_object(0)\n        CRYSTAL\n    end\n\n    it \"does not report if first argument is underscored\" do\n      expect_no_issues subject, <<-CRYSTAL\n        collection.each_with_object(0) do |_, obj|\n          puts i\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if there are more than 2 args\" do\n      expect_no_issues subject, <<-CRYSTAL\n        tup.each_with_object(0) do |key, value, obj|\n          puts i\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/require_parentheses_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe RequireParentheses do\n    subject = RequireParentheses.new\n\n    it \"passes if logical operator in call args has parentheses\" do\n      expect_no_issues subject, <<-CRYSTAL\n        foo.includes?(\"bar\") || foo.includes?(\"baz\")\n        foo.includes?(\"bar\" || foo.includes? \"baz\")\n        CRYSTAL\n    end\n\n    it \"passes if logical operator in call doesn't involve another method call\" do\n      expect_no_issues subject, <<-CRYSTAL\n        foo.includes? \"bar\" || \"baz\"\n        CRYSTAL\n    end\n\n    it \"passes if logical operator in call involves another method call with no arguments\" do\n      expect_no_issues subject, <<-CRYSTAL\n        foo.includes? \"bar\" || foo.not_nil!\n        CRYSTAL\n    end\n\n    it \"passes if logical operator is used in an assignment call\" do\n      expect_no_issues subject, <<-CRYSTAL\n        foo.bar = \"baz\" || bat.call :foo\n        foo.bar ||= \"baz\" || bat.call :foo\n        foo[bar] = \"baz\" || bat.call :foo\n        CRYSTAL\n    end\n\n    it \"passes if logical operator is used in a square bracket call\" do\n      expect_no_issues subject, <<-CRYSTAL\n        foo[\"bar\" || baz.call :bat]\n        foo[\"bar\" || baz.call :bat]?\n        CRYSTAL\n    end\n\n    it \"fails if logical operator in call args doesn't have parentheses\" do\n      expect_issue subject, <<-CRYSTAL\n        foo.includes? \"bar\" || foo.includes? \"baz\"\n        # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use parentheses in the method call to avoid confusion about precedence\n\n        foo.in? \"bar\", \"baz\" || foo.ends_with? \"bat\"\n        # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use parentheses in the method call to avoid confusion about precedence\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/self_initialize_definition_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe SelfInitializeDefinition do\n    subject = SelfInitializeDefinition.new\n\n    {% for keyword in %w[module enum].map(&.id) %}\n      context \"{{ keyword }}\" do\n        it \"passes for `initialize` method definition with a `self` receiver\" do\n          expect_no_issues subject, <<-CRYSTAL\n            {{ keyword }} Foo\n              def self.initialize\n              end\n            end\n            CRYSTAL\n        end\n      end\n    {% end %}\n\n    {% for keyword in %w[struct class].map(&.id) %}\n      context \"{{ keyword }}\" do\n        it \"passes for `initialize` method definition without a receiver\" do\n          expect_no_issues subject, <<-CRYSTAL\n            {{ keyword }} Foo\n              def initialize\n              end\n            end\n            CRYSTAL\n        end\n\n        it \"passes for `initialize` method definition with an explicit receiver\" do\n          expect_no_issues subject, <<-CRYSTAL\n            {{ keyword }} Foo\n            end\n\n            def Foo.initialize\n            end\n            CRYSTAL\n        end\n\n        it \"fails for `initialize` method definition with a `self` receiver\" do\n          source = expect_issue subject, <<-CRYSTAL\n            {{ keyword }} Foo\n              def self.initialize\n            # ^^^^^^^^^^^^^^^^^^^ error: `initialize` method definition should not have a receiver\n              end\n            end\n            CRYSTAL\n\n          expect_correction source, <<-CRYSTAL\n            {{ keyword }} Foo\n              def initialize\n              end\n            end\n            CRYSTAL\n        end\n      end\n    {% end %}\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/shadowed_argument_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe ShadowedArgument do\n    subject = ShadowedArgument.new\n\n    it \"doesn't report if there is not a shadowed argument\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(bar)\n          baz = 1\n        end\n\n        3.times do |i|\n          a = 1\n        end\n\n        proc = -> (a : Int32) {\n          b = 2\n        }\n        CRYSTAL\n    end\n\n    it \"reports if there is a shadowed method argument\" do\n      expect_issue subject, <<-CRYSTAL\n        def foo(bar)\n          bar = 1\n        # ^^^^^^^ error: Argument `bar` is assigned before it is used\n          bar\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there is a shadowed block argument\" do\n      expect_issue subject, <<-CRYSTAL\n        3.times do |i|\n          i = 2\n        # ^^^^^ error: Argument `i` is assigned before it is used\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there is a shadowed proc argument\" do\n      expect_issue subject, <<-CRYSTAL\n        -> (x : Int32) {\n          x = 20\n        # ^^^^^^ error: Argument `x` is assigned before it is used\n          x\n        }\n        CRYSTAL\n    end\n\n    it \"doesn't report if the argument is referenced before the assignment\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(bar)\n          bar\n          bar = 1\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if the argument is conditionally reassigned\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(bar = nil)\n          bar ||= true\n          bar\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if the op assign is followed by another assignment\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(bar)\n          bar ||= 3\n          bar = 43\n          bar\n        end\n        CRYSTAL\n    end\n\n    it \"reports if the shadowing assignment is followed by op assign\" do\n      expect_issue subject, <<-CRYSTAL\n        def foo(bar)\n          bar = 42\n        # ^^^^^^^^ error: Argument `bar` is assigned before it is used\n          bar ||= 43\n          bar\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if the argument is unused\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(bar)\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if the argument is reassigned from super result\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(bar)\n          bar = super\n          bar\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if the argument is reassigned from previous_def result\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(bar)\n          bar = previous_def\n          bar\n        end\n        CRYSTAL\n    end\n\n    it \"reports if the argument is shadowed before super\" do\n      expect_issue subject, <<-CRYSTAL\n        def foo(bar)\n          bar = 1\n        # ^^^^^^^ error: Argument `bar` is assigned before it is used\n          super\n        end\n        CRYSTAL\n    end\n\n    context \"branch\" do\n      it \"doesn't report if the argument is not shadowed in a condition\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo(bar, baz)\n            bar = 1 if baz\n            bar\n          end\n          CRYSTAL\n      end\n\n      it \"reports if the argument is shadowed after the condition\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo(foo)\n            if something\n              foo = 42\n            end\n            foo = 43\n          # ^^^^^^^^ error: Argument `foo` is assigned before it is used\n            foo\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if the argument is conditionally assigned in a branch\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo(bar)\n            if something\n              bar ||= 22\n            end\n            bar\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"inner scopes\" do\n      it \"doesn't report if the argument is used in an inner block\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo(catch_all = false)\n            items.each do |item|\n              if catch_all\n                do_something\n              end\n              catch_all = true\n            end\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if the argument is captured by a block\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo(token)\n            loop do\n              token = next_token(token.state)\n              process(token)\n              break if token.eof?\n            end\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if the argument is referenced in an inner scope\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo(x)\n            x = 1\n            3.times { puts x }\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if the argument is used in a macro\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo(bar)\n            bar = 1\n            {% if flag?(:release) %}\n              use(bar)\n            {% end %}\n          end\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/shadowed_exception_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe ShadowedException do\n    subject = ShadowedException.new\n\n    it \"passes if there isn't shadowed exception\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method\n          do_something\n        rescue ArgumentError\n          handle_argument_error_exception\n        rescue Exception\n          handle_exception\n        end\n\n        def method\n        rescue Exception\n          handle_exception\n        end\n\n        def method\n        rescue ex : ArgumentError\n          handle_argument_error_exception\n        rescue ex : Exception\n          handle_exception\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a shadowed exception\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n          do_something\n        rescue Exception\n          handle_exception\n        rescue ArgumentError\n             # ^^^^^^^^^^^^^ error: Shadowed exception found: `ArgumentError`\n          handle_argument_error_exception\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a custom shadowed exceptions\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n          1\n        rescue Exception\n          2\n        rescue MySuperException\n             # ^^^^^^^^^^^^^^^^ error: Shadowed exception found: `MySuperException`\n          3\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a shadowed exception in a type list\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n        rescue Exception | IndexError\n                         # ^^^^^^^^^^ error: Shadowed exception found: `IndexError`\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a first shadowed exception in a type list\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n        rescue IndexError | Exception\n             # ^^^^^^^^^^ error: Shadowed exception found: `IndexError`\n        rescue Exception\n             # ^^^^^^^^^ error: Shadowed exception found: `Exception`\n        rescue\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a shadowed duplicated exception\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n        rescue IndexError\n        rescue ArgumentError\n        rescue IndexError\n             # ^^^^^^^^^^ error: Shadowed exception found: `IndexError`\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a shadowed duplicated exception in a type list\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n        rescue IndexError\n        rescue ArgumentError | IndexError\n                             # ^^^^^^^^^^ error: Shadowed exception found: `IndexError`\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is only shadowed duplicated exceptions\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n        rescue IndexError\n        rescue IndexError\n             # ^^^^^^^^^^ error: Shadowed exception found: `IndexError`\n        rescue Exception\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is only shadowed duplicated exceptions in a type list\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n        rescue IndexError | IndexError\n                          # ^^^^^^^^^^ error: Shadowed exception found: `IndexError`\n        end\n        CRYSTAL\n    end\n\n    it \"fails if all rescues are shadowed and there is a catch-all rescue\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n        rescue Exception\n        rescue ArgumentError\n             # ^^^^^^^^^^^^^ error: Shadowed exception found: `ArgumentError`\n        rescue IndexError\n             # ^^^^^^^^^^ error: Shadowed exception found: `IndexError`\n        rescue KeyError | IO::Error\n                        # ^^^^^^^^^ error: Shadowed exception found: `IO::Error`\n             # ^^^^^^^^ error: Shadowed exception found: `KeyError`\n        rescue\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there are shadowed exception with args\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n        rescue Exception\n        rescue ex : IndexError\n                  # ^^^^^^^^^^ error: Shadowed exception found: `IndexError`\n        rescue\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there are multiple shadowed exceptions\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n        rescue Exception\n        rescue ArgumentError\n             # ^^^^^^^^^^^^^ error: Shadowed exception found: `ArgumentError`\n        rescue IndexError\n             # ^^^^^^^^^^ error: Shadowed exception found: `IndexError`\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there are multiple shadowed exceptions in a type list\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n        rescue Exception\n        rescue ArgumentError | IndexError\n                             # ^^^^^^^^^^ error: Shadowed exception found: `IndexError`\n             # ^^^^^^^^^^^^^ error: Shadowed exception found: `ArgumentError`\n        rescue IO::Error\n             # ^^^^^^^^^ error: Shadowed exception found: `IO::Error`\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there are multiple shadowed exceptions in a single rescue\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n          do_something\n        rescue Exception | IndexError | ArgumentError\n                                      # ^^^^^^^^^^^^^ error: Shadowed exception found: `ArgumentError`\n                         # ^^^^^^^^^^ error: Shadowed exception found: `IndexError`\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/shadowing_outer_local_var_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe ShadowingOuterLocalVar do\n    subject = ShadowingOuterLocalVar.new\n\n    it \"doesn't report if there is no shadowing\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def some_method\n          foo = 1\n\n          3.times do |bar|\n            bar\n          end\n\n          -> (baz : Int32) { }\n          -> (bar : String) { }\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there is a shadowing in a block\" do\n      expect_issue subject, <<-CRYSTAL\n        def some_method\n          foo = 1\n\n          3.times do |foo|\n                    # ^^^ error: Shadowing outer local variable `foo`\n          end\n        end\n        CRYSTAL\n    end\n\n    pending \"reports if there is a shadowing in an unpacked variable in a block\" do\n      expect_issue subject, <<-CRYSTAL\n        def some_method\n          foo = 1\n\n          [{3}].each do |(foo)|\n                        # ^^^ error: Shadowing outer local variable `foo`\n          end\n        end\n        CRYSTAL\n    end\n\n    pending \"reports if there is a shadowing in an unpacked variable in a block (2)\" do\n      expect_issue subject, <<-CRYSTAL\n        def some_method\n          foo = 1\n\n          [{[3]}].each do |((foo))|\n                           # ^^^ error: Shadowing outer local variable `foo`\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report outer vars declared below shadowed block\" do\n      expect_no_issues subject, <<-CRYSTAL\n        methods = klass.methods.select { |m| m.annotation(MyAnn) }\n        m = methods.last\n        CRYSTAL\n    end\n\n    it \"reports if there is a shadowing in a proc\" do\n      expect_issue subject, <<-CRYSTAL\n        def some_method\n          foo = 1\n\n          -> (foo : Int32) { }\n            # ^^^ error: Shadowing outer local variable `foo`\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there is a shadowing in an inner scope\" do\n      expect_issue subject, <<-CRYSTAL\n        def foo\n          foo = 1\n\n          3.times do |i|\n            3.times { |foo| foo }\n                     # ^^^ error: Shadowing outer local variable `foo`\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"reports if variable is shadowed twice\" do\n      expect_issue subject, <<-CRYSTAL\n        foo = 1\n\n        3.times do |foo|\n                  # ^^^ error: Shadowing outer local variable `foo`\n          -> (foo : Int32) { foo + 1 }\n            # ^^^ error: Shadowing outer local variable `foo`\n        end\n        CRYSTAL\n    end\n\n    it \"reports if a splat block argument shadows local var\" do\n      expect_issue subject, <<-CRYSTAL\n        foo = 1\n\n        3.times do |*foo|\n                   # ^^^ error: Shadowing outer local variable `foo`\n        end\n        CRYSTAL\n    end\n\n    it \"reports if a &block argument is shadowed\" do\n      expect_issue subject, <<-CRYSTAL\n        def method_with_block(a, &block)\n          3.times do |block|\n                    # ^^^^^ error: Shadowing outer local variable `block`\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there are multiple args and one shadows local var\" do\n      expect_issue subject, <<-CRYSTAL\n        foo = 1\n        [1, 2, 3].each_with_index do |i, foo|\n                                       # ^^^ error: Shadowing outer local variable `foo`\n          i + foo\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if an outer var is reassigned in a block\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo\n          foo = 1\n          3.times do |i|\n            foo = 2\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if an argument is a black hole '_'\" do\n      expect_no_issues subject, <<-CRYSTAL\n        _ = 1\n        3.times do |_|\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if it shadows record type declaration\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class FooBar\n          record Foo, index : String\n\n          def bar\n            3.times do |index|\n            end\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if it shadows type declaration\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class FooBar\n          getter index : String\n\n          def bar\n            3.times do |index|\n            end\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if it shadows throwaway arguments\" do\n      expect_no_issues subject, <<-CRYSTAL\n        data = [{1, \"a\"}, {2, \"b\"}, {3, \"c\"}]\n\n        data.each do |_, string|\n          data.each do |number, _|\n            puts string, number\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if argument shadows an ivar assignment\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def bar(@foo)\n          @foo.try do |foo|\n          end\n        end\n        CRYSTAL\n    end\n\n    context \"macro\" do\n      it \"does not report shadowed vars in outer scope\" do\n        expect_no_issues subject, <<-CRYSTAL\n          macro included\n            def foo\n              {% for ivar in instance_vars %}\n                {% ann = ivar.annotation(Name) %}\n              {% end %}\n            end\n\n            def bar\n              {% instance_vars.reject { |ivar| ivar } %}\n            end\n          end\n          CRYSTAL\n      end\n\n      it \"does not report shadowed vars in macro within the same scope\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {% methods = klass.methods.select { |m| m.annotation(MyAnn) } %}\n\n          {% for m, m_idx in methods %}\n            {% if d = m.annotation(MyAnn) %}\n              {% d %}\n            {% end %}\n          {% end %}\n          CRYSTAL\n      end\n\n      it \"does not report shadowed vars within nested macro\" do\n        expect_no_issues subject, <<-CRYSTAL\n          module Foo\n            macro included\n              def foo\n                {% for ann in instance_vars %}\n                  {% pos_args = ann.args.empty? ? \"Tuple.new\".id : ann.args %}\n                {% end %}\n              end\n\n              def bar\n                {{\n                  @type.instance_vars.map do |ivar|\n                    ivar.annotations(Name).each do |ann|\n                      puts ann.args\n                    end\n                  end\n                }}\n              end\n            end\n          end\n          CRYSTAL\n      end\n\n      it \"does not report scoped vars to MacroFor\" do\n        expect_no_issues subject, <<-CRYSTAL\n          struct Test\n            def test\n              {% for ivar in @type.instance_vars %}\n                {% var_type = ivar %}\n              {% end %}\n\n              {% [\"a\", \"b\"].map { |ivar| puts ivar } %}\n            end\n          end\n          CRYSTAL\n      end\n\n      # https://github.com/crystal-ameba/ameba/issues/224#issuecomment-822245167\n      it \"does not report scoped vars to MacroFor (2)\" do\n        expect_no_issues subject, <<-CRYSTAL\n          struct Test\n            def test\n              {% begin %}\n                {% for ivar in @type.instance_vars %}\n                  {% var_type = ivar %}\n                {% end %}\n\n                {% [\"a\", \"b\"].map { |ivar| puts ivar } %}\n              {% end %}\n            end\n          end\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/shared_var_in_fiber_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe SharedVarInFiber do\n    subject = SharedVarInFiber.new\n\n    it \"doesn't report if there is only local shared var in fiber\" do\n      expect_no_issues subject, <<-CRYSTAL\n        spawn do\n          i = 1\n          puts i\n        end\n\n        Fiber.yield\n        CRYSTAL\n    end\n\n    it \"doesn't report if there is only block shared var in fiber\" do\n      expect_no_issues subject, <<-CRYSTAL\n        10.times do |i|\n          spawn do\n            puts i\n          end\n        end\n\n        Fiber.yield\n        CRYSTAL\n    end\n\n    it \"doesn't report if there a spawn macro is used\" do\n      expect_no_issues subject, <<-CRYSTAL\n        i = 0\n        while i < 10\n          spawn puts(i)\n          i += 1\n        end\n\n        Fiber.yield\n        CRYSTAL\n    end\n\n    it \"reports if there is a shared var in spawn (while)\" do\n      source = expect_issue subject, <<-CRYSTAL\n        idx = 0\n        while idx < 10\n          spawn do\n            puts(idx)\n               # ^^^ error: Shared variable `idx` is used in fiber\n          end\n          idx += 1\n        end\n\n        Fiber.yield\n        CRYSTAL\n\n      expect_no_corrections source\n    end\n\n    it \"reports if there is a shared var in spawn (loop)\" do\n      source = expect_issue subject, <<-CRYSTAL\n        i = 0\n        loop do\n          break if i >= 10\n          spawn do\n            puts(i)\n               # ^ error: Shared variable `i` is used in fiber\n          end\n          i += 1\n        end\n\n        Fiber.yield\n        CRYSTAL\n\n      expect_no_corrections source\n    end\n\n    it \"reports reassigned reference to shared var in spawn\" do\n      source = expect_issue subject, <<-CRYSTAL\n        channel = Channel(String).new\n        num = 0\n\n        while num < 10\n          num = num + 1\n          spawn do\n            m = num\n              # ^^^ error: Shared variable `num` is used in fiber\n            channel.send m\n          end\n        end\n        CRYSTAL\n\n      expect_no_corrections source\n    end\n\n    it \"doesn't report reassigned reference to shared var in block\" do\n      expect_no_issues subject, <<-CRYSTAL\n        channel = Channel(String).new\n        n = 0\n\n        while n < 3\n          n = n + 1\n          m = n\n          spawn do\n            channel.send m\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report block is called in a spawn\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method(block)\n          spawn do\n            block.call(10)\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"reports multiple shared variables in spawn\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo, bar, baz = 0, 0, 0\n        while foo < 10\n          baz += 1\n          spawn do\n            puts foo\n               # ^^^ error: Shared variable `foo` is used in fiber\n            puts foo + bar + baz\n               # ^^^ error: Shared variable `foo` is used in fiber\n                           # ^^^ error: Shared variable `baz` is used in fiber\n          end\n          foo += 1\n        end\n        CRYSTAL\n\n      expect_no_corrections source\n    end\n\n    it \"doesn't report if variable is passed to the proc\" do\n      expect_no_issues subject, <<-CRYSTAL\n        i = 0\n        while i < 10\n          proc = -> (x : Int32) do\n            spawn do\n            puts(x)\n            end\n          end\n          proc.call(i)\n          i += 1\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if a channel is declared in outer scope\" do\n      expect_no_issues subject, <<-CRYSTAL\n        channel = Channel(Nil).new\n        spawn { channel.send(nil) }\n        channel.receive\n        CRYSTAL\n    end\n\n    it \"doesn't report if there is a loop in spawn\" do\n      expect_no_issues subject, <<-CRYSTAL\n        channel = Channel(String).new\n\n        spawn do\n          server = TCPServer.new(\"0.0.0.0\", 8080)\n          socket = server.accept\n          while line = socket.gets\n            channel.send(line)\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if a var is mutated in spawn and referenced outside\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method\n          foo = 1\n          spawn { foo = 2 }\n          foo\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if variable is changed without iterations\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo\n          i = 0\n          i += 1\n          spawn { i }\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if variable is in a loop inside spawn\" do\n      expect_no_issues subject, <<-CRYSTAL\n        i = 0\n        spawn do\n          while i < 10\n            i += 1\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if variable declared inside loop\" do\n      expect_no_issues subject, <<-CRYSTAL\n        while true\n          i = 0\n          spawn { i += 1 }\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/signal_trap_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe SignalTrap do\n    subject = SignalTrap.new\n\n    it \"reports when `Signal::INT/HUP/TERM.trap` is used\" do\n      source = expect_issue subject, <<-CRYSTAL\n        ::Signal::INT.trap { shutdown }\n        # ^^^^^^^^^^^^^^^^ error: Use `Process.on_terminate` instead of `::Signal::INT.trap`\n        Signal::HUP.trap { shutdown }\n        # ^^^^^^^^^^^^^^ error: Use `Process.on_terminate` instead of `Signal::HUP.trap`\n        Signal::TERM.trap &shutdown\n        # ^^^^^^^^^^^^^^^ error: Use `Process.on_terminate` instead of `Signal::TERM.trap`\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        Process.on_terminate { shutdown }\n        Process.on_terminate { shutdown }\n        Process.on_terminate &shutdown\n        CRYSTAL\n    end\n\n    it \"respects the comment between the path and the call name\" do\n      source = expect_issue subject, <<-CRYSTAL\n        Signal::INT\n        # ^^^^^^^^^ error: Use `Process.on_terminate` instead of `Signal::INT.trap`\n          # foo\n          .trap { shutdown }\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        Process\n          # foo\n          .on_terminate { shutdown }\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/spec_eq_with_bool_or_nil_literal_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe SpecEqWithBoolOrNilLiteral do\n    subject = SpecEqWithBoolOrNilLiteral.new\n\n    it \"does not report `eq` method calls that are not arguments to `should`\" do\n      expect_no_issues subject, <<-CRYSTAL, path: \"source_spec.cr\"\n        foo.bar? eq true\n        foo.bar? eq false\n        foo.bar? eq nil\n        CRYSTAL\n    end\n\n    it \"does not report if `be_true` / `be_false` expectation is used\" do\n      expect_no_issues subject, <<-CRYSTAL, path: \"source_spec.cr\"\n        foo.is_a?(String).should be_true\n        foo.is_a?(Int32).should be_false\n        foo.is_a?(String).should_not be_true\n        foo.is_a?(Int32).should_not be_false\n        CRYSTAL\n    end\n\n    it \"does not report if `be_nil` expectation is used\" do\n      expect_no_issues subject, <<-CRYSTAL, path: \"source_spec.cr\"\n        foo.as?(Symbol).should be_nil\n        foo.as?(Symbol).should_not be_nil\n        CRYSTAL\n    end\n\n    it \"reports if `eq` expectation with bool literal is used\" do\n      source = expect_issue subject, <<-CRYSTAL, path: \"source_spec.cr\"\n        foo.is_a?(String).should eq true\n                               # ^^^^^^^ error: Use `be_true` instead of `eq(true)` expectation\n        foo.is_a?(Int32).should eq false\n                              # ^^^^^^^^ error: Use `be_false` instead of `eq(false)` expectation\n        foo.is_a?(String).should_not eq true\n                                   # ^^^^^^^ error: Use `be_true` instead of `eq(true)` expectation\n        foo.is_a?(Int32).should_not eq false\n                                  # ^^^^^^^^ error: Use `be_false` instead of `eq(false)` expectation\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo.is_a?(String).should be_true\n        foo.is_a?(Int32).should be_false\n        foo.is_a?(String).should_not be_true\n        foo.is_a?(Int32).should_not be_false\n        CRYSTAL\n    end\n\n    it \"reports if `eq` expectation with nil literal is used\" do\n      source = expect_issue subject, <<-CRYSTAL, path: \"source_spec.cr\"\n        foo.as?(Symbol).should eq nil\n                             # ^^^^^^ error: Use `be_nil` instead of `eq(nil)` expectation\n        foo.as?(Symbol).should_not eq nil\n                                 # ^^^^^^ error: Use `be_nil` instead of `eq(nil)` expectation\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo.as?(Symbol).should be_nil\n        foo.as?(Symbol).should_not be_nil\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/spec_filename_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe SpecFilename do\n    subject = SpecFilename.new\n\n    it \"passes if relative file path does not start with `spec/`\" do\n      expect_no_issues subject, code: \"\", path: \"src/spec/foo.cr\"\n      expect_no_issues subject, code: \"\", path: \"src/spec/foo/bar.cr\"\n    end\n\n    it \"passes if file extension is not `.cr`\" do\n      expect_no_issues subject, code: \"\", path: \"spec/foo.json\"\n      expect_no_issues subject, code: \"\", path: \"spec/foo/bar.json\"\n    end\n\n    it \"passes if filename is correct\" do\n      expect_no_issues subject, code: \"\", path: \"spec/foo_spec.cr\"\n      expect_no_issues subject, code: \"\", path: \"spec/foo/bar_spec.cr\"\n    end\n\n    it \"fails if filename is wrong\" do\n      expect_issue subject, <<-CRYSTAL, path: \"spec/foo.cr\"\n\n        # ^{} error: Spec filename should have `_spec` suffix: `foo_spec.cr`, not `foo.cr`\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      context \"#ignored_dirs\" do\n        it \"provide sane defaults\" do\n          expect_no_issues subject, code: \"\", path: \"spec/support/foo.cr\"\n          expect_no_issues subject, code: \"\", path: \"spec/fixtures/foo.cr\"\n          expect_no_issues subject, code: \"\", path: \"spec/data/foo.cr\"\n        end\n      end\n\n      context \"#ignored_filenames\" do\n        it \"ignores spec_helper by default\" do\n          expect_no_issues subject, code: \"\", path: \"spec/spec_helper.cr\"\n          expect_no_issues subject, code: \"\", path: \"spec/foo/spec_helper.cr\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/spec_focus_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe SpecFocus do\n    subject = SpecFocus.new\n\n    it \"does not report if spec is not focused\" do\n      expect_no_issues subject, <<-CRYSTAL, path: \"source_spec.cr\"\n        context \"context\" {}\n        describe \"describe\" {}\n        it \"it\" {}\n        pending \"pending\" {}\n        CRYSTAL\n    end\n\n    it \"reports if there is a focused context\" do\n      expect_issue subject, <<-CRYSTAL, path: \"source_spec.cr\"\n        context \"context\", focus: true do\n                         # ^^^^^^^^^^^ error: Focused spec item detected\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there is a focused describe block\" do\n      expect_issue subject, <<-CRYSTAL, path: \"source_spec.cr\"\n        describe \"describe\", focus: true do\n                           # ^^^^^^^^^^^ error: Focused spec item detected\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there is a focused describe block (with block argument)\" do\n      expect_issue subject, <<-CRYSTAL, path: \"source_spec.cr\"\n        describe \"describe\", focus: true, &block\n                           # ^^^^^^^^^^^ error: Focused spec item detected\n        CRYSTAL\n    end\n\n    it \"reports if there is a focused it block\" do\n      expect_issue subject, <<-CRYSTAL, path: \"source_spec.cr\"\n        it \"it\", focus: true do\n               # ^^^^^^^^^^^ error: Focused spec item detected\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there is a focused pending block\" do\n      expect_issue subject, <<-CRYSTAL, path: \"source_spec.cr\"\n        pending \"pending\", focus: true do\n                         # ^^^^^^^^^^^ error: Focused spec item detected\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there is a spec item with `focus: false`\" do\n      expect_issue subject, <<-CRYSTAL, path: \"source_spec.cr\"\n        it \"it\", focus: false do\n               # ^^^^^^^^^^^^ error: Focused spec item detected\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there is a spec item with `focus: !true`\" do\n      expect_issue subject, <<-CRYSTAL, path: \"source_spec.cr\"\n        it \"it\", focus: !true do\n               # ^^^^^^^^^^^^ error: Focused spec item detected\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if there is non spec block with :focus\" do\n      expect_no_issues subject, <<-CRYSTAL, path: \"source_spec.cr\"\n        some_method \"foo\", focus: true do\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if there is a parameterized focused spec item\" do\n      expect_no_issues subject, <<-CRYSTAL, path: \"source_spec.cr\"\n        def assert_foo(focus = false)\n          it \"foo\", focus: focus { yield }\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if there is a tagged item with :focus\" do\n      expect_no_issues subject, <<-CRYSTAL, path: \"source_spec.cr\"\n        it \"foo\", tags: \"focus\" do\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if there are focused spec items without blocks\" do\n      expect_no_issues subject, <<-CRYSTAL, path: \"source_spec.cr\"\n        describe \"foo\", focus: true\n        context \"foo\", focus: true\n        it \"foo\", focus: true\n        pending \"foo\", focus: true\n        CRYSTAL\n    end\n\n    it \"does not report if there are focused items out of spec file\" do\n      expect_no_issues subject, <<-CRYSTAL\n        describe \"foo\", focus: true {}\n        context \"foo\", focus: true {}\n        it \"foo\", focus: true {}\n        pending \"foo\", focus: true {}\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/syntax_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe Syntax do\n    subject = Syntax.new\n\n    it \"passes if there is no invalid syntax\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def hello\n          puts \"totally valid\"\n        rescue ex : Exception\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is an invalid syntax\" do\n      expect_issue subject, <<-CRYSTAL\n        def hello\n          puts \"invalid\"\n        rescue Exception => e\n                       # ^ error: expecting any of these tokens: ;, NEWLINE (not '=>')\n        end\n        CRYSTAL\n    end\n\n    it \"has highest severity\" do\n      subject.severity.should eq Severity::Error\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/top_level_operator_definition_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe TopLevelOperatorDefinition do\n    subject = TopLevelOperatorDefinition.new\n\n    it \"passes for procs\" do\n      expect_no_issues subject, <<-CRYSTAL\n        -> { nil }\n        CRYSTAL\n    end\n\n    it \"passes if an operator method is defined within a class\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def +(other)\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"passes if an operator method is defined within an enum\" do\n      expect_no_issues subject, <<-CRYSTAL\n        enum Foo\n          def +(other)\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"passes if an operator method is defined within a module\" do\n      expect_no_issues subject, <<-CRYSTAL\n        module Foo\n          def +(other)\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"passes if a top-level operator method has a receiver\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def Foo.+(other)\n        end\n        CRYSTAL\n    end\n\n    it \"passes if a top-level operator method is defined in a record body\" do\n      expect_no_issues subject, <<-CRYSTAL\n        record Foo do\n          def +(other)\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"fails if a + operator method is defined top-level\" do\n      expect_issue subject, <<-CRYSTAL\n        def +(other)\n        # ^^^^^^^^^^ error: Top level operator method definitions cannot be called\n        end\n        CRYSTAL\n    end\n\n    it \"fails if an index operator method is defined top-level\" do\n      expect_issue subject, <<-CRYSTAL\n        def [](other)\n        # ^^^^^^^^^^^ error: Top level operator method definitions cannot be called\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/trailing_rescue_exception_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe TrailingRescueException do\n    subject = TrailingRescueException.new\n\n    it \"passes for trailing rescue with literal values\" do\n      expect_no_issues subject, <<-CRYSTAL\n        puts \"foo\" rescue \"bar\"\n        puts :foo rescue 42\n        CRYSTAL\n    end\n\n    it \"passes for trailing rescue with class initialization\" do\n      expect_no_issues subject, <<-CRYSTAL\n        puts \"foo\" rescue MyClass.new\n        CRYSTAL\n    end\n\n    it \"fails if trailing rescue has exception name\" do\n      expect_issue subject, <<-CRYSTAL\n        puts \"hello\" rescue MyException\n                          # ^^^^^^^^^^^ error: Use a block variant of `rescue` to filter by the exception type\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/typos_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nprivate def check_typos_bin!\n  unless Ameba::Rule::Lint::Typos::BIN_PATH\n    pending! \"`typos` executable is not available\"\n  end\nend\n\nmodule Ameba::Rule::Lint\n  describe Typos do\n    subject = Typos.new\n    subject.fail_on_error = true\n\n    it \"reports typos\" do\n      check_typos_bin!\n\n      source = expect_issue subject, <<-CRYSTAL\n        # method with no arugments\n                       # ^^^^^^^^^ error: Typo found: `arugments` -> `arguments`\n        def tpos\n          # ^^^^ error: Typo found: `tpos` -> `typos`\n          :otput\n         # ^^^^^ error: Typo found: `otput` -> `output`\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        # method with no arguments\n        def typos\n          :output\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/unneeded_disable_directive_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe UnneededDisableDirective do\n    subject = UnneededDisableDirective.new\n\n    it \"passes if there are no comments\" do\n      expect_no_issues subject, <<-CRYSTAL\n        a = 1\n        CRYSTAL\n    end\n\n    it \"passes if there is disable directive\" do\n      expect_no_issues subject, <<-CRYSTAL\n        a = 1 # my super var\n        CRYSTAL\n    end\n\n    it \"doesn't report if there is disable directive and it is needed\" do\n      source = Source.new <<-CRYSTAL\n        # ameba:disable #{NamedRule.name}\n        a = 1\n        CRYSTAL\n      source.add_issue NamedRule.new, location: {2, 1},\n        message: \"Useless assignment\", status: :disabled\n      subject.catch(source).should be_valid\n    end\n\n    it \"passes if there is inline disable directive and it is needed\" do\n      source = Source.new <<-CRYSTAL\n        a = 1 # ameba:disable #{NamedRule.name}\n        CRYSTAL\n      source.add_issue NamedRule.new, location: {1, 1},\n        message: \"Alarm!\", status: :disabled\n      subject.catch(source).should be_valid\n    end\n\n    it \"ignores commented out disable directive\" do\n      source = Source.new <<-CRYSTAL\n        # # ameba:disable #{NamedRule.name}\n        a = 1\n        CRYSTAL\n      source.add_issue NamedRule.new, location: {2, 1},\n        message: \"Alarm!\", status: :disabled\n      subject.catch(source).should be_valid\n    end\n\n    it \"fails if there is unneeded directive\" do\n      expect_issue subject, <<-CRYSTAL, rule_name: NamedRule.name\n        # ameba:disable %{rule_name}\n        # ^{rule_name}^^^^^^^^^^^^^^ error: Unnecessary disabling of `%{rule_name}`\n        a = 1\n        CRYSTAL\n    end\n\n    it \"fails if there is inline unneeded directive\" do\n      expect_issue subject, <<-CRYSTAL, rule_name: NamedRule.name\n        a = 1 # ameba:disable %{rule_name}\n            # ^{rule_name}^^^^^^^^^^^^^^^^ error: Unnecessary disabling of `%{rule_name}`\n        CRYSTAL\n    end\n\n    it \"ignores non-existent rules\" do\n      expect_no_issues subject, <<-CRYSTAL\n        # ameba:disable Rule1, Rule2\n        a = 1 # ameba:disable Rule3\n        CRYSTAL\n    end\n\n    it \"passes if the rule is excluded for this source\" do\n      source = Source.new <<-CRYSTAL\n        a = 1 # ameba:disable #{NamedRule.name}\n        CRYSTAL\n      subject.test(source, Set{NamedRule.name})\n      source.should be_valid\n    end\n\n    it \"fails if there is disabled UnneededDisableDirective\" do\n      source = Source.new <<-CRYSTAL\n        # ameba:disable #{UnneededDisableDirective.rule_name}\n        a = 1\n        CRYSTAL\n      source.add_issue UnneededDisableDirective.new, location: {2, 1},\n        message: \"Alarm!\", status: :disabled\n      subject.catch(source).should_not be_valid\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/unreachable_code_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe UnreachableCode do\n    subject = UnreachableCode.new\n\n    context \"return\" do\n      it \"reports if there is unreachable code after return\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            a = 1\n            return false\n            b = 2\n          # ^^^^^ error: Unreachable code detected\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if there is return in if\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            a = 1\n            return false if bar\n            b = 2\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if there are returns in if-then-else\" do\n        expect_no_issues subject, <<-CRYSTAL\n          if a > 0\n            return :positive\n          else\n            return :negative\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if there is no else in if\" do\n        expect_no_issues subject, <<-CRYSTAL\n          if a > 0\n            return :positive\n          end\n          :reachable\n          CRYSTAL\n      end\n\n      it \"doesn't report return in on-line if\" do\n        expect_no_issues subject, <<-CRYSTAL\n          return :positive if a > 0\n          CRYSTAL\n      end\n\n      it \"doesn't report if return is used in a block\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            bar = obj.try do\n              if something\n                a = 1\n              end\n              return nil\n            end\n            bar\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there is unreachable code after if-then-else\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            if a > 0\n              return :positive\n            else\n              return :negative\n            end\n            :unreachable\n          # ^^^^^^^^^^^^ error: Unreachable code detected\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there is unreachable code after if-then-else-if\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            if a > 0\n              return :positive\n            elsif a != 0\n              return :negative\n            else\n              return :zero\n            end\n            :unreachable\n          # ^^^^^^^^^^^^ error: Unreachable code detected\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if there is no unreachable code after if-then-else\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            if a > 0\n              return :positive\n            else\n              return :negative\n            end\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if there is no unreachable in inner branch\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            if a > 0\n              return :positive if a != 1\n            else\n              return :negative\n            end\n            :not_unreachable\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if there is no unreachable in exception handler\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            puts :bar\n          rescue Exception\n            raise \"Error!\"\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if there is multiple conditions with return\" do\n        expect_no_issues subject, <<-CRYSTAL\n          if :foo\n            if :bar\n              return :foobar\n            else\n              return :foobaz\n            end\n          elsif :fox\n            return :foofox\n          end\n          return :reachable\n          CRYSTAL\n      end\n\n      it \"reports if there is unreachable code after unless\" do\n        expect_issue subject, <<-CRYSTAL\n          unless :foo\n            return :bar\n          else\n            return :foo\n          end\n          :unreachable\n          # ^^^^^^^^^^ error: Unreachable code detected\n          CRYSTAL\n      end\n\n      it \"doesn't report if there is no unreachable code after unless\" do\n        expect_no_issues subject, <<-CRYSTAL\n          unless :foo\n            return :bar\n          end\n          :reachable\n          CRYSTAL\n      end\n    end\n\n    context \"binary op\" do\n      it \"reports unreachable code in a binary operator\" do\n        expect_issue subject, <<-CRYSTAL\n          (return 22) && puts \"a\"\n                       # ^^^^^^^^ error: Unreachable code detected\n          CRYSTAL\n      end\n\n      it \"reports unreachable code in inner binary operator\" do\n        expect_issue subject, <<-CRYSTAL\n          do_something || (return 22) && puts \"a\"\n                                       # ^^^^^^^^ error: Unreachable code detected\n          CRYSTAL\n      end\n\n      it \"reports unreachable code after the binary op\" do\n        expect_issue subject, <<-CRYSTAL\n          (return 22) && break\n                       # ^^^^^ error: Unreachable code detected\n          :unreachable\n          # ^^^^^^^^^^ error: Unreachable code detected\n          CRYSTAL\n      end\n\n      it \"doesn't report if return is not the right\" do\n        expect_no_issues subject, <<-CRYSTAL\n          puts \"a\" && return\n          CRYSTAL\n      end\n\n      it \"doesn't report unreachable code in multiple binary expressions\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo || bar || baz\n          CRYSTAL\n      end\n    end\n\n    context \"case\" do\n      it \"reports if there is unreachable code after case\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            case cond\n            when 1\n              something\n              return\n            when 2\n              something2\n              return\n            else\n              something3\n              return\n            end\n            :unreachable\n          # ^^^^^^^^^^^^ error: Unreachable code detected\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if case does not have else\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            case cond\n            when 1\n              something\n              return\n            when 2\n              something2\n              return\n            end\n            :reachable\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if one when does not return\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            case cond\n            when 1\n              something\n              return\n            when 2\n              something2\n            else\n              something3\n              return\n            end\n            :reachable\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"exception handler\" do\n      it \"reports unreachable code if it returns in body and rescues\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            begin\n              return false\n            rescue Error\n              return false\n            rescue Exception\n              return false\n            end\n            :unreachable\n          # ^^^^^^^^^^^^ error: Unreachable code detected\n          end\n          CRYSTAL\n      end\n\n      it \"reports unreachable code if it returns in rescues and else\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            begin\n              do_something\n            rescue Error\n              return :error\n            else\n              return true\n            end\n            :unreachable\n          # ^^^^^^^^^^^^ error: Unreachable code detected\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if there is no else and ensure doesn't return\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            begin\n              return false\n            rescue Error\n              puts \"error\"\n            rescue Exception\n              return false\n            end\n            :reachable\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if there is no else and body doesn't return\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            begin\n              do_something\n            rescue Error\n              return true\n            rescue Exception\n              return false\n            end\n            :reachable\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if there is else and ensure doesn't return\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            begin\n              do_something\n            rescue Error\n              puts \"yo\"\n            else\n              return true\n            end\n            :reachable\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if there is else and it doesn't return\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            begin\n              do_something\n            rescue Error\n              return false\n            else\n              puts \"yo\"\n            end\n            :reachable\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there is unreachable code in rescue\" do\n        expect_issue subject, <<-CRYSTAL\n          def method\n          rescue\n            return 22\n            :unreachable\n          # ^^^^^^^^^^^^ error: Unreachable code detected\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"while/until\" do\n      it \"does not report if there is no unreachable code after while\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def method\n            while something\n              if :foo\n                return :foo\n              else\n                return :foobar\n              end\n            end\n            :unreachable\n          end\n          CRYSTAL\n      end\n\n      it \"does not report if there is no unreachable code after until\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def method\n            until something\n              if :foo\n                return :foo\n              else\n                return :foobar\n              end\n            end\n            :unreachable\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if there is reachable code after while with break\" do\n        expect_no_issues subject, <<-CRYSTAL\n          while something\n            break\n          end\n          :reachable\n          CRYSTAL\n      end\n    end\n\n    context \"rescue\" do\n      it \"reports unreachable code in rescue\" do\n        expect_issue subject, <<-CRYSTAL\n          begin\n\n          rescue ex\n            raise ex\n            :unreachable\n          # ^^^^^^^^^^^^ error: Unreachable code detected\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if there is no unreachable code in rescue\" do\n        expect_no_issues subject, <<-CRYSTAL\n          begin\n\n          rescue ex\n            raise ex\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"when\" do\n      it \"reports unreachable code in when\" do\n        expect_issue subject, <<-CRYSTAL\n          case\n          when valid?\n            return 22\n            :unreachable\n          # ^^^^^^^^^^^^ error: Unreachable code detected\n          else\n\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if there is no unreachable code in when\" do\n        expect_no_issues subject, <<-CRYSTAL\n          case\n          when valid?\n            return 22\n          else\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"break\" do\n      it \"reports if there is unreachable code after break\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            loop do\n              break\n              a = 1\n            # ^^^^^ error: Unreachable code detected\n            end\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if break is in a condition\" do\n        expect_no_issues subject, <<-CRYSTAL\n          a = -100\n          while true\n            break if a > 0\n            a += 1\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"next\" do\n      it \"reports if there is unreachable code after next\" do\n        expect_issue subject, <<-CRYSTAL\n          a = 1\n          while a < 5\n            next\n            puts a\n          # ^^^^^^ error: Unreachable code detected\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if next is in a condition\" do\n        expect_no_issues subject, <<-CRYSTAL\n          a = 1\n          while a < 5\n            if a == 3\n              next\n            end\n            puts a\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"raise\" do\n      it \"reports if there is unreachable code after raise\" do\n        expect_issue subject, <<-CRYSTAL\n          a = 1\n          raise \"exception\"\n          b = 2\n          # ^^^ error: Unreachable code detected\n          CRYSTAL\n      end\n\n      it \"doesn't report if raise is in a condition\" do\n        expect_no_issues subject, <<-CRYSTAL\n          a = 1\n          raise \"exception\" if a > 0\n          b = 2\n          CRYSTAL\n      end\n    end\n\n    context \"exit\" do\n      it \"reports if there is unreachable code after exit without args\" do\n        expect_issue subject, <<-CRYSTAL\n          a = 1\n          exit\n          b = 2\n          # ^^^ error: Unreachable code detected\n          CRYSTAL\n      end\n\n      it \"reports if there is unreachable code after exit with exit code\" do\n        expect_issue subject, <<-CRYSTAL\n          a = 1\n          exit 1\n          b = 2\n          # ^^^ error: Unreachable code detected\n          CRYSTAL\n      end\n\n      it \"doesn't report if exit is in a condition\" do\n        expect_no_issues subject, <<-CRYSTAL\n          a = 1\n          exit if a > 0\n          b = 2\n          CRYSTAL\n      end\n    end\n\n    context \"abort\" do\n      it \"reports if there is unreachable code after abort with one argument\" do\n        expect_issue subject, <<-CRYSTAL\n          a = 1\n          abort \"abort\"\n          b = 2\n          # ^^^ error: Unreachable code detected\n          CRYSTAL\n      end\n\n      it \"reports if there is unreachable code after abort with two args\" do\n        expect_issue subject, <<-CRYSTAL\n          a = 1\n          abort \"abort\", 1\n          b = 2\n          # ^^^ error: Unreachable code detected\n          CRYSTAL\n      end\n\n      it \"doesn't report if abort is in a condition\" do\n        expect_no_issues subject, <<-CRYSTAL\n          a = 1\n          abort \"abort\" if a > 0\n          b = 2\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/unused_argument_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe UnusedArgument do\n    subject = UnusedArgument.new\n    subject.ignore_defs = false\n\n    it \"doesn't report if arguments are used\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method(a, b, c)\n          a + b + c\n        end\n\n        3.times do |i|\n          i + 1\n        end\n\n        -> (i : Int32) { i + 1 }\n        CRYSTAL\n    end\n\n    it \"reports if method argument is unused\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def method(foo, bar, baz : Symbol)\n                           # ^^^^^^^^^^^^ error: Unused argument `baz`. [...]\n          foo + bar\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def method(foo, bar, _baz : Symbol)\n          foo + bar\n        end\n        CRYSTAL\n    end\n\n    it \"reports if block argument is unused\" do\n      source = expect_issue subject, <<-CRYSTAL\n        [1, 2].each_with_index do |foo, bar|\n                                      # ^^^ error: Unused argument `bar`. [...]\n          foo\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        [1, 2].each_with_index do |foo, _|\n          foo\n        end\n        CRYSTAL\n    end\n\n    it \"reports if proc argument is unused\" do\n      source = expect_issue subject, <<-CRYSTAL\n        -> (foo : Int32, bar : String) do\n                       # ^^^^^^^^^^^^ error: Unused argument `bar`. [...]\n          foo += 1\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        -> (foo : Int32, _bar : String) do\n          foo += 1\n        end\n        CRYSTAL\n    end\n\n    it \"reports multiple unused args\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def method(foo, bar, baz)\n                 # ^^^ error: Unused argument `foo`. If it's necessary, use `_foo` as an argument name to indicate that it won't be used.\n                      # ^^^ error: Unused argument `bar`. If it's necessary, use `_bar` as an argument name to [...]\n                           # ^^^ error: Unused argument `baz`. If it's necessary, use `_baz` as an argument name to [...]\n          nil\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def method(_foo, _bar, _baz)\n          nil\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if it is an instance var argument\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class A\n          def method(@name)\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if a typed argument is used\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method(x : Int32)\n          3.times do\n            puts x\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if an argument with default value is used\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method(x = 1)\n          puts x\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if argument starts with a _\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method(_x)\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if it is a block and used\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method(&block)\n          block.call\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if block arg is not used\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method(&block)\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if unused and there is yield\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method(&block)\n          yield 1\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if it's an anonymous block\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method(&)\n          yield 1\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if variable is referenced implicitly\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Bar < Foo\n          def method(a, b)\n            super\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if arg if referenced in case\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(a)\n          case a\n          when /foo/\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if enum in a record\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Class\n          record Record do\n            enum Enum\n              CONSTANT\n            end\n          end\n        end\n        CRYSTAL\n    end\n\n    context \"super\" do\n      it \"reports if variable is not referenced implicitly by super\" do\n        source = expect_issue subject, <<-CRYSTAL\n          class Bar < Foo\n            def method(foo, bar)\n                          # ^^^ error: Unused argument `bar`. [...]\n              super foo\n            end\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          class Bar < Foo\n            def method(foo, _bar)\n              super foo\n            end\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"macro\" do\n      it \"doesn't report if it is a used macro argument\" do\n        expect_no_issues subject, <<-CRYSTAL\n          macro my_macro(arg)\n            {% arg %}\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if it is a used macro block argument\" do\n        expect_no_issues subject, <<-CRYSTAL\n          macro my_macro(&block)\n            {% block %}\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report used macro args with equal names in record\" do\n        expect_no_issues subject, <<-CRYSTAL\n          record X do\n            macro foo(a, b)\n              {{ a }} + {{ b }}\n            end\n\n            macro bar(a, b, c)\n              {{ a }} + {{ b }} + {{ c }}\n            end\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report used args in macro literals\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def print(f : Array(U)) forall U\n            f.size.times do |i|\n              {% if U == Float64 %}\n                puts f[i].round(3)\n              {% else %}\n                puts f[i]\n              {% end %}\n            end\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"properties\" do\n      describe \"#ignore_defs\" do\n        it \"lets the rule to ignore def scopes if true\" do\n          rule = UnusedArgument.new\n          rule.ignore_defs = true\n\n          expect_no_issues rule, <<-CRYSTAL\n            def method(foo)\n            end\n            CRYSTAL\n        end\n\n        it \"lets the rule not to ignore def scopes if false\" do\n          rule = UnusedArgument.new\n          rule.ignore_defs = false\n\n          expect_issue rule, <<-CRYSTAL\n            def method(foo)\n                     # ^^^ error: Unused argument `foo`. [...]\n            end\n            CRYSTAL\n        end\n      end\n\n      context \"#ignore_blocks\" do\n        it \"lets the rule to ignore block scopes if true\" do\n          rule = UnusedArgument.new\n          rule.ignore_blocks = true\n\n          expect_no_issues rule, <<-CRYSTAL\n            3.times { |idx| puts \"yo!\" }\n            CRYSTAL\n        end\n\n        it \"lets the rule not to ignore block scopes if false\" do\n          rule = UnusedArgument.new\n          rule.ignore_blocks = false\n\n          expect_issue rule, <<-CRYSTAL\n            3.times { |idx| puts \"yo!\" }\n                     # ^^^ error: Unused argument `idx`. [...]\n            CRYSTAL\n        end\n      end\n\n      context \"#ignore_procs\" do\n        it \"lets the rule to ignore proc scopes if true\" do\n          rule = UnusedArgument.new\n          rule.ignore_procs = true\n\n          expect_no_issues rule, <<-CRYSTAL\n            -> (foo : Int32) { }\n            CRYSTAL\n        end\n\n        it \"lets the rule not to ignore proc scopes if false\" do\n          rule = UnusedArgument.new\n          rule.ignore_procs = false\n\n          expect_issue rule, <<-CRYSTAL\n            -> (foo : Int32) { }\n              # ^^^^^^^^^^^ error: Unused argument `foo`. [...]\n            CRYSTAL\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/unused_block_argument_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe UnusedBlockArgument do\n    subject = UnusedBlockArgument.new\n\n    it \"doesn't report if it is an instance var argument\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class A\n          def initialize(&@callback)\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if anonymous\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method(a, b, c, &)\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if argument name starts with a `_`\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method(a, b, c, &_block)\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if it is a block and used\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method(a, b, c, &block)\n          block.call\n        end\n        CRYSTAL\n    end\n\n    it \"reports if block arg is not used\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def method(a, b, c, &block)\n                           # ^^^^^ error: Unused block argument `block`. [...]\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def method(a, b, c, &_block)\n        end\n        CRYSTAL\n    end\n\n    it \"reports if unused and there is yield\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def method(a, b, c, &block)\n                           # ^^^^^ error: Use `&` as an argument name to indicate that it won't be referenced\n          3.times do |i|\n            i.try do\n              yield i\n            end\n          end\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def method(a, b, c, &)\n          3.times do |i|\n            i.try do\n              yield i\n            end\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if anonymous and there is yield\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method(a, b, c, &)\n          yield 1\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if variable is referenced implicitly\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Bar < Foo\n          def method(a, b, c, &block)\n            super\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if used in abstract def\" do\n      expect_no_issues subject, <<-CRYSTAL\n        abstract def debug(id : String, &on_message: Callback)\n        abstract def info(&on_message: Callback)\n        CRYSTAL\n    end\n\n    context \"super\" do\n      it \"reports if variable is not referenced implicitly by super\" do\n        source = expect_issue subject, <<-CRYSTAL\n          class Bar < Foo\n            def method(a, b, c, &block)\n                               # ^^^^^ error: Unused block argument `block`. [...]\n              super a, b, c\n            end\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          class Bar < Foo\n            def method(a, b, c, &_block)\n              super a, b, c\n            end\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"macro\" do\n      it \"doesn't report if it is a used macro block argument\" do\n        expect_no_issues subject, <<-CRYSTAL\n          macro my_macro(&block)\n            {% block %}\n          end\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/unused_expression_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe UnusedExpression do\n    subject = UnusedExpression.new\n\n    context \"class variable access\" do\n      it \"passes if class variables are used for assignment\" do\n        expect_no_issues subject, <<-CRYSTAL\n          class MyClass\n            foo = @@ivar\n          end\n          CRYSTAL\n      end\n\n      it \"passes if an class variable is used as a target in multi-assignment\" do\n        expect_no_issues subject, <<-CRYSTAL\n          class MyClass\n            @@foo, @@bar = 1, 2\n          end\n          CRYSTAL\n      end\n\n      it \"fails if class variables are unused in void context of class\" do\n        expect_issue subject, <<-CRYSTAL\n          class Actor\n            @@name : String = \"George\"\n\n            @@name\n          # ^^^^^^ error: Class variable access is unused\n          end\n          CRYSTAL\n      end\n\n      it \"fails if class variables are unused in void context of method\" do\n        expect_issue subject, <<-'CRYSTAL'\n          def hello : String\n            @@name\n          # ^^^^^^ error: Class variable access is unused\n\n            \"Hello, #{@@name}!\"\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"comparison\" do\n      it \"passes if comparison used in assign\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo = 1 == \"1\"\n          bar = begin\n            2 == \"2\"\n          end\n          CRYSTAL\n      end\n\n      it \"passes if comparison used in if condition\" do\n        expect_no_issues subject, <<-CRYSTAL\n          if foo == bar\n            puts \"baz\"\n          end\n          CRYSTAL\n      end\n\n      it \"passes if comparison implicitly returns from method body\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            1 == 2\n          end\n          CRYSTAL\n      end\n\n      it \"passes for implicit object comparisons\" do\n        expect_no_issues subject, <<-CRYSTAL\n          case obj\n          when .> 1 then foo\n          when .< 0 then bar\n          end\n          CRYSTAL\n      end\n\n      it \"passes for comparisons inside '||' and '&&' where the other arg is a call\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo(bar) == baz || raise \"bat\"\n          foo(bar) == baz && raise \"bat\"\n          CRYSTAL\n      end\n\n      it \"passes for unused comparisons with `===`, `=~`, and `!~`\" do\n        expect_no_issues subject, <<-CRYSTAL\n          /foo(bar)?/ =~ baz\n          /foo(bar)?/ !~ baz\n          \"foo\" === bar\n          CRYSTAL\n      end\n\n      it \"fails if a comparison operation with `==` is unused\" do\n        expect_issue subject, <<-CRYSTAL\n          foo == 2\n          # ^^^^^^ error: Comparison operation is unused\n          CRYSTAL\n      end\n\n      it \"fails if a comparison operation with `!=` is unused\" do\n        expect_issue subject, <<-CRYSTAL\n          foo != 2\n          # ^^^^^^ error: Comparison operation is unused\n          CRYSTAL\n      end\n\n      it \"fails if a comparison operation with `<` is unused\" do\n        expect_issue subject, <<-CRYSTAL\n          foo < 2\n          # ^^^^^ error: Comparison operation is unused\n          CRYSTAL\n      end\n\n      it \"fails if a comparison operation with `<=` is unused\" do\n        expect_issue subject, <<-CRYSTAL\n          foo <= 2\n          # ^^^^^^ error: Comparison operation is unused\n          CRYSTAL\n      end\n\n      it \"fails if a comparison operation with `>` is unused\" do\n        expect_issue subject, <<-CRYSTAL\n          foo > 2\n          # ^^^^^ error: Comparison operation is unused\n          CRYSTAL\n      end\n\n      it \"fails if a comparison operation with `>=` is unused\" do\n        expect_issue subject, <<-CRYSTAL\n          foo >= 2\n          # ^^^^^^ error: Comparison operation is unused\n          CRYSTAL\n      end\n\n      it \"fails if a comparison operation with `<=>` is unused\" do\n        expect_issue subject, <<-CRYSTAL\n          foo <=> 2\n          # ^^^^^^^ error: Comparison operation is unused\n          CRYSTAL\n      end\n\n      it \"fails for an unused comparison in a begin block\" do\n        expect_issue subject, <<-CRYSTAL\n          begin\n            x = 1\n            x == 2\n          # ^^^^^^ error: Comparison operation is unused\n            puts x\n          end\n          CRYSTAL\n      end\n\n      it \"fails for unused comparisons in if/elsif/else bodies\" do\n        expect_issue subject, <<-CRYSTAL\n          a = if x = 1\n                x == 1\n              # ^^^^^^ error: Comparison operation is unused\n                x == 2\n              elsif true\n                x == 1\n              # ^^^^^^ error: Comparison operation is unused\n                x == 2\n              else\n                x == 2\n              # ^^^^^^ error: Comparison operation is unused\n                x == 3\n              end\n          CRYSTAL\n      end\n\n      it \"fails for unused comparisons in a proc body\" do\n        expect_issue subject, <<-CRYSTAL\n          a = -> do\n            x == 1\n          # ^^^^^^ error: Comparison operation is unused\n            \"meow\"\n          end\n          CRYSTAL\n      end\n\n      it \"fails for unused comparison in top-level if statement body\" do\n        expect_issue subject, <<-CRYSTAL\n          if true\n            x == 1\n          # ^^^^^^ error: Comparison operation is unused\n          else\n            x == 2\n          # ^^^^^^ error: Comparison operation is unused\n          end\n          CRYSTAL\n      end\n\n      it \"fails for unused comparison in void of method body\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            if x == 3\n              x < 1\n            # ^^^^^ error: Comparison operation is unused\n            else\n              x > 1\n            # ^^^^^ error: Comparison operation is unused\n            end\n\n            return\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"generic or union\" do\n      it \"passes if a generic is used in a top-level type declaration\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo : Bar?\n          CRYSTAL\n      end\n\n      it \"passes if a union is used in a top-level type declaration\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo : Bar | Baz\n          CRYSTAL\n      end\n\n      it \"passes if a generic is used in an assign\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo = Bar?\n          CRYSTAL\n      end\n\n      it \"passes if a union is used in an assign\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo = Bar | Baz\n          CRYSTAL\n      end\n\n      it \"passes if a generic or union is used in a cast\" do\n        expect_no_issues subject, <<-CRYSTAL\n          bar = foo.as(Bar?)\n          baz = bar.as?(Baz | Qux)\n          CRYSTAL\n      end\n\n      it \"passes if a generic or union is used as a method argument\" do\n        expect_no_issues subject, <<-CRYSTAL\n          puts StaticArray(Int32, 10)\n          CRYSTAL\n      end\n\n      it \"passes if a generic is used as a method call object\" do\n        expect_no_issues subject, <<-CRYSTAL\n          MyClass(String).new\n          CRYSTAL\n      end\n\n      it \"passes if something that looks like a union but isn't is top-level\" do\n        expect_no_issues subject, <<-CRYSTAL\n          # Not a union\n          Foo | \"Bar\"\n          CRYSTAL\n      end\n\n      it \"passes for an unused path\" do\n        expect_no_issues subject, \"Foo\"\n      end\n\n      it \"passes if a generic is used for a parameter type restriction\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo(bar : Baz?)\n          end\n          CRYSTAL\n      end\n\n      it \"passes if a generic is used for a method return type restriction\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo : Baz?\n          end\n          CRYSTAL\n      end\n\n      it \"passes if a union is used for a parameter type restriction\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo(bar : Baz | Qux)\n          end\n          CRYSTAL\n      end\n\n      it \"passes if a union is used for a method return type restriction\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo : Baz | Qux\n          end\n          CRYSTAL\n      end\n\n      it \"fails for an unused top-level generic\" do\n        expect_issue subject, <<-CRYSTAL\n          String?\n          # ^^^^^ error: Generic type is unused\n          StaticArray(Int32, 10)\n          # ^^^^^^^^^^^^^^^^^^^^ error: Generic type is unused\n          CRYSTAL\n      end\n\n      it \"fails for an unused top-level union\" do\n        expect_issue subject, <<-CRYSTAL\n          Int32 | Float64 | Nil\n          # ^^^^^^^^^^^^^^^^^^^ error: Union type is unused\n          CRYSTAL\n      end\n\n      it \"fails for an unused top-level union of self, typeof, and underscore\" do\n        expect_issue subject, <<-CRYSTAL\n          self | typeof(1) | _\n          # ^^^^^^^^^^^^^^^^^^ error: Union type is unused\n          CRYSTAL\n      end\n\n      it \"fails if a generic is in void of method body\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            Float64?\n          # ^^^^^^^^ error: Generic type is unused\n            nil\n          end\n          CRYSTAL\n      end\n\n      it \"fails if a union is in void of method body\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            Bar | Baz\n          # ^^^^^^^^^ error: Union type is unused\n            nil\n          end\n          CRYSTAL\n      end\n\n      it \"fails if a generic is in void of class body\" do\n        expect_issue subject, <<-CRYSTAL\n          class MyClass\n            String?\n          # ^^^^^^^ error: Generic type is unused\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"instance variable access\" do\n      it \"passes if instance variables are used for assignment\" do\n        expect_no_issues subject, <<-CRYSTAL\n          class MyClass\n            foo = @ivar\n          end\n          CRYSTAL\n      end\n\n      it \"passes if an instance variable is used as a target in multi-assignment\" do\n        expect_no_issues subject, <<-CRYSTAL\n          class MyClass\n            @foo, @bar = 1, 2\n          end\n          CRYSTAL\n      end\n\n      it \"fails if instance variables are unused in void context of class\" do\n        expect_issue subject, <<-CRYSTAL\n          class Actor\n            @name : String = \"George\"\n\n            @name\n          # ^^^^^ error: Instance variable access is unused\n          end\n          CRYSTAL\n      end\n\n      it \"fails if instance variables are unused in void context of method\" do\n        expect_issue subject, <<-'CRYSTAL'\n          def hello : String\n            @name\n          # ^^^^^ error: Instance variable access is unused\n\n            \"Hello, #{@name}!\"\n          end\n          CRYSTAL\n      end\n\n      it \"passes if @type is unused within a macro expression\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            {% @type %}\n            :bar\n          end\n          CRYSTAL\n      end\n\n      it \"fails if instance variable is unused within a macro expression\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            {% @bar %}\n             # ^^^^ error: Instance variable access is unused\n            :baz\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"literal\" do\n      it \"passes if a number literal is used to assign\" do\n        expect_no_issues subject, <<-CRYSTAL\n          a = 1\n          CRYSTAL\n      end\n\n      it \"passes if a char literal is used to assign\" do\n        expect_no_issues subject, <<-'CRYSTAL'\n          c = '\\t'\n          CRYSTAL\n      end\n\n      it \"passes if a string literal is used to assign\" do\n        expect_no_issues subject, <<-'CRYSTAL'\n          b = \"foo\"\n          g = \"bar #{baz}\"\n          CRYSTAL\n      end\n\n      it \"passes if a heredoc is used to assign\" do\n        expect_no_issues subject, <<-CRYSTAL\n          h = <<-HEREDOC\n            foo\n            HEREDOC\n          CRYSTAL\n      end\n\n      it \"passes if a symbol literal is used to assign\" do\n        expect_no_issues subject, <<-CRYSTAL\n          c = :foo\n          CRYSTAL\n      end\n\n      it \"passes if a named tuple literal is used to assign\" do\n        expect_no_issues subject, <<-CRYSTAL\n          d = {foo: 1, bar: 2}\n          CRYSTAL\n      end\n\n      it \"passes if an array literal is used to assign\" do\n        expect_no_issues subject, <<-CRYSTAL\n          e = [10_f32, 20_f32, 30_f32]\n          CRYSTAL\n      end\n\n      it \"passes if a proc literal is used to assign\" do\n        expect_no_issues subject, <<-CRYSTAL\n          f = -> { }\n          CRYSTAL\n      end\n\n      it \"passes if literals inside an if statement are implicitly returned from a method\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            if true\n              :bar\n            else\n              \"baz\"\n            end\n          end\n          CRYSTAL\n      end\n\n      it \"passes if an unused literal is beyond a return statement in a method body\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo : Nil\n            return\n            :bar\n            nil\n          end\n          CRYSTAL\n      end\n\n      it \"passes if a literal is the object of a call\" do\n        expect_no_issues subject, <<-CRYSTAL\n          { foo: \"bar\" }.to_json(io)\n          CRYSTAL\n      end\n\n      it \"passes for a literal in a generic type\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo = StaticArray(Int32, 3)\n          bar = Int32[3]\n          CRYSTAL\n      end\n\n      it \"passes if a literal is passed to with or yield\" do\n        expect_no_issues subject, <<-CRYSTAL\n          yield 1\n          with \"2\" yield :three\n          CRYSTAL\n      end\n\n      it \"passes if a literal value is the object of a cast\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo = 1.as(Int64)\n          bar = \"2\".as?(String)\n          CRYSTAL\n      end\n\n      it \"passes if a literal value is the object of a cast\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo = 1.as(Int64)\n          bar = \"2\".as?(String)\n          CRYSTAL\n      end\n\n      it \"fails if a number literal is top-level\" do\n        expect_issue subject, <<-CRYSTAL\n          1234\n          # ^^ error: Literal value is unused\n          1234_f32\n          # ^^^^^^ error: Literal value is unused\n          CRYSTAL\n      end\n\n      it \"fails if a string literal is top-level\" do\n        expect_issue subject, <<-'CRYSTAL'\n          \"hello world\"\n          # ^^^^^^^^^^^ error: Literal value is unused\n          \"foo #{bar}\"\n          # ^^^^^^^^^^ error: Literal value is unused\n          CRYSTAL\n      end\n\n      it \"fails if an array literal is top-level\" do\n        expect_issue subject, <<-CRYSTAL\n          [1, 2, 3, 4, 5]\n          # ^^^^^^^^^^^^^ error: Literal value is unused\n          CRYSTAL\n      end\n\n      it \"fails if a hash literal is top-level\" do\n        expect_issue subject, <<-CRYSTAL\n          {\"foo\" => \"bar\"}\n          # ^^^^^^^^^^^^^^ error: Literal value is unused\n          CRYSTAL\n      end\n\n      it \"fails if a char literal is top-level\" do\n        expect_issue subject, <<-'CRYSTAL'\n          '\\t'\n          # ^^ error: Literal value is unused\n          CRYSTAL\n      end\n\n      it \"fails if a range literal is top-level\" do\n        expect_issue subject, <<-CRYSTAL\n          1..2\n          # ^^ error: Literal value is unused\n          CRYSTAL\n      end\n\n      it \"fails if a tuple literal is top-level\" do\n        expect_issue subject, <<-CRYSTAL\n          {1, 2, 3}\n          # ^^^^^^^ error: Literal value is unused\n          CRYSTAL\n      end\n\n      it \"fails if a named tuple literal is top-level\" do\n        expect_issue subject, <<-CRYSTAL\n          {foo: bar}\n          # ^^^^^^^^ error: Literal value is unused\n          CRYSTAL\n      end\n\n      it \"fails if a heredoc is top-level\" do\n        expect_issue subject, <<-CRYSTAL\n          <<-HEREDOC\n          # ^^^^^^^^ error: Literal value is unused\n            this is a heredoc\n            HEREDOC\n          CRYSTAL\n      end\n\n      it \"fails if a number literal is in void of method body\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            1234\n          # ^^^^ error: Literal value is unused\n            1234_f32\n          # ^^^^^^^^ error: Literal value is unused\n            return\n          end\n          CRYSTAL\n      end\n\n      it \"fails if a string literal is in void of method body\" do\n        expect_issue subject, <<-'CRYSTAL'\n          def foo\n            \"hello world\"\n          # ^^^^^^^^^^^^^ error: Literal value is unused\n            \"foo #{bar}\"\n          # ^^^^^^^^^^^^ error: Literal value is unused\n            return\n          end\n          CRYSTAL\n      end\n\n      it \"fails if an array literal is in void of method body\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            [1, 2, 3, 4, 5]\n          # ^^^^^^^^^^^^^^^ error: Literal value is unused\n            return\n          end\n          CRYSTAL\n      end\n\n      it \"fails if a hash literal is in void of method body\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            {\"foo\" => \"bar\"}\n          # ^^^^^^^^^^^^^^^^ error: Literal value is unused\n            return\n          end\n          CRYSTAL\n      end\n\n      it \"fails if a char literal is in void of method body\" do\n        expect_issue subject, <<-'CRYSTAL'\n          def foo\n            '\\t'\n          # ^^^^ error: Literal value is unused\n            return\n          end\n          CRYSTAL\n      end\n\n      it \"fails if a range literal is in void of method body\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            1..2\n          # ^^^^ error: Literal value is unused\n            return\n          end\n          CRYSTAL\n      end\n\n      it \"fails if a tuple literal is in void of method body\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            {1, 2, 3}\n          # ^^^^^^^^^ error: Literal value is unused\n            return\n          end\n          CRYSTAL\n      end\n\n      it \"fails if a named tuple literal is in void of method body\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            {foo: bar}\n          # ^^^^^^^^^^ error: Literal value is unused\n            return\n          end\n          CRYSTAL\n      end\n\n      it \"fails if a heredoc is in void of method body\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            <<-HEREDOC\n          # ^^^^^^^^^^ error: Literal value is unused\n              this is a heredoc\n              HEREDOC\n            return\n          end\n          CRYSTAL\n      end\n\n      it \"fails if a number literal is in void of if statement body\" do\n        expect_issue subject, <<-CRYSTAL\n          if true\n            1234\n          # ^^^^ error: Literal value is unused\n            1234_f32\n          # ^^^^^^^^ error: Literal value is unused\n            nil\n          end\n          CRYSTAL\n      end\n\n      it \"fails if a string literal is in void of if statement body\" do\n        expect_issue subject, <<-'CRYSTAL'\n          if true\n            \"hello world\"\n          # ^^^^^^^^^^^^^ error: Literal value is unused\n            \"foo #{bar}\"\n          # ^^^^^^^^^^^^ error: Literal value is unused\n            nil\n          end\n          CRYSTAL\n      end\n\n      it \"fails if an array literal is in void of if statement body\" do\n        expect_issue subject, <<-CRYSTAL\n          if true\n            [1, 2, 3, 4, 5]\n          # ^^^^^^^^^^^^^^^ error: Literal value is unused\n            nil\n          end\n          CRYSTAL\n      end\n\n      it \"fails if a hash literal is in void of if statement body\" do\n        expect_issue subject, <<-CRYSTAL\n          if true\n            {\"foo\" => \"bar\"}\n          # ^^^^^^^^^^^^^^^^ error: Literal value is unused\n            nil\n          end\n          CRYSTAL\n      end\n\n      it \"fails if a char literal is in void of if statement body\" do\n        expect_issue subject, <<-'CRYSTAL'\n          if true\n            '\\t'\n          # ^^^^ error: Literal value is unused\n            nil\n          end\n          CRYSTAL\n      end\n\n      it \"fails if a range literal is in void of if statement body\" do\n        expect_issue subject, <<-CRYSTAL\n          if true\n            1..2\n          # ^^^^ error: Literal value is unused\n            nil\n          end\n          CRYSTAL\n      end\n\n      it \"fails if a tuple literal is in void of if statement body\" do\n        expect_issue subject, <<-CRYSTAL\n          if true\n            {1, 2, 3}\n          # ^^^^^^^^^ error: Literal value is unused\n            nil\n          end\n          CRYSTAL\n      end\n\n      it \"fails if a named tuple literal is in void of if statement body\" do\n        expect_issue subject, <<-CRYSTAL\n          if true\n            {foo: bar}\n          # ^^^^^^^^^^ error: Literal value is unused\n            nil\n          end\n          CRYSTAL\n      end\n\n      it \"fails if a heredoc is in void of if statement body\" do\n        expect_issue subject, <<-CRYSTAL\n          if true\n            <<-HEREDOC\n          # ^^^^^^^^^^ error: Literal value is unused\n              this is a heredoc\n              HEREDOC\n            nil\n          end\n          CRYSTAL\n      end\n\n      it \"fails if an unused literal is in begin or ensure body\" do\n        expect_issue subject, <<-CRYSTAL\n          a = begin\n                1234\n              # ^^^^ error: Literal value is unused\n              rescue Foo\n                1234\n              else\n                1234\n              ensure\n                1234\n              # ^^^^ error: Literal value is unused\n              end\n          CRYSTAL\n      end\n\n      it \"fails if an unused literal is in void of class body\" do\n        expect_issue subject, <<-CRYSTAL\n          class MyClass\n            1234\n          # ^^^^ error: Literal value is unused\n          end\n          CRYSTAL\n      end\n\n      it \"passes if an unused method call is the last line of a method with a Nil return type restriction\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo : Nil\n            bar(\"baz\")\n          end\n          CRYSTAL\n      end\n\n      it \"fails if an unused literal is the last line of a method with a Nil return type restriction\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo : Nil\n            1234\n          # ^^^^ error: Literal value is unused\n          end\n          CRYSTAL\n      end\n\n      it \"fails if an unused literal is the last line of an initialize method\" do\n        expect_issue subject, <<-CRYSTAL\n          def initialize\n            1234\n          # ^^^^ error: Literal value is unused\n          end\n          CRYSTAL\n      end\n\n      it \"passes if a literal is used in outputting macro expression\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {{ \"foo\" }}\n          CRYSTAL\n      end\n\n      it \"fails if a literal is used in non-outputting macro expression\" do\n        expect_issue subject, <<-CRYSTAL\n          {% \"foo\" %}\n           # ^^^^^ error: Literal value is unused\n          CRYSTAL\n      end\n\n      it \"fails if a literal is unused in macro expressions inside of a macro if\" do\n        expect_issue subject, <<-CRYSTAL\n          {% if true %}\n            {% \"foo\" %}\n             # ^^^^^ error: Literal value is unused\n          {% end %}\n          CRYSTAL\n      end\n\n      it \"fails if a literal is unused in macro expressions inside of a macro for\" do\n        expect_issue subject, <<-CRYSTAL\n          {% for i in [1, 2, 3] %}\n            {% \"foo\" %}\n             # ^^^^^ error: Literal value is unused\n          {% end %}\n          CRYSTAL\n      end\n\n      it \"fails if a literal is unused in macro defs\" do\n        expect_issue subject, <<-CRYSTAL\n          macro name(foo)\n            {% \"bar\" %}\n             # ^^^^^ error: Literal value is unused\n          end\n          CRYSTAL\n      end\n\n      it \"fails if a regex literal is unused\" do\n        expect_issue subject, <<-'CRYSTAL'\n          foo = /hello world/\n          /goodnight moon/\n          # ^^^^^^^^^^^^^^ error: Literal value is unused\n          bar = /goodnight moon, #{foo}/\n          /goodnight moon, #{foo}/\n          # ^^^^^^^^^^^^^^^^^^^^^^ error: Literal value is unused\n          CRYSTAL\n      end\n    end\n\n    context \"local variable access\" do\n      it \"passes if local variables are used in assign\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo = 1\n          foo += 1\n          foo, bar = 2, 3\n          CRYSTAL\n      end\n\n      it \"passes if a local variable is a call argument\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo = 1\n          puts foo\n          CRYSTAL\n      end\n\n      it \"passes if local variable on left side of a comparison\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def hello\n            foo = 1\n            foo || (puts \"foo is falsey\")\n            foo\n          end\n          CRYSTAL\n      end\n\n      it \"passes if skip_file is used in a macro\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {% skip_file %}\n          CRYSTAL\n      end\n\n      it \"passes if debug is used in a macro\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {% debug %}\n          CRYSTAL\n      end\n\n      it \"fails if a local variable is in a void context\" do\n        expect_issue subject, <<-CRYSTAL\n          foo = 1\n\n          begin\n            foo\n          # ^^^ error: Local variable access is unused\n            puts foo\n          end\n          CRYSTAL\n      end\n\n      it \"fails if a parameter is in a void context\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo(bar)\n            if bar > 0\n              bar\n            # ^^^ error: Local variable access is unused\n            end\n\n            nil\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"pseudo-method call\" do\n      it \"passes if typeof is unused\" do\n        expect_no_issues subject, <<-CRYSTAL\n          typeof(1)\n          CRYSTAL\n      end\n\n      it \"passes if as is unused\" do\n        expect_no_issues subject, <<-CRYSTAL\n          as(Int32)\n          CRYSTAL\n      end\n\n      it \"fails if pointerof is unused\" do\n        expect_issue subject, <<-CRYSTAL\n          pointerof(Int32)\n          # ^^^^^^^^^^^^^^ error: Pseudo-method call is unused\n          CRYSTAL\n      end\n\n      it \"fails if sizeof is unused\" do\n        expect_issue subject, <<-CRYSTAL\n          sizeof(Int32)\n          # ^^^^^^^^^^^ error: Pseudo-method call is unused\n          CRYSTAL\n      end\n\n      it \"fails if instance_sizeof is unused\" do\n        expect_issue subject, <<-CRYSTAL\n          instance_sizeof(Int32)\n          # ^^^^^^^^^^^^^^^^^^^^ error: Pseudo-method call is unused\n          CRYSTAL\n      end\n\n      it \"fails if alignof is unused\" do\n        expect_issue subject, <<-CRYSTAL\n          alignof(Int32)\n          # ^^^^^^^^^^^^ error: Pseudo-method call is unused\n          CRYSTAL\n      end\n\n      it \"fails if instance_alignof is unused\" do\n        expect_issue subject, <<-CRYSTAL\n          instance_alignof(Int32)\n          # ^^^^^^^^^^^^^^^^^^^^^ error: Pseudo-method call is unused\n          CRYSTAL\n      end\n\n      it \"fails if offsetof is unused\" do\n        expect_issue subject, <<-CRYSTAL\n          offsetof(Int32, 1)\n          # ^^^^^^^^^^^^^^^^ error: Pseudo-method call is unused\n          CRYSTAL\n      end\n\n      it \"fails if is_a? is unused\" do\n        expect_issue subject, <<-CRYSTAL\n          foo = 1\n          foo.is_a?(Int32)\n          # ^^^^^^^^^^^^^^ error: Pseudo-method call is unused\n          CRYSTAL\n      end\n\n      it \"fails if as? is unused\" do\n        expect_issue subject, <<-CRYSTAL\n          foo = 1\n          foo.as?(Int32)\n          # ^^^^^^^^^^^^ error: Pseudo-method call is unused\n          CRYSTAL\n      end\n\n      it \"fails if responds_to? is unused\" do\n        expect_issue subject, <<-CRYSTAL\n          foo = 1\n          foo.responds_to?(:bar)\n          # ^^^^^^^^^^^^^^^^^^^^ error: Pseudo-method call is unused\n          CRYSTAL\n      end\n\n      it \"fails if nil? is unused\" do\n        expect_issue subject, <<-CRYSTAL\n          foo = 1\n          foo.nil?\n          # ^^^^^^ error: Pseudo-method call is unused\n          CRYSTAL\n      end\n\n      it \"fails if prefix not is unused\" do\n        expect_issue subject, <<-CRYSTAL\n          foo = 1\n          !foo\n          # ^^ error: Pseudo-method call is unused\n          CRYSTAL\n      end\n\n      it \"fails if suffix not is unused\" do\n        expect_issue subject, <<-CRYSTAL\n          foo = 1\n          foo.!\n          # ^^^ error: Pseudo-method call is unused\n          CRYSTAL\n      end\n\n      it \"passes if pointerof is used as an assign value\" do\n        expect_no_issues subject, <<-CRYSTAL\n          var = pointerof(Int32)\n          CRYSTAL\n      end\n\n      it \"passes if sizeof is used as an assign value\" do\n        expect_no_issues subject, <<-CRYSTAL\n          var = sizeof(Int32)\n          CRYSTAL\n      end\n\n      it \"passes if instance_sizeof is used as an assign value\" do\n        expect_no_issues subject, <<-CRYSTAL\n          var = instance_sizeof(Int32)\n          CRYSTAL\n      end\n\n      it \"passes if alignof is used as an assign value\" do\n        expect_no_issues subject, <<-CRYSTAL\n          var = alignof(Int32)\n          CRYSTAL\n      end\n\n      it \"passes if instance_alignof is used as an assign value\" do\n        expect_no_issues subject, <<-CRYSTAL\n          var = instance_alignof(Int32)\n          CRYSTAL\n      end\n\n      it \"passes if offsetof is used as an assign value\" do\n        expect_no_issues subject, <<-CRYSTAL\n          var = offsetof(Int32, 1)\n          CRYSTAL\n      end\n\n      it \"passes if is_a? is used as an assign value\" do\n        expect_no_issues subject, <<-CRYSTAL\n          var = is_a?(Int32)\n          CRYSTAL\n      end\n\n      it \"passes if as? is used as an assign value\" do\n        expect_no_issues subject, <<-CRYSTAL\n          var = as?(Int32)\n          CRYSTAL\n      end\n\n      it \"passes if responds_to? is used as an assign value\" do\n        expect_no_issues subject, <<-CRYSTAL\n          var = responds_to?(:foo)\n          CRYSTAL\n      end\n\n      it \"passes if nil? is used as an assign value\" do\n        expect_no_issues subject, <<-CRYSTAL\n          var = nil?\n          CRYSTAL\n      end\n\n      it \"passes if prefix not is used as an assign value\" do\n        expect_no_issues subject, <<-CRYSTAL\n          var = !true\n          CRYSTAL\n      end\n\n      it \"passes if suffix not is used as an assign value\" do\n        expect_no_issues subject, <<-CRYSTAL\n          var = true.!\n          CRYSTAL\n      end\n    end\n\n    context \"self access\" do\n      it \"passes if self is used as receiver for a method def\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def self.foo\n          end\n          CRYSTAL\n      end\n\n      it \"passes if self is used as object of call\" do\n        expect_no_issues subject, <<-CRYSTAL\n          self.foo\n          CRYSTAL\n      end\n\n      it \"passes if self is used as method of call\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo.self\n          CRYSTAL\n      end\n\n      it \"fails if self is unused in void context of class body\" do\n        expect_issue subject, <<-CRYSTAL\n          class MyClass\n            self\n          # ^^^^ error: `self` access is unused\n          end\n          CRYSTAL\n      end\n\n      it \"fails if self is unused in void context of begin\" do\n        expect_issue subject, <<-CRYSTAL\n          begin\n            self\n          # ^^^^ error: `self` access is unused\n\n            break\n          end\n          CRYSTAL\n      end\n\n      it \"fails if self is unused in void context of method def\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            self\n          # ^^^^ error: `self` access is unused\n            \"bar\"\n          end\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/unused_rescue_variable_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe UnusedRescueVariable do\n    subject = UnusedRescueVariable.new\n\n    it \"passes if rescue has no variable\" do\n      expect_no_issues subject, <<-CRYSTAL\n        begin\n          raise MyException.new(\"OH NO!\")\n        rescue MyException\n          puts \"Rescued MyException\"\n        end\n        CRYSTAL\n    end\n\n    it \"passes if rescue variable is used\" do\n      expect_no_issues subject, <<-CRYSTAL\n        begin\n          raise MyException.new(\"OH NO!\")\n        rescue ex : MyException\n          puts ex.message\n        end\n        CRYSTAL\n    end\n\n    it \"passes if rescue variable is used in multiple statements\" do\n      expect_no_issues subject, <<-CRYSTAL\n        begin\n          raise MyException.new(\"OH NO!\")\n        rescue ex : MyException\n          puts ex.class\n          puts ex.message\n        end\n        CRYSTAL\n    end\n\n    it \"passes if rescue variable is within `MacroIf`\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        begin\n          raise MyException.new(\"OH NO!\")\n        rescue ex : MyException\n          {% if flag?(:debug) %}\n            STDERR.puts \"Error: #{ex.message}\"\n          {% end %}\n        end\n        CRYSTAL\n    end\n\n    it \"passes if rescue variable is within `MacroFor`\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        begin\n          raise MyException.new(\"OH NO!\")\n        rescue ex : MyException\n          {% for key in %w[foo bar] %}\n            STDERR.puts \"{{ key.id }}: #{ex.message}\"\n          {% end %}\n        end\n        CRYSTAL\n    end\n\n    it \"fails if rescue variable is not used\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n          raise MyException.new(\"OH NO!\")\n        rescue ex : MyException\n             # ^^ error: Unused `rescue` variable `ex`\n          puts \"Rescued MyException\"\n        end\n        CRYSTAL\n    end\n\n    it \"fails if rescue variable is not used with multiple exception types\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n          raise MyException.new(\"OH NO!\")\n        rescue ex : MyException | ArgumentError\n             # ^^ error: Unused `rescue` variable `ex`\n          puts \"Rescued exception\"\n        end\n        CRYSTAL\n    end\n\n    it \"fails if rescue variable is not used with generic exception type\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n          raise Exception.new(\"OH NO!\")\n        rescue ex : Exception\n             # ^^ error: Unused `rescue` variable `ex`\n          puts \"Rescued Exception\"\n        end\n        CRYSTAL\n    end\n\n    it \"passes when variable is used in nested block\" do\n      expect_no_issues subject, <<-CRYSTAL\n        begin\n          raise MyException.new(\"OH NO!\")\n        rescue ex : MyException\n          [1, 2, 3].each do |i|\n            puts ex.message\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"fails when variable is shadowed by nested block parameter\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n          raise MyException.new(\"OH NO!\")\n        rescue ex : MyException\n             # ^^ error: Unused `rescue` variable `ex`\n          ([1, 2, 3]).each do |ex|\n            puts ex\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"fails when variable is shadowed by an uninitialized variable\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n          raise MyException.new(\"OH NO!\")\n        rescue ex : MyException\n             # ^^ error: Unused `rescue` variable `ex`\n          ex = uninitialized ArgumentError\n        end\n        CRYSTAL\n    end\n\n    it \"fails when variable is shadowed by an assignment\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n          raise MyException.new(\"OH NO!\")\n        rescue ex : MyException\n             # ^^ error: Unused `rescue` variable `ex`\n          ex = 42\n        end\n        CRYSTAL\n    end\n\n    it \"fails when variable is shadowed by an multiple assignment\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n          raise MyException.new(\"OH NO!\")\n        rescue ex : MyException\n             # ^^ error: Unused `rescue` variable `ex`\n          ex, ox = 42, 24\n        end\n        CRYSTAL\n    end\n\n    it \"passes when variable is used in interpolation\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        begin\n          raise MyException.new(\"OH NO!\")\n        rescue ex : MyException\n          puts \"Error: #{ex.message}\"\n        end\n        CRYSTAL\n    end\n\n    it \"handles multiple rescue blocks correctly\" do\n      expect_issue subject, <<-CRYSTAL\n        begin\n          raise MyException.new(\"OH NO!\")\n        rescue ex : MyException\n             # ^^ error: Unused `rescue` variable `ex`\n          puts \"Rescued MyException\"\n        rescue e : ArgumentError\n          puts e.message\n        end\n        CRYSTAL\n    end\n\n    it \"passes when all rescue blocks use their variables\" do\n      expect_no_issues subject, <<-CRYSTAL\n        begin\n          raise MyException.new(\"OH NO!\")\n        rescue ex : MyException\n          puts ex.message\n        rescue e : ArgumentError\n          puts e.message\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/useless_assign_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe UselessAssign do\n    subject = UselessAssign.new\n\n    it \"does not report used assignments\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method\n          foo = 2\n          foo\n        end\n        CRYSTAL\n    end\n\n    it \"reports a useless assignment in a method\" do\n      expect_issue subject, <<-CRYSTAL\n        def method\n          foo = 2\n        # ^^^ error: Useless assignment to variable `foo`\n        end\n        CRYSTAL\n    end\n\n    it \"reports a useless assignment in a proc\" do\n      expect_issue subject, <<-CRYSTAL\n        -> {\n          foo = 2\n        # ^^^ error: Useless assignment to variable `foo`\n        }\n        CRYSTAL\n    end\n\n    it \"reports a useless assignment in a proc passed as an argument to a call\" do\n      expect_issue subject, <<-CRYSTAL\n        foo -> {\n          bar = 1\n        # ^^^ error: Useless assignment to variable `bar`\n        }\n        CRYSTAL\n    end\n\n    it \"reports a useless assignment in a proc passed as a named argument to a call\" do\n      expect_issue subject, <<-CRYSTAL\n        foo bar: -> {\n          baz = 1\n        # ^^^ error: Useless assignment to variable `baz`\n        }\n        CRYSTAL\n    end\n\n    it \"reports a useless assignment nested within an argument to a call\" do\n      expect_issue subject, <<-CRYSTAL\n        foo Proc(Nil).new do\n          bar %w[foo bar].map do |v|\n            baz = 1\n          # ^^^ error: Useless assignment to variable `baz`\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"reports a useless assignment nested within an argument to a call (2)\" do\n      expect_issue subject, <<-CRYSTAL\n        foo Proc(Nil).new do\n          bar(%w[foo bar].map do |v|\n            baz = 1\n          # ^^^ error: Useless assignment to variable `baz`\n          end)\n        end\n        CRYSTAL\n    end\n\n    it \"reports a useless assignment in a block\" do\n      expect_issue subject, <<-CRYSTAL\n        def method\n          3.times do\n            foo = 1\n          # ^^^ error: Useless assignment to variable `foo`\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"reports a useless assignment in a proc inside def\" do\n      expect_issue subject, <<-CRYSTAL\n        def method\n          -> {\n            foo = 2\n          # ^^^ error: Useless assignment to variable `foo`\n          }\n        end\n        CRYSTAL\n    end\n\n    it \"does not report ignored assignments\" do\n      expect_no_issues subject, <<-CRYSTAL\n        payload, _header = decode\n        puts payload\n        CRYSTAL\n    end\n\n    it \"reports a useless assignment in a proc inside a block\" do\n      expect_issue subject, <<-CRYSTAL\n        def method\n          3.times do\n            -> {\n              foo = 2\n            # ^^^ error: Useless assignment to variable `foo`\n            }\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if variable is used by bare super\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(bar)\n          bar = super\n          bar\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if variable is used by bare previous_def\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(bar)\n          bar = previous_def\n          bar\n        end\n        CRYSTAL\n    end\n\n    it \"reports useless assignment before super with explicit args\" do\n      expect_issue subject, <<-CRYSTAL\n        def foo(bar)\n          baz = 1\n        # ^^^ error: Useless assignment to variable `baz`\n          super(42)\n        end\n        CRYSTAL\n    end\n\n    it \"does not report useless assignment of instance var\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Cls\n          def initialize(@name)\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if assignment used in the inner block scope\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method\n          var = true\n          3.times { var = false }\n        end\n        CRYSTAL\n    end\n\n    it \"reports if assigned is not referenced in the inner block scope\" do\n      expect_issue subject, <<-CRYSTAL\n        def method\n          var = true\n        # ^^^ error: Useless assignment to variable `var`\n          3.times {}\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if assignment in referenced in inner block\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method\n          two = true\n\n          3.times do\n            mutex.synchronize do\n              two = 2\n            end\n          end\n\n          two.should be_true\n        end\n        CRYSTAL\n    end\n\n    it \"reports if first assignment is useless\" do\n      expect_issue subject, <<-CRYSTAL\n        def method\n          var = true\n        # ^^^ error: Useless assignment to variable `var`\n          var = false\n          var\n        end\n        CRYSTAL\n    end\n\n    it \"reports if variable reassigned and not used\" do\n      expect_issue subject, <<-CRYSTAL\n        def method\n          var = true\n        # ^^^ error: Useless assignment to variable `var`\n          var = false\n        # ^^^ error: Useless assignment to variable `var`\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if variable used in a condition\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method\n          a = 1\n          if a\n            nil\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"reports second assignment as useless\" do\n      expect_issue subject, <<-CRYSTAL\n        def method\n          a = 1\n          a = a + 1\n        # ^ error: Useless assignment to variable `a`\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if variable is referenced in other assignment\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method\n          if f = get_something\n            @f = f\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if variable is referenced in a setter\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method\n          foo = 2\n          table[foo] ||= \"bar\"\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if variable is reassigned but not referenced\" do\n      expect_issue subject, <<-CRYSTAL\n        def method\n          foo = 1\n          puts foo\n          foo = 2\n        # ^^^ error: Useless assignment to variable `foo`\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if variable is referenced in a call\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method\n          if f = FORMATTER\n            @formatter = f.new\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if a setter is invoked with operator assignment\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method\n          obj = {} of Symbol => Int32\n          obj[:name] = 3\n        end\n        CRYSTAL\n    end\n\n    context \"block unpacking\" do\n      it \"does not report if the first arg is transformed and not used\" do\n        expect_no_issues subject, <<-CRYSTAL\n          collection.each do |(a, b)|\n            puts b\n          end\n          CRYSTAL\n      end\n\n      it \"does not report if the second arg is transformed and not used\" do\n        expect_no_issues subject, <<-CRYSTAL\n          collection.each do |(a, b)|\n            puts a\n          end\n          CRYSTAL\n      end\n\n      it \"does not report if all transformed args are not used in a block\" do\n        expect_no_issues subject, <<-CRYSTAL\n          collection.each do |(foo, bar), (baz, _qux), index, object|\n          end\n          CRYSTAL\n      end\n    end\n\n    it \"does not report if assignment is referenced in a proc\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method\n          called = false\n          -> { called = true }\n          called\n        end\n        CRYSTAL\n    end\n\n    it \"reports if variable is shadowed in inner scope\" do\n      expect_issue subject, <<-CRYSTAL\n        def method\n          i = 1\n        # ^ error: Useless assignment to variable `i`\n          3.times do |i|\n            i + 1\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if parameter is referenced after the branch\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method(param)\n          3.times do\n            param = 3\n          end\n          param\n        end\n        CRYSTAL\n    end\n\n    describe \"is aware of separate variable scopes (#623)\" do\n      it \"def\" do\n        expect_issue subject, <<-CRYSTAL\n          x = 1\n\n          def bar\n            x = 2\n          # ^ error: Useless assignment to variable `x`\n          end\n\n          puts x\n          CRYSTAL\n      end\n\n      it \"fun\" do\n        expect_issue subject, <<-CRYSTAL\n          x = 1\n\n          fun bar\n            x = 2\n          # ^ error: Useless assignment to variable `x`\n          end\n\n          puts x\n          CRYSTAL\n      end\n\n      it \"macro\" do\n        expect_no_issues subject, <<-CRYSTAL\n          x = 1\n\n          macro bar\n            x = 2\n          end\n\n          puts x\n          CRYSTAL\n      end\n\n      context \"assigns\" do\n        it \"does not report outer variable used after const initializer (#632)\" do\n          expect_no_issues subject, <<-CRYSTAL\n            x = 1\n            BAR = begin\n              42\n            end\n            puts x\n            CRYSTAL\n        end\n\n        it \"path\" do\n          expect_issue subject, <<-CRYSTAL\n            x = 1\n\n            BAR = begin\n              x = 2\n            # ^ error: Useless assignment to variable `x`\n            end\n\n            puts x\n            CRYSTAL\n        end\n\n        it \"ivar\" do\n          expect_issue subject, <<-CRYSTAL\n            class Foo\n              x = 1\n\n              @bar = begin\n                x = 2\n              # ^ error: Useless assignment to variable `x`\n              end\n\n              puts x\n            end\n            CRYSTAL\n        end\n\n        it \"ivar in def\" do\n          expect_issue subject, <<-CRYSTAL\n            class Foo\n              def foo\n                x = 1\n              # ^ error: Useless assignment to variable `x`\n\n                @bar = begin\n                  x = 2\n                end\n\n                puts x\n              end\n            end\n            CRYSTAL\n        end\n\n        it \"cvar\" do\n          expect_issue subject, <<-CRYSTAL\n            class Foo\n              x = 1\n\n              @@bar = begin\n                x = 2\n              # ^ error: Useless assignment to variable `x`\n              end\n\n              puts x\n            end\n            CRYSTAL\n        end\n\n        it \"cvar in def\" do\n          expect_issue subject, <<-CRYSTAL\n            class Foo\n              def foo\n                x = 1\n              # ^ error: Useless assignment to variable `x`\n\n                @@bar = begin\n                  x = 2\n                end\n\n                puts x\n              end\n            end\n            CRYSTAL\n        end\n\n        it \"does not report inner variable used within initializer\" do\n          expect_no_issues subject, <<-CRYSTAL\n            BAR = begin\n              x = 2\n              x + 1\n            end\n            CRYSTAL\n        end\n\n        it \"path in class body\" do\n          expect_issue subject, <<-CRYSTAL\n            class Foo\n              x = 1\n\n              BAR = begin\n                x = 2\n              # ^ error: Useless assignment to variable `x`\n              end\n\n              puts x\n            end\n            CRYSTAL\n        end\n\n        it \"does not report unrelated variables\" do\n          expect_issue subject, <<-CRYSTAL\n            x = 1\n            y = 2\n\n            BAR = begin\n              x = 3\n            # ^ error: Useless assignment to variable `x`\n            end\n\n            puts x\n            puts y\n            CRYSTAL\n        end\n\n        it \"cvar op-assign\" do\n          expect_issue subject, <<-CRYSTAL\n            class Foo\n              x = 1\n\n              @@bar ||= begin\n                x = 2\n              # ^ error: Useless assignment to variable `x`\n              end\n\n              puts x\n            end\n            CRYSTAL\n        end\n      end\n    end\n\n    context \"op assigns\" do\n      it \"does not report if variable is referenced below the op assign\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def method\n            a = 1\n            a += 1\n            a\n          end\n          CRYSTAL\n      end\n\n      it \"does not report if variable is referenced in op assign few times\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def method\n            a = 1\n            a += 1\n            a += 1\n            a = a + 1\n            a\n          end\n          CRYSTAL\n      end\n\n      it \"reports if variable is not referenced below the op assign\" do\n        expect_issue subject, <<-CRYSTAL\n          def method\n            a = 1\n            a += 1\n          # ^ error: Useless assignment to variable `a`\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"multi assigns\" do\n      it \"does not report if all assigns are referenced\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def method\n            a, b = {1, 2}\n            a + b\n          end\n          CRYSTAL\n      end\n\n      it \"reports if one assign is not referenced\" do\n        expect_issue subject, <<-CRYSTAL\n          def method\n            a, b = {1, 2}\n             # ^ error: Useless assignment to variable `b`\n            a\n          end\n          CRYSTAL\n      end\n\n      it \"reports if both assigns are reassigned and useless\" do\n        expect_issue subject, <<-CRYSTAL\n          def method\n            a, b = {1, 2}\n          # ^ error: Useless assignment to variable `a`\n             # ^ error: Useless assignment to variable `b`\n            a, b = {3, 4}\n          # ^ error: Useless assignment to variable `a`\n             # ^ error: Useless assignment to variable `b`\n          end\n          CRYSTAL\n      end\n\n      it \"reports if both assigns are not referenced\" do\n        expect_issue subject, <<-CRYSTAL\n          def method\n            a, b = {1, 2}\n          # ^ error: Useless assignment to variable `a`\n             # ^ error: Useless assignment to variable `b`\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"top level\" do\n      it \"reports if assignment is not referenced\" do\n        expect_issue subject, <<-CRYSTAL\n          a = 1\n          # ^{} error: Useless assignment to variable `a`\n          a = 2\n          # ^{} error: Useless assignment to variable `a`\n          CRYSTAL\n      end\n\n      it \"doesn't report if assignments are referenced\" do\n        expect_no_issues subject, <<-CRYSTAL\n          a = 1\n          a += 1\n          a\n\n          b, c = {1, 2}\n          b\n          c\n          CRYSTAL\n      end\n\n      it \"doesn't report if assignment is captured by block\" do\n        expect_no_issues subject, <<-CRYSTAL\n          a = 1\n\n          3.times do\n            a = 2\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if assignment initialized and captured by block\" do\n        expect_no_issues subject, <<-CRYSTAL\n          a : String? = nil\n\n          1.times do\n            a = \"Fotis\"\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report record declaration\" do\n        expect_no_issues subject, <<-CRYSTAL\n          record Foo, foo : String\n          record Foo, foo = \"foo\"\n          CRYSTAL\n      end\n\n      it \"doesn't report record declarations (generics)\" do\n        expect_no_issues subject, <<-CRYSTAL\n          record Foo(T), foo : T\n          record Foo(T), foo = T.new\n          CRYSTAL\n      end\n\n      it \"doesn't report type declaration as a call argument\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo Foo(T), foo : T\n          foo Foo, foo : Nil\n          foo foo : String\n          foo foo : String, bar : Int32?, baz : Bool\n          foo bar : String = \"\"\n          foo bar : String = baz\n          foo bar = baz\n          foo bar = \"\"\n          CRYSTAL\n      end\n\n      it \"doesn't report if variable is used inside an array literal in a call\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def method\n            source = get_source\n            run([source])\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report special variables like $~\" do\n        expect_no_issues subject, <<-CRYSTAL\n          class Regex\n            def ===(other)\n              value = self === other.raw\n              $~ = $~\n              value\n            end\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report accessor declarations\" do\n        accessor_macros = %w[setter class_setter]\n        %w[getter class_getter property class_property].each do |name|\n          accessor_macros << name\n          accessor_macros << \"#{name}?\"\n          accessor_macros << \"#{name}!\"\n        end\n        accessor_macros.each do |accessor|\n          expect_no_issues subject, <<-CRYSTAL\n            class Foo\n              #{accessor} foo : String?\n              #{accessor} bar = \"bar\"\n            end\n            CRYSTAL\n        end\n      end\n\n      it \"does not report if assignment is referenced after the record declaration\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo = 2\n          record Bar, foo = 3 # foo = 3 is not parsed as assignment\n          puts foo\n          CRYSTAL\n      end\n\n      it \"reports if assignment is not referenced after the record declaration\" do\n        expect_issue subject, <<-CRYSTAL\n          foo = 2\n          # ^ error: Useless assignment to variable `foo`\n          record Bar, foo = 3\n          CRYSTAL\n      end\n\n      it \"doesn't report if type declaration assigned inside module and referenced\" do\n        expect_no_issues subject, <<-CRYSTAL\n          module A\n            foo : String? = \"foo\"\n\n            bar do\n              foo = \"bar\"\n            end\n\n            p foo\n          end\n          CRYSTAL\n      end\n\n      it \"reports if type declaration assigned inside class\" do\n        expect_issue subject, <<-CRYSTAL\n          class A\n            foo : String? = \"foo\"\n          # ^^^ error: Useless assignment to variable `foo`\n\n            def method\n              foo = \"bar\"\n            # ^^^ error: Useless assignment to variable `foo`\n            end\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"branching\" do\n      context \"if-then-else\" do\n        it \"reports initial assignment as dead when overwritten in all branches\" do\n          expect_issue subject, <<-CRYSTAL\n            def method\n              a = 0\n            # ^ error: Useless assignment to variable `a`\n              if something\n                a = 1\n              else\n                a = 2\n              end\n              a\n            end\n            CRYSTAL\n        end\n\n        it \"doesn't report if assignment is in one branch\" do\n          expect_no_issues subject, <<-CRYSTAL\n            def method\n              a = 0\n              if something\n                a = 1\n              else\n                nil\n              end\n              a\n            end\n            CRYSTAL\n        end\n\n        it \"doesn't report if assignment is in one line branch\" do\n          expect_no_issues subject, <<-CRYSTAL\n            def method\n              a = 0\n              a = 1 if something\n              a\n            end\n            CRYSTAL\n        end\n\n        it \"doesn't report if assignment is referenced within a method call\" do\n          expect_no_issues subject, <<-CRYSTAL\n            if v = rand\n              puts(v = 1)\n            end\n            v\n            CRYSTAL\n\n          expect_no_issues subject, <<-CRYSTAL\n            puts v = 1 unless v = rand\n            v\n            CRYSTAL\n        end\n\n        it \"reports if assignment is useless in the branch\" do\n          expect_issue subject, <<-CRYSTAL\n            def method(a)\n              if a\n                a = 2\n              # ^ error: Useless assignment to variable `a`\n              end\n            end\n            CRYSTAL\n        end\n\n        it \"reports if only last assignment is referenced in a branch\" do\n          expect_issue subject, <<-CRYSTAL\n            def method(a)\n              a = 1\n              if a\n                a = 2\n              # ^ error: Useless assignment to variable `a`\n                a = 3\n              end\n              a\n            end\n            CRYSTAL\n        end\n\n        it \"does not report of assignments are referenced in all branches\" do\n          expect_no_issues subject, <<-CRYSTAL\n            def method\n              if matches\n                matches = owner.lookup_matches signature\n              else\n                matches = owner.lookup_matches signature\n              end\n              matches\n            end\n            CRYSTAL\n        end\n\n        it \"reports initial assignment as dead when overwritten in all branches\" do\n          expect_issue subject, <<-CRYSTAL\n            def method\n              has_newline = false\n            # ^^^^^^^^^^^ error: Useless assignment to variable `has_newline`\n\n              if something\n                do_something unless false\n                has_newline = false\n              else\n                do_something if true\n                has_newline = true\n              end\n\n              has_newline\n            end\n            CRYSTAL\n        end\n      end\n\n      context \"unless-then-else\" do\n        it \"reports initial assignment as dead when overwritten in all branches\" do\n          expect_issue subject, <<-CRYSTAL\n            def method\n              a = 0\n            # ^ error: Useless assignment to variable `a`\n              unless something\n                a = 1\n              else\n                a = 2\n              end\n              a\n            end\n            CRYSTAL\n        end\n\n        it \"reports if there is a useless assignment in a branch\" do\n          expect_issue subject, <<-CRYSTAL\n            def method\n              a = 0\n            # ^ error: Useless assignment to variable `a`\n              unless something\n                a = 1\n              # ^ error: Useless assignment to variable `a`\n                a = 2\n              else\n                a = 2\n              end\n              a\n            end\n            CRYSTAL\n        end\n      end\n\n      context \"case\" do\n        context \"when\" do\n          it \"does not report if assignment is referenced\" do\n            expect_no_issues subject, <<-CRYSTAL\n              def method(a)\n                case\n                when a = foo\n                when a = bar\n                end\n                puts a\n              end\n              CRYSTAL\n          end\n\n          it \"reports if assignment is useless\" do\n            expect_issue subject, <<-CRYSTAL\n              def method(a)\n                case\n                when a = foo\n                   # ^ error: Useless assignment to variable `a`\n                when a = bar\n                   # ^ error: Useless assignment to variable `a`\n                end\n              end\n              CRYSTAL\n          end\n        end\n\n        it \"does not report if assignment is referenced\" do\n          expect_no_issues subject, <<-CRYSTAL\n            def method(a)\n              case a\n              when /foo/\n                a = 1\n              when /bar/\n                a = 2\n              end\n              puts a\n            end\n            CRYSTAL\n        end\n\n        it \"reports if assignment is useless\" do\n          expect_issue subject, <<-CRYSTAL\n            def method(a)\n              case a\n              when /foo/\n                a = 1\n              # ^ error: Useless assignment to variable `a`\n              when /bar/\n                a = 2\n              # ^ error: Useless assignment to variable `a`\n              end\n            end\n            CRYSTAL\n        end\n\n        it \"doesn't report if assignment is referenced in cond\" do\n          expect_no_issues subject, <<-CRYSTAL\n            def method\n              a = 2\n              case a\n              when /foo/\n              end\n            end\n            CRYSTAL\n        end\n      end\n\n      context \"select\" do\n        context \"when\" do\n          it \"does not report if assignment is referenced\" do\n            expect_no_issues subject, <<-CRYSTAL\n              def method(a)\n                select\n                when a = foo\n                when a = bar\n                end\n                puts a\n              end\n              CRYSTAL\n          end\n\n          it \"reports if assignment is useless\" do\n            expect_issue subject, <<-CRYSTAL\n              def method(a)\n                select\n                when a = foo\n                   # ^ error: Useless assignment to variable `a`\n                when a = bar\n                   # ^ error: Useless assignment to variable `a`\n                end\n              end\n              CRYSTAL\n          end\n        end\n      end\n\n      context \"binary operator\" do\n        it \"does not report if assignment is referenced\" do\n          expect_no_issues subject, <<-CRYSTAL\n            def method(a)\n              (a = 1) && (b = 1)\n              a + b\n            end\n            CRYSTAL\n        end\n\n        it \"reports if assignment is useless\" do\n          expect_issue subject, <<-CRYSTAL\n            def method(a)\n              (a = 1) || (b = 1)\n                        # ^ error: Useless assignment to variable `b`\n              a\n            end\n            CRYSTAL\n        end\n      end\n\n      context \"while\" do\n        it \"does not report if assignment is referenced\" do\n          expect_no_issues subject, <<-CRYSTAL\n            def method(a)\n              while a < 10\n                a = a + 1\n              end\n              a\n            end\n            CRYSTAL\n        end\n\n        it \"reports if assignment is useless\" do\n          expect_issue subject, <<-CRYSTAL\n            def method(a)\n              while a < 10\n                b = a\n              # ^ error: Useless assignment to variable `b`\n              end\n            end\n            CRYSTAL\n        end\n\n        it \"does not report if assignment is referenced in a loop\" do\n          expect_no_issues subject, <<-CRYSTAL\n            def method\n              a = 3\n              result = 0\n\n              while result < 10\n                result += a\n                a = a + 1\n              end\n              result\n            end\n            CRYSTAL\n        end\n\n        it \"does not report if assignment is referenced as param in a loop\" do\n          expect_no_issues subject, <<-CRYSTAL\n            def method(a)\n              result = 0\n\n              while result < 10\n                result += a\n                a = a + 1\n              end\n              result\n            end\n            CRYSTAL\n        end\n\n        it \"does not report if assignment is referenced in loop and inner branch\" do\n          expect_no_issues subject, <<-CRYSTAL\n            def method(a)\n              result = 0\n\n              while result < 10\n                result += a\n                if result > 0\n                  a = a + 1\n                else\n                  a = 3\n                end\n              end\n              result\n            end\n            CRYSTAL\n        end\n\n        it \"works properly if there is branch with blank node\" do\n          expect_no_issues subject, <<-CRYSTAL\n            def visit\n              count = 0\n              while true\n                break if count == 1\n                case something\n                when :any\n                else\n                  :anything_else\n                end\n                count += 1\n              end\n            end\n            CRYSTAL\n        end\n\n        it \"does not report if assignment is used after break\" do\n          expect_no_issues subject, <<-CRYSTAL\n            def method\n              found = false\n              while true\n                if something\n                  found = true\n                  break\n                end\n              end\n              found\n            end\n            CRYSTAL\n        end\n\n        it \"does not report if assignment before next is used in next iteration\" do\n          expect_no_issues subject, <<-CRYSTAL\n            def method\n              atomic = parse_atomic\n              while true\n                if @token.instance_var?\n                  atomic = parse_ivar(atomic)\n                  next\n                end\n                break\n              end\n              atomic\n            end\n            CRYSTAL\n        end\n      end\n\n      context \"until\" do\n        it \"does not report if assignment is referenced\" do\n          expect_no_issues subject, <<-CRYSTAL\n            def method(a)\n              until a > 10\n                a = a + 1\n              end\n              a\n            end\n            CRYSTAL\n        end\n\n        it \"reports if assignment is useless\" do\n          expect_issue subject, <<-CRYSTAL\n            def method(a)\n              until a > 10\n                b = a + 1\n              # ^ error: Useless assignment to variable `b`\n              end\n            end\n            CRYSTAL\n        end\n      end\n\n      context \"exception handler\" do\n        it \"does not report if assignment is referenced in body\" do\n          expect_no_issues subject, <<-CRYSTAL\n            def method(a)\n              a = 2\n            rescue\n              a\n            end\n            CRYSTAL\n        end\n\n        it \"doesn't report if assignment is referenced in ensure\" do\n          expect_no_issues subject, <<-CRYSTAL\n            def method(a)\n              a = 2\n            ensure\n              a\n            end\n            CRYSTAL\n        end\n\n        it \"doesn't report if assignment is referenced in else\" do\n          expect_no_issues subject, <<-CRYSTAL\n            def method(a)\n              a = 2\n            rescue\n            else\n              a\n            end\n            CRYSTAL\n        end\n\n        it \"reports if assignment is useless\" do\n          expect_issue subject, <<-CRYSTAL\n            def method(a)\n            rescue\n              a = 2\n            # ^ error: Useless assignment to variable `a`\n            end\n            CRYSTAL\n        end\n\n        it \"does not report if variable is referenced in rescue with break in body\" do\n          expect_no_issues subject, <<-'CRYSTAL'\n            3.times do\n              start = Time.instant\n              begin\n                perform_foo\n                break\n              rescue IO::TimeoutError\n                puts \"Timeout [#{start.elapsed.to_i}s]\"\n              end\n            end\n            CRYSTAL\n        end\n      end\n    end\n\n    context \"typeof\" do\n      it \"reports useless assignments in typeof\" do\n        expect_issue subject, <<-CRYSTAL\n          typeof(begin\n            foo = 1\n          # ^^^ error: Useless assignment to variable `foo`\n            bar = 2\n          # ^^^ error: Useless assignment to variable `bar`\n          end)\n          CRYSTAL\n      end\n    end\n\n    context \"macro\" do\n      it \"doesn't report if assignment is referenced in macro\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def method\n            a = 2\n            {% if flag?(:bits64) %}\n              a.to_s\n            {% else %}\n              a\n            {% end %}\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report referenced assignments in macro literal\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def method\n            a = 2\n            {% if flag?(:bits64) %}\n              a = 3\n            {% else %}\n              a = 4\n            {% end %}\n            puts a\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if assignment is referenced in macro def\" do\n        expect_no_issues subject, <<-CRYSTAL\n          macro macro_call\n            puts x\n          end\n\n          def foo\n            x = 1\n            macro_call\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if assignment is referenced in a macro below\" do\n        expect_no_issues subject, <<-CRYSTAL\n          class Foo\n            def foo\n              a = 1\n              macro_call\n            end\n\n            macro macro_call\n              puts a\n            end\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if assignment is referenced in a macro expression as string\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo = 1\n          puts {{ \"foo\".id }}\n          CRYSTAL\n      end\n\n      it \"doesn't report if assignment is referenced in for macro in exp\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo = 22\n\n          {% for x in %w[foo] %}\n            add({{ x.id }})\n          {% end %}\n          CRYSTAL\n      end\n\n      it \"doesn't report if assignment is referenced in for macro in body\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo = 22\n\n          {% for x in %w[bar] %}\n            puts {{ \"foo\".id }}\n          {% end %}\n          CRYSTAL\n      end\n\n      it \"doesn't report if assignment is referenced in if macro in cond\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo = 22\n\n          {% if \"foo\".id %}\n          {% end %}\n          CRYSTAL\n      end\n\n      it \"doesn't report if assignment is referenced in if macro in then\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo = 22\n\n          {% if true %}\n             puts {{ \"foo\".id }}\n          {% end %}\n          CRYSTAL\n      end\n\n      it \"doesn't report if assignment is referenced in if macro in else\" do\n        expect_no_issues subject, <<-CRYSTAL\n          foo = 22\n\n          {% if true %}\n          {% else %}\n             puts {{ \"foo\".id }}\n          {% end %}\n          CRYSTAL\n      end\n    end\n\n    it \"does not report if variable is referenced and there is a deep level scope\" do\n      expect_no_issues subject, <<-CRYSTAL\n        response = JSON.build do |json|\n          json.object do\n            json.object do\n              json.object do\n                json.object do\n                  json.object do\n                    json.object do\n                      json.object do\n                        json.object do\n                          json.object do\n                            json.object do\n                              json.object do\n                                json.object do\n                                  json.object do\n                                    json.object do\n                                      json.object do\n                                        anything\n                                      end\n                                    end\n                                  end\n                                end\n                              end\n                            end\n                          end\n                        end\n                      end\n                    end\n                  end\n                end\n              end\n            end\n          end\n        end\n\n        response = JSON.parse(response)\n        response\n        CRYSTAL\n    end\n\n    context \"type declaration\" do\n      it \"doesn't report if it's not referenced at a top level\" do\n        expect_no_issues subject, <<-CRYSTAL\n          a : String?\n          CRYSTAL\n      end\n\n      it \"doesn't report if it's not referenced at a top level + in a method\" do\n        expect_no_issues subject, <<-CRYSTAL\n          a : String?\n\n          def foo\n            b : String?\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if it's not referenced in a method\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            a : String?\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if it's not referenced in a class\" do\n        expect_no_issues subject, <<-CRYSTAL\n          class Foo\n            a : String?\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if it's referenced in a lib\" do\n        expect_no_issues subject, <<-CRYSTAL\n          lib LibFoo\n            struct Foo\n              a : Int32\n            end\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if it's referenced\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            a : String?\n            a\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if it's used after conditional assignment\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            a : Foo?\n\n            if bar?\n              a = Foo.new\n            else\n              a = nil\n            end\n\n            puts a\n          end\n          CRYSTAL\n      end\n\n      it \"reports if type declaration with value is not referenced\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            a : String? = \"foo\"\n          # ^ error: Useless assignment to variable `a`\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"uninitialized\" do\n      it \"reports if uninitialized assignment is not referenced at a top level\" do\n        expect_issue subject, <<-CRYSTAL\n          a = uninitialized U\n          # ^{} error: Useless assignment to variable `a`\n          CRYSTAL\n      end\n\n      it \"reports if uninitialized assignment is not referenced at a top level + in a method\" do\n        expect_issue subject, <<-CRYSTAL\n          a = uninitialized U\n          # ^{} error: Useless assignment to variable `a`\n\n          def foo\n            b = uninitialized U\n          # ^ error: Useless assignment to variable `b`\n          end\n          CRYSTAL\n      end\n\n      it \"reports if uninitialized assignment is not referenced in a method\" do\n        expect_issue subject, <<-CRYSTAL\n          def foo\n            a = uninitialized U\n          # ^ error: Useless assignment to variable `a`\n          end\n          CRYSTAL\n      end\n\n      it \"doesn't report if uninitialized assignment is referenced\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            a = uninitialized U\n            a\n          end\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/useless_condition_in_when_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe UselessConditionInWhen do\n    subject = UselessConditionInWhen.new\n\n    it \"passes if there is not useless condition\" do\n      expect_no_issues subject, <<-CRYSTAL\n        case\n        when utc?\n          io << \" UTC\"\n        when local?\n          Format.new(\" %:z\").format(self, io) if utc?\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is useless if condition\" do\n      expect_issue subject, <<-CRYSTAL\n        case\n        when utc?\n          io << \" UTC\" if utc?\n                        # ^^^^ error: Useless condition in `when` detected\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/useless_visibility_modifier_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe UselessVisibilityModifier do\n    subject = UselessVisibilityModifier.new\n\n    it \"passes for procs\" do\n      expect_no_issues subject, <<-CRYSTAL\n        -> { nil }\n        CRYSTAL\n    end\n\n    it \"passes for definitions with a receiver\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n        end\n\n        protected def Foo.foo\n        end\n        CRYSTAL\n    end\n\n    it \"passes for calls\" do\n      expect_no_issues subject, <<-CRYSTAL\n        record Foo do\n          protected def foo\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"passes if a `protected` method visibility modifier is not used\" do\n      expect_no_issues subject, <<-CRYSTAL\n        private def foo; end\n        def bar; end\n        CRYSTAL\n    end\n\n    {% for keyword in %w[enum class module].map(&.id) %}\n      it \"passes if a `protected` method visibility modifier is used within a {{ keyword }}\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {{ keyword }} Foo\n            protected def foo\n            end\n          end\n          CRYSTAL\n      end\n    {% end %}\n\n    it \"fails if a `protected` method visibility modifier is used at the top level\" do\n      source = expect_issue subject, <<-CRYSTAL\n        protected def foo\n                # ^^^^^^^ error: Useless visibility modifier\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def foo\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/void_outside_lib_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe VoidOutsideLib do\n    subject = VoidOutsideLib.new\n\n    it \"passes if Void is used in a fun def\" do\n      expect_no_issues subject, <<-CRYSTAL\n        lib LibFoo\n          fun foo(foo : Void) : Void\n          fun bar(bar : Void*) : Void\n        end\n        CRYSTAL\n    end\n\n    it \"passes if `Pointer(Void)` is used as a parameter type restriction\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(bar : Pointer(Void))\n        end\n        CRYSTAL\n    end\n\n    it \"fails if `Void` is used as a parameter type restriction\" do\n      expect_issue subject, <<-CRYSTAL\n        def foo(bar : Void)\n                    # ^^^^ error: `Void` is not allowed in this context\n        end\n        CRYSTAL\n    end\n\n    it \"passes if `Pointer(Void)` is used as return type restriction\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(bar) : Pointer(Void)\n        end\n        CRYSTAL\n    end\n\n    it \"passes if `Pointer(Void) | Nil` is used as return type restriction\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(bar) : Pointer(Void) | Nil\n        end\n        CRYSTAL\n    end\n\n    it \"fails if `Pointer(Void | Int32)` is used as return type restriction\" do\n      expect_issue subject, <<-CRYSTAL\n        def foo(bar) : Pointer(Void | Int32)\n                             # ^^^^ error: `Void` is not allowed in this context\n        end\n        CRYSTAL\n    end\n\n    it \"fails if `Array(Void)` is used as return type restriction\" do\n      expect_issue subject, <<-CRYSTAL\n        def foo(bar) : Array(Void)\n                           # ^^^^ error: `Void` is not allowed in this context\n        end\n        CRYSTAL\n    end\n\n    it \"passes if Void is used as name of a class\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          class Void\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"fails if Void is inherited from\" do\n      expect_issue subject, <<-CRYSTAL\n        struct Foo < Void\n                   # ^^^^ error: `Void` is not allowed in this context\n        end\n        CRYSTAL\n    end\n\n    it \"passes if Void is name of alias\" do\n      expect_no_issues subject, <<-CRYSTAL\n        alias Void = Foo\n        CRYSTAL\n    end\n\n    it \"fails if Void is value of alias\" do\n      expect_issue subject, <<-CRYSTAL\n        alias Foo = Void\n                  # ^^^^ error: `Void` is not allowed in this context\n        CRYSTAL\n    end\n\n    it \"fails if `Void` is used for an uninitialized var\" do\n      expect_issue subject, <<-CRYSTAL\n        var = uninitialized Void\n                          # ^^^^ error: `Void` is not allowed in this context\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/lint/whitespace_around_macro_expression_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Lint\n  describe WhitespaceAroundMacroExpression do\n    subject = WhitespaceAroundMacroExpression.new\n\n    it \"passes if macro expression is wrapped with whitespace\" do\n      expect_no_issues subject, <<-CRYSTAL\n        {{ foo }}\n        CRYSTAL\n    end\n\n    it \"passes if macro expression is multiline\" do\n      expect_no_issues subject, <<-CRYSTAL\n        {{\n          if foo > 1\n            foo\n          else\n            \"default\"\n          end\n        }}\n        CRYSTAL\n    end\n\n    it \"reports macro expression without whitespace around\" do\n      source = expect_issue subject, <<-CRYSTAL\n        {{foo}}\n        # ^^^^^ error: Missing spaces around macro expression\n        {{ bar}}\n        # ^^^^^^ error: Missing spaces around macro expression\n        {{baz }}\n        # ^^^^^^ error: Missing spaces around macro expression\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        {{ foo }}\n        {{ bar }}\n        {{ baz }}\n        CRYSTAL\n    end\n\n    # https://github.com/crystal-lang/crystal/pull/15524\n    it \"reports macro expression without whitespace around within a macro body\" do\n      source = expect_issue subject, <<-CRYSTAL\n        {% begin %}\n          {{foo}}\n        # ^^^^^^^ error: Missing spaces around macro expression\n          {{ bar}}\n        # ^^^^^^^^ error: Missing spaces around macro expression\n          {{baz }}\n        # ^^^^^^^^ error: Missing spaces around macro expression\n        {% end %}\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        {% begin %}\n          {{ foo }}\n          {{ bar }}\n          {{ baz }}\n        {% end %}\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/metrics/cyclomatic_complexity_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Metrics\n  describe CyclomaticComplexity do\n    subject = CyclomaticComplexity.new\n    complex_method = <<-CRYSTAL\n      def hello(a, b, c)\n        if a && b && c\n          begin\n            while true\n              return if false && b\n            end\n            \"\"\n          rescue\n            \"\"\n          end\n        end\n      end\n      CRYSTAL\n\n    it \"passes for empty methods\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def hello\n        end\n        CRYSTAL\n    end\n\n    it \"reports one issue for a complex method\" do\n      rule = CyclomaticComplexity.new\n      rule.max_complexity = 5\n\n      source = Source.new(complex_method, \"source.cr\")\n      rule.catch(source).should_not be_valid\n\n      issue = source.issues.first\n      issue.rule.should eq rule\n      issue.location.to_s.should eq \"source.cr:1:5\"\n      issue.end_location.to_s.should eq \"source.cr:1:9\"\n      issue.message.should eq \"Cyclomatic complexity too high [8/5]\"\n    end\n\n    it \"doesn't report an issue for an increased threshold\" do\n      rule = CyclomaticComplexity.new\n      rule.max_complexity = 100\n\n      expect_no_issues rule, complex_method\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/naming/accessor_method_name_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Naming\n  describe AccessorMethodName do\n    subject = AccessorMethodName.new\n\n    it \"passes if accessor method name is correct\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def self.instance\n          end\n\n          def self.instance=(value)\n          end\n\n          def user\n          end\n\n          def user=(user)\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"passes if accessor method is defined in top-level scope\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def get_user\n        end\n\n        def set_user(user)\n        end\n        CRYSTAL\n    end\n\n    it \"fails if accessor method is defined with receiver in top-level scope\" do\n      expect_issue subject, <<-CRYSTAL\n        def Foo.get_user\n              # ^^^^^^^^ error: Favour method name `user` over `get_user`\n        end\n\n        def Foo.set_user(user)\n              # ^^^^^^^^ error: Favour method name `user=` over `set_user`\n        end\n        CRYSTAL\n    end\n\n    it \"fails if accessor method name is wrong\" do\n      expect_issue subject, <<-CRYSTAL\n        class Foo\n          def self.get_instance\n                 # ^^^^^^^^^^^^ error: Favour method name `instance` over `get_instance`\n          end\n\n          def self.set_instance(value)\n                 # ^^^^^^^^^^^^ error: Favour method name `instance=` over `set_instance`\n          end\n\n          def get_user\n            # ^^^^^^^^ error: Favour method name `user` over `get_user`\n          end\n\n          def set_user(user)\n            # ^^^^^^^^ error: Favour method name `user=` over `set_user`\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"ignores if alternative name isn't valid syntax\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def get_404\n          end\n\n          def set_404(value)\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"ignores if the method has unexpected arity\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def get_user(type)\n          end\n\n          def set_user(user, type)\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"ignores if the method has unexpected arity (splat)\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def get_user(*props)\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"ignores if the method has unexpected arity (double splat)\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def get_user(**kwargs)\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"ignores if the method has block\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def get_user(&)\n            yield self\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"ignores if the method has block argument\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def get_user(&block)\n          end\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/naming/ascii_identifiers_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Naming\n  describe AsciiIdentifiers do\n    subject = AsciiIdentifiers.new\n\n    it \"reports classes with names containing non-ascii characters\" do\n      expect_issue subject, <<-CRYSTAL\n        class BigAwesome🐺\n            # ^^^^^^^^^^^ error: Identifier contains non-ascii characters\n          @🐺_name : String\n        # ^^^^^^^ error: Identifier contains non-ascii characters\n        end\n        CRYSTAL\n    end\n\n    it \"reports modules with names containing non-ascii characters\" do\n      expect_issue subject, <<-CRYSTAL\n        module Bąk\n             # ^^^ error: Identifier contains non-ascii characters\n          @@bąk_name : String\n        # ^^^^^^^^^^ error: Identifier contains non-ascii characters\n        end\n        CRYSTAL\n    end\n\n    it \"reports enums with names containing non-ascii characters\" do\n      expect_issue subject, <<-CRYSTAL\n        enum TypeOf🔥\n           # ^^^^^^^ error: Identifier contains non-ascii characters\n        end\n        CRYSTAL\n    end\n\n    it \"reports defs with names containing non-ascii characters\" do\n      expect_issue subject, <<-CRYSTAL\n        def łódź\n          # ^^^^ error: Identifier contains non-ascii characters\n        end\n        CRYSTAL\n    end\n\n    it \"reports defs with parameter names containing non-ascii characters\" do\n      expect_issue subject, <<-CRYSTAL\n        def forest_adventure(include_🐺 = true, include_🐿 = true)\n                           # ^^^^^^^^^ error: Identifier contains non-ascii characters\n                                             # ^^^^^^^^^ error: Identifier contains non-ascii characters\n        end\n        CRYSTAL\n    end\n\n    it \"reports defs with parameter default values containing non-ascii characters\" do\n      expect_issue subject, <<-CRYSTAL\n        def forest_adventure(animal_type = :🐺)\n                                         # ^^ error: Identifier contains non-ascii characters\n        end\n        CRYSTAL\n    end\n\n    it \"reports argument names containing non-ascii characters\" do\n      expect_issue subject, <<-CRYSTAL\n        %w[wensleydale cheddar brie].each { |🧀| nil }\n                                           # ^ error: Identifier contains non-ascii characters\n        CRYSTAL\n    end\n\n    it \"reports calls with arguments containing non-ascii characters\" do\n      expect_issue subject, <<-CRYSTAL\n        %i[🐺 🐿].index!(:🐺)\n                     # ^^ error: Identifier contains non-ascii characters\n        CRYSTAL\n    end\n\n    it \"reports calls with named arguments containing non-ascii characters\" do\n      expect_issue subject, <<-CRYSTAL\n        %i[🐺 🐿].index!(obj: :🐺)\n                          # ^^ error: Identifier contains non-ascii characters\n        CRYSTAL\n    end\n\n    it \"reports aliases with names containing non-ascii characters\" do\n      expect_issue subject, <<-CRYSTAL\n        alias JSON🧀 = JSON::Any\n            # ^^^^^ error: Identifier contains non-ascii characters\n        CRYSTAL\n    end\n\n    it \"reports constants with names containing non-ascii characters\" do\n      expect_issue subject, <<-CRYSTAL\n        I_LOVE_🍣 = true\n        # ^^^^^^ error: Identifier contains non-ascii characters\n        CRYSTAL\n    end\n\n    it \"reports assignments with variable names containing non-ascii characters\" do\n      expect_issue subject, <<-CRYSTAL\n        space_👾 = true\n        # ^^^^^ error: Identifier contains non-ascii characters\n        CRYSTAL\n    end\n\n    it \"reports multiple assignments with variable names containing non-ascii characters\" do\n      expect_issue subject, <<-CRYSTAL\n        foo, space_👾 = true, true\n           # ^^^^^^^ error: Identifier contains non-ascii characters\n        CRYSTAL\n    end\n\n    it \"reports assignments with symbol literals containing non-ascii characters\" do\n      expect_issue subject, <<-CRYSTAL\n        foo = :신장\n            # ^^^ error: Identifier contains non-ascii characters\n        CRYSTAL\n    end\n\n    it \"reports multiple assignments with symbol literals containing non-ascii characters\" do\n      expect_issue subject, <<-CRYSTAL\n        foo, bar = :신장, true\n                 # ^^^ error: Identifier contains non-ascii characters\n        CRYSTAL\n    end\n\n    it \"passes for strings with non-ascii characters\" do\n      expect_no_issues subject, <<-CRYSTAL\n        space = \"👾\"\n        space = :invader # 👾\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      context \"#ignore_symbols\" do\n        it \"returns `false` by default\" do\n          rule = AsciiIdentifiers.new\n          rule.ignore_symbols?.should be_false\n        end\n\n        it \"stops reporting symbol literals if set to `true`\" do\n          rule = AsciiIdentifiers.new\n          rule.ignore_symbols = true\n\n          expect_no_issues rule, <<-CRYSTAL\n            def forest_adventure(animal_type = :🐺); end\n            %i[🐺 🐿].index!(:🐺)\n            foo, bar = :신장, true\n            foo = :신장\n            CRYSTAL\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/naming/binary_operator_parameter_name_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Naming\n  describe BinaryOperatorParameterName do\n    subject = BinaryOperatorParameterName.new\n\n    it \"ignores `other` parameter name in binary method definitions\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def +(other); end\n        def -(other); end\n        def *(other); end\n        CRYSTAL\n    end\n\n    it \"ignores binary method definitions with arity other than 1\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def +; end\n        def +(foo, bar); end\n        def -; end\n        def -(foo, bar); end\n        CRYSTAL\n    end\n\n    it \"ignores non-binary method definitions\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(bar); end\n        def bąk(genus); end\n        CRYSTAL\n    end\n\n    it \"reports binary methods definitions with incorrectly named parameter\" do\n      expect_issue subject, <<-CRYSTAL\n        def +(foo); end\n            # ^^^ error: When defining the `+` operator, name its argument `other`\n        def -(foo); end\n            # ^^^ error: When defining the `-` operator, name its argument `other`\n        def *(foo); end\n            # ^^^ error: When defining the `*` operator, name its argument `other`\n        CRYSTAL\n    end\n\n    it \"ignores methods from #excluded_operators\" do\n      subject.excluded_operators.each do |op|\n        expect_no_issues subject, <<-CRYSTAL\n          def #{op}(foo); end\n          CRYSTAL\n      end\n    end\n\n    context \"properties\" do\n      context \"#allowed_names\" do\n        it \"uses `other` as the default\" do\n          expect_issue subject, <<-CRYSTAL\n            def +(foo); end\n                # ^^^ error: When defining the `+` operator, name its argument `other`\n            CRYSTAL\n        end\n\n        it \"allows setting custom names\" do\n          rule = BinaryOperatorParameterName.new\n\n          rule.allowed_names = %w[a b c]\n          expect_issue rule, <<-CRYSTAL\n            def +(foo); end\n                # ^^^ error: When defining the `+` operator, name its argument `a` or `b` or `c`\n            CRYSTAL\n\n          rule.allowed_names = %w[foo bar baz]\n          expect_no_issues rule, <<-CRYSTAL\n            def +(foo); end\n            def -(bar); end\n            def /(baz); end\n            CRYSTAL\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/naming/block_parameter_name_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Naming\n  describe BlockParameterName do\n    subject = BlockParameterName.new\n    subject.min_name_length = 3\n    subject.allowed_names = %w[e i j k v]\n\n    it \"passes if block parameter name matches #allowed_names\" do\n      subject.allowed_names.each do |name|\n        expect_no_issues subject, <<-CRYSTAL\n          %w[].each { |#{name}| }\n          CRYSTAL\n      end\n    end\n\n    it \"passes if block parameter name starts with '_'\" do\n      expect_no_issues subject, <<-CRYSTAL\n        %w[].each { |_, _foo, _bar| }\n        CRYSTAL\n    end\n\n    it \"fails if block parameter name doesn't match #allowed_names\" do\n      expect_issue subject, <<-CRYSTAL\n        %w[].each { |x| }\n                   # ^ error: Disallowed block parameter name found\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      context \"#min_name_length\" do\n        it \"allows setting custom values\" do\n          rule = BlockParameterName.new\n          rule.allowed_names = %w[a b c]\n\n          rule.min_name_length = 3\n          expect_issue rule, <<-CRYSTAL\n            %w[].each { |x| }\n                       # ^ error: Disallowed block parameter name found\n            CRYSTAL\n\n          rule.min_name_length = 1\n          expect_no_issues rule, <<-CRYSTAL\n            %w[].each { |x| }\n            CRYSTAL\n        end\n      end\n\n      context \"#allow_names_ending_in_numbers\" do\n        it \"allows setting custom values\" do\n          rule = BlockParameterName.new\n          rule.min_name_length = 1\n          rule.allowed_names = %w[]\n\n          rule.allow_names_ending_in_numbers = false\n          expect_issue rule, <<-CRYSTAL\n            %w[].each { |x1| }\n                       # ^^ error: Disallowed block parameter name found\n            CRYSTAL\n\n          rule.allow_names_ending_in_numbers = true\n          expect_no_issues rule, <<-CRYSTAL\n            %w[].each { |x1| }\n            CRYSTAL\n        end\n      end\n\n      context \"#allowed_names\" do\n        it \"allows setting custom names\" do\n          rule = BlockParameterName.new\n          rule.min_name_length = 3\n\n          rule.allowed_names = %w[a b c]\n          expect_issue rule, <<-CRYSTAL\n            %w[].each { |x| }\n                       # ^ error: Disallowed block parameter name found\n            CRYSTAL\n\n          rule.allowed_names = %w[x y z]\n          expect_no_issues rule, <<-CRYSTAL\n            %w[].each { |x| }\n            CRYSTAL\n        end\n      end\n\n      context \"#forbidden_names\" do\n        it \"allows setting custom names\" do\n          rule = BlockParameterName.new\n          rule.min_name_length = 1\n          rule.allowed_names = %w[]\n\n          rule.forbidden_names = %w[x y z]\n          expect_issue rule, <<-CRYSTAL\n            %w[].each { |x| }\n                       # ^ error: Disallowed block parameter name found\n            CRYSTAL\n\n          rule.forbidden_names = %w[a b c]\n          expect_no_issues rule, <<-CRYSTAL\n            %w[].each { |x| }\n            CRYSTAL\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/naming/constant_names_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nprivate def it_reports_constant(name, value, expected, *, file = __FILE__, line = __LINE__)\n  it \"reports constant name #{expected}\", file, line do\n    rule = Ameba::Rule::Naming::ConstantNames.new\n    expect_issue rule, <<-CRYSTAL, name: name, file: file, line: line\n        %{name} = #{value}\n      # ^{name} error: Constant name should be screaming-cased: `#{expected}`, not `#{name}`\n      CRYSTAL\n  end\nend\n\nmodule Ameba::Rule::Naming\n  describe ConstantNames do\n    subject = ConstantNames.new\n\n    it \"passes if type names are screaming-cased\" do\n      expect_no_issues subject, <<-CRYSTAL\n        LUCKY_NUMBERS     = [3, 7, 11]\n        DOCUMENTATION_URL = \"https://crystal-lang.org/docs\"\n\n        Int32\n\n        s : String = \"str\"\n\n        def works(n : Int32)\n        end\n\n        Log = ::Log.for(\"db\")\n\n        a = 1\n        myVar = 2\n        m_var = 3\n        CRYSTAL\n    end\n\n    it \"doesn't report single-line constant assigns inside namespaces\" do\n      expect_no_issues subject, <<-CRYSTAL\n        Example::FOO_BAR = \"bar\"\n        CRYSTAL\n    end\n\n    # it_reports_constant \"MyBadConstant\", \"1\", \"MYBADCONSTANT\"\n    it_reports_constant \"Wrong_NAME\", \"2\", \"WRONG_NAME\"\n    it_reports_constant \"Wrong_Name\", \"3\", \"WRONG_NAME\"\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/naming/filename_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Naming\n  describe Filename do\n    subject = Filename.new\n\n    it \"passes if filename is correct\" do\n      expect_no_issues subject, code: \"\", path: \"src/foo.cr\"\n      expect_no_issues subject, code: \"\", path: \"src/foo_bar.cr\"\n    end\n\n    it \"fails if filename is wrong\" do\n      expect_issue subject, <<-CRYSTAL, path: \"src/fooBar.cr\"\n\n        # ^{} error: Filename should be underscore-cased: `foo_bar.cr`, not `fooBar.cr`\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/naming/method_names_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nprivate def it_reports_method_name(name, expected, *, file = __FILE__, line = __LINE__)\n  it \"reports method name #{expected}\", file, line do\n    rule = Ameba::Rule::Naming::MethodNames.new\n    expect_issue rule, <<-CRYSTAL, name: name, file: file, line: line\n      def %{name}; end\n        # ^{name} error: Method name should be underscore-cased: `#{expected}`, not `%{name}`\n      CRYSTAL\n  end\nend\n\nmodule Ameba::Rule::Naming\n  describe MethodNames do\n    subject = MethodNames.new\n\n    it \"passes if method names are underscore-cased\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Person\n          def first_name\n          end\n\n          def date_of_birth\n          end\n\n          def homepage_url\n          end\n\n          def valid?\n          end\n\n          def name\n          end\n        end\n        CRYSTAL\n    end\n\n    it_reports_method_name \"firstName\", \"first_name\"\n    it_reports_method_name \"date_of_Birth\", \"date_of_birth\"\n    it_reports_method_name \"homepageURL\", \"homepage_url\"\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/naming/predicate_name_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Naming\n  describe PredicateName do\n    subject = PredicateName.new\n\n    it \"passes if predicate name is correct\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def valid?(x)\n        end\n\n        class Image\n          def picture?(x)\n          end\n        end\n\n        def allow_this_picture?\n        end\n        CRYSTAL\n    end\n\n    it \"fails if predicate name is wrong\" do\n      expect_issue subject, <<-CRYSTAL\n        class Image\n          def self.is_valid?(x)\n                 # ^^^^^^^^^ error: Favour method name `valid?` over `is_valid?`\n          end\n        end\n\n        def is_valid?(x)\n          # ^^^^^^^^^ error: Favour method name `valid?` over `is_valid?`\n        end\n\n        def is_valid(x)\n          # ^^^^^^^^ error: Favour method name `valid?` over `is_valid`\n        end\n        CRYSTAL\n    end\n\n    it \"ignores if alternative name isn't valid syntax\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Image\n          def is_404?(x)\n            true\n          end\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/naming/query_bool_methods_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Naming\n  describe QueryBoolMethods do\n    subject = QueryBoolMethods.new\n\n    it \"passes for valid cases\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          class_property? foo = true\n          property? foo = true\n          property foo2 : Bool? = true\n          setter panda = true\n        end\n\n        module Bar\n          class_getter? bar : Bool = true\n          getter? bar : Bool\n          getter bar2 : Bool? = true\n          setter panda : Bool = true\n\n          def initialize(@bar = true)\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"reports only valid properties\" do\n      expect_issue subject, <<-CRYSTAL\n        class Foo\n          class_property? foo = true\n          class_property bar = true\n                       # ^^^ error: Consider using `class_property?` for `bar`\n          class_property baz = true\n                       # ^^^ error: Consider using `class_property?` for `baz`\n        end\n        CRYSTAL\n    end\n\n    {% for call in %w[getter class_getter property class_property] %}\n      it \"reports `{{ call.id }}` assign with Bool\" do\n        expect_issue subject, <<-CRYSTAL, call: {{ call }}\n          class Foo\n            %{call}   foo = true\n            _{call} # ^^^ error: Consider using `%{call}?` for `foo`\n          end\n          CRYSTAL\n      end\n\n      it \"reports `{{ call.id }}` type declaration assign with Bool\" do\n        expect_issue subject, <<-CRYSTAL, call: {{ call }}\n          class Foo\n            %{call}   foo : Bool = true\n            _{call} # ^^^ error: Consider using `%{call}?` for `foo`\n          end\n          CRYSTAL\n      end\n\n      it \"reports `{{ call.id }}` type declaration with Bool\" do\n        expect_issue subject, <<-CRYSTAL, call: {{ call }}\n          class Foo\n            %{call}   foo : Bool\n            _{call} # ^^^ error: Consider using `%{call}?` for `foo`\n\n            def initialize(@foo = true)\n            end\n          end\n          CRYSTAL\n      end\n    {% end %}\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/naming/rescued_exceptions_variable_name_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Naming\n  describe RescuedExceptionsVariableName do\n    subject = RescuedExceptionsVariableName.new\n\n    it \"passes if exception handler variable name matches #allowed_names\" do\n      subject.allowed_names.each do |name|\n        expect_no_issues subject, <<-CRYSTAL\n          def foo\n            raise \"foo\"\n          rescue #{name}\n            nil\n          end\n          CRYSTAL\n      end\n    end\n\n    it \"fails if exception handler variable name doesn't match #allowed_names\" do\n      expect_issue subject, <<-CRYSTAL\n        def foo\n          raise \"foo\"\n        rescue wtf : ArgumentError\n             # ^^^ error: Disallowed variable name, use one of these instead: `e`, `ex`, `exception`, `err`, `error`\n          nil\n        end\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      context \"#allowed_names\" do\n        it \"returns sensible defaults\" do\n          rule = RescuedExceptionsVariableName.new\n          rule.allowed_names.should eq %w[e ex exception err error]\n        end\n\n        it \"allows setting custom names\" do\n          rule = RescuedExceptionsVariableName.new\n          rule.allowed_names = %w[foo]\n\n          expect_issue rule, <<-CRYSTAL\n            def foo\n              raise \"foo\"\n            rescue e\n                 # ^ error: Disallowed variable name, use `foo` instead\n              nil\n            end\n            CRYSTAL\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/naming/type_names_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nprivate def it_reports_name(type, name, expected, *, file = __FILE__, line = __LINE__)\n  it \"reports type name #{expected}\", file, line do\n    rule = Ameba::Rule::Naming::TypeNames.new\n    expect_issue rule, <<-CRYSTAL, type: type, name: name, file: file, line: line\n      %{type}   %{name}; end\n      _{type} # ^{name} error: Type name should be camelcased: `#{expected}`, not `%{name}`\n      CRYSTAL\n  end\nend\n\nmodule Ameba::Rule::Naming\n  describe TypeNames do\n    subject = TypeNames.new\n\n    it \"passes if type names are camelcased\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class ParseError < Exception\n        end\n\n        module HTTP\n          class RequestHandler\n          end\n        end\n\n        alias NumericValue = Float32 | Float64 | Int32 | Int64\n\n        lib LibYAML\n        end\n\n        struct TagDirective\n        end\n\n        enum Time::DayOfWeek\n        end\n        CRYSTAL\n    end\n\n    it_reports_name \"class\", \"My_class\", \"MyClass\"\n    it_reports_name \"module\", \"HTT_p\", \"HTTP\"\n    it_reports_name \"lib\", \"Lib_YAML\", \"LibYAML\"\n    it_reports_name \"struct\", \"Tag_directive\", \"TagDirective\"\n    it_reports_name \"enum\", \"Time_enum::Day_of_week\", \"TimeEnum::DayOfWeek\"\n\n    it \"reports alias name\" do\n      expect_issue subject, <<-CRYSTAL\n        alias Numeric_value = Int32\n            # ^^^^^^^^^^^^^ error: Type name should be camelcased: `NumericValue`, not `Numeric_value`\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/naming/variable_names_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nprivate def it_reports_var_name(name, value, expected, *, file = __FILE__, line = __LINE__)\n  it \"reports variable name #{expected}\", file, line do\n    rule = Ameba::Rule::Naming::VariableNames.new\n    expect_issue rule, <<-CRYSTAL, name: name, file: file, line: line\n        %{name} = #{value}\n      # ^{name} error: Variable name should be underscore-cased: `#{expected}`, not `%{name}`\n      CRYSTAL\n  end\nend\n\nmodule Ameba::Rule::Naming\n  describe VariableNames do\n    subject = VariableNames.new\n\n    it \"passes if var names are underscore-cased\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Greeting\n          @@default_greeting = \"Hello world\"\n\n          def initialize(@custom_greeting = nil)\n          end\n\n          def print_greeting\n            greeting = @custom_greeting || @@default_greeting\n            puts greeting\n          end\n        end\n        CRYSTAL\n    end\n\n    it_reports_var_name \"myBadNamedVar\", \"1\", \"my_bad_named_var\"\n    it_reports_var_name \"wrong_Name\", \"'y'\", \"wrong_name\"\n\n    it \"reports instance variable name\" do\n      expect_issue subject, <<-CRYSTAL\n        class Greeting\n          def initialize(@badNamed = nil)\n                       # ^^^^^^^^^ error: Variable name should be underscore-cased: `@bad_named`, not `@badNamed`\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"reports method with multiple instance variables\" do\n      expect_issue subject, <<-CRYSTAL\n        class Location\n          def at(@startLocation = nil, @endLocation = nil)\n               # ^^^^^^^^^^^^^^ error: Variable name should be underscore-cased: `@start_location`, not `@startLocation`\n                                     # ^^^^^^^^^^^^ error: Variable name should be underscore-cased: `@end_location`, not `@endLocation`\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"reports class variable name\" do\n      expect_issue subject, <<-CRYSTAL\n        class Greeting\n          @@defaultGreeting = \"Hello world\"\n        # ^^^^^^^^^^^^^^^^^ error: Variable name should be underscore-cased: `@@default_greeting`, not `@@defaultGreeting`\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/performance/any_after_filter_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n  describe AnyAfterFilter do\n    subject = AnyAfterFilter.new\n\n    it \"passes if there is no potential performance improvements\" do\n      expect_no_issues subject, <<-CRYSTAL\n        [1, 2, 3].select { |e| e > 1 }.any?(&.zero?)\n        [1, 2, 3].reject { |e| e > 1 }.any?(&.zero?)\n        [1, 2, 3].select { |e| e > 1 }.any?(&block)\n        [1, 2, 3].select { |e| e > 1 }\n        [1, 2, 3].reject { |e| e > 1 }\n        [1, 2, 3].any? { |e| e > 1 }\n        CRYSTAL\n    end\n\n    it \"reports if there is select followed by any? without a block\" do\n      source = expect_issue subject, <<-CRYSTAL\n        [1, 2, 3].select { |e| e > 2 }.any?\n                # ^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `any? {...}` instead of `select {...}.any?`\n        CRYSTAL\n\n      expect_no_corrections source\n    end\n\n    it \"does not report if source is a spec\" do\n      expect_no_issues subject, <<-CRYSTAL, \"source_spec.cr\"\n        [1, 2, 3].select { |e| e > 2 }.any?\n        CRYSTAL\n    end\n\n    it \"reports if there is reject followed by any? without a block\" do\n      source = expect_issue subject, <<-CRYSTAL\n        [1, 2, 3].reject { |e| e > 2 }.any?\n                # ^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `any? {...}` instead of `reject {...}.any?`\n        CRYSTAL\n\n      expect_no_corrections source\n    end\n\n    it \"does not report if any? calls contains a block\" do\n      expect_no_issues subject, <<-CRYSTAL\n        [1, 2, 3].select { |e| e > 2 }.any?(&.zero?)\n        [1, 2, 3].reject { |e| e > 2 }.any?(&.zero?)\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      it \"#filter_names\" do\n        rule = AnyAfterFilter.new\n        rule.filter_names = %w[select]\n\n        expect_no_issues rule, <<-CRYSTAL\n          [1, 2, 3].reject { |e| e > 2 }.any?\n          CRYSTAL\n      end\n    end\n\n    context \"macro\" do\n      it \"reports in macro scope\" do\n        source = expect_issue subject, <<-CRYSTAL\n          {{ [1, 2, 3].reject { |e| e > 2  }.any? }}\n                     # ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `any? {...}` instead of `reject {...}.any?`\n          CRYSTAL\n\n        expect_no_corrections source\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/performance/any_instead_of_present_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n  describe AnyInsteadOfPresent do\n    subject = AnyInsteadOfPresent.new\n\n    it \"passes if there is no potential performance improvements\" do\n      expect_no_issues subject, <<-CRYSTAL\n        [1, 2, 3].any?(&.zero?)\n        [1, 2, 3].any?(&block)\n        [1, 2, 3].any?(String)\n        [1, 2, 3].any?(1..3)\n        [1, 2, 3].any? { |e| e > 1 }\n        CRYSTAL\n    end\n\n    it \"reports if there is any? call without a block nor argument\" do\n      source = expect_issue subject, <<-CRYSTAL\n        %w[foo bar].any?\n                  # ^^^^ error: Use `{...}.present?` instead of `{...}.any?`\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        %w[foo bar].present?\n        CRYSTAL\n    end\n\n    it \"does not report if source is a spec\" do\n      expect_no_issues subject, <<-CRYSTAL, \"source_spec.cr\"\n        [1, 2, 3].any?\n        CRYSTAL\n    end\n\n    context \"macro\" do\n      it \"does not report in macro scope\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {{ [1, 2, 3].any? }}\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/performance/base_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n  describe Base do\n    subject = PerfRule.new\n\n    describe \"#catch\" do\n      it \"ignores spec files\" do\n        source = Source.new path: \"source_spec.cr\"\n        subject.catch(source).should be_valid\n      end\n\n      it \"reports perf issues for non-spec files\" do\n        source = Source.new path: \"source.cr\"\n        subject.catch(source).should_not be_valid\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/performance/chained_call_with_no_bang_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n  describe ChainedCallWithNoBang do\n    subject = ChainedCallWithNoBang.new\n\n    it \"passes if there is no potential performance improvements\" do\n      expect_no_issues subject, <<-CRYSTAL\n        (1..3).select { |e| e > 1 }.sort!\n        (1..3).select { |e| e > 1 }.sort_by!(&.itself)\n        (1..3).select { |e| e > 1 }.uniq!\n        (1..3).select { |e| e > 1 }.shuffle!\n        (1..3).select { |e| e > 1 }.reverse!\n        (1..3).select { |e| e > 1 }.rotate!\n        CRYSTAL\n    end\n\n    it \"reports if there is select followed by reverse\" do\n      source = expect_issue subject, <<-CRYSTAL\n        [1, 2, 3].select { |e| e > 1 }.reverse\n                                     # ^^^^^^^ error: Use bang method variant `reverse!` after chained `select` call\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        [1, 2, 3].select { |e| e > 1 }.reverse!\n        CRYSTAL\n    end\n\n    it \"does not report if source is a spec\" do\n      expect_no_issues subject, <<-CRYSTAL, \"source_spec.cr\"\n        [1, 2, 3].select { |e| e > 1 }.reverse\n        CRYSTAL\n    end\n\n    it \"reports if there is select followed by reverse followed by other call\" do\n      source = expect_issue subject, <<-CRYSTAL\n        [1, 2, 3].select { |e| e > 2 }.reverse.size\n                                     # ^^^^^^^ error: Use bang method variant `reverse!` after chained `select` call\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        [1, 2, 3].select { |e| e > 2 }.reverse!.size\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      it \"#call_names\" do\n        rule = ChainedCallWithNoBang.new\n        rule.call_names = %w[uniq]\n\n        expect_no_issues rule, <<-CRYSTAL\n          [1, 2, 3].select { |e| e > 2 }.reverse\n          CRYSTAL\n      end\n    end\n\n    context \"macro\" do\n      it \"doesn't report in macro scope\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {{ [1, 2, 3].select { |e| e > 2  }.reverse }}\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/performance/compact_after_map_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n  describe CompactAfterMap do\n    subject = CompactAfterMap.new\n\n    it \"passes if there is no potential performance improvements\" do\n      expect_no_issues subject, <<-CRYSTAL\n        (1..3).compact_map(&.itself)\n        (1..3).compact_map(&block)\n        CRYSTAL\n    end\n\n    it \"passes if there is map followed by a bang call\" do\n      expect_no_issues subject, <<-CRYSTAL\n        (1..3).map(&.itself).compact!\n        CRYSTAL\n    end\n\n    it \"reports if there is map followed by compact call\" do\n      expect_issue subject, <<-CRYSTAL\n        (1..3).map(&.itself).compact\n             # ^^^^^^^^^^^^^^^^^^^^^ error: Use `compact_map {...}` instead of `map {...}.compact`\n        (1..3).map(&block).compact\n             # ^^^^^^^^^^^^^^^^^^^ error: Use `compact_map {...}` instead of `map {...}.compact`\n        CRYSTAL\n    end\n\n    it \"does not report if source is a spec\" do\n      expect_no_issues subject, path: \"source_spec.cr\", code: <<-CRYSTAL\n        (1..3).map(&.itself).compact\n        CRYSTAL\n    end\n\n    context \"macro\" do\n      it \"doesn't report in macro scope\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {{ [1, 2, 3].map(&.to_s).compact }}\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/performance/excessive_allocations_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n  describe ExcessiveAllocations do\n    subject = ExcessiveAllocations.new\n\n    it \"passes if there is no potential performance improvements\" do\n      expect_no_issues subject, <<-CRYSTAL\n        \"Alice\".chars.each(arg) { |c| puts c }\n        \"Alice\".chars(arg).each { |c| puts c }\n        \"Alice\\nBob\".lines.each(arg) { |l| puts l }\n        \"Alice\\nBob\".lines(arg).each { |l| puts l }\n        CRYSTAL\n    end\n\n    it \"reports if there is a collection method followed by each\" do\n      source = expect_issue subject, <<-CRYSTAL\n        \"Alice\".chars.each { |c| puts c }\n              # ^^^^^^^^^^ error: Use `each_char {...}` instead of `chars.each {...}` to avoid excessive allocation\n        \"Alice\".chars.each(&block)\n              # ^^^^^^^^^^ error: Use `each_char {...}` instead of `chars.each {...}` to avoid excessive allocation\n        \"Alice\\nBob\".lines.each { |l| puts l }\n           # ^^^^^^^^^^ error: Use `each_line {...}` instead of `lines.each {...}` to avoid excessive allocation\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        \"Alice\".each_char { |c| puts c }\n        \"Alice\".each_char(&block)\n        \"Alice\\nBob\".each_line { |l| puts l }\n        CRYSTAL\n    end\n\n    it \"does not report if source is a spec\" do\n      expect_no_issues subject, <<-CRYSTAL, \"source_spec.cr\"\n        \"Alice\".chars.each { |c| puts c }\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      it \"#call_names\" do\n        rule = ExcessiveAllocations.new\n        rule.call_names = {\n          \"children\" => \"each_child\",\n        }\n\n        expect_no_issues rule, <<-CRYSTAL\n          \"Alice\".chars.each { |c| puts c }\n          CRYSTAL\n      end\n    end\n\n    context \"macro\" do\n      it \"doesn't report in macro scope\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {{ \"Alice\".chars.each { |c| puts c } }}\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/performance/first_last_after_filter_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n  describe FirstLastAfterFilter do\n    subject = FirstLastAfterFilter.new\n\n    it \"passes if there is no potential performance improvements\" do\n      expect_no_issues subject, <<-CRYSTAL\n        [1, 2, 3].select { |e| e > 1 }\n        [1, 2, 3].reverse.select { |e| e > 1 }\n        [1, 2, 3].reverse.last\n        [1, 2, 3].reverse.first\n        [1, 2, 3].reverse.first\n        CRYSTAL\n    end\n\n    it \"reports if there is select followed by last\" do\n      expect_issue subject, <<-CRYSTAL\n        [1, 2, 3].select { |e| e > 2 }.last\n                # ^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `reverse_each.find {...}` instead of `select {...}.last`\n        [1, 2, 3].select(&block).last\n                # ^^^^^^^^^^^^^^^^^^^ error: Use `reverse_each.find {...}` instead of `select {...}.last`\n        CRYSTAL\n    end\n\n    it \"does not report if source is a spec\" do\n      expect_no_issues subject, path: \"source_spec.cr\", code: <<-CRYSTAL\n        [1, 2, 3].select { |e| e > 2 }.last\n        CRYSTAL\n    end\n\n    it \"reports if there is select followed by last?\" do\n      expect_issue subject, <<-CRYSTAL\n        [1, 2, 3].select { |e| e > 2 }.last?\n                # ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `reverse_each.find {...}` instead of `select {...}.last?`\n        CRYSTAL\n    end\n\n    it \"reports if there is select followed by first\" do\n      expect_issue subject, <<-CRYSTAL\n        [1, 2, 3].select { |e| e > 2 }.first\n                # ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `find {...}` instead of `select {...}.first`\n        CRYSTAL\n    end\n\n    it \"does not report if there is selected followed by first with arguments\" do\n      expect_no_issues subject, <<-CRYSTAL\n        [1, 2, 3].select { |n| n % 2 == 0 }.first(2)\n        CRYSTAL\n    end\n\n    it \"reports if there is select followed by first?\" do\n      expect_issue subject, <<-CRYSTAL\n        [1, 2, 3].select { |e| e > 2 }.first?\n                # ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `find {...}` instead of `select {...}.first?`\n        CRYSTAL\n    end\n\n    it \"does not report if there is select followed by any other call\" do\n      expect_no_issues subject, <<-CRYSTAL\n        [1, 2, 3].select { |e| e > 2 }.size\n        [1, 2, 3].select { |e| e > 2 }.any?\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      it \"#filter_names\" do\n        rule = FirstLastAfterFilter.new\n        rule.filter_names = %w[reject]\n\n        expect_no_issues rule, <<-CRYSTAL\n          [1, 2, 3].select { |e| e > 2 }.first\n          CRYSTAL\n      end\n    end\n\n    context \"macro\" do\n      it \"doesn't report in macro scope\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {{ [1, 2, 3].select { |e| e > 2  }.last }}\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/performance/flatten_after_map_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n  describe FlattenAfterMap do\n    subject = FlattenAfterMap.new\n\n    it \"passes if there is no potential performance improvements\" do\n      expect_no_issues subject, <<-CRYSTAL\n        %w[Alice Bob].flat_map(&.chars)\n        CRYSTAL\n    end\n\n    it \"reports if there is map followed by flatten call\" do\n      expect_issue subject, <<-CRYSTAL\n        %w[Alice Bob].map(&.chars).flatten\n                    # ^^^^^^^^^^^^^^^^^^^^ error: Use `flat_map {...}` instead of `map {...}.flatten`\n        %w[Alice Bob].map(&block).flatten\n                    # ^^^^^^^^^^^^^^^^^^^ error: Use `flat_map {...}` instead of `map {...}.flatten`\n        CRYSTAL\n    end\n\n    it \"does not report is source is a spec\" do\n      expect_no_issues subject, path: \"source_spec.cr\", code: <<-CRYSTAL\n        %w[Alice Bob].map(&.chars).flatten\n        CRYSTAL\n    end\n\n    context \"macro\" do\n      it \"doesn't report in macro scope\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {{ %w[Alice Bob].map(&.chars).flatten }}\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/performance/map_instead_of_block_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n  describe MapInsteadOfBlock do\n    subject = MapInsteadOfBlock.new\n\n    it \"passes if there is no potential performance improvements\" do\n      expect_no_issues subject, <<-CRYSTAL\n        (1..3).sum(&.*(2))\n        (1..3).product(&.*(2))\n        CRYSTAL\n    end\n\n    it \"reports if there is map followed by sum without a block\" do\n      expect_issue subject, <<-CRYSTAL\n        (1..3).map(&.to_u64).sum\n             # ^^^^^^^^^^^^^^^^^ error: Use `sum {...}` instead of `map {...}.sum`\n        (1..3).map(&block).sum\n            #  ^^^^^^^^^^^^^^^ error: Use `sum {...}` instead of `map {...}.sum`\n        CRYSTAL\n    end\n\n    it \"does not report if source is a spec\" do\n      expect_no_issues subject, path: \"source_spec.cr\", code: <<-CRYSTAL\n        (1..3).map(&.to_s).join\n        CRYSTAL\n    end\n\n    it \"reports if there is map followed by sum without a block (with argument)\" do\n      expect_issue subject, <<-CRYSTAL\n        (1..3).map(&.to_u64).sum(0)\n             # ^^^^^^^^^^^^^^^^^ error: Use `sum {...}` instead of `map {...}.sum`\n        CRYSTAL\n    end\n\n    it \"reports if there is map followed by sum with a block\" do\n      expect_issue subject, <<-CRYSTAL\n        (1..3).map(&.to_u64).sum(&.itself)\n             # ^^^^^^^^^^^^^^^^^ error: Use `sum {...}` instead of `map {...}.sum`\n        CRYSTAL\n    end\n\n    context \"macro\" do\n      it \"doesn't report in macro scope\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {{ [1, 2, 3].map(&.to_u64).sum }}\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/performance/minmax_after_map_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n  describe MinMaxAfterMap do\n    subject = MinMaxAfterMap.new\n\n    it \"passes if there are no potential performance improvements\" do\n      expect_no_issues subject, <<-CRYSTAL\n        %w[Alice Bob].map { |name| name.size }.min(2)\n        %w[Alice Bob].map { |name| name.size }.max(2)\n        CRYSTAL\n    end\n\n    it \"reports if there is a `min/max/minmax` call followed by `map`\" do\n      source = expect_issue subject, <<-CRYSTAL\n        %w[Alice Bob].map { |name| name.size }.min\n                    # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `min_of {...}` instead of `map {...}.min`\n        %w[Alice Bob].map(&.size).max.zero?\n                    # ^^^^^^^^^^^^^^^ error: Use `max_of {...}` instead of `map {...}.max`\n        %w[Alice Bob].map(&.size).minmax?\n                    # ^^^^^^^^^^^^^^^^^^^ error: Use `minmax_of? {...}` instead of `map {...}.minmax?`\n        %w[Alice Bob].map(&block).minmax?\n                    # ^^^^^^^^^^^^^^^^^^^ error: Use `minmax_of? {...}` instead of `map {...}.minmax?`\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        %w[Alice Bob].min_of { |name| name.size }\n        %w[Alice Bob].max_of(&.size).zero?\n        %w[Alice Bob].minmax_of?(&.size)\n        %w[Alice Bob].minmax_of?(&block)\n        CRYSTAL\n    end\n\n    it \"does not report if source is a spec\" do\n      expect_no_issues subject, path: \"source_spec.cr\", code: <<-CRYSTAL\n        %w[Alice Bob].map(&.size).min\n        CRYSTAL\n    end\n\n    context \"macro\" do\n      it \"doesn't report in macro scope\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {{ %w[Alice Bob].map(&.size).min }}\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/performance/size_after_filter_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n  describe SizeAfterFilter do\n    subject = SizeAfterFilter.new\n\n    it \"passes if there is no potential performance improvements\" do\n      expect_no_issues subject, <<-CRYSTAL\n        [1, 2, 3].select { |e| e > 2 }\n        [1, 2, 3].reject { |e| e < 2 }\n        [1, 2, 3].count { |e| e > 2 && e.odd? }\n        [1, 2, 3].count { |e| e < 2 && e.even? }\n\n        User.select(\"field AS name\").count\n        Company.select(:value).count\n        CRYSTAL\n    end\n\n    it \"reports if there is a select followed by size\" do\n      expect_issue subject, <<-CRYSTAL\n        [1, 2, 3].select { |e| e > 2 }.size\n                # ^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `count {...}` instead of `select {...}.size`\n        [1, 2, 3].select(&block).size\n                # ^^^^^^^^^^^^^^^^^^^ error: Use `count {...}` instead of `select {...}.size`\n        CRYSTAL\n    end\n\n    it \"does not report if source is a spec\" do\n      expect_no_issues subject, path: \"source_spec.cr\", code: <<-CRYSTAL\n        [1, 2, 3].select { |e| e > 2 }.size\n        CRYSTAL\n    end\n\n    it \"reports if there is a reject followed by size\" do\n      expect_issue subject, <<-CRYSTAL\n        [1, 2, 3].reject { |e| e < 2 }.size\n                # ^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `count {...}` instead of `reject {...}.size`\n        CRYSTAL\n    end\n\n    it \"reports if a block shorthand used\" do\n      expect_issue subject, <<-CRYSTAL\n        [1, 2, 3].reject(&.empty?).size\n                # ^^^^^^^^^^^^^^^^^^^^^ error: Use `count {...}` instead of `reject {...}.size`\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      it \"#filter_names\" do\n        rule = SizeAfterFilter.new\n        rule.filter_names = %w[select]\n\n        expect_no_issues rule, <<-CRYSTAL\n          [1, 2, 3].reject(&.empty?).size\n          CRYSTAL\n      end\n    end\n\n    context \"macro\" do\n      it \"doesn't report in macro scope\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {{[1, 2, 3].select { |v| v > 1 }.size}}\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/performance/times_map_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Performance\n  describe TimesMap do\n    subject = TimesMap.new\n\n    it \"passes if there is no potential performance improvements\" do\n      expect_no_issues subject, <<-CRYSTAL\n        3.times.map { |i| i * i }.to_a { |i| i * -1 }\n        3.times.map { |i| i * i }\n        3.times { |i| i * i }\n        CRYSTAL\n    end\n\n    it \"reports if there is map followed by flatten call\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo.bar.times.map do |i|\n        # ^^^^^^^^^^^^^^^^^^^^^^ error: Use `Array.new(foo.bar) {...}` instead of `foo.bar.times.map {...}.to_a`\n          i * i\n        end.to_a\n        3.times.map { |i| i * i }.to_a\n        # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `Array.new(3) {...}` instead of `3.times.map {...}.to_a`\n        3.times.map(&block).to_a\n        # ^^^^^^^^^^^^^^^^^^^^^^ error: Use `Array.new(3) {...}` instead of `3.times.map {...}.to_a`\n        3.times.map(&block).to_a.select(&.odd?)\n        # ^^^^^^^^^^^^^^^^^^^^^^ error: Use `Array.new(3) {...}` instead of `3.times.map {...}.to_a`\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        Array.new(foo.bar) do |i|\n          i * i\n        end\n        Array.new(3) { |i| i * i }\n        Array.new(3, &block)\n        Array.new(3, &block).select(&.odd?)\n        CRYSTAL\n    end\n\n    it \"does not report is source is a spec\" do\n      expect_no_issues subject, path: \"source_spec.cr\", code: <<-CRYSTAL\n        3.times.map { |i| i * i }.to_a\n        CRYSTAL\n    end\n\n    context \"macro\" do\n      it \"doesn't report in macro scope\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {{ 3.times.map { |i| i * i }.to_a }}\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/array_literal_syntax_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe ArrayLiteralSyntax do\n    subject = ArrayLiteralSyntax.new\n\n    it \"passes for an array literal with elements\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def print_numbers\n          numbers = [1, 2] of Int32\n          puts numbers\n        end\n        CRYSTAL\n    end\n\n    it \"passes for an array-like literal\" do\n      expect_no_issues subject, <<-CRYSTAL\n        Array{1, 2}\n        CRYSTAL\n    end\n\n    # Array literals in macros are semantically different from `Array(T).new`\n    it \"passes for an empty array literal in a macro\" do\n      expect_no_issues subject, <<-CRYSTAL\n        macro foo(bar = [] of String)\n          {% for b in bar %}\n            {{ b.id }}\n          {% end %}\n\n          {% baz = [] of Int32 %}\n        end\n\n        {% qux = [] of Int32 %}\n        CRYSTAL\n    end\n\n    it \"fails for an empty array literal\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def print_numbers\n          numbers = [] of Int32\n                  # ^^^^^^^^^^^ error: Use `Array(Int32).new` for creating an empty array\n          numbers << 1\n          numbers << 2\n          puts numbers\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def print_numbers\n          numbers = Array(Int32).new\n          numbers << 1\n          numbers << 2\n          puts numbers\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/call_parentheses_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe CallParentheses do\n    subject = CallParentheses.new\n\n    it \"ignores ECR files\" do\n      expect_no_issues subject, <<-ECR, path: \"foo.ecr\"\n        <%= foo bar %>\n        ECR\n    end\n\n    it \"passes for valid method calls\" do\n      expect_no_issues subject, <<-CRYSTAL\n        foo\n        foo.bar?\n        foo[0] = bar\n        foo.bar = baz\n        foo + bar\n        foo.+ bar\n        foo.bar(&.== baz)\n        CRYSTAL\n    end\n\n    it \"passes if method call has parentheses\" do\n      expect_no_issues subject, <<-CRYSTAL\n        foo(bar: 1)\n        CRYSTAL\n    end\n\n    it \"fails for method call with positional arguments\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo bar\n        # ^^^^^ error: Missing parentheses in method call\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo(bar)\n        CRYSTAL\n    end\n\n    it \"fails for method call with named arguments\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo bar: 1\n        # ^^^^^^^^ error: Missing parentheses in method call\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo(bar: 1)\n        CRYSTAL\n    end\n\n    it \"fails for nested method call with named arguments\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo(bar path: \"bar.cr\")\n          # ^^^^^^^^^^^^^^^^^^ error: Missing parentheses in method call\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo(bar(path: \"bar.cr\"))\n        CRYSTAL\n    end\n\n    it \"fails for method call with positional + named arguments\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo bar, baz: 1\n        # ^^^^^^^^^^^^^ error: Missing parentheses in method call\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo(bar, baz: 1)\n        CRYSTAL\n    end\n\n    it \"fails for method call with positional + named arguments\" do\n      source = expect_issue subject, <<-CRYSTAL\n        bats = bats [Bat.new path: \"bat.cr\"]\n             # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Missing parentheses in method call\n                   # ^^^^^^^^^^^^^^^^^^^^^^ error: Missing parentheses in method call\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        bats = bats([Bat.new(path: \"bat.cr\")])\n        CRYSTAL\n    end\n\n    it \"fails for method call with positional + named arguments\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo bar, baz: baz if baz.fooable?\n        # ^^^^^^^^^^^^^^^ error: Missing parentheses in method call\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo(bar, baz: baz) if baz.fooable?\n        CRYSTAL\n    end\n\n    it \"fails for method call with block arg\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo &proc\n        # ^^^^^^^ error: Missing parentheses in method call\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo(&proc)\n        CRYSTAL\n    end\n\n    it \"fails for method call with block (short)\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo &.baz?\n        # ^^^^^^^^ error: Missing parentheses in method call\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo(&.baz?)\n        CRYSTAL\n    end\n\n    it \"fails for method call with block\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo bar: 1 do |x, y|\n        # ^^^^^^^^^^^^^^^^^^ error: Missing parentheses in method call\n          baz(x, y)\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo(bar: 1) do |x, y|\n          baz(x, y)\n        end\n        CRYSTAL\n    end\n\n    it \"fails for method call with block (single line)\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo bar: 1 { |x, y| baz(x, y) }\n        # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Missing parentheses in method call\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo(bar: 1) { |x, y| baz(x, y) }\n        CRYSTAL\n    end\n\n    it \"fails for method call with heredoc argument\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo <<-HEREDOC\n        # ^^^^^^^^^^^^ error: Missing parentheses in method call\n          HEREDOC\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo(<<-HEREDOC)\n          HEREDOC\n        CRYSTAL\n    end\n\n    it \"fails for method call with multiple heredoc arguments\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo <<-FOO, <<-BAR\n        # ^^^^^^^^^^^^^^^^ error: Missing parentheses in method call\n          FOO\n          BAR\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo(<<-FOO, <<-BAR)\n          FOO\n          BAR\n        CRYSTAL\n    end\n\n    it \"fails for method call with heredoc named argument\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo 123,\n        # ^^^^^^ error: Missing parentheses in method call\n          bar: <<-HEREDOC,\n            bar\n            HEREDOC\n          baz: <<-HEREDOC\n            baz\n            HEREDOC\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo(123,\n          bar: <<-HEREDOC,\n            bar\n            HEREDOC\n          baz: <<-HEREDOC)\n            baz\n            HEREDOC\n        CRYSTAL\n    end\n\n    it \"removes stray backslash from the end of a first line\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo \\\\\n        # ^^^ error: Missing parentheses in method call\n          bar: 1,\n          baz: 2\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo(\n          bar: 1,\n          baz: 2)\n        CRYSTAL\n    end\n\n    it \"fails for method call with named + heredoc argument\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo <<-HEREDOC, bar: 42\n        # ^^^^^^^^^^^^^^^^^^^^^ error: Missing parentheses in method call\n          HEREDOC\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo(<<-HEREDOC, bar: 42)\n          HEREDOC\n        CRYSTAL\n    end\n\n    it \"fails for method call with heredoc argument + more arguments following\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo <<-HEREDOC,\n        # ^^^^^^^^^^^^^ error: Missing parentheses in method call\n          foo\n          HEREDOC\n          \"bar\"\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo(<<-HEREDOC,\n          foo\n          HEREDOC\n          \"bar\")\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      context \"#exclude_type_declarations\" do\n        it \"ignores type declarations when enabled\" do\n          rule = CallParentheses.new\n          rule.exclude_type_declarations = true\n\n          expect_no_issues rule, <<-CRYSTAL\n            foo bar : Symbol\n            CRYSTAL\n        end\n\n        it \"reports type declarations when disabled\" do\n          rule = CallParentheses.new\n          rule.exclude_type_declarations = false\n\n          source = expect_issue rule, <<-CRYSTAL\n            foo bar : Symbol\n            # ^^^^^^^^^^^^^^ error: Missing parentheses in method call\n            CRYSTAL\n\n          expect_correction source, <<-CRYSTAL\n            foo(bar : Symbol)\n            CRYSTAL\n        end\n      end\n\n      context \"#exclude_heredocs\" do\n        it \"ignores calls with heredoc arguments when enabled\" do\n          rule = CallParentheses.new\n          rule.exclude_heredocs = true\n\n          expect_no_issues rule, <<-CRYSTAL\n            foo bar : Symbol\n            CRYSTAL\n        end\n\n        it \"reports calls with heredoc arguments when disabled\" do\n          rule = CallParentheses.new\n          rule.exclude_heredocs = false\n\n          source = expect_issue rule, <<-CRYSTAL\n            foo.should eq <<-HEREDOC\n                     # ^^^^^^^^^^^^^ error: Missing parentheses in method call\n              HEREDOC\n            CRYSTAL\n\n          expect_correction source, <<-CRYSTAL\n            foo.should eq(<<-HEREDOC)\n              HEREDOC\n            CRYSTAL\n        end\n      end\n\n      context \"#excluded_toplevel_call_names\" do\n        it \"ignores top level calls\" do\n          rule = CallParentheses.new\n          rule.excluded_toplevel_call_names = %w[foo bar]\n\n          expect_no_issues rule, <<-CRYSTAL\n            foo bar\n            bar baz\n            CRYSTAL\n\n          expect_issue rule, <<-CRYSTAL\n            foo.bar baz\n            # ^^^^^^^^^ error: Missing parentheses in method call\n            CRYSTAL\n        end\n      end\n\n      context \"#excluded_call_names\" do\n        it \"ignores non-top level calls\" do\n          rule = CallParentheses.new\n          rule.excluded_call_names = %w[foo bar]\n\n          expect_no_issues rule, <<-CRYSTAL\n            foo.bar baz\n            bar.foo baz\n            CRYSTAL\n\n          expect_issue rule, <<-CRYSTAL\n            foo bar\n            # ^^^^^ error: Missing parentheses in method call\n            CRYSTAL\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/elsif_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe Elsif do\n    subject = Elsif.new\n\n    it \"does not report an issue for if statements\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def func\n          if something\n            foo\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report an issue for if/else statements\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def func\n          if something\n            foo\n          else\n            bar\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report an issue for if/else statements with suffix if by default\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def func\n          if something\n            foo\n          else\n            bar if something_else\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report an issue for if statements (ternary)\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def func\n          something ? foo : bar\n        end\n        CRYSTAL\n    end\n\n    it \"does not report an issue for if statements (else + ternary if)\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def func\n          something ? foo : something_else ? bar : baz\n        end\n        CRYSTAL\n    end\n\n    it \"reports an issue for if/elsif statements\" do\n      expect_issue subject, <<-CRYSTAL\n        def func\n          if something\n        # ^^^^^^^^^^^^ error: Prefer `case/when` over `if/elsif`\n            foo\n          elsif something_else\n            bar\n          end\n        end\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      it \"#ignore_suffix\" do\n        rule = Elsif.new\n        rule.ignore_suffix = false\n\n        expect_issue rule, <<-CRYSTAL\n          def func\n            if something\n          # ^^^^^^^^^^^^ error: Prefer `case/when` over `if/elsif`\n              foo\n            else\n              bar if something_else\n            end\n          end\n          CRYSTAL\n      end\n\n      it \"#allowed_branches\" do\n        rule = Elsif.new\n        rule.max_branches = 1\n\n        expect_no_issues rule, <<-CRYSTAL\n          def func\n            if something\n              foo\n            elsif something_else\n              bar\n            end\n          end\n          CRYSTAL\n\n        expect_issue rule, <<-CRYSTAL\n          def func\n            if something\n          # ^^^^^^^^^^^^ error: Prefer `case/when` over `if/elsif`\n              foo\n            elsif something_bar\n              bar\n            elsif something_baz\n              baz\n            end\n          end\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/guard_clause_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nprivate def it_reports_body(body, *, file = __FILE__, line = __LINE__)\n  rule = Ameba::Rule::Style::GuardClause.new\n\n  it \"reports an issue if method body is if / unless without else\", file, line do\n    source = expect_issue rule, <<-CRYSTAL, file: file, line: line\n      def func\n        if something\n      # ^^ error: Use a guard clause (`return unless something`) instead of wrapping the code inside a conditional expression\n          #{body}\n        end\n      end\n\n      def func\n        unless something\n      # ^^^^^^ error: Use a guard clause (`return if something`) instead of wrapping the code inside a conditional expression\n          #{body}\n        end\n      end\n      CRYSTAL\n\n    expect_correction source, <<-CRYSTAL, file: file, line: line\n      def func\n        return unless something\n          #{body}\n       #{trailing_whitespace}\n      end\n\n      def func\n        return if something\n          #{body}\n       #{trailing_whitespace}\n      end\n      CRYSTAL\n  end\n\n  it \"reports an issue if method body ends with if / unless without else\", file, line do\n    source = expect_issue rule, <<-CRYSTAL, file: file, line: line\n      def func\n        test\n        if something\n      # ^^ error: Use a guard clause (`return unless something`) instead of wrapping the code inside a conditional expression\n          #{body}\n        end\n      end\n\n      def func\n        test\n        unless something\n      # ^^^^^^ error: Use a guard clause (`return if something`) instead of wrapping the code inside a conditional expression\n          #{body}\n        end\n      end\n      CRYSTAL\n\n    expect_correction source, <<-CRYSTAL, file: file, line: line\n      def func\n        test\n        return unless something\n          #{body}\n       #{trailing_whitespace}\n      end\n\n      def func\n        test\n        return if something\n          #{body}\n       #{trailing_whitespace}\n      end\n      CRYSTAL\n  end\nend\n\nprivate def it_reports_control_expression(kw, *, file = __FILE__, line = __LINE__)\n  rule = Ameba::Rule::Style::GuardClause.new\n\n  it \"reports an issue with #{kw} in the if branch\", file, line do\n    source = expect_issue rule, <<-CRYSTAL, file: file, line: line\n      def func\n        if something\n      # ^^ error: Use a guard clause (`#{kw} if something`) instead of wrapping the code inside a conditional expression\n          #{kw}\n        else\n          puts \"hello\"\n        end\n      end\n      CRYSTAL\n\n    expect_no_corrections source, file: file, line: line\n  end\n\n  it \"reports an issue with #{kw} in the else branch\", file, line do\n    source = expect_issue rule, <<-CRYSTAL, file: file, line: line\n      def func\n        if something\n      # ^^ error: Use a guard clause (`#{kw} unless something`) instead of wrapping the code inside a conditional expression\n        puts \"hello\"\n        else\n          #{kw}\n        end\n      end\n      CRYSTAL\n\n    expect_no_corrections source, file: file, line: line\n  end\n\n  it \"doesn't report an issue if condition has multiple lines\", file, line do\n    expect_no_issues rule, <<-CRYSTAL, file: file, line: line\n      def func\n        if something &&\n             something_else\n          #{kw}\n        else\n          puts \"hello\"\n        end\n      end\n      CRYSTAL\n  end\n\n  it \"does not report an issue if #{kw} is inside elsif\", file, line do\n    expect_no_issues rule, <<-CRYSTAL, file: file, line: line\n      def func\n        if something\n          a\n        elsif something_else\n          #{kw}\n        end\n      end\n      CRYSTAL\n  end\n\n  it \"does not report an issue if #{kw} is inside if..elsif..else..end\", file, line do\n    expect_no_issues rule, <<-CRYSTAL, file: file, line: line\n      def func\n        if something\n          a\n        elsif something_else\n          b\n        else\n          #{kw}\n        end\n      end\n      CRYSTAL\n  end\n\n  it \"doesn't report an issue if control flow expr has multiple lines\", file, line do\n    expect_no_issues rule, <<-CRYSTAL, file: file, line: line\n      def func\n        if something\n          #{kw} \\\\\n                \"blah blah blah\" \\\\\n                \"blah blah blah\"\n        else\n          puts \"hello\"\n        end\n      end\n      CRYSTAL\n  end\n\n  it \"reports an issue if non-control-flow branch has multiple lines\", file, line do\n    source = expect_issue rule, <<-CRYSTAL, file: file, line: line\n      def func\n        if something\n      # ^^ error: Use a guard clause (`#{kw} if something`) instead of wrapping the code inside a conditional expression\n          #{kw}\n        else\n          puts \"hello\" \\\\\n               \"blah blah blah\"\n        end\n      end\n      CRYSTAL\n\n    expect_no_corrections source, file: file, line: line\n  end\nend\n\nmodule Ameba::Rule::Style\n  describe GuardClause do\n    subject = GuardClause.new\n\n    it_reports_body \"work\"\n    it_reports_body \"# TODO\"\n\n    pending \"does not report an issue if `else` branch is present but empty\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method\n          if bar = foo\n            puts bar\n          else\n            # nothing\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report an issue if body is if..elsif..end\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def func\n          if something\n            a\n          elsif something_else\n            b\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report an issue if condition has multiple lines\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def func\n          if something &&\n               something_else\n            work\n          end\n        end\n\n        def func\n          unless something &&\n                   something_else\n            work\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"accepts a method body that is if / unless with else\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def func\n          if something\n            work\n          else\n            test\n          end\n        end\n\n        def func\n          unless something\n            work\n          else\n            test\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"reports an issue when using `|| raise` in `then` branch\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def func\n          if something\n        # ^^ error: Use a guard clause (`work || raise(\"message\") if something`) instead of [...]\n            work || raise(\"message\")\n          else\n            test\n          end\n        end\n        CRYSTAL\n\n      expect_no_corrections source\n    end\n\n    it \"reports an issue when using `|| raise` in `else` branch\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def func\n          if something\n        # ^^ error: Use a guard clause (`test || raise(\"message\") unless something`) instead of [...]\n            work\n          else\n            test || raise(\"message\")\n          end\n        end\n        CRYSTAL\n\n      expect_no_corrections source\n    end\n\n    it \"reports an issue when using `&& return` in `then` branch\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def func\n          if something\n        # ^^ error: Use a guard clause (`work && return if something`) instead of [...]\n            work && return\n          else\n            test\n          end\n        end\n        CRYSTAL\n\n      expect_no_corrections source\n    end\n\n    it \"reports an issue when using `&& return` in `else` branch\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def func\n          if something\n        # ^^ error: Use a guard clause (`test && return unless something`) instead of [...]\n            work\n          else\n            test && return\n          end\n        end\n        CRYSTAL\n\n      expect_no_corrections source\n    end\n\n    it \"accepts a method body that does not end with if / unless\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def func\n          if something\n            work\n          end\n          test\n        end\n\n        def func\n          unless something\n            work\n          end\n          test\n        end\n        CRYSTAL\n    end\n\n    it \"accepts a method body that is a modifier if / unless\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def func\n          work if something\n        end\n\n        def func\n          work unless something\n        end\n        CRYSTAL\n    end\n\n    it \"accepts a method with empty parentheses as its body\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def func\n          ()\n        end\n        CRYSTAL\n    end\n\n    it \"does not report an issue when assigning the result of a guard condition with `else`\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def func\n          result =\n            if something\n              work || raise(\"message\")\n            else\n              test\n            end\n        end\n        CRYSTAL\n    end\n\n    it_reports_control_expression \"return\"\n    it_reports_control_expression \"next\"\n    it_reports_control_expression \"break\"\n    it_reports_control_expression %(raise \"error\")\n\n    context \"method in module\" do\n      it \"reports an issue for instance method\" do\n        source = expect_issue subject, <<-CRYSTAL\n          module CopTest\n            def test\n              if something\n            # ^^ error: Use a guard clause (`return unless something`) instead of [...]\n                work\n              end\n            end\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          module CopTest\n            def test\n              return unless something\n                work\n             #{trailing_whitespace}\n            end\n          end\n          CRYSTAL\n      end\n\n      it \"reports an issue for singleton methods\" do\n        source = expect_issue subject, <<-CRYSTAL\n          module CopTest\n            def self.test\n              if something && something_else\n            # ^^ error: Use a guard clause (`return unless something && something_else`) instead of [...]\n                work\n              end\n            end\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          module CopTest\n            def self.test\n              return unless something && something_else\n                work\n             #{trailing_whitespace}\n            end\n          end\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/hash_literal_syntax_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe HashLiteralSyntax do\n    subject = HashLiteralSyntax.new\n\n    it \"passes for an hash literal with elements\" do\n      expect_no_issues subject, <<-CRYSTAL\n        {1 => 2, 3 => 4} of Int32 => Int32\n        CRYSTAL\n    end\n\n    it \"passes for an hash-like literal\" do\n      expect_no_issues subject, <<-CRYSTAL\n        Hash{1 => 2, 3 => 4}\n        CRYSTAL\n    end\n\n    # Hash literals in macros are semantically different from `Hash(K, V).new`\n    it \"passes for an empty hash literal in a macro\" do\n      expect_no_issues subject, <<-CRYSTAL\n        macro foo(bar = {} of String => String)\n          {% for b, c in bar %}\n            {{ b.id }} % {{ c.id }}\n          {% end %}\n\n          {% baz = {} of Int32 => Int32 %}\n        end\n\n        {% qux = {} of Int32 => Int32 %}\n        CRYSTAL\n    end\n\n    it \"fails for an hash literal without elements\" do\n      source = expect_issue subject, <<-CRYSTAL\n        {} of Int32 => Int32\n        # ^^^^^^^^^^^^^^^^^^ error: Use `Hash(Int32, Int32).new` for creating an empty hash\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        Hash(Int32, Int32).new\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/heredoc_escape_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe HeredocEscape do\n    subject = HeredocEscape.new\n\n    it \"passes if a heredoc doesn't contain interpolation\" do\n      expect_no_issues subject, <<-CRYSTAL\n        <<-HEREDOC\n          foo\n          HEREDOC\n        CRYSTAL\n    end\n\n    it \"passes if a heredoc contains interpolation\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        <<-HEREDOC\n          foo #{:bar}\n          HEREDOC\n        CRYSTAL\n    end\n\n    it \"passes if a heredoc contains normal and escaped interpolation\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        <<-HEREDOC\n          foo \\#{:bar} #{:baz}\n          HEREDOC\n        CRYSTAL\n    end\n\n    it \"passes if a heredoc contains an escape sequence and escaped interpolation\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        <<-HEREDOC\n          foo \\377 \\xFF \\uFFFF \\u{0} \\t \\n \\#{:baz}\n          HEREDOC\n        CRYSTAL\n    end\n\n    it \"passes if a heredoc contains an escaped escape sequence and interpolation\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        <<-HEREDOC\n          foo \\\\377 \\\\xFF \\\\uFFFF \\\\u{0} \\\\t \\\\n #{:baz}\n          HEREDOC\n        CRYSTAL\n    end\n\n    it \"fails if a heredoc contains escaped interpolation\" do\n      expect_issue subject, <<-'CRYSTAL'\n        <<-HEREDOC\n        # ^^^^^^^^ error: Use an escaped heredoc marker: `<<-'HEREDOC'`\n          foo \\#{:bar}\n          HEREDOC\n        CRYSTAL\n    end\n\n    it \"fails if a heredoc contains escaped interpolation and escaped escape sequences\" do\n      expect_issue subject, <<-'CRYSTAL'\n        <<-HEREDOC\n        # ^^^^^^^^ error: Use an escaped heredoc marker: `<<-'HEREDOC'`\n          foo \\\\t \\#{:bar}\n          HEREDOC\n        CRYSTAL\n    end\n\n    it \"passes if a heredoc contains normal and escaped escape sequences\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        <<-HEREDOC\n          foo \\t \\n | \\\\t \\\\n\n          HEREDOC\n        CRYSTAL\n    end\n\n    it \"fails if a heredoc contains escaped escape sequences\" do\n      expect_issue subject, <<-'CRYSTAL'\n        <<-HEREDOC\n        # ^^^^^^^^ error: Use an escaped heredoc marker: `<<-'HEREDOC'`\n          \\\\t \\\\n\n          HEREDOC\n        CRYSTAL\n    end\n\n    it \"passes if an escaped heredoc contains interpolation\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        <<-'HEREDOC'\n          foo #{:bar}\n          HEREDOC\n        CRYSTAL\n    end\n\n    it \"passes if an escaped heredoc contains escaped interpolation\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        <<-'HEREDOC'\n          foo \\#{:bar}\n          HEREDOC\n        CRYSTAL\n    end\n\n    it \"passes if an escaped heredoc contains escape sequences\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        <<-'HEREDOC'\n          foo \\377 \\xFF \\uFFFF \\u{0} \\t \\n\n          HEREDOC\n        CRYSTAL\n    end\n\n    it \"passes if an escaped heredoc contains Regex escape sequences\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        <<-'REGEX'\n          ^(?:(?<days>\\d+)(?:d|\\s*days?),?\\s*)?\n          (?:(?<hours>\\d+)(?:h|\\s*hours?),?\\s*)?\n          (?:(?<minutes>\\d+)(?:m|\\s*min(?:utes?)?),?\\s*)?\n          (?:(?<seconds>\\d+)(?:s|\\s*sec(?:onds?)?))?$\n          REGEX\n        CRYSTAL\n    end\n\n    it \"passes if an escaped heredoc contains escaped escape sequences\" do\n      expect_no_issues subject, <<-'CRYSTAL'\n        <<-'HEREDOC'\n          foo \\\\377 \\\\xFF \\\\uFFFF \\\\u{0} \\\\t \\\\n\n          HEREDOC\n        CRYSTAL\n    end\n\n    it \"fails if an escaped heredoc doesn't contain interpolation\" do\n      expect_issue subject, <<-CRYSTAL\n        <<-'HEREDOC'\n        # ^^^^^^^^^^ error: Use an unescaped heredoc marker: `<<-HEREDOC`\n          foo\n          HEREDOC\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/heredoc_indent_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe HeredocIndent do\n    subject = HeredocIndent.new\n\n    it \"passes if heredoc body indented one level\" do\n      expect_no_issues subject, <<-CRYSTAL\n        <<-HEREDOC\n          hello world\n          HEREDOC\n\n          <<-HEREDOC\n            hello world\n            HEREDOC\n        CRYSTAL\n    end\n\n    it \"fails if the heredoc body is indented incorrectly\" do\n      source = expect_issue subject, <<-CRYSTAL\n        <<-ONE\n        # ^^^^ error: Heredoc body should be indented by 2 spaces\n        hello world\n        ONE\n\n          <<-TWO\n        # ^^^^^^ error: Heredoc body should be indented by 2 spaces\n          hello world\n          TWO\n\n          <<-THREE\n        # ^^^^^^^^ error: Heredoc body should be indented by 2 spaces\n             hello world\n             THREE\n\n          <<-FOUR\n        # ^^^^^^^ error: Heredoc body should be indented by 2 spaces\n        hello world\n        FOUR\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        <<-ONE\n          hello world\n          ONE\n\n          <<-TWO\n            hello world\n            TWO\n\n          <<-THREE\n            hello world\n            THREE\n\n          <<-FOUR\n            hello world\n            FOUR\n        CRYSTAL\n    end\n\n    it \"keeps the indentation within the heredoc string\" do\n      source = expect_issue subject, <<-CRYSTAL\n        <<-HTML\n        # ^^^^^ error: Heredoc body should be indented by 2 spaces\n        <article>\n          <header>\n            <h1>{{ article.name }}</h1>\n          </header>\n        </article>\n        HTML\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        <<-HTML\n          <article>\n            <header>\n              <h1>{{ article.name }}</h1>\n            </header>\n          </article>\n          HTML\n        CRYSTAL\n    end\n\n    it \"fixes the indentation within the heredoc string\" do\n      source = expect_issue subject, <<-CRYSTAL\n        <<-HTML\n        # ^^^^^ error: Heredoc body should be indented by 2 spaces\n          <article>\n            <header>\n              <h1>{{ article.name }}</h1>\n            </header>\n          </article>\n        HTML\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        <<-HTML\n          <article>\n            <header>\n              <h1>{{ article.name }}</h1>\n            </header>\n          </article>\n          HTML\n        CRYSTAL\n    end\n\n    it \"fixes the indentation within the heredoc string with empty line\" do\n      source = expect_issue subject, <<-CRYSTAL\n        <<-HTML\n        # ^^^^^ error: Heredoc body should be indented by 2 spaces\n          <article>\n            <header/>\n          </article>\n\n          <hr/>\n        HTML\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        <<-HTML\n          <article>\n            <header/>\n          </article>\n\n          <hr/>\n          HTML\n        CRYSTAL\n    end\n\n    it \"works with empty heredoc string\" do\n      source = expect_issue subject, <<-CRYSTAL\n        <<-HTML\n        # ^^^^^ error: Heredoc body should be indented by 2 spaces\n        HTML\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        <<-HTML\n          HTML\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      context \"#indent_by\" do\n        rule = HeredocIndent.new\n        rule.indent_by = 0\n\n        it \"passes if heredoc body has the same indent level\" do\n          expect_no_issues rule, <<-CRYSTAL\n            <<-HEREDOC\n            hello world\n            HEREDOC\n\n              <<-HEREDOC\n              hello world\n              HEREDOC\n            CRYSTAL\n        end\n\n        it \"fails if the heredoc body is indented incorrectly\" do\n          source = expect_issue rule, <<-CRYSTAL\n            <<-ONE\n            # ^^^^ error: Heredoc body should be indented by 0 spaces\n              hello world\n              ONE\n\n              <<-TWO\n            # ^^^^^^ error: Heredoc body should be indented by 0 spaces\n                hello world\n                TWO\n\n              <<-FOUR\n            # ^^^^^^^ error: Heredoc body should be indented by 0 spaces\n            hello world\n            FOUR\n            CRYSTAL\n\n          expect_correction source, <<-CRYSTAL\n            <<-ONE\n            hello world\n            ONE\n\n              <<-TWO\n              hello world\n              TWO\n\n              <<-FOUR\n              hello world\n              FOUR\n            CRYSTAL\n        end\n      end\n\n      context \"#body_auto_dedent\" do\n        rule = HeredocIndent.new\n        rule.body_auto_dedent = false\n\n        it \"leaves the indentation within the heredoc string\" do\n          source = expect_issue rule, <<-CRYSTAL\n            <<-HTML\n            # ^^^^^ error: Heredoc body should be indented by 2 spaces\n              <article>\n                <header>\n                  <h1>{{ article.name }}</h1>\n                </header>\n              </article>\n            HTML\n            CRYSTAL\n\n          expect_correction source, <<-CRYSTAL\n            <<-HTML\n                <article>\n                  <header>\n                    <h1>{{ article.name }}</h1>\n                  </header>\n                </article>\n              HTML\n            CRYSTAL\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/is_a_filter_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe IsAFilter do\n    subject = IsAFilter.new\n\n    it \"passes if there is no potential performance improvements\" do\n      expect_no_issues subject, <<-CRYSTAL\n        [1, 2, nil].select(Int32)\n        [1, 2, nil].reject(Nil)\n        CRYSTAL\n    end\n\n    it \"reports if there is .is_a? call within select\" do\n      source = expect_issue subject, <<-CRYSTAL\n        [1, 2, nil].select(&.is_a?(Int32))\n                  # ^^^^^^^^^^^^^^^^^^^^^^ error: Use `select(Int32)` instead of `select {...}`\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        [1, 2, nil].select(Int32)\n        CRYSTAL\n    end\n\n    it \"reports if there is .nil? call within reject\" do\n      source = expect_issue subject, <<-CRYSTAL\n        [1, 2, nil].reject(&.nil?)\n                  # ^^^^^^^^^^^^^^ error: Use `reject(Nil)` instead of `reject {...}`\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        [1, 2, nil].reject(Nil)\n        CRYSTAL\n    end\n\n    it \"does not report if there .is_a? call within block with multiple arguments\" do\n      expect_no_issues subject, <<-CRYSTAL\n        t.all? { |_, v| v.is_a?(String) }\n        t.all? { |foo, bar| foo.is_a?(String) }\n        t.all? { |foo, bar| bar.is_a?(String) }\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      it \"#filter_names\" do\n        rule = IsAFilter.new\n        rule.filter_names = %w[select]\n\n        expect_no_issues rule, <<-CRYSTAL\n          [1, 2, nil].reject(&.nil?)\n          CRYSTAL\n      end\n    end\n\n    context \"macro\" do\n      it \"doesn't report in macro scope\" do\n        expect_no_issues subject, <<-CRYSTAL\n          {{ [1, 2, nil].reject(&.nil?) }}\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/is_a_nil_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe IsANil do\n    subject = IsANil.new\n\n    it \"doesn't report if there are no is_a?(Nil) calls\" do\n      expect_no_issues subject, <<-CRYSTAL\n        a = 1\n        a.nil?\n        a.is_a?(NilLiteral)\n        a.is_a?(Custom::Nil)\n        CRYSTAL\n    end\n\n    it \"reports if there is a call to is_a?(Nil) without receiver\" do\n      source = expect_issue subject, <<-CRYSTAL\n        a = is_a?(Nil)\n                # ^^^ error: Use `nil?` instead of `is_a?(Nil)`\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        a = self.nil?\n        CRYSTAL\n    end\n\n    it \"reports if there is a call to is_a?(Nil) with receiver\" do\n      source = expect_issue subject, <<-CRYSTAL\n        a.is_a?(Nil)\n              # ^^^ error: Use `nil?` instead of `is_a?(Nil)`\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        a.nil?\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/large_numbers_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nprivate def it_transforms(number, expected, *, file = __FILE__, line = __LINE__)\n  it \"transforms large number #{number}\", file, line do\n    rule = Ameba::Rule::Style::LargeNumbers.new\n    rule.int_min_digits = 5\n\n    source = expect_issue rule, <<-CRYSTAL, number: number, file: file, line: line\n      number = %{number}\n             # ^{number} error: Large numbers should be written with underscores: `#{expected}`\n      CRYSTAL\n\n    expect_correction source, <<-CRYSTAL\n      number = #{expected}\n      CRYSTAL\n  end\nend\n\nmodule Ameba::Rule::Style\n  describe LargeNumbers do\n    subject = LargeNumbers.new\n\n    it \"passes if large number does not require underscore\" do\n      expect_no_issues subject, <<-CRYSTAL\n        1 2 3 4 5 6 7 8 9 10 11 12 13 14 15\n        16 17 18 19 20 30 40 50 60 70 80 90\n        100\n        999\n        1000\n        1_000\n        9999\n        9_999\n        10_000\n        100_000\n        200_000\n        300_000\n        400_000\n        500_000\n        600_000\n        700_000\n        800_000\n        900_000\n        1_000_000\n\n        -9_223_372_036_854_775_808\n        9_223_372_036_854_775_807\n\n        141_592_654\n        141_592_654.0\n        141_592_654.001\n        141_592_654.001_2\n        141_592_654.001_23\n        141_592_654.001_234\n        141_592_654.001_234_5\n\n        0b1101\n        0o123\n        0xFE012D\n        0xfe012d\n        0xfe012dd11\n\n        1_i8\n        12_i16\n        123_i32\n        1_234_i64\n\n        12_u8\n        123_u16\n        1_234_u32\n        9_223_372_036_854_775_808_u64\n        9_223_372_036_854_775_808.000_123_456_789_f64\n\n        +100_u32\n        -900_000_i32\n\n        1_234.5e-7\n        11_234e10_f32\n        +1.123\n        -0.000_5\n\n        1200.0\n        1200.01\n        1200.012\n        CRYSTAL\n    end\n\n    it_transforms \"10000\", \"10_000\"\n    it_transforms \"+10000\", \"+10_000\"\n    it_transforms \"-10000\", \"-10_000\"\n\n    it_transforms \"9223372036854775808\", \"9_223_372_036_854_775_808\"\n    it_transforms \"-9223372036854775808\", \"-9_223_372_036_854_775_808\"\n    it_transforms \"+9223372036854775808\", \"+9_223_372_036_854_775_808\"\n\n    it_transforms \"1_00000\", \"100_000\"\n\n    it_transforms \"10000_i16\", \"10_000_i16\"\n    it_transforms \"10000_i32\", \"10_000_i32\"\n    it_transforms \"10000_i64\", \"10_000_i64\"\n    it_transforms \"10000_i128\", \"10_000_i128\"\n\n    it_transforms \"10000_u16\", \"10_000_u16\"\n    it_transforms \"10000_u32\", \"10_000_u32\"\n    it_transforms \"10000_u64\", \"10_000_u64\"\n    it_transforms \"10000_u128\", \"10_000_u128\"\n\n    it_transforms \"123456_f32\", \"123_456_f32\"\n    it_transforms \"123456_f64\", \"123_456_f64\"\n\n    it_transforms \"123456.5e-7_f32\", \"123_456.5e-7_f32\"\n    it_transforms \"123456e10_f64\", \"123_456e10_f64\"\n\n    it_transforms \"123456.5e-7\", \"123_456.5e-7\"\n    it_transforms \"123456e10\", \"123_456e10\"\n\n    it_transforms \"3.00_1\", \"3.001\"\n    it_transforms \"3.0012\", \"3.001_2\"\n    it_transforms \"3.00123\", \"3.001_23\"\n    it_transforms \"3.001234\", \"3.001_234\"\n    it_transforms \"3.0012345\", \"3.001_234_5\"\n\n    context \"properties\" do\n      it \"#int_min_digits\" do\n        rule = Rule::Style::LargeNumbers.new\n        rule.int_min_digits = 10\n        expect_no_issues rule, \"1200000\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/multiline_curly_block_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe MultilineCurlyBlock do\n    subject = MultilineCurlyBlock.new\n\n    it \"doesn't report if a curly block is on a single line\" do\n      expect_no_issues subject, <<-CRYSTAL\n        foo { :bar }\n        CRYSTAL\n    end\n\n    it \"doesn't report for `do`...`end` blocks\" do\n      expect_no_issues subject, <<-CRYSTAL\n        foo do\n          :bar\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report for `do`...`end` blocks on a single line\" do\n      expect_no_issues subject, <<-CRYSTAL\n        foo do :bar end\n        CRYSTAL\n    end\n\n    it \"reports if there is a multi-line curly block\" do\n      expect_issue subject, <<-CRYSTAL\n        foo {\n          # ^ error: Use `do`...`end` instead of curly brackets for multi-line blocks\n          :bar\n        }\n        CRYSTAL\n    end\n\n    it \"reports if there is a multi-line curly block with arguments\" do\n      expect_issue subject, <<-CRYSTAL\n        foo(:bar) {\n                # ^ error: Use `do`...`end` instead of curly brackets for multi-line blocks\n          :baz\n        }\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/multiline_string_literal_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe MultilineStringLiteral do\n    subject = MultilineStringLiteral.new\n\n    context \"string literals\" do\n      it \"doesn't report if a string is on a single line\" do\n        expect_no_issues subject, <<-CRYSTAL\n          \"foo\"\n          CRYSTAL\n      end\n\n      it \"doesn't report if a string containing `\\\\n` is on a single line\" do\n        expect_no_issues subject, <<-'CRYSTAL'\n          \"\\nfoo\\n\"\n          CRYSTAL\n      end\n\n      it \"doesn't report heredocs\" do\n        expect_no_issues subject, <<-CRYSTAL\n          <<-FOO\n            foo\n            bar\n            FOO\n          CRYSTAL\n      end\n\n      it \"reports if there is a multi-line string\" do\n        expect_issue subject, <<-CRYSTAL\n          \"\n          # ^{} error: Use `<<-HEREDOC` markers for multiline strings\n            foo\n            bar\n          \"\n          CRYSTAL\n      end\n\n      it \"reports if there is a multi-line percent string: `%()`\" do\n        expect_issue subject, <<-CRYSTAL\n          %(\n          # ^{} error: Use `<<-HEREDOC` markers for multiline strings\n            foo\n            bar\n          )\n          CRYSTAL\n      end\n\n      it \"reports if there is a multi-line percent string: `%q()`\" do\n        expect_issue subject, <<-CRYSTAL\n          %q(\n          # ^ error: Use `<<-HEREDOC` markers for multiline strings\n            foo\n            bar\n          )\n          CRYSTAL\n      end\n    end\n\n    context \"string interpolations\" do\n      it \"doesn't report if a string is on a single line\" do\n        expect_no_issues subject, <<-CRYSTAL\n          \"#{\"foo\"}\"\n          CRYSTAL\n      end\n\n      it \"doesn't report regex literals\" do\n        expect_no_issues subject, <<-'CRYSTAL'\n          /\\A#{foo}\\Z/\n          CRYSTAL\n      end\n\n      it \"doesn't report multiline regex literals\" do\n        expect_no_issues subject, <<-'CRYSTAL'\n          %r{\n            \\A#{foo}\\Z\n          }x\n          CRYSTAL\n      end\n\n      it \"doesn't report multiline command literals\" do\n        expect_no_issues subject, <<-'CRYSTAL'\n          %x{\n            echo \"#{foo}\"\n          }\n          CRYSTAL\n      end\n\n      it \"doesn't report command literals\" do\n        expect_no_issues subject, <<-'CRYSTAL'\n          `#{foo}`\n          CRYSTAL\n      end\n\n      it \"doesn't report if a string containing `\\\\n` is on a single line\" do\n        expect_no_issues subject, <<-'CRYSTAL'\n          \"#{\"\\nfoo\\n\"}\"\n          CRYSTAL\n      end\n\n      it \"doesn't report heredocs\" do\n        expect_no_issues subject, <<-CRYSTAL\n          <<-FOO\n            foo\n            #{\"bar\"}\n            baz\n            FOO\n          CRYSTAL\n      end\n\n      it \"reports if there is a multi-line string\" do\n        expect_issue subject, <<-'CRYSTAL'\n          \"\n          # ^{} error: Use `<<-HEREDOC` markers for multiline strings\n            #{\"foo\"}\n            bar\n          \"\n          CRYSTAL\n      end\n    end\n\n    context \"properties\" do\n      describe \"#allow_backslash_split_strings\" do\n        it \"passes on formatter errors by default\" do\n          rule = MultilineStringLiteral.new\n\n          expect_no_issues rule, <<-CRYSTAL\n            \"foo\" \\\\\n            \"bar\" \\\\\n            \"baz\"\n            CRYSTAL\n        end\n\n        it \"reports on formatter errors when disabled\" do\n          rule = MultilineStringLiteral.new\n          rule.allow_backslash_split_strings = false\n\n          expect_issue rule, <<-CRYSTAL\n            \"foo\" \\\\\n            # ^^^^^ error: Use `<<-HEREDOC` markers for multiline strings\n            \"bar\" \\\\\n            \"baz\"\n            CRYSTAL\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/negated_conditions_in_unless_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe NegatedConditionsInUnless do\n    subject = NegatedConditionsInUnless.new\n\n    it \"passes with a unless without negated condition\" do\n      expect_no_issues subject, <<-CRYSTAL\n        unless a\n          :ok\n        end\n\n        :ok unless b\n\n        unless s.empty?\n          :ok\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a negated condition in unless\" do\n      expect_issue subject, <<-CRYSTAL\n        unless !a\n        # ^^^^^^^ error: Avoid negated conditions in unless blocks\n          :nok\n        end\n        CRYSTAL\n    end\n\n    it \"fails if one of AND conditions is negated\" do\n      expect_issue subject, <<-CRYSTAL\n        unless a && !b\n        # ^^^^^^^^^^^^ error: Avoid negated conditions in unless blocks\n          :nok\n        end\n        CRYSTAL\n    end\n\n    it \"fails if one of OR conditions is negated\" do\n      expect_issue subject, <<-CRYSTAL\n        unless a || !b\n        # ^^^^^^^^^^^^ error: Avoid negated conditions in unless blocks\n          :nok\n        end\n        CRYSTAL\n    end\n\n    it \"fails if one of inner conditions is negated\" do\n      expect_issue subject, <<-CRYSTAL\n        unless a && (b || !c)\n        # ^^^^^^^^^^^^^^^^^^^ error: Avoid negated conditions in unless blocks\n          :nok\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/parentheses_around_condition_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe ParenthesesAroundCondition do\n    subject = ParenthesesAroundCondition.new\n\n    {% for keyword in %w[if unless while until] %}\n      context \"{{ keyword.id }}\" do\n        it \"reports if redundant parentheses are found\" do\n          source = expect_issue subject, <<-CRYSTAL, keyword: {{ keyword }}\n            %{keyword}   (foo > 10)\n            _{keyword} # ^^^^^^^^^^ error: Redundant parentheses\n              foo\n            end\n            CRYSTAL\n\n          expect_correction source, <<-CRYSTAL\n            {{ keyword.id }}   foo > 10\n              foo\n            end\n            CRYSTAL\n        end\n      end\n    {% end %}\n\n    {% for keyword in %w[if unless].map(&.id) %}\n      context \"{{ keyword }}\" do\n        it \"ignores expressions with `rescue`\" do\n          expect_no_issues subject, <<-CRYSTAL\n            {{ keyword }} (foo rescue nil)\n              foo\n            end\n            CRYSTAL\n        end\n\n        it \"ignores postfix expressions with `rescue`\" do\n          expect_no_issues subject, <<-CRYSTAL\n            foo {{ keyword }} (foo rescue nil)\n            CRYSTAL\n        end\n\n        it \"ignores expressions with `ensure`\" do\n          expect_no_issues subject, <<-CRYSTAL\n            {{ keyword }} (foo ensure bar)\n              foo\n            end\n            CRYSTAL\n        end\n\n        it \"ignores postfix expressions with `ensure`\" do\n          expect_no_issues subject, <<-CRYSTAL\n            foo {{ keyword }} (foo ensure bar)\n            CRYSTAL\n        end\n\n        it \"ignores expressions with `if`\" do\n          expect_no_issues subject, <<-CRYSTAL\n            {{ keyword }} (foo if bar)\n              foo\n            end\n            CRYSTAL\n        end\n\n        it \"ignores postfix expressions with `if`\" do\n          expect_no_issues subject, <<-CRYSTAL\n            foo {{ keyword }} (foo if bar)\n            CRYSTAL\n        end\n\n        it \"ignores expressions with `unless`\" do\n          expect_no_issues subject, <<-CRYSTAL\n            {{ keyword }} (foo unless bar)\n              foo\n            end\n            CRYSTAL\n        end\n\n        it \"ignores postfix expressions with `unless`\" do\n          expect_no_issues subject, <<-CRYSTAL\n            foo {{ keyword }} (foo unless bar)\n            CRYSTAL\n        end\n      end\n    {% end %}\n\n    context \"case\" do\n      it \"reports if redundant parentheses are found\" do\n        source = expect_issue subject, <<-CRYSTAL\n          case (foo = @foo)\n             # ^^^^^^^^^^^^ error: Redundant parentheses\n          when String then \"string\"\n          when Symbol then \"symbol\"\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          case foo = @foo\n          when String then \"string\"\n          when Symbol then \"symbol\"\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"properties\" do\n      context \"#exclude_ternary\" do\n        it \"reports ternary control expressions by default\" do\n          expect_issue subject, <<-CRYSTAL\n            (foo.empty?) ? true : false\n            # ^^^^^^^^^^ error: Redundant parentheses\n            CRYSTAL\n\n          expect_no_issues subject, <<-CRYSTAL\n            (foo && bar) ? true : false\n            (foo || bar) ? true : false\n            (foo = @foo) ? true : false\n            foo == 42 ? true : false\n            (foo = 42) ? true : false\n            (foo > 42) ? true : false\n            (foo >= 42) ? true : false\n            (3 >= foo >= 42) ? true : false\n            (3.in? 0..42) ? true : false\n            (yield 42) ? true : false\n            (foo rescue 42) ? true : false\n            CRYSTAL\n        end\n\n        it \"allows to skip ternary control expressions\" do\n          rule = ParenthesesAroundCondition.new\n          rule.exclude_ternary = true\n\n          expect_no_issues rule, <<-CRYSTAL\n            (foo.empty?) ? true : false\n            CRYSTAL\n        end\n      end\n\n      context \"#exclude_multiline\" do\n        it \"reports multiline expressions by default\" do\n          expect_issue subject, <<-CRYSTAL\n            if (\n             # ^ error: Redundant parentheses\n                foo.empty? ||\n                bar.empty?\n              )\n              baz\n            end\n            CRYSTAL\n        end\n\n        it \"allows to skip multiline expressions\" do\n          rule = ParenthesesAroundCondition.new\n          rule.exclude_multiline = true\n\n          expect_no_issues rule, <<-CRYSTAL\n            if (\n                foo.empty? ||\n                bar.empty?\n              )\n              baz\n            end\n            CRYSTAL\n        end\n      end\n\n      context \"#allow_safe_assignment\" do\n        it \"reports assignments by default\" do\n          expect_issue subject, <<-CRYSTAL\n            if (foo = @foo)\n             # ^^^^^^^^^^^^ error: Redundant parentheses\n              foo\n            end\n            CRYSTAL\n\n          expect_no_issues subject, <<-CRYSTAL\n            if !(foo = @foo)\n              foo\n            end\n            CRYSTAL\n\n          expect_no_issues subject, <<-CRYSTAL\n            if foo = @foo\n              foo\n            end\n            CRYSTAL\n        end\n\n        it \"allows to configure assignments\" do\n          rule = ParenthesesAroundCondition.new\n          rule.allow_safe_assignment = true\n\n          source = expect_issue rule, <<-CRYSTAL\n            if foo = @foo\n             # ^^^^^^^^^^ error: Missing parentheses\n              foo\n            end\n            CRYSTAL\n\n          expect_correction source, <<-CRYSTAL\n            if (foo = @foo)\n              foo\n            end\n            CRYSTAL\n\n          expect_no_issues rule, <<-CRYSTAL\n            if (foo = @foo)\n              foo\n            end\n            CRYSTAL\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/percent_literal_delimiters_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe PercentLiteralDelimiters do\n    subject = PercentLiteralDelimiters.new\n\n    it \"passes if percent literal delimiters are written correctly\" do\n      expect_no_issues subject, <<-CRYSTAL\n        %(one two three)\n        %w[one two three]\n        %i[one two three]\n        %r{one(two )?three[!]}\n        CRYSTAL\n    end\n\n    it \"fails if percent literal delimiters are written incorrectly\" do\n      expect_issue subject, <<-CRYSTAL\n        puts %[one two three]\n           # ^ error: `%`-literals should be delimited by `(` and `)`\n        puts %w(one two three)\n           # ^^ error: `%w`-literals should be delimited by `[` and `]`\n        puts %i(one two three)\n           # ^^ error: `%i`-literals should be delimited by `[` and `]`\n        puts %r|one two three|\n           # ^^ error: `%r`-literals should be delimited by `{` and `}`\n        CRYSTAL\n    end\n\n    it \"corrects incorrect percent literal delimiters\" do\n      source = expect_issue subject, <<-CRYSTAL\n        puts %[[] () {}]\n           # ^ error: `%`-literals should be delimited by `(` and `)`\n        puts %w(\n           # ^^ error: `%w`-literals should be delimited by `[` and `]`\n          one two three\n        )\n        puts %i(\n           # ^^ error: `%i`-literals should be delimited by `[` and `]`\n          one\n          two\n          three\n        )\n        puts %r|one(two )?three[!]|\n           # ^^ error: `%r`-literals should be delimited by `{` and `}`\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        puts %([] () {})\n        puts %w[\n          one two three\n        ]\n        puts %i[\n          one\n          two\n          three\n        ]\n        puts %r{one(two )?three[!]}\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      context \"#default_delimiters\" do\n        it \"allows setting custom values\" do\n          rule = PercentLiteralDelimiters.new\n          rule.default_delimiters = \"||\"\n          rule.preferred_delimiters = {\n            \"%w\" => \"{}\",\n          } of String => String?\n\n          expect_no_issues rule, <<-CRYSTAL\n            %w{one two three}\n            %i|one two three|\n            CRYSTAL\n        end\n\n        it \"allows ignoring default delimiters by setting them to `nil`\" do\n          rule = PercentLiteralDelimiters.new\n          rule.default_delimiters = nil\n          rule.preferred_delimiters = {\n            \"%Q\" => \"{}\",\n          } of String => String?\n\n          expect_no_issues rule, <<-CRYSTAL\n            %w(one two three)\n            %i|one two three|\n            %r<foo(bar)?>\n            CRYSTAL\n\n          expect_issue rule, <<-CRYSTAL\n            %Q[one two three]\n            # ^{} error: `%Q`-literals should be delimited by `{` and `}`\n            CRYSTAL\n        end\n      end\n\n      context \"#preferred_delimiters\" do\n        it \"allows setting custom values\" do\n          rule = PercentLiteralDelimiters.new\n          rule.preferred_delimiters = {\n            \"%w\" => \"()\",\n            \"%i\" => \"||\",\n          } of String => String?\n\n          expect_no_issues rule, <<-CRYSTAL\n            %w(one two three)\n            %i|one two three|\n            CRYSTAL\n        end\n\n        it \"allows ignoring certain delimiters by setting them to `nil`\" do\n          rule = PercentLiteralDelimiters.new\n          rule.preferred_delimiters[\"%r\"] = nil\n\n          expect_no_issues rule, <<-CRYSTAL\n            %r[foo(bar)?]\n            %r{foo(bar)?}\n            %r<foo(bar)?>\n            CRYSTAL\n        end\n      end\n\n      context \"#ignore_literals_containing_delimiters?\" do\n        it \"ignores different delimiters if enabled\" do\n          rule = PercentLiteralDelimiters.new\n          rule.ignore_literals_containing_delimiters = true\n\n          expect_issue rule, <<-CRYSTAL\n            %[one two three]\n            # ^{} error: `%`-literals should be delimited by `(` and `)`\n            %w(one two three)\n            # ^{} error: `%w`-literals should be delimited by `[` and `]`\n            %i(one two three)\n            # ^{} error: `%i`-literals should be delimited by `[` and `]`\n            %r<foo[o]>\n            # ^{} error: `%r`-literals should be delimited by `{` and `}`\n            CRYSTAL\n\n          expect_no_issues rule, <<-CRYSTAL\n            %[one (two) three]\n            %w([] []?)\n            %i([] []?)\n            %r<foo[o]{1,3}>\n            CRYSTAL\n        end\n\n        it \"ignores different delimiters if disabled\" do\n          rule = PercentLiteralDelimiters.new\n          rule.ignore_literals_containing_delimiters = false\n\n          expect_issue rule, <<-CRYSTAL\n            %[(one two three)]\n            # ^{} error: `%`-literals should be delimited by `(` and `)`\n            %w([] []?)\n            # ^{} error: `%w`-literals should be delimited by `[` and `]`\n            %i([] []?)\n            # ^{} error: `%i`-literals should be delimited by `[` and `]`\n            %r<foo[o]{1,3}>\n            # ^{} error: `%r`-literals should be delimited by `{` and `}`\n            CRYSTAL\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/redundant_begin_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe RedundantBegin do\n    subject = RedundantBegin.new\n\n    it \"passes if there is no redundant begin blocks\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method\n          do_something\n        rescue\n          do_something_else\n        end\n\n        def method\n          do_something\n          do_something_else\n        ensure\n          handle_something\n        end\n\n        def method\n          yield\n        rescue\n        end\n\n        def method; end\n        def method; a = 1; rescue; end\n        def method; begin; rescue; end; end\n        CRYSTAL\n    end\n\n    it \"passes if there is a correct begin block in a handler\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def handler_and_expression\n          begin\n            open_file\n          rescue\n            close_file\n          end\n          do_some_stuff\n        end\n\n        def multiple_handlers\n          begin\n            begin1\n          rescue\n          end\n\n          begin\n            begin2\n          rescue\n          end\n        rescue\n          do_something_else\n        end\n\n        def assign_and_begin\n          @result ||=\n            begin\n              do_something\n              do_something_else\n              returnit\n            end\n        rescue\n        end\n\n        def inner_handler\n          s = begin\n              rescue\n              end\n        rescue\n        end\n\n        def begin_and_expression\n          begin\n            a = 1\n            b = 2\n          end\n          expr\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a redundant begin block\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def method(a : String) : String\n          begin\n        # ^^^^^ error: Redundant `begin` block detected\n            open_file\n            do_some_stuff\n          ensure\n            close_file\n          end\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def method(a : String) : String\n         #{trailing_whitespace}\n            open_file\n            do_some_stuff\n          ensure\n            close_file\n         #{trailing_whitespace}\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a redundant begin block in a method without args\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def method\n          begin\n        # ^^^^^ error: Redundant `begin` block detected\n            open_file\n          ensure\n            close_file\n          end\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def method\n         #{trailing_whitespace}\n            open_file\n          ensure\n            close_file\n         #{trailing_whitespace}\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a redundant block in a method with return type\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def method : String\n          begin\n        # ^^^^^ error: Redundant `begin` block detected\n            open_file\n          ensure\n            close_file\n          end\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def method : String\n         #{trailing_whitespace}\n            open_file\n          ensure\n            close_file\n         #{trailing_whitespace}\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a redundant block in a method with multiple args\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def method(a : String,\n                  b : String)\n          begin\n        # ^^^^^ error: Redundant `begin` block detected\n            open_file\n          ensure\n            close_file\n          end\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def method(a : String,\n                  b : String)\n         #{trailing_whitespace}\n            open_file\n          ensure\n            close_file\n         #{trailing_whitespace}\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a redundant block in a method with multiple args\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def method(a : String,\n                  b : String\n        )\n          begin\n        # ^^^^^ error: Redundant `begin` block detected\n            open_file\n          ensure\n            close_file\n          end\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def method(a : String,\n                  b : String\n        )\n         #{trailing_whitespace}\n            open_file\n          ensure\n            close_file\n         #{trailing_whitespace}\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if there is an inner redundant block\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method\n          begin\n            open_file\n          ensure\n            close_file\n          end\n        rescue\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a redundant block with yield\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def method\n          begin\n        # ^^^^^ error: Redundant `begin` block detected\n            yield\n          ensure\n            close_file\n          end\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def method\n         #{trailing_whitespace}\n            yield\n          ensure\n            close_file\n         #{trailing_whitespace}\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is a redundant block with string with inner quotes\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def method\n          begin\n        # ^^^^^ error: Redundant `begin` block detected\n            \"'\"\n          rescue\n          end\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def method\n         #{trailing_whitespace}\n            \"'\"\n          rescue\n         #{trailing_whitespace}\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is top level redundant block in a method\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def method\n          begin\n        # ^^^^^ error: Redundant `begin` block detected\n            a = 1\n            b = 2\n          end\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def method\n         #{trailing_whitespace}\n            a = 1\n            b = 2\n         #{trailing_whitespace}\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if begin-end block in a proc literal\" do\n      expect_no_issues subject, <<-CRYSTAL\n        foo = ->{\n          begin\n            raise \"Foo!\"\n          rescue ex\n            pp ex\n          end\n        }\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/redundant_next_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe RedundantNext do\n    subject = RedundantNext.new\n\n    it \"does not report if there is no redundant next\" do\n      expect_no_issues subject, <<-CRYSTAL\n        array.map { |x| x + 1 }\n        CRYSTAL\n    end\n\n    it \"reports if there is redundant next with argument in the block\" do\n      source = expect_issue subject, <<-CRYSTAL\n        block do |v|\n          next v + 1\n        # ^^^^^^^^^^ error: Redundant `next` detected\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        block do |v|\n          v + 1\n        end\n        CRYSTAL\n    end\n\n    context \"if\" do\n      it \"doesn't report if there is not redundant next in if branch\" do\n        expect_no_issues subject, <<-CRYSTAL\n          block do |v|\n            next if v > 10\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there is redundant next in if/else branch\" do\n        source = expect_issue subject, <<-CRYSTAL\n          block do |a|\n            if a > 0\n              next a + 1\n            # ^^^^^^^^^^ error: Redundant `next` detected\n            else\n              next a + 2\n            # ^^^^^^^^^^ error: Redundant `next` detected\n            end\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          block do |a|\n            if a > 0\n              a + 1\n            else\n              a + 2\n            end\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"unless\" do\n      it \"doesn't report if there is no redundant next in unless branch\" do\n        expect_no_issues subject, <<-CRYSTAL\n          block do |v|\n            next unless v > 10\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there is redundant next in unless/else branch\" do\n        source = expect_issue subject, <<-CRYSTAL\n          block do |a|\n            unless a > 0\n              next a + 1\n            # ^^^^^^^^^^ error: Redundant `next` detected\n            else\n              next a + 2\n            # ^^^^^^^^^^ error: Redundant `next` detected\n            end\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          block do |a|\n            unless a > 0\n              a + 1\n            else\n              a + 2\n            end\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"expressions\" do\n      it \"doesn't report if there is no redundant next in expressions\" do\n        expect_no_issues subject, <<-CRYSTAL\n          block do |v|\n            a = 1\n            a + v\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there is redundant next in expressions\" do\n        source = expect_issue subject, <<-CRYSTAL\n          block do |a|\n            a = 1\n            next a\n          # ^^^^^^ error: Redundant `next` detected\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          block do |a|\n            a = 1\n            a\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"binary-op\" do\n      it \"doesn't report if there is no redundant next in binary op\" do\n        expect_no_issues subject, <<-CRYSTAL\n          block do |v|\n            a && v\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there is redundant next in binary op\" do\n        source = expect_issue subject, <<-CRYSTAL\n          block do |a|\n            a && next a\n               # ^^^^^^ error: Redundant `next` detected\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          block do |a|\n            a && a\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"exception handler\" do\n      it \"doesn't report if there is no redundant next in exception handler\" do\n        expect_no_issues subject, <<-CRYSTAL\n          block do |v|\n            v + 1\n          rescue\n            next v if v > 0\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there is redundant next in exception handler\" do\n        source = expect_issue subject, <<-CRYSTAL\n          block do |a|\n            next a + 1\n          # ^^^^^^^^^^ error: Redundant `next` detected\n          rescue ArgumentError\n            next a + 2\n          # ^^^^^^^^^^ error: Redundant `next` detected\n          rescue Exception\n            a + 2\n            next a\n          # ^^^^^^ error: Redundant `next` detected\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          block do |a|\n            a + 1\n          rescue ArgumentError\n            a + 2\n          rescue Exception\n            a + 2\n            a\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"properties\" do\n      context \"#allow_multi_next\" do\n        it \"allows multi next statements by default\" do\n          expect_no_issues subject, <<-CRYSTAL\n            block do |a, b|\n              next a, b\n            end\n            CRYSTAL\n        end\n\n        it \"allows to configure multi next statements\" do\n          rule = RedundantNext.new\n          rule.allow_multi_next = false\n          source = expect_issue rule, <<-CRYSTAL\n            block do |a, b|\n              next a, b\n            # ^^^^^^^^^ error: Redundant `next` detected\n            end\n            CRYSTAL\n\n          expect_correction source, <<-CRYSTAL\n            block do |a, b|\n              {a, b}\n            end\n            CRYSTAL\n        end\n      end\n\n      context \"#allow_empty_next\" do\n        it \"allows empty next statements by default\" do\n          expect_no_issues subject, <<-CRYSTAL\n            block do\n              next\n            end\n            CRYSTAL\n        end\n\n        it \"allows to configure empty next statements\" do\n          rule = RedundantNext.new\n          rule.allow_empty_next = false\n          source = expect_issue rule, <<-CRYSTAL\n            block do\n              next\n            # ^^^^ error: Redundant `next` detected\n            end\n            CRYSTAL\n\n          expect_no_corrections source\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/redundant_nil_in_control_expression_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe RedundantNilInControlExpression do\n    subject = RedundantNilInControlExpression.new\n\n    it \"passes for return with no expression\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo\n          return if empty?\n        end\n        CRYSTAL\n    end\n\n    it \"passes for return with expression\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo\n          return :nil if empty?\n        end\n        CRYSTAL\n    end\n\n    it \"reports `return nil` constructs\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def foo\n          return nil if empty?\n               # ^^^ error: Redundant `nil` detected\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def foo\n          return if empty?\n        end\n        CRYSTAL\n    end\n\n    it \"reports `return(nil)` constructs\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def foo\n          return(nil) if empty?\n               # ^^^ error: Redundant `nil` detected\n        end\n        CRYSTAL\n\n      expect_no_corrections source\n    end\n\n    it \"reports `return nil` constructs (deep)\" do\n      expect_issue subject, <<-CRYSTAL\n        def foo\n          if foo?\n            %w[foo bar].each do |v|\n              return nil if v.empty?\n                   # ^^^ error: Redundant `nil` detected\n            end\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"reports `break nil` constructs\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def foo\n          %w[foo bar].any? do |word|\n            break nil if word == \"foo\"\n                # ^^^ error: Redundant `nil` detected\n            word.ascii_only?\n          end\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def foo\n          %w[foo bar].any? do |word|\n            break if word == \"foo\"\n            word.ascii_only?\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"reports `next nil` constructs\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def foo\n          %w[foo bar].any? do |word|\n            next nil if word == \"foo\"\n               # ^^^ error: Redundant `nil` detected\n            word.ascii_only?\n          end\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def foo\n          %w[foo bar].any? do |word|\n            next if word == \"foo\"\n            word.ascii_only?\n          end\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/redundant_return_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe RedundantReturn do\n    subject = RedundantReturn.new\n\n    it \"does not report if there is no return\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def inc(a)\n          a + 1\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there is redundant return in method body\" do\n      source = expect_issue subject, <<-CRYSTAL\n        def inc(a)\n          return a + 1\n        # ^^^^^^^^^^^^ error: Redundant `return` detected\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        def inc(a)\n          a + 1\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if it returns tuple literal\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(a)\n          return a, a + 2\n        end\n        CRYSTAL\n    end\n\n    it \"doesn't report if there are other expressions after control flow\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def method(a)\n          case a\n          when true then return true\n          when .nil? then return :nil\n          end\n          false\n        rescue\n          nil\n        end\n        CRYSTAL\n    end\n\n    context \"if\" do\n      it \"doesn't report if there is return in if branch\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def inc(a)\n            return a + 1 if a > 0\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there are returns in if/else branch\" do\n        source = expect_issue subject, <<-CRYSTAL\n          def inc(a)\n            do_something(a)\n            if a > 0\n              return :positive\n            # ^^^^^^^^^^^^^^^^ error: Redundant `return` detected\n            else\n              return :negative\n            # ^^^^^^^^^^^^^^^^ error: Redundant `return` detected\n            end\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          def inc(a)\n            do_something(a)\n            if a > 0\n              :positive\n            else\n              :negative\n            end\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"unless\" do\n      it \"doesn't report if there is return in unless branch\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def inc(a)\n            return a + 1 unless a > 0\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there are returns in unless/else branch\" do\n        source = expect_issue subject, <<-CRYSTAL\n          def inc(a)\n            do_something(a)\n            unless a < 0\n              return :positive\n            # ^^^^^^^^^^^^^^^^ error: Redundant `return` detected\n            else\n              return :negative\n            # ^^^^^^^^^^^^^^^^ error: Redundant `return` detected\n            end\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          def inc(a)\n            do_something(a)\n            unless a < 0\n              :positive\n            else\n              :negative\n            end\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"binary op\" do\n      it \"doesn't report if there is no return in the right binary op node\" do\n        expect_no_issues subject, <<-CRYSTAL\n          def can_create?(a)\n            valid? && a > 0\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there is return in the right binary op node\" do\n        source = expect_issue subject, <<-CRYSTAL\n          def can_create?(a)\n            valid? && return a > 0\n                    # ^^^^^^^^^^^^ error: Redundant `return` detected\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          def can_create?(a)\n            valid? && a > 0\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"case\" do\n      it \"reports if there are returns in whens\" do\n        source = expect_issue subject, <<-CRYSTAL\n          def foo(a)\n            case a\n            when .nil?\n              puts \"blah\"\n              return nil\n            # ^^^^^^^^^^ error: Redundant `return` detected\n            when .blank?\n              return \"\"\n            # ^^^^^^^^^ error: Redundant `return` detected\n            when true\n              true\n            end\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          def foo(a)\n            case a\n            when .nil?\n              puts \"blah\"\n              nil\n            when .blank?\n              \"\"\n            when true\n              true\n            end\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there is return in else\" do\n        source = expect_issue subject, <<-CRYSTAL\n          def foo(a)\n            do_something_with(a)\n\n            case a\n            when true\n              true\n            else\n              return false\n            # ^^^^^^^^^^^^ error: Redundant `return` detected\n            end\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          def foo(a)\n            do_something_with(a)\n\n            case a\n            when true\n              true\n            else\n              false\n            end\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"exception handler\" do\n      it \"reports if there are returns in body\" do\n        source = expect_issue subject, <<-CRYSTAL\n          def foo(a)\n            return true\n          # ^^^^^^^^^^^ error: Redundant `return` detected\n          rescue\n            false\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          def foo(a)\n            true\n          rescue\n            false\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there are returns in rescues\" do\n        source = expect_issue subject, <<-CRYSTAL\n          def foo(a)\n            true\n          rescue ArgumentError\n            return false\n          # ^^^^^^^^^^^^ error: Redundant `return` detected\n          rescue RuntimeError\n            \"\"\n          rescue Exception\n            return nil\n          # ^^^^^^^^^^ error: Redundant `return` detected\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          def foo(a)\n            true\n          rescue ArgumentError\n            false\n          rescue RuntimeError\n            \"\"\n          rescue Exception\n            nil\n          end\n          CRYSTAL\n      end\n\n      it \"reports if there are returns in else\" do\n        source = expect_issue subject, <<-CRYSTAL\n          def foo(a)\n            true\n          rescue Exception\n            nil\n          else\n            puts \"else branch\"\n            return :bar\n          # ^^^^^^^^^^^ error: Redundant `return` detected\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          def foo(a)\n            true\n          rescue Exception\n            nil\n          else\n            puts \"else branch\"\n            :bar\n          end\n          CRYSTAL\n      end\n    end\n\n    context \"properties\" do\n      context \"#allow_multi_return\" do\n        it \"allows multi returns by default\" do\n          expect_no_issues subject, <<-CRYSTAL\n            def method(a, b)\n              return a, b\n            end\n            CRYSTAL\n        end\n\n        it \"allows to configure multi returns\" do\n          rule = RedundantReturn.new\n          rule.allow_multi_return = false\n          source = expect_issue rule, <<-CRYSTAL\n            def method(a, b)\n              return a, b\n            # ^^^^^^^^^^^ error: Redundant `return` detected\n            end\n            CRYSTAL\n\n          expect_correction source, <<-CRYSTAL\n            def method(a, b)\n              {a, b}\n            end\n            CRYSTAL\n        end\n      end\n\n      context \"#allow_empty_return\" do\n        it \"allows empty returns by default\" do\n          expect_no_issues subject, <<-CRYSTAL\n            def method\n              return\n            end\n            CRYSTAL\n        end\n\n        it \"allows to configure empty returns\" do\n          rule = RedundantReturn.new\n          rule.allow_empty_return = false\n          source = expect_issue rule, <<-CRYSTAL\n            def method\n              return\n            # ^^^^^^ error: Redundant `return` detected\n            end\n            CRYSTAL\n\n          expect_no_corrections source\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/redundant_self_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe RedundantSelf do\n    subject = RedundantSelf.new\n\n    it \"does not report solitary `self` reference\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def bar\n            self\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report calls without a receiver\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def foo; end\n\n          def foo!\n            foo || 42\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if `self` is used in a method call with a reserved keyword\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def foo\n            if self.responds_to?(:with)\n              self.with { 42 }\n            end\n            self.is_a?(Foo) ? self.class : self.as?(Foo)\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if `self` is used in the presence of a method argument with the same name\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def foo; end\n\n          def bar(foo)\n            foo || self.foo\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if `self` is used in the presence of a block argument with the same name\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def foo\n            42\n          end\n\n          def bar\n            [1, 11, 111].map { |foo| self.foo + foo }\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if `self` is used in the presence of a block argument with the same name (inside block)\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def foo\n            42\n          end\n\n          def bar\n            foo : Int32?\n\n            1.try do |x|\n              2.try do |y|\n                3.try do |z|\n                  self.foo + foo\n                end\n              end\n            end\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if `self` is used in the presence of a method argument with the same name inherited from the parent scope\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def foo\n            42\n          end\n\n          def bar(foo)\n            [1, 11, 111].map { |n| self.foo + n }\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if `self` is used in the presence of a proc argument with the same name\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def foo\n            42\n          end\n\n          def bar\n            -> (foo : Int32) { self.foo + foo }\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if `self` is used within the definition of a variable with the same name\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def foo; end\n\n          def foo!\n            foo = self.foo\n            foo\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if `self` is used in the presence of a variable with the same name\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def foo; end\n\n          def foo!\n            foo = 42\n            bar = self.foo\n            bar\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if `self` is used in the presence of a type declaration variable with the same name\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def foo; end\n\n          def foo!\n            foo : Int32 = 42\n            bar = self.foo\n            bar\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if `self` is used in the presence of a type declaration variable with the same name (2)\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def foo; end\n\n          def foo!\n            foo : Int32?\n            bar = self.foo\n            bar\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if `self` is used with a setter\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def foo=(value); end\n\n          def foo!\n            self.foo = 42\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if `self` is used with an operator assign\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def foo=(value); end\n\n          def foo!\n            self.foo ||= 42\n            self.foo &&= 42\n            self.foo += 42\n            self.foo -= 42\n            self.foo *= 42\n            self.foo /= 42\n            self.foo %= 42\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if `self` is used with an operator\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def +(value); end\n          def %(value); end\n          def <<(value); end\n\n          def foo\n            self + \"foo\"\n            self % \"foo\"\n            self << \"foo\"\n          end\n\n          self | String\n        end\n        CRYSTAL\n    end\n\n    it \"does not report if `self` is used with a square bracket operator\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          def []?(i); end\n          def [](i); end\n          def []=(i, value); end\n\n          def foo?\n            self[0]?\n          end\n\n          def foo\n            self[0]\n          end\n\n          def foo!\n            self[0] = 42\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"reports if `self` is used in the presence of a type declaration with the same name in outer scope\" do\n      expect_issue subject, <<-CRYSTAL\n        class Foo\n          foo : Int32?\n\n          def foo; end\n\n          def foo!\n            bar = self.foo\n                # ^^^^ error: Redundant `self` detected\n            bar\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there is redundant `self` used in a method body\" do\n      source = expect_issue subject, <<-CRYSTAL\n        class Foo\n          def foo(&); end\n\n          def foo!\n            self.foo { nil } || 42\n          # ^^^^ error: Redundant `self` detected\n          end\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        class Foo\n          def foo(&); end\n\n          def foo!\n            foo { nil } || 42\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"correctly replaces multiline calls\" do\n      source = expect_issue subject, <<-CRYSTAL\n        class Foo\n          property bar : String\n\n          def foo\n            self\n          # ^^^^ error: Redundant `self` detected\n              .bar\n              .chars.map do |char|\n                char.upcase\n              end\n              .each { |char| raise \"Boom!\" if char.empty? }\n          end\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        class Foo\n          property bar : String\n\n          def foo\n            bar\n              .chars.map do |char|\n                char.upcase\n              end\n              .each { |char| raise \"Boom!\" if char.empty? }\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there is redundant `self` used in a method arguments' default values\" do\n      source = expect_issue subject, <<-CRYSTAL\n        class Foo\n          def foo\n            42\n          end\n\n          def foo!(bar = self.foo, baz = false)\n                       # ^^^^ error: Redundant `self` detected\n          end\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        class Foo\n          def foo\n            42\n          end\n\n          def foo!(bar = foo, baz = false)\n          end\n        end\n        CRYSTAL\n    end\n\n    it \"reports if there is redundant `self` used in a string interpolation\" do\n      source = expect_issue subject, <<-'CRYSTAL'\n        class Foo\n          def foo; end\n\n          def foo!\n            \"#{self.foo || 42}\"\n             # ^^^^ error: Redundant `self` detected\n          end\n        end\n        CRYSTAL\n\n      expect_correction source, <<-'CRYSTAL'\n        class Foo\n          def foo; end\n\n          def foo!\n            \"#{foo || 42}\"\n          end\n        end\n        CRYSTAL\n    end\n\n    {% for keyword in %w[class module].map(&.id) %}\n      it \"reports if there is redundant `self` within {{ keyword }} definition\" do\n        source = expect_issue subject, <<-CRYSTAL\n          {{ keyword }} Foo\n            def self.foo; end\n\n            self.foo\n          # ^^^^ error: Redundant `self` detected\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          {{ keyword }} Foo\n            def self.foo; end\n\n            foo\n          end\n          CRYSTAL\n      end\n    {% end %}\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/unless_else_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe UnlessElse do\n    subject = UnlessElse.new\n\n    it \"passes if unless hasn't else\" do\n      expect_no_issues subject, <<-CRYSTAL\n        unless something\n          :ok\n        end\n        CRYSTAL\n    end\n\n    it \"fails if unless has else\" do\n      source = expect_issue subject, <<-CRYSTAL\n        unless something\n        # ^^^^^^^^^^^^^^ error: Favour `if` over `unless` with `else`\n          :one\n        else\n          :two\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        if something\n          :two\n        else\n          :one\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/verbose_block_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe VerboseBlock do\n    subject = VerboseBlock.new\n\n    it \"passes if there is no potential performance improvements\" do\n      expect_no_issues subject, <<-CRYSTAL\n        (1..3).any?(&.odd?)\n        (1..3).join('.', &.to_s)\n        (1..3).each_with_index { |i, idx| i * idx }\n        (1..3).map { |i| typeof(i) }\n        (1..3).map { |i| i || 0 }\n        (1..3).map { |i| :foo }\n        (1..3).map { |i| :foo.to_s.split.join('.') }\n        (1..3).map { :foo }\n        CRYSTAL\n    end\n\n    it \"passes if the block argument is used within the body\" do\n      expect_no_issues subject, <<-CRYSTAL\n        (1..3).map { |i| i * i }\n        (1..3).map { |j| j * j.to_i64 }\n        (1..3).map { |k| k.to_i64 * k }\n        (1..3).map { |l| l.to_i64 * l.to_i64 }\n        (1..3).map { |m| m.to_s[start: m.to_i64, count: 3]? }\n        (1..3).map { |n| n.to_s.split.map { |z| n.to_i * z.to_i }.join }\n        (1..3).map { |o| o.foo = foos[o.abs]? || 0 }\n        CRYSTAL\n    end\n\n    it \"reports if there is a call with a collapsible block\" do\n      source = expect_issue subject, <<-CRYSTAL\n        (1..3).any? { |i| i.odd? }\n             # ^^^^^^^^^^^^^^^^^^^ error: Use short block notation instead: `any?(&.odd?)`\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        (1..3).any?(&.odd?)\n        CRYSTAL\n    end\n\n    it \"reports if there is a call with an argument + collapsible block\" do\n      source = expect_issue subject, <<-CRYSTAL\n        (1..3).join('.') { |i| i.to_s }\n             # ^^^^^^^^^^^^^^^^^^^^^^^^ error: Use short block notation instead: `join('.', &.to_s)`\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        (1..3).join('.', &.to_s)\n        CRYSTAL\n    end\n\n    it \"reports if there is a call with a collapsible block (with chained call)\" do\n      source = expect_issue subject, <<-CRYSTAL\n        (1..3).map { |i| i.to_s.split.reverse.join.strip }\n             # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use short block notation instead: `map(&.to_s.split.reverse.join.strip)`\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        (1..3).map(&.to_s.split.reverse.join.strip)\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      it \"#exclude_calls_with_block\" do\n        rule = VerboseBlock.new\n\n        rule.exclude_calls_with_block = true\n        expect_no_issues rule, <<-CRYSTAL\n          (1..3).in_groups_of(1) { |i| i.map(&.to_s) }\n          CRYSTAL\n\n        rule.exclude_calls_with_block = false\n        source = expect_issue rule, <<-CRYSTAL\n          (1..3).in_groups_of(1) { |i| i.map(&.to_s) }\n               # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use short block notation instead: `in_groups_of(1, &.map(&.to_s))`\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          (1..3).in_groups_of(1, &.map(&.to_s))\n          CRYSTAL\n      end\n\n      it \"#exclude_multiple_line_blocks\" do\n        rule = VerboseBlock.new\n\n        rule.exclude_multiple_line_blocks = true\n        expect_no_issues rule, <<-CRYSTAL\n          (1..3).any? do |i|\n            i.odd?\n          end\n          CRYSTAL\n\n        rule.exclude_multiple_line_blocks = false\n        source = expect_issue rule, <<-CRYSTAL\n          (1..3).any? do |i|\n               # ^^^^^^^^^^^ error: Use short block notation instead: `any?(&.odd?)`\n            i.odd?\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          (1..3).any?(&.odd?)\n          CRYSTAL\n      end\n\n      it \"#exclude_prefix_operators\" do\n        rule = VerboseBlock.new\n\n        rule.exclude_prefix_operators = true\n        expect_no_issues rule, <<-CRYSTAL\n          (1..3).sum { |i| +i }\n          (1..3).sum { |i| -i }\n          (1..3).sum { |i| ~i }\n          CRYSTAL\n\n        rule.exclude_prefix_operators = false\n        rule.exclude_operators = false\n        source = expect_issue rule, <<-CRYSTAL\n          (1..3).sum { |i| +i }\n               # ^^^^^^^^^^^^^^ error: Use short block notation instead: `sum(&.+)`\n          (1..3).sum { |i| -i }\n               # ^^^^^^^^^^^^^^ error: Use short block notation instead: `sum(&.-)`\n          (1..3).sum { |i| ~i }\n               # ^^^^^^^^^^^^^^ error: Use short block notation instead: `sum(&.~)`\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          (1..3).sum(&.+)\n          (1..3).sum(&.-)\n          (1..3).sum(&.~)\n          CRYSTAL\n      end\n\n      it \"#exclude_operators\" do\n        rule = VerboseBlock.new\n\n        rule.exclude_operators = true\n        expect_no_issues rule, <<-CRYSTAL\n          (1..3).sum { |i| i * 2 }\n          CRYSTAL\n\n        rule.exclude_operators = false\n        source = expect_issue rule, <<-CRYSTAL\n          (1..3).sum { |i| i * 2 }\n               # ^^^^^^^^^^^^^^^^^ error: Use short block notation instead: `sum(&.*(2))`\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          (1..3).sum(&.*(2))\n          CRYSTAL\n      end\n\n      it \"#exclude_setters\" do\n        rule = VerboseBlock.new\n\n        rule.exclude_setters = true\n        expect_no_issues rule, <<-CRYSTAL\n          Char::Reader.new(\"abc\").tap { |reader| reader.pos = 0 }\n          CRYSTAL\n\n        rule.exclude_setters = false\n        source = expect_issue rule, <<-CRYSTAL\n          Char::Reader.new(\"abc\").tap { |reader| reader.pos = 0 }\n                                # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use short block notation instead: `tap(&.pos=(0))`\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          Char::Reader.new(\"abc\").tap(&.pos=(0))\n          CRYSTAL\n      end\n\n      it \"#max_line_length\" do\n        rule = VerboseBlock.new\n        rule.exclude_multiple_line_blocks = false\n\n        rule.max_line_length = 60\n        expect_no_issues rule, <<-CRYSTAL\n          (1..3).tap &.tap &.tap &.tap &.tap &.tap &.tap do |i|\n            i.to_s.reverse.strip.blank?\n          end\n          CRYSTAL\n\n        rule.max_line_length = nil\n        source = expect_issue rule, <<-CRYSTAL\n          (1..3).tap &.tap &.tap &.tap &.tap &.tap &.tap do |i|\n                                                   # ^^^^^^^^^^ error: Use short block notation instead: `tap(&.to_s.reverse.strip.blank?)`\n            i.to_s.reverse.strip.blank?\n          end\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          (1..3).tap &.tap &.tap &.tap &.tap &.tap &.tap(&.to_s.reverse.strip.blank?)\n          CRYSTAL\n      end\n\n      it \"#max_length\" do\n        rule = VerboseBlock.new\n\n        rule.max_length = 30\n        expect_no_issues rule, <<-CRYSTAL\n          (1..3).tap { |i| i.to_s.split.reverse.join.strip.blank? }\n          CRYSTAL\n\n        rule.max_length = nil\n        source = expect_issue rule, <<-CRYSTAL\n          (1..3).tap { |i| i.to_s.split.reverse.join.strip.blank? }\n               # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: [...] `tap(&.to_s.split.reverse.join.strip.blank?)`\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          (1..3).tap(&.to_s.split.reverse.join.strip.blank?)\n          CRYSTAL\n      end\n    end\n\n    context \"macro\" do\n      it \"reports in macro scope\" do\n        source = expect_issue subject, <<-CRYSTAL\n          {{ (1..3).any? { |i| i.odd? } }}\n                  # ^^^^^^^^^^^^^^^^^^^ error: Use short block notation instead: `any?(&.odd?)`\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          {{ (1..3).any?(&.odd?) }}\n          CRYSTAL\n      end\n    end\n\n    it \"reports call args and named_args\" do\n      rule = VerboseBlock.new\n      rule.exclude_operators = false\n\n      source = expect_issue rule, <<-CRYSTAL\n        (1..3).map { |i| i.to_s[start: 0.to_i64, count: 3]? }\n             # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: [...] `map(&.to_s.[start: 0.to_i64, count: 3]?)`\n        (1..3).map { |i| i.to_s[0.to_i64, count: 3]? }\n             # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: [...] `map(&.to_s.[0.to_i64, count: 3]?)`\n        (1..3).map { |i| i.to_s[0.to_i64, 3]? }\n             # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: [...] `map(&.to_s.[0.to_i64, 3]?)`\n        (1..3).map { |i| i.to_s[start: 0.to_i64, count: 3] = \"foo\" }\n             # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: [...] `map(&.to_s.[start: 0.to_i64, count: 3]=(\"foo\"))`\n        (1..3).map { |i| i.to_s[0.to_i64, count: 3] = \"foo\" }\n             # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: [...] `map(&.to_s.[0.to_i64, count: 3]=(\"foo\"))`\n        (1..3).map { |i| i.to_s[0.to_i64, 3] = \"foo\" }\n             # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: [...] `map(&.to_s.[0.to_i64, 3]=(\"foo\"))`\n        (1..3).map { |i| i.to_s.camelcase(lower: true) }\n             # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: [...] `map(&.to_s.camelcase(lower: true))`\n        (1..3).map { |i| i.to_s.camelcase }\n             # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: [...] `map(&.to_s.camelcase)`\n        (1..3).map { |i| i.to_s.gsub('_', '-') }\n             # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: [...] `map(&.to_s.gsub('_', '-'))`\n        (1..3).map { |i| i.in?(*{1, 2, 3}, **{foo: :bar}) }\n             # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: [...] `map(&.in?(*{1, 2, 3}, **{foo: :bar}))`\n        (1..3).map { |i| i.in?(1, *foo, 3, **bar) }\n             # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: [...] `map(&.in?(1, *foo, 3, **bar))`\n        (1..3).join(separator: '.') { |i| i.to_s }\n             # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: [...] `join(separator: '.', &.to_s)`\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        (1..3).map(&.to_s.[start: 0.to_i64, count: 3]?)\n        (1..3).map(&.to_s.[0.to_i64, count: 3]?)\n        (1..3).map(&.to_s.[0.to_i64, 3]?)\n        (1..3).map(&.to_s.[start: 0.to_i64, count: 3]=(\"foo\"))\n        (1..3).map(&.to_s.[0.to_i64, count: 3]=(\"foo\"))\n        (1..3).map(&.to_s.[0.to_i64, 3]=(\"foo\"))\n        (1..3).map(&.to_s.camelcase(lower: true))\n        (1..3).map(&.to_s.camelcase)\n        (1..3).map(&.to_s.gsub('_', '-'))\n        (1..3).map(&.in?(*{1, 2, 3}, **{foo: :bar}))\n        (1..3).map(&.in?(1, *foo, 3, **bar))\n        (1..3).join(separator: '.', &.to_s)\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/verbose_nil_type_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe VerboseNilType do\n    subject = VerboseNilType.new\n\n    it \"passes if there are no issues\" do\n      expect_no_issues subject, <<-CRYSTAL\n        foo : String | Number | NilableType? = nil\n        bar : NilableType | String | Number? = nil\n        baz : String | NilableType | Number\n        bat : String?\n        bun : Nil\n        CRYSTAL\n    end\n\n    it \"passes if the union includes metaclass (Foo.class)\" do\n      expect_no_issues subject, <<-CRYSTAL\n        foo : Foo.class | Nil = nil\n        CRYSTAL\n    end\n\n    it \"reports if there is a verbose nil type (simple)\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo : String | Nil = nil\n            # ^^^^^^^^^^^^ error: Prefer `?` instead of `| Nil` in unions\n        bar : Nil | String\n            # ^^^^^^^^^^^^ error: Prefer `?` instead of `| Nil` in unions\n        baz : String | Nil | Int\n            # ^^^^^^^^^^^^^^^^^^ error: Prefer `?` instead of `| Nil` in unions\n\n        def bat : Symbol | Nil | String\n                # ^^^^^^^^^^^^^^^^^^^^^ error: Prefer `?` instead of `| Nil` in unions\n        end\n\n        alias Foo = Nil | Symbol\n                  # ^^^^^^^^^^^^ error: Prefer `?` instead of `| Nil` in unions\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo : String? = nil\n        bar : String?\n        baz : String | Int?\n\n        def bat : Symbol | String?\n        end\n\n        alias Foo = Symbol?\n        CRYSTAL\n    end\n\n    it \"reports if there is a verbose nil type (edge-case)\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo : String? | Nil\n            # ^^^^^^^^^^^^^ error: Prefer `?` instead of `| Nil` in unions\n        bar : String | Nil?\n            # ^^^^^^^^^^^^^ error: Prefer `?` instead of `| Nil` in unions\n        baz : Nil | String? | Symbol\n            # ^^^^^^^^^^^^^^^^^^^^^^ error: Prefer `?` instead of `| Nil` in unions\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo : String?\n        bar : String?\n        baz : String | Symbol?\n        CRYSTAL\n    end\n\n    it \"reports if there is a verbose nil type (generics + nested unions)\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo : (Array(String | Nil) | Nil) | Foo\n            # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Prefer `?` instead of `| Nil` in unions\n            # ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Prefer `?` instead of `| Nil` in unions\n                   # ^^^^^^^^^^^^ error: Prefer `?` instead of `| Nil` in unions\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo : (Array(String?) | Nil) | Foo\n        CRYSTAL\n    end\n\n    it \"reports if there is a verbose nil type (generics)\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo : Array(String | Nil) | Foo\n                  # ^^^^^^^^^^^^ error: Prefer `?` instead of `| Nil` in unions\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo : Array(String?) | Foo\n        CRYSTAL\n    end\n\n    it \"corrects the parenthesized single type unions\" do\n      source = expect_issue subject, <<-CRYSTAL\n        foo : (Nil | (Symbol | Nil)) | Foo\n            # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Prefer `?` instead of `| Nil` in unions\n            # ^^^^^^^^^^^^^^^^^^^^^^ error: Prefer `?` instead of `| Nil` in unions\n                   # ^^^^^^^^^^^^^^ error: Prefer `?` instead of `| Nil` in unions\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        foo : Symbol | Foo?\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      it \"#explicit_nil\" do\n        rule = VerboseNilType.new\n        rule.explicit_nil = true\n\n        expect_no_issues rule, <<-CRYSTAL\n          foo : String | Number | Nil = nil\n          bar : String | Nil\n          CRYSTAL\n\n        source = expect_issue rule, <<-CRYSTAL\n          foo : String? = nil\n              # ^^^^^^^ error: Prefer `| Nil` instead of `?` in unions\n          bar : String?\n              # ^^^^^^^ error: Prefer `| Nil` instead of `?` in unions\n          CRYSTAL\n\n        expect_correction source, <<-CRYSTAL\n          foo : String | Nil = nil\n          bar : String | Nil\n          CRYSTAL\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/style/while_true_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Style\n  describe WhileTrue do\n    subject = WhileTrue.new\n\n    it \"passes if there is no `while true`\" do\n      expect_no_issues subject, <<-CRYSTAL\n        a = 1\n        loop do\n          a += 1\n          break if a > 5\n        end\n        CRYSTAL\n    end\n\n    it \"fails if there is `while true`\" do\n      source = expect_issue subject, <<-CRYSTAL\n        a = 1\n        while true\n        # ^^^^^^^^ error: While statement using `true` literal as condition\n          a += 1\n          break if a > 5\n        end\n        CRYSTAL\n\n      expect_correction source, <<-CRYSTAL\n        a = 1\n        loop do\n          a += 1\n          break if a > 5\n        end\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/typing/macro_call_argument_type_restriction_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Typing\n  describe MacroCallArgumentTypeRestriction do\n    subject = MacroCallArgumentTypeRestriction.new\n\n    it \"passes if macro call args have type restrictions\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          getter foo : Int32?\n          setter bar : Array(Int32)?\n          property baz : Bool?\n        end\n\n        record Task,\n          cmd : String,\n          args : Array(String)\n        CRYSTAL\n    end\n\n    it \"passes if macro call args have default values\" do\n      expect_no_issues subject, <<-CRYSTAL\n        class Foo\n          getter foo = 0\n          setter bar = [] of Int32\n          property baz = true\n        end\n\n        record Task,\n          cmd = \"\",\n          args = %w[]\n        CRYSTAL\n    end\n\n    it \"fails if a macro call arg doesn't have a type restriction\" do\n      expect_issue subject, <<-CRYSTAL\n        class Foo\n          getter foo\n               # ^^^ error: Argument should have a type restriction\n          getter :bar\n               # ^^^^ error: Argument should have a type restriction\n          getter \"baz\"\n               # ^^^^^ error: Argument should have a type restriction\n        end\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      context \"#default_value\" do\n        rule = MacroCallArgumentTypeRestriction.new\n        rule.default_value = true\n\n        it \"fails if a macro call arg with a default value doesn't have a type restriction\" do\n          expect_issue rule, <<-CRYSTAL\n            class Foo\n              getter foo = \"bar\"\n                   # ^^^ error: Argument should have a type restriction\n            end\n            CRYSTAL\n        end\n\n        it \"fails if a record call arg with default value doesn't have a type restriction\" do\n          expect_issue rule, <<-CRYSTAL\n            record Task,\n              cmd : String,\n              args = %[]\n            # ^^^^ error: Argument should have a type restriction\n            CRYSTAL\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/typing/method_parameter_type_restriction_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Typing\n  describe MethodParameterTypeRestriction do\n    subject = MethodParameterTypeRestriction.new\n\n    it \"passes if a method parameter has a type restriction\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(bar : String, baz : _) : String\n        end\n        CRYSTAL\n    end\n\n    it \"passes if a splat method parameter has a type restriction\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(*bar : String) : String\n        end\n        CRYSTAL\n    end\n\n    it \"fails if a splat method parameter with a name doesn't have a type restriction\" do\n      expect_issue subject, <<-CRYSTAL\n        def foo(*bar) : String\n               # ^^^ error: Method parameter should have a type restriction\n        end\n        CRYSTAL\n    end\n\n    it \"passes if a splat parameter without a name doesn't have a type restriction\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(bar : String, *, baz : String = \"bat\") : String\n        end\n        CRYSTAL\n    end\n\n    it \"passes if a double splat method parameter doesn't have a type restriction\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(bar : String, **opts) : String\n        end\n        CRYSTAL\n    end\n\n    it \"passes if a private method parameter doesn't have a type restriction\" do\n      expect_no_issues subject, <<-CRYSTAL\n        private def foo(bar)\n        end\n        CRYSTAL\n    end\n\n    it \"passes if a protected method parameter doesn't have a type restriction\" do\n      expect_no_issues subject, <<-CRYSTAL\n        protected def foo(bar)\n        end\n        CRYSTAL\n    end\n\n    it \"passes if a method has a `:nodoc:` annotation\" do\n      expect_no_issues subject, <<-CRYSTAL\n        # :nodoc:\n        def foo(bar); end\n        CRYSTAL\n    end\n\n    it \"fails if a public method parameter doesn't have a type restriction\" do\n      expect_issue subject, <<-CRYSTAL\n        def foo(bar)\n              # ^^^ error: Method parameter should have a type restriction\n        end\n        CRYSTAL\n    end\n\n    it \"fails if a public method external parameter doesn't have a type restriction\" do\n      expect_issue subject, <<-CRYSTAL\n        def foo(bar, ext baz)\n              # ^^^ error: Method parameter should have a type restriction\n                   # ^^^ error: Method parameter should have a type restriction\n        end\n        CRYSTAL\n    end\n\n    it \"passes if a method parameter with a default value doesn't have a type restriction\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(bar = \"baz\")\n        end\n        CRYSTAL\n    end\n\n    it \"passes if a block parameter doesn't have a type restriction\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo(&)\n        end\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      context \"#private_methods\" do\n        rule = MethodParameterTypeRestriction.new\n        rule.private_methods = true\n\n        it \"passes if a method has a parameter type restriction\" do\n          expect_no_issues rule, <<-CRYSTAL\n            private def foo(bar : String) : String\n            end\n            CRYSTAL\n        end\n\n        it \"passes if a protected method parameter doesn't have a type restriction\" do\n          expect_no_issues rule, <<-CRYSTAL\n            protected def foo(bar)\n            end\n            CRYSTAL\n        end\n\n        it \"fails if a public method doesn't have a parameter type restriction\" do\n          expect_issue rule, <<-CRYSTAL\n            def foo(bar)\n                  # ^^^ error: Method parameter should have a type restriction\n            end\n            CRYSTAL\n        end\n\n        it \"fails if a private method doesn't have a parameter type restriction\" do\n          expect_issue rule, <<-CRYSTAL\n            private def foo(bar)\n                          # ^^^ error: Method parameter should have a type restriction\n            end\n            CRYSTAL\n        end\n      end\n\n      context \"#protected_methods\" do\n        rule = MethodParameterTypeRestriction.new\n        rule.protected_methods = true\n\n        it \"passes if a method has a parameter type restriction\" do\n          expect_no_issues rule, <<-CRYSTAL\n            protected def foo(bar : String) : String\n            end\n            CRYSTAL\n        end\n\n        it \"passes if a private method parameter doesn't have a type restriction\" do\n          expect_no_issues rule, <<-CRYSTAL\n            private def foo(bar)\n            end\n            CRYSTAL\n        end\n\n        it \"fails if a public method doesn't have a parameter type restriction\" do\n          expect_issue rule, <<-CRYSTAL\n            def foo(bar)\n                  # ^^^ error: Method parameter should have a type restriction\n            end\n            CRYSTAL\n        end\n\n        it \"fails if a protected method doesn't have a parameter type restriction\" do\n          expect_issue rule, <<-CRYSTAL\n            protected def foo(bar)\n                            # ^^^ error: Method parameter should have a type restriction\n            end\n            CRYSTAL\n        end\n      end\n\n      context \"#default_value\" do\n        it \"fails if a method parameter with a default value doesn't have a type restriction\" do\n          rule = MethodParameterTypeRestriction.new\n          rule.default_value = true\n\n          expect_issue rule, <<-CRYSTAL\n            def foo(bar = \"baz\")\n                  # ^^^ error: Method parameter should have a type restriction\n            end\n            CRYSTAL\n        end\n      end\n\n      context \"#block_parameters\" do\n        rule = MethodParameterTypeRestriction.new\n        rule.block_parameters = true\n\n        it \"fails if a block parameter without a name doesn't have a type restriction\" do\n          expect_issue rule, <<-CRYSTAL\n            def foo(&)\n                   # ^ error: Method parameter should have a type restriction\n            end\n            CRYSTAL\n        end\n\n        it \"fails if a block parameter with a name doesn't have a type restriction\" do\n          expect_issue rule, <<-CRYSTAL\n            def foo(&block)\n                   # ^^^^^ error: Method parameter should have a type restriction\n            end\n            CRYSTAL\n        end\n      end\n\n      context \"#nodoc_methods\" do\n        rule = MethodParameterTypeRestriction.new\n        rule.nodoc_methods = true\n\n        it \"fails if a public method parameter doesn't have a type restriction\" do\n          expect_issue rule, <<-CRYSTAL\n            # :nodoc:\n            def foo(bar)\n                  # ^^^ error: Method parameter should have a type restriction\n            end\n            CRYSTAL\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/typing/method_return_type_restriction_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Typing\n  describe MethodReturnTypeRestriction do\n    subject = MethodReturnTypeRestriction.new\n\n    it \"passes if a public method has a return type restriction\" do\n      expect_no_issues subject, <<-CRYSTAL\n        def foo : String\n        end\n        CRYSTAL\n    end\n\n    it \"passes if a private method has a return type restriction\" do\n      expect_no_issues subject, <<-CRYSTAL\n        private def foo : String\n        end\n        CRYSTAL\n    end\n\n    it \"passes if a protected method has a return type restriction\" do\n      expect_no_issues subject, <<-CRYSTAL\n        protected def foo : String\n        end\n        CRYSTAL\n    end\n\n    it \"passes if a private method doesn't have a return type restriction\" do\n      expect_no_issues subject, <<-CRYSTAL\n        private def foo\n        end\n        CRYSTAL\n    end\n\n    it \"passes if a protected method doesn't have a return type restriction\" do\n      expect_no_issues subject, <<-CRYSTAL\n        protected def foo\n        end\n        CRYSTAL\n    end\n\n    it \"passes if a public method has a `:nodoc:` annotation\" do\n      expect_no_issues subject, <<-CRYSTAL\n        # :nodoc:\n        def foo; end\n        CRYSTAL\n    end\n\n    it \"fails if a public method doesn't have a return type restriction\" do\n      expect_issue subject, <<-CRYSTAL\n        def foo\n        # ^^^^^ error: Method should have a return type restriction\n        end\n        CRYSTAL\n    end\n\n    context \"properties\" do\n      context \"#private_methods\" do\n        rule = MethodReturnTypeRestriction.new\n        rule.private_methods = true\n\n        it \"passes if a public method has a return type restriction\" do\n          expect_no_issues rule, <<-CRYSTAL\n            def foo : String\n            end\n            CRYSTAL\n        end\n\n        it \"passes if a private method has a return type restriction\" do\n          expect_no_issues rule, <<-CRYSTAL\n            private def foo : String\n            end\n            CRYSTAL\n        end\n\n        it \"passes if a protected method has a return type restriction\" do\n          expect_no_issues rule, <<-CRYSTAL\n            protected def foo : String\n            end\n            CRYSTAL\n        end\n\n        it \"passes if a protected method doesn't have a return type restriction\" do\n          expect_no_issues rule, <<-CRYSTAL\n            protected def foo\n            end\n            CRYSTAL\n        end\n\n        it \"fails if a public method doesn't have a return type restriction\" do\n          expect_issue rule, <<-CRYSTAL\n            def foo\n            # ^^^^^ error: Method should have a return type restriction\n            end\n            CRYSTAL\n        end\n\n        it \"fails if a private method doesn't have a return type restriction\" do\n          expect_issue rule, <<-CRYSTAL\n            private def foo\n                  # ^^^^^^^ error: Method should have a return type restriction\n            end\n            CRYSTAL\n        end\n      end\n\n      context \"#protected_methods\" do\n        rule = MethodReturnTypeRestriction.new\n        rule.protected_methods = true\n\n        it \"passes if a public method has a return type restriction\" do\n          expect_no_issues rule, <<-CRYSTAL\n            def foo : String\n            end\n            CRYSTAL\n        end\n\n        it \"passes if a private method doesn't have a return type restriction\" do\n          expect_no_issues rule, <<-CRYSTAL\n            private def foo\n            end\n            CRYSTAL\n        end\n\n        it \"passes if a protected method has a return type restriction\" do\n          expect_no_issues rule, <<-CRYSTAL\n            protected def foo : String\n            end\n            CRYSTAL\n        end\n\n        it \"fails if a public method doesn't have a return type restriction\" do\n          expect_issue rule, <<-CRYSTAL\n            def foo\n            # ^^^^^ error: Method should have a return type restriction\n            end\n            CRYSTAL\n        end\n\n        it \"fails if a protected method doesn't have a return type restriction\" do\n          expect_issue rule, <<-CRYSTAL\n            protected def foo\n                    # ^^^^^^^ error: Method should have a return type restriction\n            end\n            CRYSTAL\n        end\n      end\n\n      context \"#nodoc_methods\" do\n        rule = MethodReturnTypeRestriction.new\n        rule.nodoc_methods = true\n\n        it \"fails if a public method doesn't have a return type restriction\" do\n          expect_issue rule, <<-CRYSTAL\n            # :nodoc:\n            def foo\n            # ^^^^^ error: Method should have a return type restriction\n            end\n            CRYSTAL\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/rule/typing/proc_literal_return_type_restriction_spec.cr",
    "content": "require \"../../../spec_helper\"\n\nmodule Ameba::Rule::Typing\n  describe ProcLiteralReturnTypeRestriction do\n    subject = ProcLiteralReturnTypeRestriction.new\n\n    it \"passes if a proc literal has a return type restriction\" do\n      expect_no_issues subject, <<-CRYSTAL\n        foo = -> (bar : String) : Nil { }\n        CRYSTAL\n    end\n\n    it \"fails if a proc literal doesn't have a return type restriction\" do\n      expect_issue subject, <<-CRYSTAL\n        foo = -> (bar : String) { }\n            # ^^^^^^^^^^^^^^^^^^^^^ error: Proc literal should have a return type restriction\n        CRYSTAL\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/runner_spec.cr",
    "content": "require \"../spec_helper\"\n\nmodule Ameba\n  private def runner(files = [__FILE__], formatter = DummyFormatter.new)\n    config = Config.load\n    config.formatter = formatter\n    config.globs = files.to_set\n\n    config.update_rule VersionedRule.rule_name, enabled: false\n    config.update_rule ErrorRule.rule_name, enabled: false\n    config.update_rule PerfRule.rule_name, enabled: false\n    config.update_rule AtoAA.rule_name, enabled: false\n    config.update_rule AtoB.rule_name, enabled: false\n    config.update_rule BtoA.rule_name, enabled: false\n    config.update_rule BtoC.rule_name, enabled: false\n    config.update_rule CtoA.rule_name, enabled: false\n    config.update_rule ClassToModule.rule_name, enabled: false\n    config.update_rule ModuleToClass.rule_name, enabled: false\n\n    Runner.new(config)\n  end\n\n  describe Runner do\n    formatter = DummyFormatter.new\n    default_severity = Severity::Convention\n\n    describe \"#run\" do\n      it \"returns self\" do\n        runner.run.should be_a(Runner)\n      end\n\n      context \"invokes hooks\" do\n        before_each do\n          runner(formatter: formatter).run\n        end\n\n        it \"calls started callback\" do\n          formatter.started_sources.should_not be_nil\n        end\n\n        it \"calls finished callback\" do\n          formatter.finished_sources.should_not be_nil\n        end\n\n        it \"calls source_started callback\" do\n          formatter.started_source.should_not be_nil\n        end\n\n        it \"calls source_finished callback\" do\n          formatter.finished_source.should_not be_nil\n        end\n      end\n\n      it \"checks accordingly to the rule #since_version\" do\n        rules = [VersionedRule.new] of Rule::Base\n        source = Source.new path: \"source.cr\"\n\n        v1_0_0 = SemanticVersion.parse(\"1.0.0\")\n        Runner.new(rules, [source], formatter, default_severity, false, v1_0_0).run.success?.should be_true\n\n        v1_5_0 = SemanticVersion.parse(\"1.5.0\")\n        Runner.new(rules, [source], formatter, default_severity, false, v1_5_0).run.success?.should be_false\n\n        v1_10_0 = SemanticVersion.parse(\"1.10.0\")\n        Runner.new(rules, [source], formatter, default_severity, false, v1_10_0).run.success?.should be_false\n      end\n\n      it \"skips rules based on severity\" do\n        rules = [ErrorRule.new] of Rule::Base\n\n        Source.new.tap do |source|\n          Runner.new(rules, [source], formatter, :convention).run\n          source.issues.size.should eq(1)\n        end\n\n        Source.new.tap do |source|\n          Runner.new(rules, [source], formatter, :warning).run\n          source.issues.should be_empty\n        end\n      end\n\n      it \"skips rule check if source is excluded\" do\n        path = \"source.cr\"\n        source = Source.new(path: path)\n\n        all_rules = ([] of Rule::Base).tap do |rules|\n          rule = ErrorRule.new\n          rule.excluded = Set{path}\n          rules << rule\n        end\n\n        Runner.new(all_rules, [source], formatter, default_severity).run.success?.should be_true\n      end\n\n      it \"aborts because of an infinite loop\" do\n        rules = [AtoAA.new] of Rule::Base\n        source = Source.new \"class A; end\", \"source.cr\"\n        message = \"Infinite loop in source.cr caused by Ameba/AtoAA\"\n\n        expect_raises(Runner::InfiniteCorrectionLoopError, message) do\n          Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run\n        end\n      end\n\n      context \"exception in rule\" do\n        it \"raises an exception raised in fiber while running a rule\" do\n          rule = RaiseRule.new\n          rule.should_raise = true\n          rules = [rule] of Rule::Base\n          source = Source.new path: \"source.cr\"\n\n          expect_raises(Exception, \"something went wrong\") do\n            Runner.new(rules, [source], formatter, default_severity).run\n          end\n        end\n      end\n\n      context \"issues sorting\" do\n        it \"sorts issues by severity, line number, and column number\" do\n          rules = [\n            ErrorRule.from_yaml(\"{ Severity: convention, Message: foo }\"),\n            ErrorRule.from_yaml(\"{ Severity: convention, Message: bar, LineNumber: 2 }\"),\n            ErrorRule.from_yaml(\"{ Severity: warning, Message: baz, LineNumber: 2 }\"),\n            ErrorRule.from_yaml(\"{ Severity: error, Message: bat, LineNumber: 2 }\"),\n            ErrorRule.from_yaml(\"{ Severity: warning, Message: baq }\"),\n          ] of Rule::Base\n          source = Source.new \"foo\\nbar\"\n\n          Runner.new(rules, [source], formatter, default_severity).run\n          source.should_not be_valid\n          source.issues.map(&.message).should eq %w[foo baq bar baz bat]\n        end\n      end\n\n      context \"invalid syntax\" do\n        it \"reports a syntax error\" do\n          rules = [Rule::Lint::Syntax.new] of Rule::Base\n          source = Source.new \"def bad_syntax\"\n\n          Runner.new(rules, [source], formatter, default_severity).run\n          source.should_not be_valid\n          source.issues.first.rule.should be_a Rule::Lint::Syntax\n        end\n\n        it \"does not run other rules\" do\n          rules = [Rule::Lint::Syntax.new, Rule::Naming::ConstantNames.new]\n          source = Source.new <<-CRYSTAL\n            MyBadConstant = 1\n\n            when my_bad_syntax\n            CRYSTAL\n\n          Runner.new(rules, [source], formatter, default_severity).run\n          source.should_not be_valid\n          source.issues.size.should eq 1\n        end\n      end\n\n      context \"unneeded disables\" do\n        it \"reports an issue if such disable exists\" do\n          rules = [\n            Rule::Lint::UnneededDisableDirective.new,\n            Rule::Lint::NotNil.new,\n          ] of Rule::Base\n          source = Source.new <<-CRYSTAL\n            a = 1 # ameba:disable Lint/NotNil\n            CRYSTAL\n\n          Runner.new(rules, [source], formatter, default_severity).run\n          source.should_not be_valid\n          source.issues.first.rule.should be_a Rule::Lint::UnneededDisableDirective\n        end\n\n        it \"does not report if the disabled rule is excluded for the source\" do\n          path = \"source.cr\"\n          error_rule = ErrorRule.new\n          error_rule.excluded = Set{path}\n          rules = [error_rule, Rule::Lint::UnneededDisableDirective.new] of Rule::Base\n          source = Source.new <<-CRYSTAL, path\n            a = 1 # ameba:disable #{ErrorRule.rule_name}\n            CRYSTAL\n\n          Runner.new(rules, [source], formatter, default_severity).run\n          source.should be_valid\n        end\n\n        it \"respects Excluded config of UnneededDisableDirective\" do\n          path = \"source.cr\"\n          udd_rule = Rule::Lint::UnneededDisableDirective.new\n          udd_rule.excluded = Set{path}\n          rules = [udd_rule] of Rule::Base\n          source = Source.new <<-CRYSTAL, path\n            a = 1 # ameba:disable LineLength\n            CRYSTAL\n\n          Runner.new(rules, [source], formatter, default_severity).run\n          source.should be_valid\n        end\n      end\n\n      pending \"handles rules with incompatible autocorrect\" do\n        rules = [Rule::Performance::MinMaxAfterMap.new, Rule::Style::VerboseBlock.new]\n        source = Source.new \"list.map { |i| i.size }.max\", File.tempname(\"source\", \".cr\")\n\n        Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run\n        source.code.should eq \"list.max_of(&.size)\"\n      end\n    end\n\n    describe \"#explain\" do\n      output = IO::Memory.new\n\n      before_each do\n        output.clear\n      end\n\n      it \"writes nothing if sources are valid\" do\n        runner = runner(formatter: formatter).run\n        runner.explain(Crystal::Location.new(\"source.cr\", 1, 2), output)\n        output.to_s.should be_empty\n      end\n\n      it \"writes the explanation if sources are not valid and location found\" do\n        rules = [ErrorRule.new] of Rule::Base\n        source = Source.new \"a = 1\", \"source.cr\"\n\n        runner = Runner.new(rules, [source], formatter, default_severity).run\n        runner.explain(Crystal::Location.new(\"source.cr\", 1, 1), output)\n        output.to_s.should_not be_empty\n      end\n\n      it \"writes nothing if sources are not valid and location is not found\" do\n        rules = [ErrorRule.new] of Rule::Base\n        source = Source.new \"a = 1\", \"source.cr\"\n\n        runner = Runner.new(rules, [source], formatter, default_severity).run\n        runner.explain(Crystal::Location.new(\"source.cr\", 1, 2), output)\n        output.to_s.should be_empty\n      end\n    end\n\n    describe \"#success?\" do\n      it \"returns true if runner has not been run\" do\n        runner.success?.should be_true\n      end\n\n      it \"returns true if all sources are valid\" do\n        runner.run.success?.should be_true\n      end\n\n      it \"returns false if there are invalid sources\" do\n        rules = Rule.rules.map &.new.as(Rule::Base)\n        source = Source.new \"WrongConstant = 5\"\n\n        Runner.new(rules, [source], formatter, default_severity).run.success?.should be_false\n      end\n\n      it \"depends on the level of severity\" do\n        rules = Rule.rules.map &.new.as(Rule::Base)\n        source = Source.new \"WrongConstant = 5\\n\"\n\n        Runner.new(rules, [source], formatter, :error).run.success?.should be_true\n        Runner.new(rules, [source], formatter, :warning).run.success?.should be_true\n        Runner.new(rules, [source], formatter, :convention).run.success?.should be_false\n      end\n\n      it \"returns false if issue is disabled\" do\n        rules = [NamedRule.new] of Rule::Base\n        source = Source.new <<-CRYSTAL\n          def foo\n            bar = 1 # ameba:disable #{NamedRule.name}\n          end\n          CRYSTAL\n        source.add_issue NamedRule.new, location: {2, 1},\n          message: \"Useless assignment\"\n\n        Runner\n          .new(rules, [source], formatter, default_severity)\n          .run.success?.should be_true\n      end\n    end\n\n    describe \"#run with rules autocorrecting each other\" do\n      context \"with two conflicting rules\" do\n        context \"if there is an offense in an inspected file\" do\n          it \"aborts because of an infinite loop\" do\n            rules = [AtoB.new, BtoA.new]\n            source = Source.new \"class A; end\", \"source.cr\"\n            message = \"Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoA\"\n\n            expect_raises(Runner::InfiniteCorrectionLoopError, message) do\n              Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run\n            end\n          end\n        end\n\n        context \"if there are multiple offenses in an inspected file\" do\n          it \"aborts because of an infinite loop\" do\n            rules = [AtoB.new, BtoA.new]\n            source = Source.new <<-CRYSTAL, \"source.cr\"\n              class A; end\n              class A_A; end\n              CRYSTAL\n            message = \"Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoA\"\n\n            expect_raises(Runner::InfiniteCorrectionLoopError, message) do\n              Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run\n            end\n          end\n        end\n      end\n\n      context \"with two pairs of conflicting rules\" do\n        it \"aborts because of an infinite loop\" do\n          rules = [ClassToModule.new, ModuleToClass.new, AtoB.new, BtoA.new]\n          source = Source.new \"class A_A; end\", \"source.cr\"\n          message = \"Infinite loop in source.cr caused by Ameba/ClassToModule, Ameba/AtoB -> Ameba/ModuleToClass, Ameba/BtoA\"\n\n          expect_raises(Runner::InfiniteCorrectionLoopError, message) do\n            Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run\n          end\n        end\n      end\n\n      context \"with three rule cycle\" do\n        it \"aborts because of an infinite loop\" do\n          rules = [AtoB.new, BtoC.new, CtoA.new]\n          source = Source.new \"class A; end\", \"source.cr\"\n          message = \"Infinite loop in source.cr caused by Ameba/AtoB -> Ameba/BtoC -> Ameba/CtoA\"\n\n          expect_raises(Runner::InfiniteCorrectionLoopError, message) do\n            Runner.new(rules, [source], formatter, default_severity, autocorrect: true).run\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/severity_spec.cr",
    "content": "require \"../spec_helper\"\n\nmodule Ameba\n  describe Severity do\n    describe \"#symbol\" do\n      it \"returns the symbol for each severity in the enum\" do\n        Severity.values.each(&.symbol.should_not(be_nil))\n      end\n\n      it \"returns symbol for Error\" do\n        Severity::Error.symbol.should eq 'E'\n      end\n\n      it \"returns symbol for Warning\" do\n        Severity::Warning.symbol.should eq 'W'\n      end\n\n      it \"returns symbol for Convention\" do\n        Severity::Convention.symbol.should eq 'C'\n      end\n    end\n\n    describe \".parse\" do\n      it \"creates error severity by name\" do\n        Severity.parse(\"Error\").should eq Severity::Error\n      end\n\n      it \"creates warning severity by name\" do\n        Severity.parse(\"Warning\").should eq Severity::Warning\n      end\n\n      it \"creates convention severity by name\" do\n        Severity.parse(\"Convention\").should eq Severity::Convention\n      end\n\n      it \"raises when name is incorrect\" do\n        expect_raises(Exception, \"Incorrect severity name BadName. Try one of: Error, Warning, Convention\") do\n          Severity.parse(\"BadName\")\n        end\n      end\n    end\n  end\n\n  struct SeverityConvertible\n    include YAML::Serializable\n\n    @[YAML::Field(converter: Ameba::SeverityYamlConverter)]\n    property severity : Severity?\n  end\n\n  describe SeverityYamlConverter do\n    describe \".from_yaml\" do\n      it \"converts from yaml to Severity::Error\" do\n        yaml = {severity: \"error\"}.to_yaml\n        converted = SeverityConvertible.from_yaml(yaml)\n        converted.severity.should eq Severity::Error\n      end\n\n      it \"converts from yaml to Severity::Warning\" do\n        yaml = {severity: \"warning\"}.to_yaml\n        converted = SeverityConvertible.from_yaml(yaml)\n        converted.severity.should eq Severity::Warning\n      end\n\n      it \"converts from yaml to Severity::Convention\" do\n        yaml = {severity: \"convention\"}.to_yaml\n        converted = SeverityConvertible.from_yaml(yaml)\n        converted.severity.should eq Severity::Convention\n      end\n\n      it \"raises if severity is not a scalar\" do\n        yaml = {severity: {convention: true}}.to_yaml\n        expect_raises(Exception, \"Severity must be a scalar\") do\n          SeverityConvertible.from_yaml(yaml)\n        end\n      end\n\n      it \"raises if severity has a wrong type\" do\n        yaml = {severity: [1, 2, 3]}.to_yaml\n        expect_raises(Exception, \"Severity must be a scalar\") do\n          SeverityConvertible.from_yaml(yaml)\n        end\n      end\n    end\n\n    describe \".to_yaml\" do\n      it \"converts Severity::Error to yaml\" do\n        yaml = {severity: \"error\"}.to_yaml\n        converted = SeverityConvertible.from_yaml(yaml).to_yaml\n        converted.should eq \"---\\nseverity: Error\\n\"\n      end\n\n      it \"converts Severity::Warning to yaml\" do\n        yaml = {severity: \"warning\"}.to_yaml\n        converted = SeverityConvertible.from_yaml(yaml).to_yaml\n        converted.should eq \"---\\nseverity: Warning\\n\"\n      end\n\n      it \"converts Severity::Convention to yaml\" do\n        yaml = {severity: \"convention\"}.to_yaml\n        converted = SeverityConvertible.from_yaml(yaml).to_yaml\n        converted.should eq \"---\\nseverity: Convention\\n\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/source/rewriter_spec.cr",
    "content": "require \"../../spec_helper\"\n\nclass Ameba::Source\n  describe Rewriter do\n    code = \"puts(:hello, :world)\"\n    hello = {5, 11}\n    comma_space = {11, 13}\n    world = {13, 19}\n\n    it \"can remove\" do\n      rewriter = Rewriter.new(code)\n      rewriter.remove(*hello)\n      rewriter.process.should eq \"puts(, :world)\"\n    end\n\n    it \"can insert before\" do\n      rewriter = Rewriter.new(code)\n      rewriter.insert_before(*world, \"42, \")\n      rewriter.process.should eq \"puts(:hello, 42, :world)\"\n    end\n\n    it \"can insert after\" do\n      rewriter = Rewriter.new(code)\n      rewriter.insert_after(*hello, \", 42\")\n      rewriter.process.should eq \"puts(:hello, 42, :world)\"\n    end\n\n    it \"can wrap\" do\n      rewriter = Rewriter.new(code)\n      rewriter.wrap(*hello, '[', ']')\n      rewriter.process.should eq \"puts([:hello], :world)\"\n    end\n\n    it \"can replace\" do\n      rewriter = Rewriter.new(code)\n      rewriter.replace(*hello, \":hi\")\n      rewriter.process.should eq \"puts(:hi, :world)\"\n    end\n\n    it \"accepts crossing deletions\" do\n      rewriter = Rewriter.new(code)\n      rewriter.remove(hello[0], comma_space[1])\n      rewriter.remove(comma_space[0], world[1])\n      rewriter.process.should eq \"puts()\"\n    end\n\n    it \"accepts multiple actions\" do\n      rewriter = Rewriter.new(code)\n      rewriter.replace(*comma_space, \" => \")\n      rewriter.wrap(hello[0], world[1], '{', '}')\n      rewriter.replace(*world, \":everybody\")\n      rewriter.wrap(*world, '[', ']')\n      rewriter.process.should eq \"puts({:hello => [:everybody]})\"\n    end\n\n    it \"can wrap the same range\" do\n      rewriter = Rewriter.new(code)\n      rewriter.wrap(*hello, '(', ')')\n      rewriter.wrap(*hello, '[', ']')\n      rewriter.process.should eq \"puts([(:hello)], :world)\"\n    end\n\n    it \"can insert on empty ranges\" do\n      rewriter = Rewriter.new(code)\n      rewriter.insert_before(hello[0], '{')\n      rewriter.replace(hello[0], hello[0], 'x')\n      rewriter.insert_after(hello[0], '}')\n      rewriter.insert_before(hello[1], '[')\n      rewriter.replace(hello[1], hello[1], 'y')\n      rewriter.insert_after(hello[1], ']')\n      rewriter.process.should eq \"puts({x}:hello[y], :world)\"\n    end\n\n    it \"can replace the same range\" do\n      rewriter = Rewriter.new(code)\n      rewriter.replace(*hello, \":hi\")\n      rewriter.replace(*hello, \":hey\")\n      rewriter.process.should eq \"puts(:hey, :world)\"\n    end\n\n    it \"can swallow insertions\" do\n      rewriter = Rewriter.new(code)\n      rewriter.wrap(hello[0] + 1, hello[1], \"__\", \"__\")\n      rewriter.replace(world[0], world[1] - 2, \"xx\")\n      rewriter.replace(hello[0], world[1], \":hi\")\n      rewriter.process.should eq \"puts(:hi)\"\n    end\n\n    it \"rejects out-of-range ranges\" do\n      rewriter = Rewriter.new(code)\n      expect_raises(IndexError) { rewriter.insert_before(0, 100, \"hola\") }\n    end\n\n    it \"ignores trivial actions\" do\n      rewriter = Rewriter.new(code)\n      rewriter.empty?.should be_true\n\n      # This is a trivial wrap\n      rewriter.wrap(2, 5, \"\", \"\")\n      rewriter.empty?.should be_true\n\n      # This is a trivial deletion\n      rewriter.remove(2, 2)\n      rewriter.empty?.should be_true\n\n      rewriter.remove(2, 5)\n      rewriter.empty?.should be_false\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/source_spec.cr",
    "content": "require \"../spec_helper\"\n\nmodule Ameba\n  describe Source do\n    describe \".new\" do\n      it \"allows to create a source by code and path\" do\n        source = Source.new \"code\", \"path\"\n        source.path.should eq \"path\"\n        source.code.should eq \"code\"\n        source.lines.should eq [\"code\"]\n      end\n    end\n\n    describe \"#fullpath\" do\n      it \"returns a relative path of the source\" do\n        source = Source.new path: \"./source_spec.cr\"\n        source.fullpath.should contain \"source_spec.cr\"\n      end\n\n      it \"returns fullpath if path is blank\" do\n        source = Source.new\n        source.fullpath.should_not be_nil\n      end\n    end\n\n    describe \"#spec?\" do\n      it \"returns true if the source is a spec file\" do\n        source = Source.new path: \"./source_spec.cr\"\n        source.spec?.should be_true\n      end\n\n      it \"returns false if the source is not a spec file\" do\n        source = Source.new path: \"./source.cr\"\n        source.spec?.should be_false\n      end\n    end\n\n    describe \"#pos\" do\n      it \"works\" do\n        source = Source.new <<-CRYSTAL\n          foo\n          bar\n          fizz\n          buzz\n          CRYSTAL\n\n        location = Crystal::Location.new(\"\", 2, 1)\n        end_location = Crystal::Location.new(\"\", 3, 4)\n\n        range = Range.new(\n          source.pos(location),\n          source.pos(end_location, end: true),\n          exclusive: true\n        )\n        source.code[range].should eq <<-CRYSTAL\n          bar\n          fizz\n          CRYSTAL\n      end\n    end\n\n    describe \"#correct!\" do\n      it \"skips disabled issues\" do\n        source = Source.new <<-CRYSTAL\n          foo.match(\"bar\").not_nil! # ameba:disable Lint/NotNilAfterNoBang\n          CRYSTAL\n\n        rule = Rule::Lint::NotNilAfterNoBang.new\n        rule.catch(source)\n\n        source.issues.size.should eq 1\n        source.issues.first.disabled?.should be_true\n        source.correct!.should be_false\n        source.code.should contain \".not_nil!\"\n      end\n\n      it \"corrects enabled issues\" do\n        source = Source.new <<-CRYSTAL\n          foo.match(\"bar\").not_nil!\n          CRYSTAL\n\n        rule = Rule::Lint::NotNilAfterNoBang.new\n        rule.catch(source)\n\n        source.issues.size.should eq 1\n        source.issues.first.enabled?.should be_true\n        source.correct!.should be_true\n        source.code.should contain \".match!\"\n        source.code.should_not contain \".not_nil!\"\n      end\n    end\n\n    describe \"#ast\" do\n      it \"parses an ECR file\" do\n        source = Source.new <<-ECR, \"filename.ecr\"\n          hello <%= \"world\" %>\n          ECR\n\n        source.ast.to_s.should eq <<-CRYSTAL\n          __str__ << \"hello \"\n          (\"world\")\n            .to_s(__str__)\n\n          CRYSTAL\n      end\n\n      it \"raises an exception when ECR parsing fails\" do\n        source = Source.new <<-ECR, \"filename.ecr\"\n          hello <%= \"world\" >\n          ECR\n\n        expect_raises(Crystal::SyntaxException) do\n          source.ast\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/spec/annotated_source_spec.cr",
    "content": "require \"../../spec_helper\"\n\nprivate def dummy_issue(code,\n                        message,\n                        position : {Int32, Int32}?,\n                        end_position : {Int32, Int32}?,\n                        path = \"\")\n  location, end_location = nil, nil\n  location = Crystal::Location.new(path, *position) if position\n  end_location = Crystal::Location.new(path, *end_position) if end_position\n\n  Ameba::Issue.new(\n    code: code,\n    rule: Ameba::DummyRule.new,\n    location: location,\n    end_location: end_location,\n    message: message\n  )\nend\n\nprivate def expect_invalid_location(code,\n                                    position,\n                                    end_position,\n                                    message exception_message,\n                                    file = __FILE__,\n                                    line = __LINE__)\n  expect_raises Exception, exception_message, file, line do\n    Ameba::Spec::AnnotatedSource.new(\n      lines: code.lines,\n      issues: [dummy_issue(code, \"Message\", position, end_position, \"path\")]\n    )\n  end\nend\n\nmodule Ameba::Spec\n  describe AnnotatedSource do\n    annotated_text = <<-EOS\n      line 1\n        # ^^ error: Message 1\n      line 2 # error: Message 2\n      EOS\n\n    text_without_annotations = <<-EOS\n      line 1\n      line 2\n      EOS\n\n    text_without_source = <<-EOS\n      # ^ error: Message 1\n      # ^ error: Message 2\n      EOS\n\n    describe \".parse\" do\n      it \"accepts annotated text\" do\n        annotated_source = AnnotatedSource.parse(annotated_text)\n        annotated_source.lines.should eq [\"line 1\", \"line 2\"]\n        annotated_source.annotations.should eq [\n          {1, \"  # ^^ error: \", \"Message 1\"},\n          {2, \"\", \"Message 2\"},\n        ]\n      end\n\n      it \"accepts text containing source only and no annotations\" do\n        annotated_source = AnnotatedSource.parse(text_without_annotations)\n        annotated_source.lines.should eq [\"line 1\", \"line 2\"]\n        annotated_source.annotations.should be_empty\n      end\n\n      it \"accepts text containing annotations only and no source\" do\n        annotated_source = AnnotatedSource.parse(text_without_source)\n        annotated_source.lines.should be_empty\n        annotated_source.annotations.should eq [\n          {1, \"# ^ error: \", \"Message 1\"},\n          {1, \"# ^ error: \", \"Message 2\"},\n        ]\n      end\n\n      it \"accepts RuboCop-style annotations\" do\n        annotated_source = AnnotatedSource.parse <<-EOS\n          line 1\n              ^^ Message\n          line 2\n          EOS\n\n        annotated_source.lines.should eq [\"line 1\", \"line 2\"]\n\n        annotated_source.annotations.should eq [\n          {1, \"    ^^ \", \"Message\"},\n        ]\n      end\n    end\n\n    describe \"#==\" do\n      it \"accepts source lines ending with annotations\" do\n        expected = AnnotatedSource.parse <<-EOS\n          line 1 # error: Message\n          line 2\n          EOS\n\n        actual = AnnotatedSource.parse <<-EOS\n          line 1\n            # ^^ error: Message\n          line 2\n          EOS\n\n        actual.should eq expected\n      end\n\n      it \"accepts annotations that are abbreviated using '[...]'\" do\n        expected = AnnotatedSource.parse <<-EOS\n          line 1 # error: Message [...]\n          line 2\n            # ^^ error: M[...]s[...]g[...] 2\n          EOS\n\n        actual = AnnotatedSource.parse <<-EOS\n          line 1\n            # ^^ error: Message 1\n          line 2\n            # ^^ error: Message 2\n          EOS\n\n        actual.should eq expected\n      end\n    end\n\n    describe \"#to_s\" do\n      it \"accepts annotated text\" do\n        annotated_source = AnnotatedSource.parse(annotated_text)\n        annotated_source.to_s.should eq annotated_text\n      end\n\n      it \"accepts text containing source only and no annotations\" do\n        annotated_source = AnnotatedSource.parse(text_without_annotations)\n        annotated_source.to_s.should eq text_without_annotations\n      end\n\n      it \"accepts text containing annotations only and no source\" do\n        annotated_source = AnnotatedSource.parse(text_without_source)\n        annotated_source.to_s.should eq text_without_source\n      end\n    end\n\n    describe \".new(lines, annotations)\" do\n      it \"sorts the annotations\" do\n        annotated_source = AnnotatedSource.new [] of String, [\n          {2, \"\", \"Annotation C\"},\n          {1, \"\", \"Annotation B\"},\n          {1, \"\", \"Annotation A\"},\n        ]\n        annotated_source.annotations.should eq [\n          {1, \"\", \"Annotation A\"},\n          {1, \"\", \"Annotation B\"},\n          {2, \"\", \"Annotation C\"},\n        ]\n      end\n    end\n\n    describe \".new(lines, issues)\" do\n      it \"raises an exception if issue location is nil\" do\n        expect_invalid_location text_without_annotations,\n          position: nil,\n          end_position: nil,\n          message: \"Missing location for issue 'Message'\"\n      end\n\n      it \"raises an exception if issue starts at column 0\" do\n        expect_invalid_location text_without_annotations,\n          position: {1, 0},\n          end_position: nil,\n          message: \"Invalid issue location: path:1:0\"\n      end\n\n      it \"raises an exception if issue starts at line 0\" do\n        expect_invalid_location text_without_annotations,\n          position: {0, 1},\n          end_position: nil,\n          message: \"Invalid issue location: path:0:1\"\n      end\n\n      it \"raises an exception if issue starts at a non-existent line\" do\n        expect_invalid_location text_without_annotations,\n          position: {3, 1},\n          end_position: nil,\n          message: \"Invalid issue location: path:3:1\"\n      end\n\n      it \"raises an exception if issue ends at column 0\" do\n        expect_invalid_location text_without_annotations,\n          position: {1, 1},\n          end_position: {2, 0},\n          message: \"Invalid issue end location: path:2:0\"\n      end\n\n      it \"raises an exception if issue ends at a non-existent line\" do\n        expect_invalid_location text_without_annotations,\n          position: {1, 1},\n          end_position: {3, 1},\n          message: \"Invalid issue end location: path:3:1\"\n      end\n\n      it \"raises an exception if starting column number is greater than ending column number\" do\n        expect_invalid_location text_without_annotations,\n          position: {1, 2},\n          end_position: {1, 1},\n          message: <<-MSG\n            Invalid issue location\n              start: path:1:2\n              end:   path:1:1\n            MSG\n      end\n\n      it \"raises an exception if starting line number is greater than ending line number\" do\n        expect_invalid_location text_without_annotations,\n          position: {2, 1},\n          end_position: {1, 1},\n          message: <<-MSG\n            Invalid issue location\n              start: path:2:1\n              end:   path:1:1\n            MSG\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/tokenizer_spec.cr",
    "content": "require \"../spec_helper\"\n\nmodule Ameba\n  private def it_tokenizes(str, expected, *, file = __FILE__, line = __LINE__)\n    it \"tokenizes #{str}\", file, line do\n      %w[].tap do |token_types|\n        Tokenizer.new(Source.new str, normalize: false)\n          .run { |token| token_types << token.type.to_s }\n          .should be_true\n      end.should eq(expected), file: file, line: line\n    end\n  end\n\n  describe Tokenizer do\n    describe \"#run\" do\n      it_tokenizes %(\"string\"), %w[DELIMITER_START STRING DELIMITER_END EOF]\n      it_tokenizes %(100), %w[NUMBER EOF]\n      it_tokenizes %('a'), %w[CHAR EOF]\n      it_tokenizes %([]), %w[[] EOF]\n      it_tokenizes %([] of String), %w[[] SPACE IDENT SPACE CONST EOF]\n      it_tokenizes %q(\"str #{3}\"), %w[\n        DELIMITER_START STRING INTERPOLATION_START NUMBER } DELIMITER_END EOF\n      ]\n\n      it_tokenizes %(%w[1 2]),\n        %w[STRING_ARRAY_START STRING STRING STRING_ARRAY_END EOF]\n\n      it_tokenizes %(%i[one two]),\n        %w[SYMBOL_ARRAY_START STRING STRING STRING_ARRAY_END EOF]\n\n      # ameba:disable Style/MultilineStringLiteral\n      it_tokenizes %(\n        class A\n          def method\n            puts \"hello\"\n          end\n        end\n      ), %w[\n        NEWLINE SPACE IDENT SPACE CONST NEWLINE SPACE IDENT SPACE IDENT\n        NEWLINE SPACE IDENT SPACE DELIMITER_START STRING DELIMITER_END\n        NEWLINE SPACE IDENT NEWLINE SPACE IDENT NEWLINE SPACE EOF\n      ]\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba/version_spec.cr",
    "content": "require \"../spec_helper\"\n\nprivate def build_ameba_version(string)\n  Ameba::Version.new(SemanticVersion.parse(string))\nend\n\nmodule Ameba\n  describe Version do\n    context \"#to_s\" do\n      it \"outputs the `version` string\" do\n        version = build_ameba_version(\"1.2.3\")\n        version.to_s.should eq version.version.to_s\n      end\n    end\n\n    context \"#version\" do\n      it \"matches the version format\" do\n        version = build_ameba_version(\"1.2.3\")\n        version.version.to_s.should match /^\\d+\\.\\d+\\.\\d+/\n      end\n    end\n\n    context \"#dev?\" do\n      it \"returns `true` if the version pre-release identifiers contain only `dev`\" do\n        version = build_ameba_version(\"1.2.3-dev\")\n        version.dev?.should be_true\n      end\n\n      it \"returns `true` if the version pre-release identifiers contain `dev`\" do\n        version = build_ameba_version(\"1.2.3-dev.arm64\")\n        version.dev?.should be_true\n      end\n\n      it \"returns `false` if the version pre-release identifiers do not contain `dev`\" do\n        version = build_ameba_version(\"1.2.3\")\n        version.dev?.should be_false\n\n        version = build_ameba_version(\"1.2.3-devo\")\n        version.dev?.should be_false\n      end\n    end\n\n    context \"#release_candidate?\" do\n      it \"returns `true` for `rc` pre-release identifier followed by a number\" do\n        version = build_ameba_version(\"1.2.3-rc-1\")\n        version.release_candidate?.should be_true\n\n        version = build_ameba_version(\"1.2.3-rc1\")\n        version.release_candidate?.should be_true\n\n        version = build_ameba_version(\"1.2.3-RC1\")\n        version.release_candidate?.should be_false\n\n        version = build_ameba_version(\"1.2.3-rc-x\")\n        version.release_candidate?.should be_false\n      end\n\n      it \"returns `true` if the version pre-release identifiers contain only `rc`\" do\n        version = build_ameba_version(\"1.2.3-rc\")\n        version.release_candidate?.should be_true\n      end\n\n      it \"returns `true` if the version pre-release identifiers contain `rc`\" do\n        version = build_ameba_version(\"1.2.3-rc.arm64\")\n        version.release_candidate?.should be_true\n      end\n\n      it \"returns `false` if the version pre-release identifiers do not contain `rc`\" do\n        version = build_ameba_version(\"1.2.3-rcx\")\n        version.release_candidate?.should be_false\n      end\n\n      it \"returns `false` if the version pre-release identifiers are empty\" do\n        version = build_ameba_version(\"1.2.3\")\n        version.release_candidate?.should be_false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/ameba_spec.cr",
    "content": "require \"./spec_helper\"\n\ndescribe Ameba do\n  context \"VERSION\" do\n    it \"contains a version-like string\" do\n      Ameba::VERSION.should match /^\\d+\\.\\d+\\.\\d+/\n    end\n  end\n\n  context \".version\" do\n    it \"returns an `Ameba::Version` object\" do\n      Ameba.version.should be_a Ameba::Version\n    end\n\n    it \"starts with `Ameba::VERSION`\" do\n      Ameba.version.to_s.should start_with Ameba::VERSION\n    end\n  end\nend\n"
  },
  {
    "path": "spec/fixtures/.ameba.yml",
    "content": "Version: \"1.5.0\"\n\nFormatter:\n  Name: flycheck\n\nLint/ComparisonToBoolean:\n  Enabled: true\n"
  },
  {
    "path": "spec/spec_helper.cr",
    "content": "require \"spec\"\nrequire \"../src/ameba\"\nrequire \"../src/ameba/spec/support\"\n\nmodule Ameba\n  # Dummy Rule which does nothing.\n  class DummyRule < Rule::Base\n    properties do\n      description \"Dummy rule that does nothing\"\n      dummy true\n    end\n\n    def test(source)\n    end\n  end\n\n  class NamedRule < Rule::Base\n    properties do\n      description \"A rule with a custom name\"\n    end\n\n    def self.name\n      \"BreakingRule\"\n    end\n  end\n\n  class VersionedRule < Rule::Base\n    properties do\n      since_version \"1.5.0\"\n      description \"Rule with a custom version\"\n    end\n\n    def test(source)\n      issue_for({1, 1}, \"This rule always adds an error\")\n    end\n  end\n\n  # Rule extended description\n  class ErrorRule < Rule::Base\n    properties do\n      description \"Always adds an error\"\n      message \"This rule always adds an error\"\n      line_number 1\n      column_number 1\n    end\n\n    def test(source)\n      issue_for({line_number, column_number}, message)\n    end\n  end\n\n  class ScopeRule < Rule::Base\n    @[YAML::Field(ignore: true)]\n    getter scopes = [] of AST::Scope\n\n    properties do\n      description \"Internal rule to test scopes\"\n    end\n\n    def test(source, node : Crystal::VisibilityModifier, scope : AST::Scope)\n    end\n\n    def test(source, node : Crystal::ASTNode, scope : AST::Scope)\n      @scopes << scope\n    end\n  end\n\n  class SelfCallsRule < Rule::Base\n    @[YAML::Field(ignore: true)]\n    getter call_queue = {} of AST::Scope => Array(Crystal::Call)\n\n    properties do\n      description \"Internal rule to test calls to `self` in scopes\"\n    end\n\n    def test(source, node : Crystal::Call, scope : AST::Scope)\n      @call_queue[scope] ||= [] of Crystal::Call\n      @call_queue[scope] << node\n    end\n  end\n\n  class ElseIfRule < Rule::Base\n    @[YAML::Field(ignore: true)]\n    getter ifs = [] of {Crystal::If, Array(Crystal::If)?}\n\n    properties do\n      description \"Internal rule to test else if branches\"\n    end\n\n    def test(source, node : Crystal::If, ifs : Array(Crystal::If)? = nil)\n      @ifs << {node, ifs}\n    end\n  end\n\n  class FlowExpressionRule < Rule::Base\n    @[YAML::Field(ignore: true)]\n    getter expressions = [] of AST::FlowExpression\n\n    properties do\n      description \"Internal rule to test flow expressions\"\n    end\n\n    def test(source, node, flow_expression : AST::FlowExpression)\n      @expressions << flow_expression\n    end\n  end\n\n  class RedundantControlExpressionRule < Rule::Base\n    @[YAML::Field(ignore: true)]\n    getter nodes = [] of Crystal::ASTNode\n\n    properties do\n      description \"Internal rule to test redundant control expressions\"\n    end\n\n    def test(source, node, visitor : AST::RedundantControlExpressionVisitor)\n      nodes << node\n    end\n  end\n\n  class ImplicitReturnRule < Rule::Base\n    @[YAML::Field(ignore: true)]\n    getter unused_expressions = [] of Crystal::ASTNode\n\n    @[YAML::Field(ignore: true)]\n    getter macro_flags = [] of Bool\n\n    properties do\n      description \"Internal rule to test implicit returns\"\n    end\n\n    def test(source, node, in_macro : Bool)\n      @unused_expressions << node\n      @macro_flags << in_macro\n    end\n  end\n\n  # A rule that always raises an error\n  class RaiseRule < Rule::Base\n    property? should_raise = false\n\n    properties do\n      description \"Internal rule that always raises\"\n    end\n\n    def test(source)\n      should_raise? && raise \"something went wrong\"\n    end\n  end\n\n  class PerfRule < Rule::Performance::Base\n    properties do\n      description \"Sample performance rule\"\n    end\n\n    def test(source)\n      issue_for({1, 1}, \"Poor performance\")\n    end\n  end\n\n  class AtoAA < Rule::Base\n    include AST::Util\n\n    properties do\n      description \"This rule is only used to test infinite loop detection\"\n    end\n\n    def test(source, node : Crystal::ClassDef | Crystal::ModuleDef)\n      return unless name = node_source(node.name, source.lines)\n      return unless name.includes?(\"A\")\n\n      issue_for node.name, message: \"A to AA\" do |corrector|\n        corrector.replace(node.name, name.sub(\"A\", \"AA\"))\n      end\n    end\n  end\n\n  class AtoB < Rule::Base\n    include AST::Util\n\n    properties do\n      description \"This rule is only used to test infinite loop detection\"\n    end\n\n    def test(source, node : Crystal::ClassDef | Crystal::ModuleDef)\n      return unless name = node_source(node.name, source.lines)\n      return unless name.includes?(\"A\")\n\n      issue_for node.name, message: \"A to B\" do |corrector|\n        corrector.replace(node.name, name.tr(\"A\", \"B\"))\n      end\n    end\n  end\n\n  class BtoA < Rule::Base\n    include AST::Util\n\n    properties do\n      description \"This rule is only used to test infinite loop detection\"\n    end\n\n    def test(source, node : Crystal::ClassDef | Crystal::ModuleDef)\n      return unless name = node_source(node.name, source.lines)\n      return unless name.includes?(\"B\")\n\n      issue_for node.name, message: \"B to A\" do |corrector|\n        corrector.replace(node.name, name.tr(\"B\", \"A\"))\n      end\n    end\n  end\n\n  class BtoC < Rule::Base\n    include AST::Util\n\n    properties do\n      description \"This rule is only used to test infinite loop detection\"\n    end\n\n    def test(source, node : Crystal::ClassDef | Crystal::ModuleDef)\n      return unless name = node_source(node.name, source.lines)\n      return unless name.includes?(\"B\")\n\n      issue_for node.name, message: \"B to C\" do |corrector|\n        corrector.replace(node.name, name.tr(\"B\", \"C\"))\n      end\n    end\n  end\n\n  class CtoA < Rule::Base\n    include AST::Util\n\n    properties do\n      description \"This rule is only used to test infinite loop detection\"\n    end\n\n    def test(source, node : Crystal::ClassDef | Crystal::ModuleDef)\n      return unless name = node_source(node.name, source.lines)\n      return unless name.includes?(\"C\")\n\n      issue_for node.name, message: \"C to A\" do |corrector|\n        corrector.replace(node.name, name.tr(\"C\", \"A\"))\n      end\n    end\n  end\n\n  class ClassToModule < Ameba::Rule::Base\n    include Ameba::AST::Util\n\n    properties do\n      description \"This rule is only used to test infinite loop detection\"\n    end\n\n    def test(source, node : Crystal::ClassDef)\n      return unless location = node.location\n\n      end_location = location.adjust(column_number: {{ \"class\".size - 1 }})\n\n      issue_for location, end_location, message: \"class to module\" do |corrector|\n        corrector.replace(location, end_location, \"module\")\n      end\n    end\n  end\n\n  class ModuleToClass < Ameba::Rule::Base\n    include Ameba::AST::Util\n\n    properties do\n      description \"This rule is only used to test infinite loop detection\"\n    end\n\n    def test(source, node : Crystal::ModuleDef)\n      return unless location = node.location\n\n      end_location = location.adjust(column_number: {{ \"module\".size - 1 }})\n\n      issue_for location, end_location, message: \"module to class\" do |corrector|\n        corrector.replace(location, end_location, \"class\")\n      end\n    end\n  end\n\n  class DummyFormatter < Formatter::BaseFormatter\n    property started_sources : Array(Source)?\n    property finished_sources : Array(Source)?\n    property started_source : Source?\n    property finished_source : Source?\n\n    def started(sources)\n      @started_sources = sources\n    end\n\n    def source_started(source : Source)\n      @started_source = source\n    end\n\n    def source_finished(source : Source)\n      @finished_source = source\n    end\n\n    def finished(sources)\n      @finished_sources = sources\n    end\n  end\n\n  class TestNodeVisitor < Crystal::Visitor\n    NODES = {\n      Crystal::NilLiteral,\n      Crystal::Var,\n      Crystal::Assign,\n      Crystal::OpAssign,\n      Crystal::MultiAssign,\n      Crystal::Block,\n      Crystal::Macro,\n      Crystal::Def,\n      Crystal::If,\n      Crystal::While,\n      Crystal::MacroLiteral,\n      Crystal::Expressions,\n      Crystal::ControlExpression,\n      Crystal::Call,\n    }\n\n    def initialize(node)\n      node.accept self\n    end\n\n    def visit(node : Crystal::ASTNode)\n      true\n    end\n\n    {% for node in NODES %}\n      {% getter_name = node.stringify.split(\"::\").last.underscore + \"_nodes\" %}\n\n      getter {{ getter_name.id }} = [] of {{ node }}\n\n      def visit(node : {{ node }})\n        {{ getter_name.id }} << node\n        true\n      end\n    {% end %}\n  end\nend\n\ndef with_presenter(klass, *args, deansify = true, **kwargs, &)\n  io = IO::Memory.new\n\n  presenter = klass.new(io)\n  presenter.run(*args, **kwargs)\n\n  output = io.to_s\n  output = Ameba::Formatter::Util.deansify(output).to_s if deansify\n\n  yield presenter, output\nend\n\ndef as_node(source, *, wants_doc = false)\n  Crystal::Parser.new(source)\n    .tap(&.wants_doc = wants_doc)\n    .parse\nend\n\ndef as_nodes(source, *, wants_doc = false)\n  Ameba::TestNodeVisitor.new(as_node(source, wants_doc: wants_doc))\nend\n\ndef trailing_whitespace\n  ' '\nend\n"
  },
  {
    "path": "src/ameba/ast/flow_expression.cr",
    "content": "require \"./util\"\n\nmodule Ameba::AST\n  # Represents a flow expression in Crystal code.\n  # For example,\n  #\n  # ```\n  # def foobar\n  #   a = 3\n  #   return 42 # => flow expression\n  #   a + 1\n  # end\n  # ```\n  #\n  # Flow expression contains an actual node of a control expression and\n  # a parent node, which allows easily search through the related statement\n  # (i.e. find unreachable code)\n  class FlowExpression\n    include Util\n\n    # Is true only if some of the nodes parents is a loop.\n    getter? in_loop : Bool\n\n    # The actual node of the flow expression.\n    getter node : Crystal::ASTNode\n\n    delegate location, end_location, to_s,\n      to: @node\n\n    # Creates a new flow expression.\n    #\n    # ```\n    # FlowExpression.new(node, parent_node)\n    # ```\n    def initialize(@node, @in_loop)\n    end\n\n    # Returns nodes which can't be reached because of a flow command inside.\n    # For example:\n    #\n    # ```\n    # def foobar\n    #   a = 1\n    #   return 42\n    #\n    #   a + 2 # => unreachable assign node\n    # end\n    # ```\n    def unreachable_nodes\n      unreachable_nodes = [] of Crystal::ASTNode\n\n      case current_node = node\n      when Crystal::Expressions\n        control_flow_found = false\n\n        current_node.expressions.each do |exp|\n          if control_flow_found\n            unreachable_nodes << exp\n          end\n          control_flow_found ||= !loop?(exp) && flow_expression?(exp, in_loop?)\n        end\n      when Crystal::BinaryOp\n        if flow_expression?(current_node.left, in_loop?)\n          unreachable_nodes << current_node.right\n        end\n      end\n\n      unreachable_nodes\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/liveness_analyzer.cr",
    "content": "module Ameba::AST\n  # Performs backward dataflow liveness analysis on a scope's AST to detect\n  # dead stores (assignments whose values are never read before being\n  # overwritten or the scope ends).\n  #\n  # The algorithm walks the AST in reverse execution order, maintaining a\n  # set of variable names that are currently \"live\" (will be read in the\n  # future). When an assignment is encountered and its target variable is\n  # not in the live set, the assignment is marked as a dead store.\n  class LivenessAnalyzer\n    alias LiveSet = Set(String)\n\n    # Maximum iterations for fixed-point convergence in loops.\n    # In practice, convergence happens in 2-3 iterations since the live set\n    # can only grow monotonically and is bounded by the number of variables.\n    MAX_FIXED_POINT_ITERATIONS = 100\n\n    BRANCH_NODES      = %w[If Unless]\n    LOOP_NODES        = %w[While Until]\n    CASE_NODES        = %w[Case Select]\n    INNER_SCOPE_NODES = %w[\n      Block Def ProcLiteral ClassDef ModuleDef EnumDef\n      LibDef FunDef TypeDef CStructOrUnionDef TypeOf\n      Macro MacroIf MacroFor\n    ]\n\n    @dead_stores = [] of Assignment\n    @var_names : Set(String)\n    @assignment_map : Hash(Tuple(String, UInt64), Assignment)\n    @inner_scope_nodes : Set(UInt64)\n\n    # Live sets for loop flow control: `break` exits to post-loop,\n    # `next` jumps to loop condition. Without these, assignments\n    # before break/next would be incorrectly marked as dead.\n    @break_live : LiveSet?\n    @next_live : LiveSet?\n\n    def initialize(@scope : Scope)\n      @var_names = @scope.variables.map(&.name).to_set\n      @assignment_map = build_assignment_map\n      @inner_scope_nodes = @scope.inner_scopes.map(&.node.object_id).to_set\n    end\n\n    # Returns assignments where the value is never read before being\n    # overwritten or the scope ends.\n    def dead_stores : Array(Assignment)\n      analyze.dead_stores\n    end\n\n    # Returns the set of variable names that are live at scope entry.\n    # A variable live at entry means its value (e.g. from a method argument)\n    # will be read before being overwritten.\n    def entry_live_set : LiveSet\n      analyze.entry_live_set\n    end\n\n    # Performs liveness analysis in a single pass, returning both the dead\n    # stores and the entry live set.\n    def analyze : Result\n      @dead_stores.clear\n      body = scope_body(@scope.node)\n      entry_live = body ? propagate_through(body, LiveSet.new) : LiveSet.new\n      Result.new(@dead_stores, entry_live)\n    end\n\n    record Result, dead_stores : Array(Assignment), entry_live_set : LiveSet\n\n    private def build_assignment_map\n      map = Hash(Tuple(String, UInt64), Assignment).new\n      @scope.variables.each do |var|\n        var.assignments.each do |assign|\n          key = {var.name, assign.node.object_id}\n          map[key] ||= assign\n        end\n      end\n      map\n    end\n\n    # ameba:disable Metrics/CyclomaticComplexity\n    private def scope_body(node)\n      case node\n      when Crystal::Def               then node.body\n      when Crystal::FunDef            then node.body\n      when Crystal::Block             then node.body\n      when Crystal::ClassDef          then node.body\n      when Crystal::ModuleDef         then node.body\n      when Crystal::LibDef            then node.body\n      when Crystal::CStructOrUnionDef then node.body\n      when Crystal::Assign            then node.value\n      when Crystal::OpAssign          then node.value\n      when Crystal::ProcLiteral       then node.def.body\n      when Crystal::EnumDef           then Crystal::Expressions.from(node.members)\n      when Crystal::TypeOf            then Crystal::Expressions.from(node.expressions)\n      when Crystal::Expressions       then node\n      else                                 node\n      end\n    end\n\n    private def inner_scope_node?(node)\n      @inner_scope_nodes.includes?(node.object_id)\n    end\n\n    private def find_assignment(node, var_name) : Assignment?\n      @assignment_map[{var_name, node.object_id}]?\n    end\n\n    # Records a dead store if the variable is not in the live set.\n    private def mark_dead_store(assign_node, var_name, live : LiveSet) : Nil\n      return if live.includes?(var_name)\n      if assign = find_assignment(assign_node, var_name)\n        @dead_stores << assign\n      end\n    end\n\n    # Removes `var_name` from the live set. When `mark` is true,\n    # records a dead store if the variable was not live at this point.\n    private def remove_from_live_set(assign_node, var_name, live : LiveSet, mark) : LiveSet\n      mark_dead_store(assign_node, var_name, live) if mark\n      if live.includes?(var_name)\n        live = live.dup\n        live.delete(var_name)\n      end\n      live\n    end\n\n    # Type-specific overloads. Crystal dispatches to the most specific matching\n    # overload at runtime when the argument is a virtual type (Crystal::ASTNode+).\n\n    private def propagate_through(node : Crystal::Nop, live : LiveSet, mark = true) : LiveSet\n      live\n    end\n\n    private def propagate_through(node : Crystal::Expressions, live : LiveSet, mark = true) : LiveSet\n      node.expressions.reverse_each do |exp|\n        live = propagate_through(exp, live, mark)\n      end\n      live\n    end\n\n    private def propagate_through(node : Crystal::Assign, live : LiveSet, mark = true) : LiveSet\n      return live if inner_scope_node?(node)\n\n      target = node.target\n      unless target.is_a?(Crystal::Var) && @var_names.includes?(target.name)\n        live = propagate_through(node.value, live, mark)\n        return propagate_through(target, live, mark)\n      end\n\n      # Only remove from live set if this assignment is tracked in the scope.\n      # Untracked assignments (e.g. inside record/accessor macro args) are transparent.\n      if find_assignment(node, target.name)\n        live = remove_from_live_set(node, target.name, live, mark)\n      end\n      propagate_through(node.value, live, mark)\n    end\n\n    private def propagate_through(node : Crystal::OpAssign, live : LiveSet, mark = true) : LiveSet\n      return live if inner_scope_node?(node)\n\n      target = node.target\n      unless target.is_a?(Crystal::Var) && @var_names.includes?(target.name)\n        live = propagate_through(node.value, live, mark)\n        return propagate_through(target, live, mark)\n      end\n\n      # OpAssign both writes and reads the variable (x += 1 means x = x + 1).\n      # Mark the dead store if the result is never read, then ensure the\n      # variable is live (since the op-assign reads the current value).\n      mark_dead_store(node, target.name, live) if mark\n      unless live.includes?(target.name)\n        live = live.dup\n        live.add(target.name)\n      end\n      propagate_through(node.value, live, mark)\n    end\n\n    private def propagate_through(node : Crystal::MultiAssign, live : LiveSet, mark = true) : LiveSet\n      node.targets.reverse_each do |target|\n        if target.is_a?(Crystal::Var) && @var_names.includes?(target.name)\n          live = remove_from_live_set(node, target.name, live, mark)\n        end\n      end\n      node.values.reverse_each do |value|\n        live = propagate_through(value, live, mark)\n      end\n      live\n    end\n\n    private def propagate_through(node : Crystal::UninitializedVar, live : LiveSet, mark = true) : LiveSet\n      var = node.var\n      if var.is_a?(Crystal::Var) && @var_names.includes?(var.name)\n        live = remove_from_live_set(node, var.name, live, mark)\n      end\n      live\n    end\n\n    private def propagate_through(node : Crystal::TypeDeclaration, live : LiveSet, mark = true) : LiveSet\n      var = node.var\n      if var.is_a?(Crystal::Var) && @var_names.includes?(var.name)\n        if value = node.value\n          live = remove_from_live_set(node, var.name, live, mark)\n          live = propagate_through(value, live, mark)\n        else\n          # Type declarations without a value are type restrictions, not\n          # assignments — don't mark as dead. Since Crystal requires the\n          # variable to be previously undefined, kill it from the live set\n          # as no prior references can exist.\n          live = remove_from_live_set(node, var.name, live, mark: false)\n        end\n      end\n      live\n    end\n\n    private def propagate_through(node : Crystal::Var, live : LiveSet, mark = true) : LiveSet\n      if @var_names.includes?(node.name) && !live.includes?(node.name)\n        live = live.dup\n        live.add(node.name)\n      end\n      live\n    end\n\n    {% for type in BRANCH_NODES %}\n      private def propagate_through(node : Crystal::{{ type.id }}, live : LiveSet, mark = true) : LiveSet\n        then_live = propagate_through(node.then, live, mark)\n        else_live = propagate_through(node.else, live, mark)\n        merged = then_live | else_live\n        propagate_through(node.cond, merged, mark)\n      end\n    {% end %}\n\n    {% for type in LOOP_NODES %}\n      private def propagate_through(node : Crystal::{{ type.id }}, live : LiveSet, mark = true) : LiveSet\n        propagate_through_loop(node.cond, node.body, live, mark)\n      end\n    {% end %}\n\n    {% for type in CASE_NODES %}\n      private def propagate_through(node : Crystal::{{ type.id }}, live : LiveSet, mark = true) : LiveSet\n        propagate_through_case(node, live, mark)\n      end\n    {% end %}\n\n    private def propagate_through(node : Crystal::ExceptionHandler, live : LiveSet, mark = true) : LiveSet\n      post_ensure = (body = node.ensure) ? propagate_through(body, live, mark) : live\n      after_body = (body = node.else) ? propagate_through(body, post_ensure, mark) : post_ensure\n\n      # Rescue branches handle exceptions thrown at any point in the body,\n      # so collect all variables they need.\n      rescue_live = LiveSet.new\n      node.rescues.try &.each do |rescue_node|\n        rescue_live.concat(propagate_through(rescue_node.body, post_ensure, mark))\n      end\n\n      # Body can throw at any point, so variables live in any rescue\n      # branch must also be considered live throughout the body.\n      # Union rescue_live because rescue-needed variables are live before the entire handler.\n      body_live = propagate_through(node.body, after_body | rescue_live, mark)\n      body_live | rescue_live\n    end\n\n    private def propagate_through(node : Crystal::BinaryOp, live : LiveSet, mark = true) : LiveSet\n      # Right side is conditional, so union with entry state\n      right_live = propagate_through(node.right, live, mark)\n      merged = right_live | live\n      propagate_through(node.left, merged, mark)\n    end\n\n    private def propagate_through(node : Crystal::Call, live : LiveSet, mark = true) : LiveSet\n      # Bare `super` and `previous_def` (without parentheses) implicitly\n      # forward all method arguments, making each argument live.\n      if node.name.in?(\"super\", \"previous_def\") && !node.has_parentheses? && node.args.empty?\n        @scope.arguments.each do |arg|\n          name = arg.name\n          if @var_names.includes?(name) && !live.includes?(name)\n            live = live.dup\n            live.add(name)\n          end\n        end\n        return live\n      end\n\n      node.block_arg.try { |arg| live = propagate_through(arg, live, mark) }\n\n      node.named_args.try &.reverse_each do |named_arg|\n        live = propagate_through(named_arg.value, live, mark)\n      end\n\n      node.args.reverse_each do |arg|\n        live = propagate_through(arg, live, mark)\n      end\n\n      node.obj.try { |obj| live = propagate_through(obj, live, mark) }\n\n      live\n    end\n\n    private def propagate_through(node : Crystal::Return, live : LiveSet, mark = true) : LiveSet\n      target_live = LiveSet.new\n      node.exp.try { |exp| target_live = propagate_through(exp, target_live, mark) }\n      target_live\n    end\n\n    private def propagate_through(node : Crystal::Break, live : LiveSet, mark = true) : LiveSet\n      target_live = @break_live || LiveSet.new\n      node.exp.try { |exp| target_live = propagate_through(exp, target_live, mark) }\n      target_live\n    end\n\n    private def propagate_through(node : Crystal::Next, live : LiveSet, mark = true) : LiveSet\n      target_live = @next_live || LiveSet.new\n      node.exp.try { |exp| target_live = propagate_through(exp, target_live, mark) }\n      target_live\n    end\n\n    # Inner scope nodes: don't descend into nested scopes\n    {% for type in INNER_SCOPE_NODES %}\n      private def propagate_through(node : Crystal::{{ type.id }}, live : LiveSet, mark = true) : LiveSet\n        live\n      end\n    {% end %}\n\n    private def propagate_through(node, live : LiveSet, mark = true) : LiveSet\n      children = [] of Crystal::ASTNode\n      node.accept_children(ChildCollector.new(children))\n      children.reverse_each do |child|\n        live = propagate_through(child, live, mark)\n      end\n      live\n    end\n\n    private def propagate_through_loop(cond, body, live : LiveSet, mark) : LiveSet\n      # Save outer loop context before overwriting\n      outer_break = @break_live\n      outer_next = @next_live\n\n      # `break` exits to post-loop code, `next` jumps to loop condition.\n      @break_live = live\n      entry_live = live.dup\n\n      converged_cond_live = entry_live\n      MAX_FIXED_POINT_ITERATIONS.times do\n        @next_live = entry_live\n        converged_cond_live = propagate_through(cond, entry_live, false)\n        body_live = propagate_through(body, converged_cond_live, false)\n        new_entry = body_live | live\n        break if new_entry == entry_live\n        entry_live = new_entry\n      end\n\n      # Final pass with marking enabled using the converged live set\n      @next_live = entry_live\n      cond_live = propagate_through(cond, entry_live, mark)\n      propagate_through(body, cond_live, mark)\n\n      @break_live = outer_break\n      @next_live = outer_next\n\n      converged_cond_live\n    end\n\n    private def propagate_through_case(node : Crystal::Case | Crystal::Select, live : LiveSet, mark) : LiveSet\n      branch_lives = LiveSet.new\n\n      node.whens.each do |when_node|\n        when_live = propagate_through(when_node.body, live, mark)\n        when_node.conds.reverse_each do |cond|\n          when_live = propagate_through(cond, when_live, mark)\n        end\n        branch_lives.concat(when_live)\n      end\n\n      else_live = (body = node.else) ? propagate_through(body, live, mark) : live\n      branch_lives.concat(else_live)\n\n      if node.is_a?(Crystal::Case) && (cond = node.cond)\n        branch_lives = propagate_through(cond, branch_lives, mark)\n      end\n\n      branch_lives\n    end\n\n    private class ChildCollector < Crystal::Visitor\n      def initialize(@children : Array(Crystal::ASTNode))\n      end\n\n      def visit(node : Crystal::ASTNode)\n        @children << node\n        false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/scope.cr",
    "content": "require \"./variabling/*\"\n\nmodule Ameba::AST\n  # Represents a context of the local variable visibility.\n  # This is where the local variables belong to.\n  class Scope\n    # Whether the scope yields.\n    setter yields = false\n\n    # Scope visibility level\n    setter visibility : Crystal::Visibility?\n\n    # Link to local variables\n    getter variables = [] of Variable\n\n    # Link to all variable references in currency scope\n    getter references = [] of Reference\n\n    # Link to the arguments in current scope\n    getter arguments = [] of Argument\n\n    # Link to the instance variables used in current scope\n    getter ivariables = [] of InstanceVariable\n\n    # Link to the type declaration variables used in current scope\n    getter type_dec_variables = [] of TypeDecVariable\n\n    # Link to the outer scope\n    getter outer_scope : Scope?\n\n    # List of inner scopes\n    getter inner_scopes = [] of Scope\n\n    # The actual AST node that represents a current scope.\n    getter node : Crystal::ASTNode\n\n    delegate location, end_location, to_s,\n      to: @node\n\n    def_equals_and_hash node, location\n\n    # Creates a new scope. Accepts the AST node and the outer scope.\n    #\n    # ```\n    # scope = Scope.new(class_node, nil)\n    # ```\n    def initialize(@node, @outer_scope = nil)\n      @outer_scope.try &.inner_scopes.<< self\n    end\n\n    # Creates a new variable in the current scope.\n    #\n    # ```\n    # scope = Scope.new(class_node, nil)\n    # scope.add_variable(var_node)\n    # ```\n    def add_variable(node)\n      variables << Variable.new(node, self)\n    end\n\n    # Creates a new argument in the current scope.\n    #\n    # ```\n    # scope = Scope.new(class_node, nil)\n    # scope.add_argument(arg_node)\n    # ```\n    def add_argument(node)\n      add_variable Crystal::Var.new(node.name).at(node)\n      arguments << Argument.new(node, variables.last)\n    end\n\n    # Adds a new instance variable to the current scope.\n    #\n    # ```\n    # scope = Scope.new(class_node, nil)\n    # scope.add_ivariable(ivar_node)\n    # ```\n    def add_ivariable(node)\n      ivariables << InstanceVariable.new(node)\n    end\n\n    # Adds a new type declaration variable to the current scope.\n    #\n    # ```\n    # scope = Scope.new(class_node, nil)\n    # scope.add_type_dec_variable(node)\n    # ```\n    def add_type_dec_variable(node)\n      type_dec_variables << TypeDecVariable.new(node)\n    end\n\n    # Returns variable by its name or `nil` if it does not exist.\n    #\n    # ```\n    # scope = Scope.new(class_node, nil)\n    # scope.find_variable(\"foo\")\n    # ```\n    def find_variable(name : String)\n      variables.find(&.name.==(name)) ||\n        inherited? { outer_scope.try &.find_variable(name) }\n    end\n\n    # Creates a new assignment for the variable.\n    #\n    # ```\n    # scope = Scope.new(class_node, nil)\n    # scope.assign_variable(var_name, assign_node)\n    # ```\n    def assign_variable(name, node)\n      find_variable(name).try &.assign(node, self)\n    end\n\n    # Returns `true` if current scope represents a block (or proc),\n    # `false` otherwise.\n    def block?\n      node.is_a?(Crystal::Block) ||\n        node.is_a?(Crystal::ProcLiteral)\n    end\n\n    # Returns `true` if current scope represents a spawn block, e. g.\n    #\n    # ```\n    # spawn do\n    #   # ...\n    # end\n    # ```\n    def spawn_block?\n      node.as?(Crystal::Block).try(&.call).try(&.name) == \"spawn\"\n    end\n\n    # Returns `true` if current scope sits inside a macro.\n    def in_macro?\n      (node.is_a?(Crystal::Macro) ||\n        node.is_a?(Crystal::MacroIf) ||\n        node.is_a?(Crystal::MacroFor)) ||\n        !!outer_scope.try(&.in_macro?)\n    end\n\n    # Returns `true` if instance variable is assigned in this scope.\n    def assigns_ivar?(name)\n      arguments.any?(&.name.== name) &&\n        ivariables.any?(&.name.== \"@#{name}\")\n    end\n\n    # Returns `true` if type declaration variable is assigned in this scope.\n    def assigns_type_dec?(name)\n      type_dec_variables.any?(&.name.== name) ||\n        !!inherited? { outer_scope.try(&.assigns_type_dec?(name)) }\n    end\n\n    # Returns `true` if and only if current scope represents some\n    # type definition, for example a class.\n    def type_definition?\n      node.is_a?(Crystal::ClassDef) ||\n        node.is_a?(Crystal::ModuleDef) ||\n        node.is_a?(Crystal::EnumDef) ||\n        node.is_a?(Crystal::LibDef) ||\n        node.is_a?(Crystal::FunDef) ||\n        node.is_a?(Crystal::TypeDef) ||\n        node.is_a?(Crystal::CStructOrUnionDef)\n    end\n\n    # Returns `true` if current scope (or any of inner scopes) references variable,\n    # `false` otherwise.\n    def references?(variable : Variable, check_inner_scopes = true)\n      variable.references.any? do |reference|\n        (reference.scope == self) ||\n          (check_inner_scopes && inner_scopes.any?(&.references?(variable)))\n      end || variable.used_in_macro?\n    end\n\n    # Returns `true` if current scope (or any of inner scopes) yields,\n    # `false` otherwise.\n    def yields?(check_inner_scopes = true)\n      @yields || (check_inner_scopes && inner_scopes.any?(&.yields?))\n    end\n\n    # Returns visibility of the current scope (could be inherited from the outer scope).\n    def visibility\n      @visibility || inherited? { outer_scope.try(&.visibility) }\n    end\n\n    {% for type in %w[Def ClassDef ModuleDef EnumDef LibDef FunDef].map(&.id) %}\n      {% method_name = type.underscore %}\n      # Returns `true` if current scope is a {{ method_name[0..-5] }} def, `false` otherwise.\n      def {{ method_name }}?(*, check_outer_scopes = false)\n        node.is_a?(Crystal::{{ type }}) ||\n          !!(check_outer_scopes &&\n            outer_scope.try(&.{{ method_name }}?(check_outer_scopes: true)))\n      end\n    {% end %}\n\n    # Returns `true` if this scope is a top level scope, `false` otherwise.\n    def top_level?\n      outer_scope.nil?\n    end\n\n    def inherited?\n      !(node.is_a?(Crystal::Def) || node.is_a?(Crystal::FunDef) || node.is_a?(Crystal::Assign) || node.is_a?(Crystal::OpAssign))\n    end\n\n    def inherited?(&)\n      yield if inherited?\n    end\n\n    # Returns `true` if var is an argument in current scope, `false` otherwise.\n    def arg?(var)\n      case current_node = node\n      when Crystal::Def\n        var.is_a?(Crystal::Arg) && any_arg?(current_node.args, var)\n      when Crystal::Block\n        var.is_a?(Crystal::Var) && any_arg?(current_node.args, var)\n      when Crystal::ProcLiteral\n        var.is_a?(Crystal::Var) && any_arg?(current_node.def.args, var)\n      else\n        false\n      end\n    end\n\n    private def any_arg?(args, var)\n      args.any? { |arg| arg.name == var.name && arg.location == var.location }\n    end\n\n    # Returns `true` if the *node* represents exactly\n    # the same Crystal node as `@node`.\n    def eql?(node)\n      node == @node &&\n        node.location == @node.location\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/util.cr",
    "content": "# Utility module for Ameba's rules.\nmodule Ameba::AST::Util\n  extend self\n\n  # Returns tuple with two bool flags:\n  #\n  # 1. is *node* a literal?\n  # 2. can *node* be proven static?\n  protected def literal_kind?(node) : {Bool, Bool}\n    case node\n    when Crystal::NilLiteral,\n         Crystal::BoolLiteral,\n         Crystal::NumberLiteral,\n         Crystal::CharLiteral,\n         Crystal::StringLiteral,\n         Crystal::SymbolLiteral,\n         Crystal::ProcLiteral,\n         Crystal::MacroLiteral\n      {true, true}\n    when Crystal::StringInterpolation\n      {true, node.expressions.all? do |exp|\n        static_literal?(exp)\n      end}\n    when Crystal::RegexLiteral\n      {true, static_literal?(node.value)}\n    when Crystal::RangeLiteral\n      {true, static_literal?(node.from) &&\n        static_literal?(node.to)}\n    when Crystal::ArrayLiteral,\n         Crystal::TupleLiteral\n      {true, node.elements.all? do |element|\n        static_literal?(element)\n      end}\n    when Crystal::HashLiteral\n      {true, node.entries.all? do |entry|\n        static_literal?(entry.key) &&\n          static_literal?(entry.value)\n      end}\n    when Crystal::NamedTupleLiteral\n      {true, node.entries.all? do |entry|\n        static_literal?(entry.value)\n      end}\n    else\n      {false, false}\n    end\n  end\n\n  # Returns `true` if current `node` is a static literal, `false` otherwise.\n  def static_literal?(node) : Bool\n    is_literal, is_static = literal_kind?(node)\n    is_literal && is_static\n  end\n\n  # Returns `true` if current `node` is a dynamic literal, `false` otherwise.\n  def dynamic_literal?(node) : Bool\n    is_literal, is_static = literal_kind?(node)\n    is_literal && !is_static\n  end\n\n  # Returns `true` if current `node` is a literal, `false` otherwise.\n  def literal?(node) : Bool\n    is_literal, _ = literal_kind?(node)\n    is_literal\n  end\n\n  # Returns `true` if current `node` is a `Crystal::Path`\n  # matching given *name*, `false` otherwise.\n  def path_named?(node, *names : String) : Bool\n    node.is_a?(Crystal::Path) &&\n      node.names.join(\"::\").in?(names)\n  end\n\n  # Returns `true` if the *node* is a `Crystal::Call`\n  # with either `node.block` or `node.block_arg` set, `false` otherwise.\n  def has_block?(node) : Bool\n    node.is_a?(Crystal::Call) &&\n      !!(node.block || node.block_arg)\n  end\n\n  # Returns `true` if the *node* is a `Crystal::Call`\n  # with either `node.block` or `node.block_arg` set, `false` otherwise.\n  def has_arguments?(node) : Bool\n    node.is_a?(Crystal::Call) &&\n      !!(node.args.present? || node.named_args.try(&.present?))\n  end\n\n  # Returns `true` if the *node* is a `Crystal::Def`\n  # with either `args`, `splat_index`, or `double_splat` set,\n  # `false` otherwise.\n  def takes_arguments?(node) : Bool\n    node.is_a?(Crystal::Def) &&\n      !!(node.args.present? || node.splat_index || node.double_splat)\n  end\n\n  # Returns a source code for the current node.\n  # This method uses `node.location` and `node.end_location`\n  # to determine and cut a piece of source of the node.\n  def node_source(node, code_lines)\n    loc, end_loc = node.location, node.end_location\n    return unless loc && end_loc\n\n    source_between(loc, end_loc, code_lines)\n  end\n\n  # Returns the source code from *loc* to *end_loc* (inclusive).\n  def source_between(loc, end_loc, code_lines) : String?\n    line, column = loc.line_number - 1, loc.column_number - 1\n    end_line, end_column = end_loc.line_number - 1, end_loc.column_number - 1\n    node_lines = code_lines[line..end_line]\n    first_line, last_line = node_lines[0]?, node_lines[-1]?\n\n    return if first_line.nil? || last_line.nil?\n    return if first_line.size < column # compiler reports incorrect location\n\n    node_lines[0] = first_line.sub(0...column, \"\")\n\n    if line == end_line # one line\n      end_column = end_column - column\n      last_line = node_lines[0]\n    end\n\n    return if last_line.size < end_column + 1\n\n    node_lines[-1] = last_line.sub(end_column + 1...last_line.size, \"\")\n    node_lines.join('\\n')\n  end\n\n  # Returns `true` if node is a flow command, `false` otherwise.\n  # Node represents a flow command if it is a control expression,\n  # or special call node that interrupts execution (i.e. raise, exit, abort).\n  def flow_command?(node, in_loop)\n    case node\n    when Crystal::Return\n      true\n    when Crystal::Break, Crystal::Next\n      in_loop\n    when Crystal::Call\n      raise?(node) || exit?(node) || abort?(node)\n    else\n      false\n    end\n  end\n\n  # Returns `true` if node is a flow expression, `false` if not.\n  # Node represents a flow expression if it is full-filled by a flow command.\n  #\n  # For example, this node is a flow expression, because each branch contains\n  # a flow command `return`:\n  #\n  # ```\n  # if a > 0\n  #   return :positive\n  # elsif a < 0\n  #   return :negative\n  # else\n  #   return :zero\n  # end\n  # ```\n  #\n  # This node is a not a flow expression:\n  #\n  # ```\n  # if a > 0\n  #   return :positive\n  # end\n  # ```\n  #\n  # That's because not all branches return(i.e. `else` is missing).\n  def flow_expression?(node, in_loop = false)\n    return true if flow_command? node, in_loop\n\n    case node\n    when Crystal::If, Crystal::Unless\n      flow_expressions? [node.then, node.else], in_loop\n    when Crystal::BinaryOp\n      flow_expression? node.left, in_loop\n    when Crystal::Case, Crystal::Select\n      flow_expressions? [node.whens, node.else].flatten, in_loop\n    when Crystal::ExceptionHandler\n      flow_expressions? [node.else || node.body, node.rescues].flatten, in_loop\n    when Crystal::While, Crystal::Until, Crystal::Rescue, Crystal::When\n      flow_expression? node.body, in_loop\n    when Crystal::Expressions\n      node.expressions.any? { |exp| flow_expression? exp, in_loop }\n    else\n      false\n    end\n  end\n\n  private def flow_expressions?(nodes, in_loop)\n    nodes.all? { |exp| flow_expression? exp, in_loop }\n  end\n\n  # Returns `true` if node represents `raise` method call.\n  def raise?(node)\n    node.is_a?(Crystal::Call) &&\n      node.name == \"raise\" && node.args.size == 1 && node.obj.nil?\n  end\n\n  # Returns `true` if node represents `exit` method call.\n  def exit?(node)\n    node.is_a?(Crystal::Call) &&\n      node.name == \"exit\" && node.args.size <= 1 && node.obj.nil?\n  end\n\n  # Returns `true` if node represents `abort` method call.\n  def abort?(node)\n    node.is_a?(Crystal::Call) &&\n      node.name == \"abort\" && node.args.size <= 2 && node.obj.nil?\n  end\n\n  # Returns `true` if node represents a loop.\n  def loop?(node)\n    case node\n    when Crystal::While, Crystal::Until\n      true\n    when Crystal::Call\n      node.name == \"loop\" && node.args.empty? && node.obj.nil?\n    else\n      false\n    end\n  end\n\n  # Returns `true` if *name* represents operator method.\n  def operator_method_name?(name : String)\n    name != \"->\" &&\n      name.each_char.none?(&.alphanumeric?)\n  end\n\n  # Returns `true` if *node* represents operator method.\n  def operator_method?(node)\n    return false unless node.responds_to?(:name)\n    return false unless name = node.name.try(&.to_s.presence)\n\n    operator_method_name?(name)\n  end\n\n  # Returns `true` if *name* represents setter method.\n  def setter_method_name?(name : String)\n    name == \"[]=\" ||\n      !name.empty? && name[0].letter? && name.ends_with?('=')\n  end\n\n  # Returns `true` if *node* represents setter method.\n  def setter_method?(node)\n    return false unless node.responds_to?(:name)\n    return false unless name = node.name.try(&.to_s.presence)\n\n    setter_method_name?(name)\n  end\n\n  # Returns `true` if *node* is a suffix node (`if` / `unless` / `rescue` / `ensure`).\n  def suffix?(node)\n    case node\n    when Crystal::If, Crystal::Unless\n      node.location == node.then.location\n    when Crystal::ExceptionHandler\n      node.suffix\n    else\n      false\n    end\n  end\n\n  # Returns `true` if *node* represents a short block version (`&.foo?`).\n  def short_block?(node, code_lines)\n    return false unless node.is_a?(Crystal::Block)\n    return false unless location = node.location\n    return false unless end_location = node.body.end_location\n\n    !!source_between(location, end_location, code_lines)\n      .try(&.starts_with?(\"&.\"))\n  end\n\n  # Returns `true` if *node* is a call with a short block version (`&.foo?`).\n  def has_short_block?(node, code_lines)\n    node.is_a?(Crystal::Call) &&\n      short_block?(node.block, code_lines)\n  end\n\n  # Returns `true` if node has a `:nodoc:` annotation as the first line.\n  def nodoc?(node)\n    return false unless node.responds_to?(:doc)\n    return false unless doc = node.doc.presence\n\n    doc.lines.first?.try(&.strip) == \":nodoc:\"\n  end\n\n  # Returns `true` if node is a _heredoc_, `false` otherwise.\n  def heredoc?(node, source : Source)\n    return false unless node.is_a?(Crystal::StringInterpolation) ||\n                        node.is_a?(Crystal::StringLiteral)\n    return false unless location = node.location\n    return false unless location_pos = source.pos(location)\n\n    source.code[location_pos..(location_pos + 2)]? == \"<<-\"\n  end\n\n  # Returns the exp code of a control expression.\n  # Wraps implicit tuple literal with curly brackets (e.g. multi-return).\n  def control_exp_code(node : Crystal::ControlExpression, code_lines)\n    return unless exp = node.exp\n    return unless exp_code = node_source(exp, code_lines)\n    return exp_code unless exp.is_a?(Crystal::TupleLiteral) && exp_code[0] != '{'\n    return unless exp_start = exp.elements.first.location\n    return unless exp_end = exp.end_location\n\n    \"{#{source_between(exp_start, exp_end, code_lines)}}\"\n  end\n\n  def name_location_or(node : Crystal::ASTNode, *, adjust_location_column_number = nil)\n    name = node.name if node.responds_to?(:name)\n\n    return node unless name = name.try(&.to_s.presence)\n    return node unless location = name_location(node) || node.location\n\n    location =\n      location.adjust(column_number: adjust_location_column_number || 0)\n\n    end_location =\n      location.adjust(column_number: name.size - 1)\n\n    {location, end_location}\n  end\n\n  def name_location_or(token : Crystal::Token, name, *, adjust_location_column_number = nil)\n    name = name.to_s.presence\n\n    location =\n      token.location.adjust(column_number: adjust_location_column_number || 0)\n\n    end_location =\n      location.adjust(column_number: name ? name.size - 1 : 0)\n\n    {location, end_location}\n  end\n\n  # Returns `nil` if *node* does not contain a name.\n  def name_location(node)\n    if loc = node.name_location\n      return loc\n    end\n\n    return node.var.location if node.is_a?(Crystal::TypeDeclaration) ||\n                                node.is_a?(Crystal::UninitializedVar)\n    return unless node.responds_to?(:name) && (name = node.name)\n    return unless name.is_a?(Crystal::ASTNode)\n\n    name.location\n  end\n\n  # Returns zero if *node* does not contain a name.\n  def name_size(node)\n    unless (size = node.name_size).zero?\n      return size\n    end\n\n    return 0 unless node.responds_to?(:name) && (name = node.name)\n\n    case name\n    when Crystal::ASTNode     then name.name_size\n    when Crystal::Token::Kind then name.to_s.size # Crystal::MagicConstant\n    else                           name.size\n    end\n  end\n\n  # Returns `nil` if *node* does not contain a name.\n  #\n  # NOTE: Use this instead of `Crystal::Call#name_end_location` to avoid an\n  #       off-by-one error.\n  def name_end_location(node)\n    return unless loc = name_location(node)\n    return if (size = name_size(node)).zero?\n\n    loc.adjust(column_number: size - 1)\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/variabling/argument.cr",
    "content": "module Ameba::AST\n  # Represents the argument of some node.\n  # Holds the reference to the variable, thus to scope.\n  #\n  # For example, all these vars are arguments:\n  #\n  # ```\n  # def method(a, b, c = 10, &block)\n  #   3.times do |i|\n  #   end\n  #\n  #   ->(x : Int32) { }\n  # end\n  # ```\n  class Argument\n    # The actual node.\n    getter node : Crystal::Var | Crystal::Arg\n\n    # Variable of this argument (may be the same node)\n    getter variable : Variable\n\n    delegate location, end_location, to_s,\n      to: @node\n\n    # Creates a new argument.\n    #\n    # ```\n    # Argument.new(node, variable)\n    # ```\n    def initialize(@node, @variable)\n    end\n\n    # Returns `true` if the `name` is empty, `false` otherwise.\n    def anonymous?\n      name.blank?\n    end\n\n    # Returns `true` if the `name` starts with '_', `false` otherwise.\n    def ignored?\n      name.starts_with? '_'\n    end\n\n    # Name of the argument.\n    def name\n      case current_node = node\n      when Crystal::Var, Crystal::Arg\n        current_node.name\n      else\n        raise ArgumentError.new \"Invalid node\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/variabling/assignment.cr",
    "content": "require \"./reference\"\nrequire \"./variable\"\n\nmodule Ameba::AST\n  # Represents the assignment to the variable.\n  # Holds the assign node and the variable.\n  class Assignment\n    # The actual assignment node.\n    getter node : Crystal::ASTNode\n\n    # Variable of this assignment.\n    getter variable : Variable\n\n    # A scope assignment belongs to\n    getter scope : Scope\n\n    delegate location, end_location, to_s,\n      to: @node\n\n    # Creates a new assignment.\n    #\n    # ```\n    # Assignment.new(node, variable, scope)\n    # ```\n    def initialize(@node, @variable, @scope)\n    end\n\n    # Returns `true` if this assignment is an op assign, `false` if not.\n    # For example, this is an op assign:\n    #\n    # ```\n    # a ||= 1\n    # ```\n    def op_assign?\n      node.is_a?(Crystal::OpAssign)\n    end\n\n    # Returns the target node of the variable in this assignment.\n    def target_node\n      case assign = node\n      when Crystal::UninitializedVar          then assign.var\n      when Crystal::Assign, Crystal::OpAssign then assign.target\n      when Crystal::MultiAssign\n        assign.targets.find(node) do |target|\n          target.is_a?(Crystal::Var) && target.name == variable.name\n        end\n      else\n        node\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/variabling/ivariable.cr",
    "content": "module Ameba::AST\n  class InstanceVariable\n    getter node : Crystal::InstanceVar\n\n    delegate location, end_location, name, to_s,\n      to: @node\n\n    def initialize(@node)\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/variabling/reference.cr",
    "content": "require \"./variable\"\n\nmodule Ameba::AST\n  # Represents a reference to the variable.\n  # It behaves like a variable is used to distinguish a\n  # the variable from its reference.\n  class Reference < Variable\n    property? explicit = true\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/variabling/type_dec_variable.cr",
    "content": "module Ameba::AST\n  class TypeDecVariable\n    getter node : Crystal::TypeDeclaration\n\n    delegate location, end_location, to_s,\n      to: @node\n\n    def initialize(@node)\n    end\n\n    def name\n      case var = @node.var\n      when Crystal::Var, Crystal::InstanceVar, Crystal::ClassVar, Crystal::Global\n        var.name\n      else\n        raise \"Unsupported var node type: #{var.class}\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/variabling/variable.cr",
    "content": "module Ameba::AST\n  # Represents the existence of the local variable.\n  # Holds the var node and variable assignments.\n  class Variable\n    # List of the assignments of this variable.\n    getter assignments = [] of Assignment\n\n    # List of the references of this variable.\n    getter references = [] of Reference\n\n    # The actual var node.\n    getter node : Crystal::Var\n\n    # Scope of this variable.\n    getter scope : Scope\n\n    delegate location, end_location, name, to_s,\n      to: @node\n\n    # Creates a new variable(in the scope).\n    #\n    # ```\n    # Variable.new(node, scope)\n    # ```\n    def initialize(@node, @scope)\n    end\n\n    # Returns `true` if it is a special variable, i.e `$?`.\n    def special?\n      @node.special_var?\n    end\n\n    # Assigns the variable (creates a new assignment).\n    # Variable may have multiple assignments.\n    #\n    # ```\n    # variable = Variable.new(node, scope)\n    # variable.assign(node1)\n    # variable.assign(node2)\n    # variable.assignment.size # => 2\n    # ```\n    def assign(node, scope)\n      assignments << Assignment.new(node, self, scope)\n    end\n\n    # Returns `true` if variable has any reference.\n    #\n    # ```\n    # variable = Variable.new(node, scope)\n    # variable.reference(var_node, some_scope)\n    # variable.referenced? # => true\n    # ```\n    def referenced?\n      !references.empty?\n    end\n\n    # Creates a reference to this variable in some scope.\n    #\n    # ```\n    # variable = Variable.new(node, scope)\n    # variable.reference(var_node, some_scope)\n    # ```\n    def reference(node : Crystal::Var, scope : Scope)\n      Reference.new(node, scope).tap do |reference|\n        references << reference\n        scope.references << reference\n      end\n    end\n\n    # :ditto:\n    def reference(scope : Scope)\n      reference(node, scope)\n    end\n\n    # Returns `true` if the current var is referenced in\n    # in the block. For example this variable is captured\n    # by block:\n    #\n    # ```\n    # a = 1\n    # 3.times { |i| a = a + i }\n    # ```\n    #\n    # And this variable is not captured by block.\n    #\n    # ```\n    # i = 1\n    # 3.times { |i| i + 1 }\n    # ```\n    def captured_by_block?(scope = @scope)\n      scope.inner_scopes.each do |inner_scope|\n        return true if inner_scope.block? &&\n                       inner_scope.references?(self, check_inner_scopes: false)\n        return true if captured_by_block?(inner_scope)\n      end\n\n      false\n    end\n\n    # Returns `true` if current variable potentially referenced in a macro,\n    # `false` if not.\n    def used_in_macro?(scope = @scope)\n      scope.inner_scopes.each do |inner_scope|\n        return true if MacroReferenceFinder.new(inner_scope.node, node.name).references?\n      end\n      return true if MacroReferenceFinder.new(scope.node, node.name).references?\n      return true if (outer_scope = scope.outer_scope) && used_in_macro?(outer_scope)\n\n      false\n    end\n\n    # Returns `true` if the variable is a target (on the left) of the assignment,\n    # `false` otherwise.\n    def target_of?(assign)\n      case assign\n      when Crystal::UninitializedVar          then eql?(assign.var)\n      when Crystal::Assign, Crystal::OpAssign then eql?(assign.target)\n      when Crystal::MultiAssign\n        assign.targets.any? { |target| eql?(target) }\n      else\n        false\n      end\n    end\n\n    # Returns `true` if the name starts with '_', `false` if not.\n    def ignored?\n      name.starts_with? '_'\n    end\n\n    # Returns `true` if the `node` represents exactly\n    # the same Crystal node as `@node`.\n    def eql?(node)\n      node.is_a?(Crystal::Var) &&\n        node.name == @node.name &&\n        node.location == @node.location\n    end\n\n    # Returns `true` if the variable is declared before the `node`.\n    def declared_before?(node)\n      var_location, node_location = location, node.location\n\n      return unless var_location && node_location\n\n      (var_location.line_number < node_location.line_number) ||\n        (var_location.line_number == node_location.line_number &&\n          var_location.column_number < node_location.column_number)\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/visitors/base_visitor.cr",
    "content": "require \"compiler/crystal/syntax/*\"\n\n# A module that helps to traverse Crystal AST using `Crystal::Visitor`.\nmodule Ameba::AST\n  # An abstract base visitor that utilizes general logic for all visitors.\n  abstract class BaseVisitor < Crystal::Visitor\n    # A corresponding rule that uses this visitor.\n    @rule : Rule::Base\n\n    # A source that needs to be traversed.\n    @source : Source\n\n    # Creates instance of this visitor.\n    #\n    # ```\n    # visitor = Ameba::AST::NodeVisitor.new(rule, source)\n    # ```\n    def initialize(@rule, @source)\n      @source.ast.accept self\n    end\n\n    # A main visit method that accepts `Crystal::ASTNode`.\n    # Returns `true`, meaning all child nodes will be traversed.\n    def visit(node : Crystal::ASTNode)\n      true\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/visitors/counting_visitor.cr",
    "content": "module Ameba::AST\n  # AST Visitor that counts occurrences of certain keywords\n  class CountingVisitor < Crystal::Visitor\n    DEFAULT_COMPLEXITY = 1\n\n    # Returns the number of keywords that were found in the node\n    getter count = DEFAULT_COMPLEXITY\n\n    # Returns `true` if the node is within a macro condition\n    getter? macro_condition = false\n\n    # Creates a new counting visitor\n    def initialize(node : Crystal::ASTNode)\n      node.accept self\n    end\n\n    # :nodoc:\n    def visit(node : Crystal::ASTNode)\n      true\n    end\n\n    # Uses the same logic than rubocop. See\n    # https://github.com/rubocop-hq/rubocop/blob/master/lib/rubocop/cop/metrics/cyclomatic_complexity.rb#L21\n    # Except \"for\", because crystal doesn't have a \"for\" loop.\n    {% for node in %i[if unless while until rescue or and] %}\n      # :nodoc:\n      def visit(node : Crystal::{{ node.id.capitalize }})\n        unless macro_condition?\n          @count += 1\n        end\n        true\n      end\n    {% end %}\n\n    # :nodoc:\n    def visit(node : Crystal::Case)\n      unless macro_condition?\n        # Count the complexity of an exhaustive `Case` as 1\n        # Otherwise count the number of `When`s\n        @count += node.exhaustive? ? 1 : node.whens.size\n      end\n      true\n    end\n\n    # :nodoc:\n    def visit(node : Crystal::Select)\n      unless macro_condition?\n        @count += node.whens.size\n      end\n      true\n    end\n\n    def visit(node : Crystal::MacroIf | Crystal::MacroFor)\n      @macro_condition = true\n      @count = DEFAULT_COMPLEXITY\n\n      false\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/visitors/elseif_aware_node_visitor.cr",
    "content": "require \"./node_visitor\"\n\nmodule Ameba::AST\n  # A class that utilizes a logic inherited from `NodeVisitor` to traverse AST\n  # nodes and fire a source test callback with the `Crystal::If` node and an array\n  # containing all `elsif` branches (first branch is an `if` node) in case\n  # at least one `elsif` branch is reached, and `nil` otherwise.\n  #\n  # In Crystal, consecutive `elsif` branches are transformed into `if` branches\n  # attached to the `else` branch of an adjacent `if` branch.\n  #\n  # For example:\n  #\n  # ```\n  # if foo\n  #   do_foo\n  # elsif bar\n  #   do_bar\n  # elsif baz\n  #   do_baz\n  # else\n  #   do_something_else\n  # end\n  # ```\n  #\n  # is transformed into:\n  #\n  # ```\n  # if foo\n  #   do_foo\n  # else\n  #   if bar\n  #     do_bar\n  #   else\n  #     if baz\n  #       do_baz\n  #     else\n  #       do_something_else\n  #     end\n  #   end\n  # end\n  # ```\n  class ElseIfAwareNodeVisitor < NodeVisitor\n    include Util\n\n    getter? exclude_ternary : Bool\n    getter? exclude_suffix : Bool\n\n    def initialize(rule, source, *, skip : Array | Category? = nil,\n                   @exclude_ternary = true,\n                   @exclude_suffix = true)\n      super rule, source,\n        skip: if skip.is_a?(Category)\n          NodeVisitor.category_to_node_classes(skip)\n        else\n          skip\n        end\n    end\n\n    def visit(node : Crystal::If)\n      if_node = node\n      ifs = [] of Crystal::If\n\n      loop do\n        break if exclude_ternary? && if_node.ternary?\n        break if exclude_suffix? && suffix?(if_node)\n\n        ifs << if_node\n\n        if_node.cond.accept self\n        if_node.then.accept self\n\n        unless (if_node = if_node.else).is_a?(Crystal::If)\n          if_node.accept self\n          break\n        end\n      end\n\n      @rule.test @source, node, (ifs if ifs.size > 1)\n      false\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/visitors/flow_expression_visitor.cr",
    "content": "require \"../util\"\nrequire \"./base_visitor\"\n\nmodule Ameba::AST\n  # AST Visitor that traverses all the flow expressions.\n  class FlowExpressionVisitor < BaseVisitor\n    include Util\n\n    @loop_stack = [] of Crystal::ASTNode\n\n    # :nodoc:\n    def visit(node)\n      if flow_expression?(node, in_loop?)\n        @rule.test @source, node, FlowExpression.new(node, in_loop?)\n      end\n      true\n    end\n\n    # :nodoc:\n    def visit(node : Crystal::While | Crystal::Until)\n      on_loop_started(node)\n      true\n    end\n\n    # :nodoc:\n    def visit(node : Crystal::Call)\n      on_loop_started(node) if loop?(node)\n      true\n    end\n\n    # :nodoc:\n    def end_visit(node : Crystal::While | Crystal::Until)\n      on_loop_ended(node)\n    end\n\n    # :nodoc:\n    def end_visit(node : Crystal::Call)\n      on_loop_ended(node) if loop?(node)\n    end\n\n    private def on_loop_started(node)\n      @loop_stack.push(node)\n    end\n\n    private def on_loop_ended(node)\n      @loop_stack.pop?\n    end\n\n    private def in_loop?\n      !@loop_stack.empty?\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/visitors/implicit_return_visitor.cr",
    "content": "module Ameba::AST\n  # AST visitor that finds nodes that are not used by their surrounding scope.\n  #\n  # A stack is used to keep track of when a node is used, incrementing every time\n  # something will capture its implicit (or explicit) return value, such as the\n  # path in a class name or the value in an assign.\n  #\n  # This also allows for passing through whether a node is captured from a nodes\n  # parent to its children, such as from an `if` statements parent to it's body,\n  # as the body is not used by the `if` itself, but by its parent scope.\n  class ImplicitReturnVisitor < BaseVisitor\n    # When greater than zero, indicates the current node's return value is used\n    @stack : Int32 = 0\n    @in_macro : Bool = false\n\n    # The stack is swapped out here as `Crystal::Expressions` are isolated from\n    # their parents scope. Only the last line in an expressions node can be\n    # captured by their parent node.\n    def visit(node : Crystal::Expressions) : Bool\n      report_implicit_return(node)\n\n      last_idx = node.expressions.size - 1\n\n      swap_stack do |old_stack|\n        node.expressions.each_with_index do |exp, idx|\n          case\n          when exp.is_a?(Crystal::ControlExpression)\n            incr_stack { exp.accept(self) }\n            break\n          when idx == last_idx && old_stack.positive?\n            incr_stack { exp.accept(self) }\n          else\n            exp.accept(self)\n          end\n        end\n      end\n\n      false\n    end\n\n    def visit(node : Crystal::BinaryOp) : Bool\n      report_implicit_return(node)\n\n      case node.right\n      when Crystal::Call, Crystal::Expressions, Crystal::ControlExpression\n        incr_stack { node.left.accept(self) }\n      else\n        node.left.accept(self)\n      end\n      node.right.accept(self)\n\n      false\n    end\n\n    def visit(node : Crystal::Call) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.accept_children(self) }\n\n      false\n    end\n\n    def visit(node : Crystal::Arg) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.default_value.try &.accept(self) }\n\n      false\n    end\n\n    def visit(node : Crystal::EnumDef) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.members.each &.accept(self) }\n\n      false\n    end\n\n    def visit(node : Crystal::Assign | Crystal::OpAssign) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.value.accept(self) }\n\n      false\n    end\n\n    def visit(node : Crystal::MultiAssign) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.values.each &.accept(self) }\n\n      false\n    end\n\n    def visit(node : Crystal::If | Crystal::Unless) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.cond.accept(self) }\n      node.then.accept(self)\n      node.else.accept(self)\n\n      false\n    end\n\n    def visit(node : Crystal::While | Crystal::Until) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.cond.accept(self) }\n      node.body.accept(self)\n\n      false\n    end\n\n    def visit(node : Crystal::Def) : Bool\n      report_implicit_return(node)\n\n      incr_stack do\n        node.args.each &.accept(self)\n        node.double_splat.try &.accept(self)\n        node.block_arg.try &.accept(self)\n      end\n\n      if node.name == \"initialize\" || Util.path_named?(node.return_type, \"Nil\")\n        # Special case of the return type being nil, meaning the last\n        # line of the method body is ignored\n        # Last line of initialize methods are also ignored\n        swap_stack { node.body.accept(self) }\n      else\n        incr_stack { node.body.accept(self) }\n      end\n\n      false\n    end\n\n    def visit(node : Crystal::Macro) : Bool\n      report_implicit_return(node)\n\n      incr_stack do\n        node.args.each &.accept(self)\n        node.double_splat.try &.accept(self)\n        node.block_arg.try &.accept(self)\n      end\n\n      swap_stack do\n        node.body.accept(self)\n      end\n\n      false\n    end\n\n    def visit(node : Crystal::ClassDef | Crystal::ModuleDef) : Bool\n      report_implicit_return(node)\n\n      node.body.accept(self)\n\n      false\n    end\n\n    def visit(node : Crystal::FunDef) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.accept_children(self) }\n\n      false\n    end\n\n    def visit(node : Crystal::Cast | Crystal::NilableCast | Crystal::IsA | Crystal::RespondsTo) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.obj.accept(self) }\n\n      false\n    end\n\n    def visit(node : Crystal::UnaryExpression) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.accept_children(self) }\n\n      false\n    end\n\n    def visit(node : Crystal::TypeOf) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.accept_children(self) }\n\n      false\n    end\n\n    def visit(node : Crystal::Annotation) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.accept_children(self) }\n\n      false\n    end\n\n    def visit(node : Crystal::TypeDeclaration) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.value.try &.accept(self) }\n\n      false\n    end\n\n    def visit(node : Crystal::ArrayLiteral | Crystal::TupleLiteral) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.elements.each &.accept(self) }\n\n      false\n    end\n\n    def visit(node : Crystal::StringInterpolation) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.accept_children(self) }\n\n      false\n    end\n\n    def visit(node : Crystal::HashLiteral | Crystal::NamedTupleLiteral) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.entries.each &.value.accept(self) }\n\n      false\n    end\n\n    def visit(node : Crystal::Case) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.cond.try &.accept(self) }\n      node.whens.each &.accept(self)\n      node.else.try &.accept(self)\n\n      false\n    end\n\n    def visit(node : Crystal::Select) : Bool\n      report_implicit_return(node)\n\n      node.accept_children(self)\n\n      false\n    end\n\n    def visit(node : Crystal::When) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.conds.each &.accept(self) }\n      node.body.accept(self)\n\n      false\n    end\n\n    def visit(node : Crystal::Rescue) : Bool\n      report_implicit_return(node)\n\n      node.body.accept(self)\n\n      false\n    end\n\n    def visit(node : Crystal::ExceptionHandler) : Bool\n      report_implicit_return(node)\n\n      if node.else\n        # Last line of body isn't implicitly returned if there's an else\n        swap_stack { node.body.try &.accept(self) }\n      else\n        node.body.accept(self)\n      end\n\n      node.rescues.try &.each &.accept(self)\n      node.else.try &.accept(self)\n\n      # Last line of ensure isn't implicitly returned\n      swap_stack { node.ensure.try &.accept(self) }\n\n      false\n    end\n\n    def visit(node : Crystal::Block) : Bool\n      report_implicit_return(node)\n\n      node.body.accept(self)\n\n      false\n    end\n\n    def visit(node : Crystal::ControlExpression) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.accept_children(self) }\n\n      false\n    end\n\n    def visit(node : Crystal::RangeLiteral) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.accept_children(self) }\n\n      false\n    end\n\n    def visit(node : Crystal::RegexLiteral) : Bool\n      report_implicit_return(node)\n\n      # Regex literals either contain string literals or string interpolations,\n      # both of which are \"captured\" by the parent regex literal\n      incr_stack { node.accept_children(self) }\n\n      false\n    end\n\n    def visit(node : Crystal::Yield) : Bool\n      report_implicit_return(node)\n\n      incr_stack { node.exps.each &.accept(self) }\n\n      false\n    end\n\n    def visit(node : Crystal::MacroExpression) : Bool\n      report_implicit_return(node)\n\n      in_macro do\n        if node.output?\n          incr_stack { node.exp.accept(self) }\n        else\n          swap_stack { node.exp.accept(self) }\n        end\n      end\n\n      false\n    end\n\n    def visit(node : Crystal::MacroIf) : Bool\n      report_implicit_return(node)\n\n      in_macro do\n        swap_stack do\n          incr_stack { node.cond.accept(self) }\n          node.then.accept(self)\n          node.else.accept(self)\n        end\n      end\n\n      false\n    end\n\n    def visit(node : Crystal::MacroFor) : Bool\n      report_implicit_return(node)\n\n      in_macro do\n        swap_stack { node.body.accept(self) }\n      end\n\n      false\n    end\n\n    def visit(node : Crystal::Alias | Crystal::TypeDef | Crystal::MacroVar)\n      false\n    end\n\n    def visit(node : Crystal::Generic | Crystal::Path | Crystal::Union | Crystal::UninitializedVar | Crystal::OffsetOf | Crystal::LibDef | Crystal::Include | Crystal::Extend) : Bool\n      report_implicit_return(node)\n\n      false\n    end\n\n    def visit(node)\n      report_implicit_return(node)\n\n      true\n    end\n\n    private def report_implicit_return(node) : Nil\n      @rule.test(@source, node, @in_macro) unless @stack.positive?\n    end\n\n    # Indicates that any nodes visited within the block are captured / used.\n    private def incr_stack(&) : Nil\n      @stack += 1\n      yield\n    ensure\n      @stack -= 1\n    end\n\n    private def swap_stack(& : Int32 -> Nil) : Nil\n      old_stack = @stack\n      @stack = 0\n      begin\n        yield old_stack\n      ensure\n        @stack = old_stack\n      end\n    end\n\n    private def in_macro(&) : Nil\n      old_value = @in_macro\n      @in_macro = true\n      begin\n        yield\n      ensure\n        @in_macro = old_value\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/visitors/macro_reference_finder.cr",
    "content": "module Ameba::AST\n  class MacroReferenceFinder < Crystal::Visitor\n    property? references = false\n\n    def initialize(node, @reference : String)\n      node.accept self\n    end\n\n    @[AlwaysInline]\n    private def includes_reference?(val)\n      val.to_s.includes?(@reference)\n    end\n\n    def visit(node : Crystal::MacroLiteral)\n      !(@references ||= includes_reference?(node.value))\n    end\n\n    def visit(node : Crystal::MacroExpression)\n      !(@references ||= includes_reference?(node.exp))\n    end\n\n    def visit(node : Crystal::MacroFor)\n      !(@references ||= includes_reference?(node.exp) ||\n                        includes_reference?(node.body))\n    end\n\n    def visit(node : Crystal::MacroIf)\n      !(@references ||= includes_reference?(node.cond) ||\n                        includes_reference?(node.then) ||\n                        includes_reference?(node.else))\n    end\n\n    def visit(node : Crystal::ASTNode)\n      true\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/visitors/node_visitor.cr",
    "content": "require \"./base_visitor\"\n\nmodule Ameba::AST\n  # An AST Visitor that traverses the source and allows all nodes\n  # to be inspected by rules.\n  #\n  # ```\n  # visitor = Ameba::AST::NodeVisitor.new(rule, source)\n  # ```\n  class NodeVisitor < BaseVisitor\n    @[Flags]\n    enum Category\n      Macro\n    end\n\n    # List of nodes to be visited by Ameba's rules.\n    NODES = {\n      Alias,\n      ArrayLiteral,\n      Assign,\n      Block,\n      Call,\n      Case,\n      ClassDef,\n      ClassVar,\n      ControlExpression,\n      Def,\n      EnumDef,\n      ExceptionHandler,\n      Expressions,\n      HashLiteral,\n      If,\n      InstanceVar,\n      IsA,\n      LibDef,\n      MacroExpression,\n      ModuleDef,\n      MultiAssign,\n      NilLiteral,\n      ProcLiteral,\n      Rescue,\n      Select,\n      StringInterpolation,\n      StringLiteral,\n      Union,\n      Unless,\n      Until,\n      Var,\n      When,\n      While,\n    }\n\n    @skip : Array(Crystal::ASTNode.class)?\n\n    def self.category_to_node_classes(category : Category)\n      ([] of Crystal::ASTNode.class).tap do |classes|\n        classes.push(\n          Crystal::Macro,\n          Crystal::MacroExpression,\n          Crystal::MacroIf,\n          Crystal::MacroFor,\n        ) if category.macro?\n      end\n    end\n\n    def initialize(@rule, @source, *, skip : Category)\n      initialize @rule, @source,\n        skip: NodeVisitor.category_to_node_classes(skip)\n    end\n\n    def initialize(@rule, @source, *, skip : Array? = nil)\n      @skip = skip.try &.map(&.as(Crystal::ASTNode.class))\n      super @rule, @source\n    end\n\n    def visit(node : Crystal::VisibilityModifier)\n      node.exp.visibility = node.modifier\n      true\n    end\n\n    {% for name in NODES %}\n      # A visit callback for `Crystal::{{ name }}` node.\n      #\n      # Returns `true` if the child nodes should be traversed as well,\n      # `false` otherwise.\n      def visit(node : Crystal::{{ name }})\n        return false if skip?(node)\n\n        @rule.test @source, node\n        true\n      end\n    {% end %}\n\n    def visit(node)\n      !skip?(node)\n    end\n\n    private def skip?(node)\n      !!@skip.try(&.includes?(node.class))\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/visitors/redundant_control_expression_visitor.cr",
    "content": "module Ameba::AST\n  # A class that utilizes a logic to traverse AST nodes and\n  # fire a source test callback if a redundant `Crystal::ControlExpression`\n  # is reached.\n  class RedundantControlExpressionVisitor\n    # A corresponding rule that uses this visitor.\n    getter rule : Rule::Base\n\n    # A source that needs to be traversed.\n    getter source : Source\n\n    # A node to run traversal on.\n    getter node : Crystal::ASTNode\n\n    def initialize(@rule, @source, @node)\n      traverse_node node\n    end\n\n    private def traverse_control_expression(node)\n      @rule.test(@source, node, self)\n    end\n\n    private def traverse_node(node)\n      case node\n      when Crystal::ControlExpression     then traverse_control_expression node\n      when Crystal::Expressions           then traverse_expressions node\n      when Crystal::If, Crystal::Unless   then traverse_condition node\n      when Crystal::Case, Crystal::Select then traverse_case node\n      when Crystal::BinaryOp              then traverse_binary_op node\n      when Crystal::ExceptionHandler      then traverse_exception_handler node\n      end\n    end\n\n    private def traverse_expressions(node)\n      traverse_node node.expressions.last?\n    end\n\n    private def traverse_condition(node)\n      return if node.else.nil? || node.else.nop?\n\n      traverse_node(node.then)\n      traverse_node(node.else)\n    end\n\n    private def traverse_case(node)\n      node.whens.each do |when_node|\n        traverse_node when_node.body\n      end\n      traverse_node(node.else)\n    end\n\n    private def traverse_binary_op(node)\n      traverse_node(node.right)\n    end\n\n    private def traverse_exception_handler(node)\n      traverse_node node.body\n      traverse_node node.else\n\n      node.rescues.try &.each do |rescue_node|\n        traverse_node rescue_node.body\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/visitors/scope_calls_with_self_receiver_visitor.cr",
    "content": "module Ameba::AST\n  class ScopeCallsWithSelfReceiverVisitor < Crystal::Visitor\n    @current_scope : AST::Scope\n    @current_assign : Crystal::ASTNode?\n\n    getter scope_call_queue = {} of AST::Scope => Array(Crystal::Call)\n\n    def initialize(rule, source)\n      @current_scope = AST::Scope.new(source.ast) # top level scope\n\n      source.ast.accept self\n\n      scope_call_queue.each do |scope, calls|\n        calls.each do |call|\n          rule.test source, call, scope\n        end\n      end\n    end\n\n    private def on_scope_enter(node)\n      scope = AST::Scope.new(node, @current_scope)\n\n      @current_scope = scope\n      true\n    end\n\n    private def on_scope_end(node)\n      # go up if this is not a top level scope\n      if outer_scope = @current_scope.outer_scope\n        @current_scope = outer_scope\n      end\n    end\n\n    private def on_assign_end(target, node)\n      target.is_a?(Crystal::Var) &&\n        @current_scope.assign_variable(target.name, node)\n    end\n\n    # A main visit method that accepts `Crystal::ASTNode`.\n    # Returns `true`, meaning all child nodes will be traversed.\n    def visit(node : Crystal::ASTNode)\n      true\n    end\n\n    def end_visit(node : Crystal::ASTNode)\n      on_scope_end(node) if @current_scope.eql?(node)\n    end\n\n    def visit(node : Crystal::Def)\n      node.name == \"->\" || on_scope_enter(node)\n    end\n\n    def visit(node : Crystal::Block | Crystal::ProcLiteral)\n      on_scope_enter(node)\n    end\n\n    def visit(node : Crystal::ClassDef | Crystal::ModuleDef)\n      on_scope_enter(node)\n    end\n\n    def visit(node : Crystal::Assign | Crystal::OpAssign | Crystal::MultiAssign | Crystal::UninitializedVar)\n      @current_assign = node\n      true\n    end\n\n    def end_visit(node : Crystal::Assign | Crystal::OpAssign)\n      on_assign_end(node.target, node)\n      @current_assign = nil\n    end\n\n    def end_visit(node : Crystal::MultiAssign)\n      node.targets.each { |target| on_assign_end(target, node) }\n      @current_assign = nil\n    end\n\n    def end_visit(node : Crystal::UninitializedVar)\n      on_assign_end(node.var, node)\n      @current_assign = nil\n    end\n\n    def visit(node : Crystal::TypeDeclaration)\n      return unless (var = node.var).is_a?(Crystal::Var)\n\n      @current_scope.add_variable(var)\n      @current_scope.add_type_dec_variable(node)\n\n      @current_assign = node.value if node.value\n      true\n    end\n\n    def end_visit(node : Crystal::TypeDeclaration)\n      return unless (var = node.var).is_a?(Crystal::Var)\n\n      on_assign_end(var, node)\n      @current_assign = nil\n    end\n\n    def visit(node : Crystal::Arg)\n      @current_scope.add_argument(node)\n      true\n    end\n\n    def visit(node : Crystal::InstanceVar)\n      @current_scope.add_ivariable(node)\n      true\n    end\n\n    def visit(node : Crystal::Var)\n      scope = @current_scope\n      variable = scope.find_variable(node.name)\n\n      case\n      when scope.arg?(node) # node is an argument\n        scope.add_argument(node)\n      when variable.nil? && @current_assign # node is a variable\n        scope.add_variable(node)\n      when variable # node is a reference\n        variable.reference(node, scope)\n      end\n      true\n    end\n\n    def visit(node : Crystal::Call)\n      if (obj = node.obj).is_a?(Crystal::Var) && obj.name == \"self\"\n        calls = @scope_call_queue[@current_scope] ||= [] of Crystal::Call\n        calls << node\n      end\n      true\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/visitors/scope_visitor.cr",
    "content": "require \"./base_visitor\"\n\nmodule Ameba::AST\n  # AST Visitor that traverses the source and constructs scopes.\n  class ScopeVisitor < BaseVisitor\n    # Non-exhaustive list of nodes to be visited by Ameba's rules.\n    NODES = {\n      ClassDef,\n      ModuleDef,\n      EnumDef,\n      LibDef,\n      FunDef,\n      TypeDef,\n      TypeOf,\n      CStructOrUnionDef,\n      ProcLiteral,\n      Block,\n      Macro,\n      MacroIf,\n      MacroFor,\n    }\n\n    SPECIAL_NODE_NAMES = %w[super previous_def]\n\n    @scope_queue = [] of Scope\n    @current_scope : Scope\n    @current_assign : Crystal::ASTNode?\n    @current_visibility : Crystal::Visibility?\n    @skip : Array(Crystal::ASTNode.class)?\n\n    def initialize(@rule, @source, skip = nil)\n      @current_scope = Scope.new(@source.ast) # top level scope\n      @skip = skip.try &.map(&.as(Crystal::ASTNode.class))\n\n      super @rule, @source\n\n      if @scope_queue.empty?\n        @rule.test @source, @current_scope.node, @current_scope\n      else\n        @scope_queue.each do |scope|\n          @rule.test @source, scope.node, scope\n        end\n      end\n    end\n\n    private def on_scope_enter(node)\n      return if skip?(node)\n\n      scope = Scope.new(node, @current_scope)\n      scope.visibility = @current_visibility\n\n      @current_scope = scope\n      true\n    end\n\n    private def on_scope_end(node)\n      @scope_queue << @current_scope\n\n      @current_visibility = nil\n\n      # go up if this is not a top level scope\n      if outer_scope = @current_scope.outer_scope\n        @current_scope = outer_scope\n      end\n    end\n\n    private def on_assign_end(target, node)\n      target.is_a?(Crystal::Var) &&\n        @current_scope.assign_variable(target.name, node)\n    end\n\n    # :nodoc:\n    def end_visit(node : Crystal::ASTNode)\n      on_scope_end(node) if @current_scope.eql?(node)\n    end\n\n    {% for name in NODES %}\n      # :nodoc:\n      def visit(node : Crystal::{{ name }})\n        on_scope_enter(node)\n      end\n    {% end %}\n\n    # :nodoc:\n    def visit(node : Crystal::VisibilityModifier)\n      @current_visibility = node.exp.visibility = node.modifier\n      true\n    end\n\n    # :nodoc:\n    def visit(node : Crystal::Yield)\n      @current_scope.yields = true\n      true\n    end\n\n    # :nodoc:\n    def visit(node : Crystal::Def)\n      node.name == \"->\" || on_scope_enter(node)\n    end\n\n    # :nodoc:\n    def visit(node : Crystal::Assign | Crystal::OpAssign)\n      target = node.target\n      if isolated_assign_target?(target)\n        @current_assign = node\n        target.accept(self)\n        on_scope_enter(node)\n        node.value.accept(self)\n        return false\n      end\n      @current_assign = node\n      true\n    end\n\n    # :nodoc:\n    def visit(node : Crystal::MultiAssign | Crystal::UninitializedVar)\n      @current_assign = node\n      true\n    end\n\n    # :nodoc:\n    def end_visit(node : Crystal::Assign | Crystal::OpAssign)\n      on_scope_end(node) if @current_scope.eql?(node)\n      on_assign_end(node.target, node)\n      @current_assign = nil\n    end\n\n    # :nodoc:\n    def end_visit(node : Crystal::MultiAssign)\n      node.targets.each { |target| on_assign_end(target, node) }\n      @current_assign = nil\n    end\n\n    # :nodoc:\n    def end_visit(node : Crystal::UninitializedVar)\n      on_assign_end(node.var, node)\n      @current_assign = nil\n    end\n\n    # :nodoc:\n    def visit(node : Crystal::TypeDeclaration)\n      return unless (var = node.var).is_a?(Crystal::Var)\n\n      @current_scope.add_variable(var)\n      @current_scope.add_type_dec_variable(node)\n\n      @current_assign = node.value if node.value\n      true\n    end\n\n    # :nodoc:\n    def end_visit(node : Crystal::TypeDeclaration)\n      return unless (var = node.var).is_a?(Crystal::Var)\n\n      on_assign_end(var, node)\n      @current_assign = nil\n    end\n\n    # :nodoc:\n    def visit(node : Crystal::Arg)\n      @current_scope.add_argument(node)\n      true\n    end\n\n    # :nodoc:\n    def visit(node : Crystal::InstanceVar)\n      @current_scope.add_ivariable(node)\n      true\n    end\n\n    # :nodoc:\n    def visit(node : Crystal::Var)\n      variable = @current_scope.find_variable(node.name)\n      case\n      when @current_scope.arg?(node) # node is an argument\n        @current_scope.add_argument(node)\n      when variable.nil? && @current_assign # node is a variable\n        @current_scope.add_variable(node)\n      when variable # node is a reference\n        variable.reference(node, @current_scope)\n      end\n      true\n    end\n\n    # :nodoc:\n    def visit(node : Crystal::Call)\n      scope = @current_scope\n      case\n      when (scope.top_level? || scope.type_definition?) && record_macro?(node)\n        return false\n      when scope.type_definition? && accessor_macro?(node)\n        return false\n      when scope.def? && special_node?(node)\n        scope.arguments.each do |arg|\n          ref = arg.variable.reference(scope)\n          ref.explicit = false\n        end\n      end\n      true\n    end\n\n    private def special_node?(node)\n      node.name.in?(SPECIAL_NODE_NAMES) && node.args.empty?\n    end\n\n    private def accessor_macro?(node)\n      node.name.matches? /^(class_)?(getter[?!]?|setter|property[?!]?)$/\n    end\n\n    private def record_macro?(node)\n      return false unless node.name == \"record\"\n\n      case node.args.first?\n      when Crystal::Path, Crystal::Generic\n        true\n      else\n        false\n      end\n    end\n\n    private def isolated_assign_target?(target)\n      case target\n      when Crystal::Path\n        true\n      when Crystal::InstanceVar, Crystal::ClassVar\n        @current_scope.type_definition?\n      else\n        false\n      end\n    end\n\n    private def skip?(node)\n      !!@skip.try(&.includes?(node.class))\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/ast/visitors/top_level_nodes_visitor.cr",
    "content": "module Ameba::AST\n  # AST Visitor that visits certain nodes at a top level, which\n  # can characterize the source (i.e. require statements, modules etc.)\n  class TopLevelNodesVisitor < Crystal::Visitor\n    getter require_nodes = [] of Crystal::Require\n\n    # Creates a new instance of visitor\n    def initialize(node : Crystal::ASTNode)\n      node.accept self\n    end\n\n    # :nodoc:\n    def visit(node : Crystal::Require)\n      require_nodes << node\n      true\n    end\n\n    # If a top level node is `Crystal::Expressions`,\n    # then always traverse the children.\n    def visit(node : Crystal::Expressions)\n      true\n    end\n\n    # A general visit method for rest of the nodes.\n    # Returns `false`, meaning all child nodes will not be traversed.\n    def visit(node : Crystal::ASTNode)\n      false\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/cli/cmd.cr",
    "content": "require \"option_parser\"\nrequire \"../../ameba\"\n\n# :nodoc:\nmodule Ameba::CLI\n  extend self\n\n  private class Opts\n    property config : Path?\n    property version : String?\n    property formatter : Symbol | String?\n    property root = Path[Dir.current]\n    property globs : Set(String)?\n    property excluded : Set(String)?\n    property only : Set(String)?\n    property except : Set(String)?\n    property describe_rule : String?\n    property location_to_explain : Crystal::Location?\n    property severity : Severity?\n    property stdin_filename : String?\n    property? skip_reading_config = false\n    property? rules = false\n    property? rule_versions = false\n    property? all = false\n    property? colors = true\n    property? without_affected_code = false\n    property? autocorrect = false\n  end\n\n  private class ExitException < Exception\n    getter code : Int32\n\n    def initialize(@code = 0)\n      super(\"Exit with code #{code}\")\n    end\n  end\n\n  def run(args = ARGV, output : IO = STDOUT) : Bool\n    safe_colorize_toggle do\n      opts = parse_args(args, output: output)\n\n      Colorize.enabled = opts.colors?\n\n      if (location_to_explain = opts.location_to_explain) && opts.autocorrect?\n        raise \"Invalid usage: Cannot explain an issue and autocorrect at the same time.\"\n      end\n\n      if opts.stdin_filename && opts.autocorrect?\n        raise \"Invalid usage: Cannot autocorrect from stdin.\"\n      end\n\n      config = config_from_opts(opts)\n\n      if opts.rules?\n        print_rules(config.rules, output)\n        return true\n      end\n\n      if opts.rule_versions?\n        print_rule_versions(config.rules, output)\n        return true\n      end\n\n      if describe_rule_name = opts.describe_rule\n        unless rule = config.rules.find(&.name.== describe_rule_name)\n          raise \"Rule `#{describe_rule_name}` does not exist\"\n        end\n        describe_rule(rule, output)\n        return true\n      end\n\n      runner = Ameba.run(config)\n\n      if location_to_explain\n        runner.explain(location_to_explain, output)\n        return true\n      end\n\n      runner.success?\n    end\n  rescue ex : ExitException\n    ex.code.zero?\n  end\n\n  private def safe_colorize_toggle(&)\n    prev_colorize_enabled = Colorize.enabled?\n    begin\n      yield\n    ensure\n      Colorize.enabled = prev_colorize_enabled\n    end\n  end\n\n  # ameba:disable Metrics/CyclomaticComplexity\n  def parse_args(args, opts = Opts.new, output : IO = STDOUT)\n    OptionParser.parse(args) do |parser|\n      parser.banner = \"Usage: ameba [options] [file1 file2 ...]\"\n\n      parser.unknown_args do |arr|\n        case\n        when arr.size == 1 && arr.first == \"-\"\n          opts.stdin_filename = arr.first\n        when arr.size == 1 && arr.first.matches?(/.+:\\d+:\\d+/)\n          configure_explain_opts(arr.first, opts)\n        else\n          configure_globs(arr, opts) if arr.present?\n        end\n      end\n\n      parser.on(\"-v\", \"--version\", \"Print version\") do\n        print_version(output)\n        raise ExitException.new\n      end\n\n      parser.on(\"-h\", \"--help\", \"Show this help\") do\n        print_help(parser, output)\n        raise ExitException.new\n      end\n\n      parser.on(\"-r\", \"--rules\", \"Show all available rules\") do\n        opts.rules = true\n      end\n\n      parser.on(\"-R\", \"--rule-versions\", \"Show all available rule versions\") do\n        opts.rule_versions = true\n      end\n\n      parser.on(\"-s\", \"--silent\", \"Disable output\") do\n        opts.formatter = :silent\n      end\n\n      parser.on(\"-c\", \"--config PATH\", \"Specify a configuration file\") do |path|\n        opts.config = Path[path] if path.presence\n      end\n\n      parser.on(\"-u\", \"--up-to-version VERSION\", \"Choose a version\") do |version|\n        opts.version = version if version.presence\n      end\n\n      parser.on(\"-f\", \"--format FORMATTER\",\n        \"Choose an output formatter: #{Config.formatter_names}\") do |formatter|\n        opts.formatter = formatter if formatter.presence\n      end\n\n      parser.on(\"--only RULE1,RULE2,...\",\n        \"Run only given rules (or groups)\") do |rules|\n        opts.only = rules.split(',').to_set if rules.presence\n      end\n\n      parser.on(\"--except RULE1,RULE2,...\",\n        \"Disable the given rules (or groups)\") do |rules|\n        opts.except = rules.split(',').to_set if rules.presence\n      end\n\n      parser.on(\"--all\", \"Enable all available rules\") do\n        opts.all = true\n      end\n\n      parser.on(\"--fix\", \"Autocorrect issues\") do\n        opts.autocorrect = true\n      end\n\n      parser.on(\"--gen-config\",\n        \"Generate a configuration file acting as a TODO list\") do\n        opts.formatter = :todo\n        opts.skip_reading_config = true\n      end\n\n      parser.on(\"--min-severity SEVERITY\",\n        \"Minimum severity of issues to report (default: #{Rule::Base.default_severity})\") do |level|\n        opts.severity = Severity.parse(level) if level.presence\n      end\n\n      parser.on(\"-e\", \"--explain PATH:line:column\",\n        \"Explain an issue at a specified location\") do |loc|\n        configure_explain_opts(loc, opts)\n      end\n\n      parser.on(\"-d\", \"--describe Category/Rule\",\n        \"Describe a rule with specified name\") do |rule_name|\n        configure_describe_opts(rule_name, opts)\n      end\n\n      parser.on(\"--without-affected-code\",\n        \"Stop showing affected code while using a default formatter\") do\n        opts.without_affected_code = true\n      end\n\n      parser.on(\"--no-color\", \"Disable colors\") do\n        opts.colors = false\n      end\n\n      parser.on(\"--stdin-filename FILENAME\", \"Read source from STDIN\") do |filename|\n        opts.stdin_filename = filename if filename.presence\n      end\n    end\n\n    opts\n  end\n\n  private def config_from_opts(opts)\n    config = Config.load(\n      root: opts.root,\n      path: opts.config,\n      skip_reading_config: opts.skip_reading_config?,\n    )\n    config.autocorrect = opts.autocorrect?\n    config.stdin_filename = opts.stdin_filename\n\n    if version = opts.version\n      config.version = version\n    end\n    if globs = opts.globs\n      config.globs = globs\n    end\n    if excluded = opts.excluded\n      config.excluded += excluded\n    end\n    if severity = opts.severity\n      config.severity = severity\n    end\n\n    configure_formatter(config, opts)\n    configure_rules(config, opts)\n\n    config\n  end\n\n  private def configure_globs(args, opts) : Nil\n    excluded, globs =\n      args.partition(&.starts_with?('!'))\n\n    if root = root_path_from_globs(globs)\n      opts.root = root\n    end\n    if globs.present?\n      opts.globs = globs\n        .map! { |path| path_to_glob(path) }\n        .to_set\n    end\n    if excluded.present?\n      opts.excluded = excluded\n        .map! { |path| path_to_glob(path.lchop) }\n        .to_set\n    end\n  end\n\n  private def path_to_glob(path : String) : String\n    Path[path]\n      .expand(home: true)\n      .to_posix\n      .to_s\n  end\n\n  private def root_path_from_globs(globs) : Path?\n    dynasty =\n      case\n      when path = find_as_path(globs, &->File.directory?(String))\n        path.parents + [path]\n      when path = find_as_path(globs, &->File.file?(String))\n        path.parents\n      end\n\n    dynasty\n      .try &.reverse!\n        .find(&->root_path?(Path))\n        .try(&.expand(home: true))\n  end\n\n  private def find_as_path(globs, &) : Path?\n    globs\n      .find { |glob| yield glob }\n      .try(&->Path.new(String))\n  end\n\n  private def root_path?(path : Path) : Bool\n    File.exists?(path / Config::Loader::FILENAME) ||\n      File.exists?(path / \"shard.yml\")\n  end\n\n  private def configure_rules(config, opts) : Nil\n    case\n    when only = opts.only\n      config.rules.each(&.enabled = false)\n      config.update_rules(only, enabled: true)\n      # We need to clear the version to ensure that the selected rules\n      # are not affected by the version constraint\n      config.version = nil\n    when opts.all?\n      config.rules.each(&.enabled = true)\n    end\n    if except = opts.except\n      config.update_rules(except, enabled: false)\n    end\n  end\n\n  private def configure_formatter(config, opts) : Nil\n    if name = opts.formatter\n      config.formatter = name\n    end\n    config.formatter.config[:autocorrect] = opts.autocorrect?\n    config.formatter.config[:without_affected_code] =\n      opts.without_affected_code?\n  end\n\n  private def configure_describe_opts(rule_name, opts) : Nil\n    opts.describe_rule = rule_name.presence\n    opts.formatter = :silent\n  end\n\n  private def configure_explain_opts(loc, opts) : Nil\n    location_to_explain = parse_explain_location(loc)\n\n    filename = location_to_explain.original_filename\n    return unless filename\n\n    opts.location_to_explain = location_to_explain\n    opts.globs = Set{path_to_glob(filename)}\n    opts.formatter = :silent\n  end\n\n  private def parse_explain_location(arg)\n    Crystal::Location.parse(arg)\n  rescue\n    raise \"location should have PATH:line:column format\"\n  end\n\n  private def print_version(output)\n    output.puts Ameba.version\n  end\n\n  private def print_help(parser, output)\n    output.puts parser\n  end\n\n  private def describe_rule(rule, output)\n    Presenter::RulePresenter.new(output).run(rule)\n  end\n\n  private def print_rules(rules, output)\n    Presenter::RuleCollectionPresenter.new(output).run(rules)\n  end\n\n  private def print_rule_versions(rules, output)\n    Presenter::RuleVersionsPresenter.new(output).run(rules)\n  end\nend\n"
  },
  {
    "path": "src/ameba/config/loader.cr",
    "content": "class Ameba::Config\n  # By default config loads `.ameba.yml` file located in a current\n  # working directory.\n  #\n  # If it cannot be found until reaching the root directory, then it will be\n  # searched for in the user’s global config locations, which consists of a\n  # dotfile or a config file inside the XDG Base Directory specification.\n  #\n  # - `~/.ameba.yml`\n  # - `$XDG_CONFIG_HOME/ameba/config.yml` (expands to `~/.config/ameba/config.yml`\n  #   if `$XDG_CONFIG_HOME` is not set)\n  #\n  # If both files exist, the dotfile will be selected.\n  #\n  # As an example, if Ameba is invoked from inside `/path/to/project/lib/utils`,\n  # then it will use the config as specified inside the first of the following files:\n  #\n  # - `/path/to/project/lib/utils/.ameba.yml`\n  # - `/path/to/project/lib/.ameba.yml`\n  # - `/path/to/project/.ameba.yml`\n  # - `/path/to/.ameba.yml`\n  # - `/path/.ameba.yml`\n  # - `/.ameba.yml`\n  # - `~/.ameba.yml`\n  # - `~/.config/ameba/config.yml`\n  module Loader\n    extend self\n\n    XDG_CONFIG_HOME = ENV.fetch(\"XDG_CONFIG_HOME\", \"~/.config\")\n\n    FILENAME      = \".ameba.yml\"\n    DEFAULT_PATH  = Path[Dir.current] / FILENAME\n    DEFAULT_PATHS = {\n      Path[\"~\"] / FILENAME,\n      Path[XDG_CONFIG_HOME] / \"ameba\" / \"config.yml\",\n    }\n\n    # Creates a new instance of `Ameba::Config` based on YAML parameters.\n    #\n    # `Config.load` uses this constructor to instantiate new config by YAML file.\n    protected def from_yaml(config : YAML::Any, root = nil)\n      config = YAML.parse(\"{}\") if config.raw.nil?\n      config.raw.is_a?(Hash) ||\n        raise \"Invalid config file format\"\n\n      rules = Rule.rules.map &.new(config).as(Rule::Base)\n\n      new(\n        rules: rules,\n        root: root,\n        excluded: load_array_section(config, \"Excluded\", DEFAULT_EXCLUDED.dup).to_set,\n        globs: load_array_section(config, \"Globs\", DEFAULT_GLOBS.dup).to_set,\n        version: load_string_key(config, \"Version\"),\n        formatter: load_string_key(config, \"Formatter\", \"Name\"),\n      )\n    end\n\n    # Loads YAML configuration file by `path`.\n    #\n    # ```\n    # config = Ameba::Config.load\n    # ```\n    def load(path : Path | String? = nil, root : Path? = nil, skip_reading_config : Bool = false)\n      unless skip_reading_config\n        content = begin\n          if path\n            read_config(path: path)\n          else\n            read_config(root: root)\n          end\n        end\n      end\n      content ||= \"{}\"\n\n      from_yaml YAML.parse(content), root\n    rescue ex\n      raise \"Unable to load config file: #{ex.message}\"\n    end\n\n    protected def read_config(*, path : Path | String)\n      unless File.exists?(path)\n        raise \"Config file #{path.to_s.inspect} does not exist\"\n      end\n      File.read(path)\n    end\n\n    protected def read_config(*, root : Path?)\n      path = root ? root / FILENAME : DEFAULT_PATH\n\n      if config_path = find_config_path(path)\n        return File.read(config_path)\n      end\n    end\n\n    protected def find_config_path(path : Path)\n      path.parents.reverse_each do |search_path|\n        config_path =\n          search_path / FILENAME\n        return config_path if File.exists?(config_path)\n      end\n\n      DEFAULT_PATHS.each do |default_path|\n        return default_path if File.exists?(default_path)\n      end\n    end\n\n    private def load_string_key(config, *path)\n      config.dig?(*path).try(&.as_s).presence\n    end\n\n    private def load_array_section(config, section_name, default = [] of String)\n      case value = config[section_name]?\n      when .nil?  then default\n      when .as_s? then [value.as_s]\n      when .as_a? then value.as_a.map(&.as_s)\n      else\n        raise \"Incorrect `#{section_name}` section in a config files\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/config/rule_config.cr",
    "content": "class Ameba::Config\n  # :nodoc:\n  module RuleConfig\n    # Define rule properties\n    macro properties(&block)\n      {% definitions = [] of NamedTuple %}\n\n      {% if (prop = block.body).is_a? Call %}\n        {% if (named_args = prop.named_args) && (type = named_args.select(&.name.== \"as\".id).first) %}\n          {% definitions << {var: prop.name, value: prop.args.first, type: type.value} %}\n        {% else %}\n          {% definitions << {var: prop.name, value: prop.args.first} %}\n        {% end %}\n      {% elsif block.body.is_a? Expressions %}\n        {% for prop in block.body.expressions %}\n          {% if prop.is_a? Call %}\n            {% if (named_args = prop.named_args) && (type = named_args.select(&.name.== \"as\".id).first) %}\n              {% definitions << {var: prop.name, value: prop.args.first, type: type.value} %}\n            {% else %}\n              {% definitions << {var: prop.name, value: prop.args.first} %}\n            {% end %}\n          {% end %}\n        {% end %}\n      {% end %}\n\n      {% properties = {} of MacroId => NamedTuple %}\n      {% for df in definitions %}\n        {% name = df[:var].id %}\n        {% key = name.camelcase.stringify %}\n        {% value = df[:value] %}\n        {% type = df[:type] %}\n        {% converter = nil %}\n\n        {% if key == \"Severity\" %}\n          {% type = Severity %}\n          {% converter = SeverityYamlConverter %}\n        {% end %}\n\n        {% unless type %}\n          {% if value.is_a?(BoolLiteral) %}\n            {% type = Bool %}\n          {% elsif value.is_a?(StringLiteral) || value.is_a?(StringInterpolation) %}\n            {% type = String %}\n          {% elsif value.is_a?(NumberLiteral) %}\n            {% if value.kind == :i32 %}\n              {% type = Int32 %}\n            {% elsif value.kind == :i64 %}\n              {% type = Int64 %}\n            {% elsif value.kind == :i128 %}\n              {% type = Int128 %}\n            {% elsif value.kind == :f32 %}\n              {% type = Float32 %}\n            {% elsif value.kind == :f64 %}\n              {% type = Float64 %}\n            {% end %}\n          {% end %}\n        {% end %}\n\n        {% properties[name] = {key: key, default: value, type: type, converter: converter} %}\n\n        @[YAML::Field(key: {{ key }}, converter: {{ converter }})]\n        {% if type == Bool %}\n          property? {{ name }}{{ \" : #{type}\".id if type }} = {{ value }}\n        {% else %}\n          property {{ name }}{{ \" : #{type}\".id if type }} = {{ value }}\n        {% end %}\n      {% end %}\n\n      {% unless properties[\"enabled\".id] %}\n        @[YAML::Field(key: \"Enabled\")]\n        property? enabled = true\n      {% end %}\n\n      {% unless properties[\"severity\".id] %}\n        @[YAML::Field(key: \"Severity\", converter: Ameba::SeverityYamlConverter)]\n        property severity = {{ @type }}.default_severity\n      {% end %}\n\n      {% unless properties[\"excluded\".id] %}\n        @[YAML::Field(key: \"Excluded\")]\n        property excluded : Set(String)?\n      {% end %}\n\n      {% unless properties[\"since_version\".id] %}\n        @[YAML::Field(key: \"SinceVersion\")]\n        property since_version : String?\n      {% end %}\n\n      def since_version : SemanticVersion?\n        if version = @since_version\n          SemanticVersion.parse(version)\n        end\n      end\n\n      def self.to_json_schema(builder : JSON::Builder) : Nil\n        builder.string(rule_name)\n        builder.object do\n          builder.field(\"$ref\", \"#/$defs/BaseRule\")\n          builder.field(\"$comment\", documentation_url)\n          builder.field(\"title\", rule_name)\n\n          {% if description = properties[\"description\".id] %}\n            builder.field(\"description\", {{ description[:default] }})\n          {% end %}\n\n          {%\n            serializable_props =\n              properties.to_a.reject { |(key, _)| key == \"description\" }\n          %}\n\n          builder.string(\"properties\")\n          builder.object do\n            {% for prop in serializable_props %}\n              {% default_set = false %}\n\n              {% prop_name, prop = prop %}\n              {% prop_stringified = prop[:type].stringify %}\n\n              builder.string({{ prop[:key] }})\n              builder.object do\n                {% if prop[:type] == Bool %}\n                  builder.field(\"type\", \"boolean\")\n\n                {% elsif prop[:type] == String %}\n                  builder.field(\"type\", \"string\")\n\n                {% elsif prop_stringified == \"::Union(String, ::Nil)\" %}\n                  builder.string(\"type\")\n                  builder.array do\n                    builder.string(\"string\")\n                    builder.string(\"null\")\n                  end\n\n                {% elsif prop_stringified =~ /^(Int|Float)\\d+$/ %}\n                  builder.field(\"type\", \"number\")\n\n                {% elsif prop_stringified =~ /^::Union\\((Int|Float)\\d+, ::Nil\\)$/ %}\n                  builder.string(\"type\")\n                  builder.array do\n                    builder.string(\"number\")\n                    builder.string(\"null\")\n                  end\n\n                {% elsif prop[:default].is_a?(ArrayLiteral) %}\n                  builder.field(\"type\", \"array\")\n\n                  builder.string(\"items\")\n                  builder.object do\n                    # TODO: Implement type validation for array items\n                    builder.field(\"type\", \"string\")\n                  end\n\n                {% elsif prop[:default].is_a?(HashLiteral) %}\n                  builder.field(\"type\", \"object\")\n\n                  builder.string(\"properties\")\n                  builder.object do\n                    {% for pr in prop[:default] %}\n                      builder.string({{ pr }})\n                      builder.object do\n                        # TODO: Implement type validation for object properties\n                        builder.field(\"type\", \"string\")\n                        builder.field(\"default\", {{ prop[:default][pr] }})\n                      end\n                    {% end %}\n                  end\n                  {% default_set = true %}\n\n                {% elsif prop[:type] == Severity %}\n                  builder.field(\"$ref\", \"#/$defs/Severity\")\n                  builder.field(\"default\", {{ prop[:default].capitalize }})\n\n                  {% default_set = true %}\n\n                {% else %}\n                  {% raise \"Unhandled schema type for #{prop}\" %}\n                {% end %}\n\n                {% unless default_set %}\n                  builder.field(\"default\", {{ prop[:default] }})\n                {% end %}\n              end\n            {% end %}\n\n            {% unless properties[\"severity\".id] %}\n              unless default_severity == Rule::Base.default_severity\n                builder.string(\"Severity\")\n                builder.object do\n                  builder.field(\"$ref\", \"#/$defs/Severity\")\n                  builder.field(\"default\", default_severity.to_s)\n                end\n              end\n            {% end %}\n          end\n        end\n      end\n    end\n\n    macro included\n      GROUP_SEVERITY = {\n        Lint:        Ameba::Severity::Warning,\n        Metrics:     Ameba::Severity::Warning,\n        Performance: Ameba::Severity::Warning,\n      }\n\n      class_getter default_severity : Ameba::Severity do\n        GROUP_SEVERITY[group_name]? || Ameba::Severity::Convention\n      end\n\n      macro inherited\n        include YAML::Serializable\n        include YAML::Serializable::Strict\n\n        def self.new(config = nil)\n          if (raw = config.try &.raw).is_a?(Hash)\n            yaml = raw[rule_name]?.try &.to_yaml\n          end\n          from_yaml yaml || \"{}\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/config.cr",
    "content": "require \"semantic_version\"\nrequire \"yaml\"\nrequire \"ecr/processor\"\n\nrequire \"./glob_utils\"\nrequire \"./config/*\"\n\n# A configuration entry for `Ameba::Runner`.\n#\n# Config can be loaded from configuration YAML file and adjusted.\n#\n# ```\n# config = Ameba::Config.load\n# config.formatter = my_formatter\n# ```\nclass Ameba::Config\n  extend Loader\n\n  include GlobUtils\n\n  DEFAULT_EXCLUDED = Set{\"lib\"}\n  DEFAULT_GLOBS    = Set{\"**/*.{cr,ecr}\"}\n\n  AVAILABLE_FORMATTERS = {\n    progress:         Formatter::DotFormatter,\n    todo:             Formatter::TODOFormatter,\n    flycheck:         Formatter::FlycheckFormatter,\n    silent:           Formatter::BaseFormatter,\n    disabled:         Formatter::DisabledFormatter,\n    json:             Formatter::JSONFormatter,\n    \"github-actions\": Formatter::GitHubActionsFormatter,\n  }\n\n  # Returns available formatter names joined by *separator*.\n  def self.formatter_names(separator = '|')\n    AVAILABLE_FORMATTERS.keys.join(separator)\n  end\n\n  # Returns an array of configured rules.\n  getter rules : Array(Rule::Base)\n\n  # Returns minimum reported severity.\n  property severity : Severity = :convention\n\n  # Returns a root directory to be used by `Ameba::Runner`.\n  property root : Path { Path[Dir.current] }\n\n  # Returns an ameba version to be used by `Ameba::Runner`.\n  property version : SemanticVersion?\n\n  # Sets version from string.\n  #\n  # ```\n  # config = Ameba::Config.load\n  # config.version = \"1.6.0\"\n  # ```\n  def version=(version : String)\n    @version = SemanticVersion.parse(version)\n  end\n\n  # Returns a formatter to be used while inspecting files.\n  # If formatter is not set, it will return default formatter.\n  #\n  # ```\n  # config = Ameba::Config.load\n  # config.formatter = custom_formatter\n  # config.formatter\n  # ```\n  property formatter : Formatter::BaseFormatter do\n    Formatter::DotFormatter.new\n  end\n\n  # Sets formatter by name.\n  #\n  # ```\n  # config = Ameba::Config.load\n  # config.formatter = :progress\n  # ```\n  def formatter=(name : String | Symbol)\n    unless formatter = AVAILABLE_FORMATTERS[name]?\n      raise \"Unknown formatter `#{name}`. Use one of #{Config.formatter_names}.\"\n    end\n    @formatter = formatter.new\n  end\n\n  # Returns a list of paths (with wildcards) to files.\n  # Represents a list of sources to be inspected.\n  # If globs are not set, it will return default list of files.\n  #\n  # ```\n  # config = Ameba::Config.load\n  # config.globs = Set{\"**/*.cr\"}\n  # config.globs\n  # ```\n  property globs : Set(String)\n\n  # Represents a list of paths to exclude from globs.\n  # Can have wildcards.\n  #\n  # ```\n  # config = Ameba::Config.load\n  # config.excluded = Set{\"spec\", \"src/server/*.cr\"}\n  # ```\n  property excluded : Set(String)\n\n  # Returns `true` if correctable issues should be autocorrected.\n  property? autocorrect = false\n\n  # Returns a filename if reading source file from STDIN.\n  property stdin_filename : String?\n\n  # Returns rules grouped by rule group.\n  protected getter rule_groups : Hash(String, Array(Rule::Base))\n\n  protected def initialize(\n    *,\n    @rules = [] of Rule::Base,\n    @severity : Severity = :convention,\n    @root = nil,\n    @globs = Set(String).new,\n    @excluded = Set(String).new,\n    @autocorrect = false,\n    @stdin_filename = nil,\n    version = nil,\n    formatter = nil,\n  )\n    @rule_groups = @rules.group_by &.group\n\n    if version\n      self.version = version\n    end\n    if formatter\n      self.formatter = formatter\n    end\n  end\n\n  # Returns a list of sources matching globs and excluded sections.\n  #\n  # ```\n  # config = Ameba::Config.load\n  # config.sources # => list of default sources\n  # config.globs = Set{\"**/*.cr\", \"**/*.ecr\"}\n  # config.excluded = Set{\"spec\"}\n  # config.sources # => list of sources pointing to files found by the wildcards\n  # ```\n  def sources\n    if file = stdin_filename\n      [Source.new(STDIN.gets_to_end, file)]\n    else\n      files.map do |path|\n        Source.new(File.read(path), path)\n      end\n    end\n  end\n\n  # Returns a list of files matching globs and excluded sections.\n  #\n  # ```\n  # config = Ameba::Config.load\n  # config.files # => list of default files\n  # config.globs = Set{\"**/*.cr\", \"**/*.ecr\"}\n  # config.excluded = Set{\"spec\"}\n  # config.files # => list of files found by the wildcards\n  # ```\n  def files\n    find_files_by_globs(globs, root) - find_files_by_globs(excluded, root)\n  end\n\n  # Updates rule properties.\n  #\n  # ```\n  # config = Ameba::Config.load\n  # config.update_rule \"MyRuleName\", enabled: false\n  # ```\n  def update_rule(name, enabled = true, excluded = nil)\n    rule = @rules.find(&.name.==(name))\n    raise ArgumentError.new(\"Rule `#{name}` does not exist\") unless rule\n\n    rule\n      .tap(&.enabled = enabled)\n      .tap(&.excluded = excluded.try &.to_set)\n  end\n\n  # Updates rules properties.\n  #\n  # ```\n  # config = Ameba::Config.load\n  # config.update_rules %w[Rule1 Rule2], enabled: true\n  # ```\n  #\n  # also it allows to update groups of rules:\n  #\n  # ```\n  # config.update_rules %w[Group1 Group2], enabled: true\n  # ```\n  def update_rules(names : Enumerable(String), enabled = true, excluded = nil)\n    excluded = excluded.try &.to_set\n\n    names.each do |name|\n      if rules = @rule_groups[name]?\n        rules.each do |rule|\n          rule.enabled = enabled\n          rule.excluded = excluded\n        end\n      else\n        update_rule name, enabled, excluded\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/ext/location.cr",
    "content": "# Extensions to `Crystal::Location`\nmodule Ameba::Ext::Location\n  # Returns `self` relative to the given *base* directory.\n  def relative(base = Dir.current) : self\n    return self unless path = original_filename\n\n    path =\n      Path[path].relative_to(base).to_s\n\n    self.class.new(path, @line_number, @column_number)\n  end\n\n  # Returns `true` if the line numbers of `self` and *other* are the same.\n  def same_line?(other : self?) : Bool\n    !!other &&\n      @line_number == other.line_number\n  end\n\n  # Returns the same location as this location but with the line and/or\n  # column number(s) changed to the given value(s).\n  def with(line_number = @line_number, column_number = @column_number) : self\n    self.class.new(@filename, line_number, column_number)\n  end\n\n  # Returns the same location as this location but with the line and/or\n  # column number(s) adjusted by the given amount(s).\n  def adjust(line_number = 0, column_number = 0) : self\n    self.class.new(@filename, @line_number + line_number, @column_number + column_number)\n  end\n\n  # Seeks to a given *offset* relative to `self`.\n  def seek(offset : self) : self\n    if offset.filename.as?(String).presence && @filename != offset.filename\n      raise ArgumentError.new <<-MSG\n        Mismatching filenames:\n          #{@filename}\n          #{offset.filename}\n        MSG\n    end\n\n    if offset.line_number == 1\n      self.class.new(@filename, @line_number, @column_number + offset.column_number - 1)\n    else\n      self.class.new(@filename, @line_number + offset.line_number - 1, offset.column_number)\n    end\n  end\nend\n\nclass Crystal::Location\n  include Ameba::Ext::Location\nend\n"
  },
  {
    "path": "src/ameba/formatter/base_formatter.cr",
    "content": "require \"./util\"\n\n# A module that utilizes Ameba's formatters.\nmodule Ameba::Formatter\n  # A base formatter for all formatters. It uses `output` IO\n  # to report results and also implements stub methods for\n  # callbacks in `Ameba::Runner#run` method.\n  class BaseFormatter\n    # TODO: allow other IOs\n    getter output : IO::FileDescriptor | IO::Memory\n    getter config = {} of Symbol => String | Bool\n\n    def initialize(@output = STDOUT)\n    end\n\n    # Callback that indicates when inspecting is started.\n    # A list of sources to inspect is passed as an argument.\n    def started(sources) : Nil; end\n\n    # Callback that indicates when source inspection is started.\n    # A corresponding source is passed as an argument.\n    #\n    # WARNING: This method needs to be MT safe\n    def source_started(source : Source) : Nil; end\n\n    # Callback that indicates when source inspection is finished.\n    # A corresponding source is passed as an argument.\n    #\n    # WARNING: This method needs to be MT safe\n    def source_finished(source : Source) : Nil; end\n\n    # Callback that indicates when inspection is finished.\n    # A list of inspected sources is passed as an argument.\n    def finished(sources) : Nil; end\n  end\nend\n"
  },
  {
    "path": "src/ameba/formatter/disabled_formatter.cr",
    "content": "module Ameba::Formatter\n  # A formatter that shows all disabled lines by inline directives.\n  class DisabledFormatter < BaseFormatter\n    def finished(sources) : Nil\n      output << \"Disabled rules using inline directives:\\n\\n\"\n\n      sources.each do |source|\n        source.issues.each do |issue|\n          next unless issue.disabled?\n          next unless loc = issue.location\n\n          output << \"#{source.path}:#{loc.line_number}\".colorize(:cyan)\n          output << \" #{issue.rule.name}\\n\"\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/formatter/dot_formatter.cr",
    "content": "require \"./util\"\n\nmodule Ameba::Formatter\n  # A formatter that shows a progress of inspection in a terminal using dots.\n  # It is similar to Crystal's dot formatter for specs.\n  class DotFormatter < BaseFormatter\n    include Util\n\n    @started_at : Time::Instant?\n    @mutex = Mutex.new\n\n    # Reports a message when inspection is started.\n    def started(sources) : Nil\n      @started_at = Time.instant\n\n      output.puts started_message(sources.size)\n      output.puts\n    end\n\n    # Reports a result of the inspection of a corresponding source.\n    def source_finished(source : Source) : Nil\n      sym = source.valid? ? \".\".colorize(:green) : \"F\".colorize(:red)\n      @mutex.synchronize { output << sym }\n    end\n\n    # Reports a message when inspection is finished.\n    def finished(sources) : Nil\n      output.flush\n      output << \"\\n\\n\"\n\n      show_affected_code = !config[:without_affected_code]?\n      failed_sources = sources.reject &.valid?\n\n      failed_sources.each do |source|\n        source.issues.each do |issue|\n          next if issue.disabled?\n          next if (location = issue.location).nil?\n\n          output.print location.colorize(:cyan)\n          if issue.correctable?\n            if config[:autocorrect]?\n              output.print \" [Corrected]\".colorize(:green)\n            else\n              output.print \" [Correctable]\".colorize(:yellow)\n            end\n          end\n          output.puts\n          output.puts (\"[%s] %s: %s\" % {\n            issue.rule.severity.symbol,\n            issue.rule.name,\n            issue.message,\n          }).colorize(issue.rule.severity.color)\n\n          if show_affected_code && (code = affected_code(issue))\n            output << code.colorize(:default)\n          end\n\n          output.puts\n        end\n      end\n\n      output.puts finished_in_message(@started_at)\n      output.puts final_message(sources, failed_sources)\n    end\n\n    private def started_message(size)\n      \"Inspecting #{size} #{pluralize(size, \"file\")}\"\n    end\n\n    private def finished_in_message(started)\n      return unless started\n\n      \"Finished in #{to_human(started.elapsed)}\".colorize(:default)\n    end\n\n    private def final_message(sources, failed_sources)\n      total = sources.size\n      failures = failed_sources.sum(&.issues.count(&.enabled?))\n      color = failures == 0 ? :green : :red\n\n      \"#{total} inspected, #{failures} #{pluralize(failures, \"failure\")}\".colorize(color)\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/formatter/explain_formatter.cr",
    "content": "require \"./util\"\n\nmodule Ameba::Formatter\n  # A formatter that shows the detailed explanation of the issue at\n  # a specific location.\n  class ExplainFormatter\n    include Util\n\n    getter output : IO::FileDescriptor | IO::Memory\n    getter location : Crystal::Location\n\n    # Creates a new instance of `ExplainFormatter`.\n    #\n    # Accepts *output* which indicates the io where the explanation will be written to.\n    # Second argument is *location* which indicates the location to explain.\n    #\n    # ```\n    # ExplainFormatter.new output, {\n    #   file:   path,\n    #   line:   line_number,\n    #   column: column_number,\n    # }\n    # ```\n    def initialize(@output, @location)\n    end\n\n    # Reports the explanations at the *@location*.\n    def finished(sources) : Nil\n      source = sources.find(&.path.==(@location.filename))\n      return unless source\n\n      issue = source.issues.find(&.location.==(@location))\n      return unless issue\n\n      explain(source, issue)\n    end\n\n    private def explain(source, issue) : Nil\n      return unless location = issue.location\n\n      output << '\\n'\n      output_title \"Issue info\"\n      output_paragraph [\n        issue.message.colorize(:red),\n        location.to_s.colorize(:cyan),\n      ]\n\n      if affected_code = affected_code(issue, context_lines: 3)\n        output_title \"Affected code\"\n        output_paragraph affected_code\n      end\n\n      rule = issue.rule\n\n      output_title \"Rule info\"\n      output_paragraph \"%s of a %s severity\" % {\n        rule.name.colorize(:magenta),\n        rule.severity.to_s.colorize(rule.severity.color),\n      }\n      if rule_description = rule.description\n        output_paragraph colorize_markdown(rule_description)\n      end\n\n      if rule_doc = rule.class.parsed_doc\n        output_title \"Detailed description\"\n        output_paragraph colorize_markdown(rule_doc)\n      end\n    end\n\n    private def output_title(title)\n      output << \"### \".colorize(:yellow)\n      output << title.upcase.colorize(:yellow)\n      output << \"\\n\\n\"\n    end\n\n    private def output_paragraph(paragraph : String)\n      output_paragraph(paragraph.lines)\n    end\n\n    private def output_paragraph(paragraph : Array)\n      paragraph.each do |line|\n        output << \"    \" << line << '\\n'\n      end\n      output << '\\n'\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/formatter/flycheck_formatter.cr",
    "content": "module Ameba::Formatter\n  class FlycheckFormatter < BaseFormatter\n    @mutex = Mutex.new\n\n    def source_finished(source : Source) : Nil\n      source.issues.each do |issue|\n        next if issue.disabled?\n        next if issue.correctable? && config[:autocorrect]?\n\n        next unless loc = issue.location\n\n        @mutex.synchronize do\n          output.printf \"%s:%d:%d: %s: [%s] %s\\n\",\n            source.path, loc.line_number, loc.column_number, issue.rule.severity.symbol,\n            issue.rule.name, issue.message.gsub('\\n', \" \")\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/formatter/github_actions_formatter.cr",
    "content": "require \"./util\"\n\nmodule Ameba::Formatter\n  # A formatter that outputs issues in a GitHub Actions compatible format.\n  #\n  # See [GitHub Actions documentation](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions) for details.\n  class GitHubActionsFormatter < BaseFormatter\n    include Util\n\n    @started_at : Time::Instant?\n    @mutex = Mutex.new\n\n    # Reports a message when inspection is started.\n    def started(sources) : Nil\n      @started_at = Time.instant\n    end\n\n    # Reports a result of the inspection of a corresponding source.\n    def source_finished(source : Source) : Nil\n      source.issues.each do |issue|\n        next if issue.disabled?\n\n        @mutex.synchronize do\n          output << \"::\"\n          output << command_name(issue.rule.severity)\n          output << \" \"\n          output << \"file=\"\n          output << escape_property(source.path)\n          if location = issue.location\n            output << \",line=\"\n            output << location.line_number\n            output << \",col=\"\n            output << location.column_number\n          end\n          if end_location = issue.end_location\n            output << \",endLine=\"\n            output << end_location.line_number\n            output << \",endColumn=\"\n            output << end_location.column_number\n          end\n          output << \",title=\"\n          output << escape_property(issue.rule.name)\n          output << \"::\"\n          output << escape_data(issue.message)\n          output << \"\\n\"\n        end\n      end\n    end\n\n    # Reports a message when inspection is finished.\n    def finished(sources) : Nil\n      return unless step_summary_file = ENV[\"GITHUB_STEP_SUMMARY\"]?\n\n      time_elapsed =\n        @started_at.try(&.elapsed)\n\n      File.write(step_summary_file, summary(sources, time_elapsed))\n    end\n\n    private def summary(sources, time_elapsed)\n      failed_sources = sources.reject(&.valid?)\n      total = sources.size\n      failures = failed_sources.sum(&.issues.count(&.enabled?))\n\n      String.build do |output|\n        output << \"## Ameba Results %s\\n\\n\" % {\n          failures == 0 ? \":green_heart:\" : \":bug:\",\n        }\n\n        if failures.positive?\n          output << \"### Issues found\\n\\n\"\n\n          failed_sources.each do |source|\n            issues = source.issues.select(&.enabled?)\n\n            if issues.present?\n              output << \"#### `%s` (**%d** %s)\\n\\n\" % {\n                source.path,\n                issues.size,\n                pluralize(issues.size, \"issue\"),\n              }\n\n              output.puts \"| Line | Severity | Name | Message |\"\n              output.puts \"| ---- | -------- | ---- | ------- |\"\n\n              issues.each do |issue|\n                output.puts \"| %s | %s | %s | %s |\" % {\n                  issue_location_value(issue) || \"-\",\n                  issue.rule.severity,\n                  \"[%s](%s)\" % {\n                    issue.rule.name,\n                    issue.rule.class.documentation_url,\n                  },\n                  issue.message.gsub('|', \"\\\\|\"),\n                }\n              end\n              output << \"\\n\"\n            end\n          end\n          output << \"\\n\"\n        end\n\n        if time_elapsed\n          output.puts \"Finished in %s.\" % to_human(time_elapsed)\n        end\n        output.puts \"**%d** sources inspected, **%d** %s.\" % {\n          total,\n          failures,\n          pluralize(failures, \"failure\"),\n        }\n        output.puts\n        output.puts \"> Ameba version: **%s**\" % Ameba.version\n        output.puts \"> Crystal version: **%s**\" % Crystal::VERSION\n      end\n    end\n\n    private BLOB_URL = begin\n      repo = ENV[\"GITHUB_REPOSITORY\"]?\n      sha = ENV[\"GITHUB_SHA\"]?\n\n      if repo && sha\n        \"https://github.com/#{repo}/blob/#{sha}\"\n      end\n    end\n\n    private def issue_location_value(issue)\n      location, end_location =\n        issue.location, issue.end_location\n\n      return unless location\n\n      line_selector =\n        if end_location && !location.same_line?(end_location)\n          \"#{location.line_number}-#{end_location.line_number}\"\n        else\n          \"#{location.line_number}\"\n        end\n\n      if BLOB_URL\n        location_url = \"[%s](%s/%s#%s)\" % {\n          line_selector,\n          BLOB_URL,\n          location.filename,\n          line_selector\n            .split('-')\n            .join('-') { |i| \"L#{i}\" },\n        }\n      end\n\n      location_url || line_selector\n    end\n\n    private def command_name(severity : Severity) : String\n      case severity\n      in .error?      then \"error\"\n      in .warning?    then \"warning\"\n      in .convention? then \"notice\"\n      end\n    end\n\n    # See for details:\n    # - https://github.com/actions/toolkit/blob/74906bea83a0dbf6aaba2d00b732deb0c3aefd2d/packages/core/src/command.ts#L92-L97\n    # - https://github.com/actions/toolkit/issues/193\n    private def escape_data(string : String) : String\n      string\n        .gsub('%', \"%25\")\n        .gsub('\\r', \"%0D\")\n        .gsub('\\n', \"%0A\")\n    end\n\n    # See for details:\n    # - https://github.com/actions/toolkit/blob/74906bea83a0dbf6aaba2d00b732deb0c3aefd2d/packages/core/src/command.ts#L99-L106\n    private def escape_property(string : String) : String\n      string\n        .gsub('%', \"%25\")\n        .gsub('\\r', \"%0D\")\n        .gsub('\\n', \"%0A\")\n        .gsub(':', \"%3A\")\n        .gsub(',', \"%2C\")\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/formatter/json_formatter.cr",
    "content": "require \"json\"\n\nmodule Ameba::Formatter\n  # A formatter that produces the result in a json format.\n  #\n  # Example:\n  #\n  # ```json\n  # {\n  #   \"metadata\": {\n  #     \"ameba_version\": \"x.x.x\",\n  #     \"crystal_version\": \"x.x.x\",\n  #   },\n  #   \"sources\": [\n  #     {\n  #       \"issues\": [\n  #         {\n  #           \"location\": {\n  #             \"column\": 7,\n  #             \"line\": 17,\n  #           },\n  #           \"end_location\": {\n  #             \"column\": 20,\n  #             \"line\": 17,\n  #           },\n  #           \"message\": \"Useless assignment to variable `a`\",\n  #           \"rule_name\": \"UselessAssign\",\n  #           \"severity\": \"Convention\",\n  #         },\n  #         {\n  #           \"location\": {\n  #             \"column\": 7,\n  #             \"line\": 18,\n  #           },\n  #           \"end_location\": {\n  #             \"column\": 8,\n  #             \"line\": 18,\n  #           },\n  #           \"message\": \"Useless assignment to variable `a`\",\n  #           \"rule_name\": \"UselessAssign\",\n  #         },\n  #         {\n  #           \"location\": {\n  #             \"column\": 7,\n  #             \"line\": 19,\n  #           },\n  #           \"end_location\": {\n  #             \"column\": 9,\n  #             \"line\": 19,\n  #           },\n  #           \"message\": \"Useless assignment to variable `a`\",\n  #           \"rule_name\": \"UselessAssign\",\n  #           \"severity\": \"Convention\",\n  #         },\n  #       ],\n  #       \"path\": \"src/ameba/formatter/json_formatter.cr\",\n  #     },\n  #   ],\n  #   \"summary\": {\n  #     \"issues_count\": 3,\n  #     \"target_sources_count\": 1,\n  #   }\n  # }\n  # ```\n  class JSONFormatter < BaseFormatter\n    @result = AsJSON::Result.new\n    @mutex = Mutex.new\n\n    def started(sources) : Nil\n      @result.summary.target_sources_count = sources.size\n    end\n\n    def source_finished(source : Source) : Nil\n      json_source = AsJSON::Source.new(source.path)\n\n      source.issues.each do |issue|\n        next if issue.disabled?\n        next if issue.correctable? && config[:autocorrect]?\n\n        json_source.issues << AsJSON::Issue.new(\n          issue.rule.name,\n          issue.rule.severity.to_s,\n          issue.location,\n          issue.end_location,\n          issue.message\n        )\n      end\n\n      return if json_source.issues.empty?\n\n      @mutex.synchronize do\n        @result.summary.issues_count += json_source.issues.size\n        @result.sources << json_source\n      end\n    end\n\n    def finished(sources) : Nil\n      @result.to_pretty_json @output\n    end\n  end\n\n  private module AsJSON\n    record Result,\n      sources = [] of Source,\n      metadata = Metadata.new,\n      summary = Summary.new do\n      include JSON::Serializable\n    end\n\n    record Source,\n      path : String,\n      issues = [] of Issue do\n      include JSON::Serializable\n    end\n\n    record Issue,\n      rule_name : String,\n      severity : String,\n      location : Crystal::Location?,\n      end_location : Crystal::Location?,\n      message : String do\n      def to_json(json)\n        {\n          rule_name: rule_name,\n          severity:  severity,\n          message:   message,\n          location:  {\n            line:   location.try &.line_number,\n            column: location.try &.column_number,\n          },\n          end_location: {\n            line:   end_location.try &.line_number,\n            column: end_location.try &.column_number,\n          },\n        }.to_json(json)\n      end\n    end\n\n    record Metadata,\n      ameba_version : String = Ameba.version.to_s,\n      crystal_version : String = Crystal::VERSION do\n      include JSON::Serializable\n    end\n\n    class Summary\n      include JSON::Serializable\n\n      property target_sources_count = 0\n      property issues_count = 0\n\n      def initialize(@target_sources_count = 0, @issues_count = 0)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/formatter/todo_formatter.cr",
    "content": "module Ameba::Formatter\n  # A formatter that creates a TODO config.\n  #\n  # Basically, it takes all issues reported and disables corresponding rules\n  # or excludes failed sources from these rules.\n  class TODOFormatter < DotFormatter\n    getter config_path : Path\n\n    def initialize(@output = STDOUT, @config_path = Config::Loader::DEFAULT_PATH)\n    end\n\n    def finished(sources) : Nil\n      super\n\n      issues = sources.flat_map(&.issues)\n\n      if issues.none?(&.enabled?)\n        output.puts \"No issues found. File is not generated.\"\n        return\n      end\n\n      if issues.any?(&.syntax?)\n        output.puts \"Unable to generate TODO file. Please fix syntax issues.\"\n        return\n      end\n\n      issues.sort_by! do |issue|\n        location = issue.location\n        {\n          issue.rule.name,\n          location.try(&.filename).to_s,\n          location.try(&.line_number) || 0,\n          location.try(&.column_number) || 0,\n        }\n      end\n      generate_todo_config(issues)\n\n      output.puts \"Created #{config_path}\"\n    end\n\n    private def generate_todo_config(issues) : Nil\n      File.open(config_path, mode: \"w\") do |file|\n        file.puts header\n\n        YAML::Builder.build(file) do |builder|\n          builder.stream do\n            builder.document(implicit_start_indicator: true) do\n              build_yaml(file, builder, issues)\n            end\n          end\n        end\n      end\n    end\n\n    private def build_yaml(io, builder, issues)\n      builder.mapping do\n        rule_issues_map(issues).each do |rule, rule_issues|\n          builder.flush\n\n          io.puts\n\n          rule_to_yaml(builder, rule, rule_issues)\n        end\n      end\n    end\n\n    private def rule_issues_map(issues)\n      Hash(Rule::Base, Array(Issue)).new.tap do |hash|\n        issues.each do |issue|\n          next if issue.disabled? || issue.syntax?\n          next if issue.correctable? && config[:autocorrect]?\n\n          (hash[issue.rule] ||= Array(Issue).new) << issue\n        end\n      end\n    end\n\n    private def header\n      <<-YAML\n        # This configuration file was generated by `ameba --gen-config`\n        # on #{Time.utc} using Ameba version #{Ameba.version}.\n        #\n        # The point is for the user to remove these configuration records\n        # one by one as the reported problems are removed from the code base.\n        #\n        # For more details on any individual rule, run `ameba --only RuleName`.\n\n        Version: \"#{Ameba.version.for_production}\"\n\n        YAML\n    end\n\n    private def exclude_paths(issues)\n      issues\n        .compact_map(&.location.try &.filename.try &.to_s)\n        .to_set\n        .map { |path| Path[path].to_posix.to_s }\n    end\n\n    private def rule_to_yaml(yaml, rule, issues)\n      yaml.scalar rule.name\n      yaml.mapping do\n        yaml.scalar \"Excluded\"\n        yaml.sequence do\n          exclude_paths(issues).each do |path|\n            yaml.scalar path\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/formatter/util.cr",
    "content": "module Ameba::Formatter\n  module Util\n    extend self\n\n    def pluralize(count : Int, singular : String, plural = \"#{singular}s\")\n      count == 1 ? singular : plural\n    end\n\n    def to_human(span : Time::Span)\n      total_milliseconds = span.total_milliseconds\n      if total_milliseconds < 1\n        return \"#{(span.total_milliseconds * 1_000).round.to_i} microseconds\"\n      end\n\n      total_seconds = span.total_seconds\n      if total_seconds < 1\n        return \"#{span.total_milliseconds.round(2)} milliseconds\"\n      end\n\n      if total_seconds < 60\n        return \"#{total_seconds.round(2)} seconds\"\n      end\n\n      minutes = span.minutes\n      seconds = span.seconds\n\n      \"#{minutes}:#{seconds < 10 ? \"0\" : \"\"}#{seconds} minutes\"\n    end\n\n    def colorize_markdown(string : String, code_color : Colorize::Color = :dark_gray)\n      string = colorize_code_fences(string, code_color)\n      string = colorize_text_styles(string)\n      string\n    end\n\n    def colorize_code_fences(string : String, color : Colorize::Color = :dark_gray)\n      string\n        .gsub(/```(.+?)```/m, &.colorize(color))\n        .gsub(/`(?!`)(.+?)`/, &.colorize(color))\n    end\n\n    def colorize_text_styles(string : String)\n      {% begin %}\n        {%\n          modes = {\n            underline:     {/^([#]{1,6})(?=\\s+)(.+?)$/m, \"%1$s%2$s\"},\n            strikethrough: {/([~]{2})(.+?)\\1/, \"%1$s%2$s%1$s\"},\n            bold:          {/([*_]{2})(.+?)\\1/, \"%1$s%2$s%1$s\"},\n            italic:        {/([*_])(.+?)\\1/, \"%1$s%2$s%1$s\"},\n          }\n        %}\n\n        string\n        {% for mode, pattern in modes %}\n          .gsub({{ pattern[0] }}, %(<{{ mode.id }} fence=\"\\\\1\">\\\\2</{{ mode.id }}>))\n        {% end %}\n        {% for mode, pattern in modes %}\n          .gsub(/<{{ mode.id }} fence=\"(.+?)\">(.+?)<\\/{{ mode.id }}>/) do |_, match|\n            string = {{ pattern[1] }} % {match[1], match[2]}\n            string.colorize.{{ mode.id }}\n          end\n        {% end %}\n      {% end %}\n    end\n\n    def deansify(message : String?) : String?\n      message.try &.gsub(/\\x1b[^m]*m/, \"\").presence\n    end\n\n    def trim(str, max_length = 120, ellipsis = \" ...\")\n      if (str.size - ellipsis.size) > max_length\n        str = str[0, max_length]\n        if str.size > ellipsis.size\n          str = str[0...-ellipsis.size] + ellipsis\n        end\n      end\n      str\n    end\n\n    def context(lines, lineno, context_lines = 3, remove_empty = true)\n      pre_context, post_context = %w[], %w[]\n\n      lines.each_with_index do |line, i|\n        case i + 1\n        when lineno - context_lines...lineno\n          pre_context << line\n        when lineno + 1..lineno + context_lines\n          post_context << line\n        end\n      end\n\n      if remove_empty\n        # remove empty lines at the beginning ...\n        while pre_context.first?.try(&.blank?)\n          pre_context.shift\n        end\n        # ... and the end\n        while post_context.last?.try(&.blank?)\n          post_context.pop\n        end\n      end\n\n      {pre_context, post_context}\n    end\n\n    def affected_code(issue : Issue, context_lines = 0, max_length = 120, ellipsis = \" ...\", prompt = \"> \")\n      return unless location = issue.location\n\n      affected_code(issue.code, location, issue.end_location, context_lines, max_length, ellipsis, prompt)\n    end\n\n    def affected_code(code, location, end_location = nil, context_lines = 0, max_length = 120, ellipsis = \" ...\", prompt = \"> \")\n      lines = code.split('\\n') # must preserve trailing newline\n      lineno, column =\n        location.line_number, location.column_number\n\n      return unless affected_line = lines[lineno - 1]?.presence\n\n      if column < max_length\n        affected_line = trim(affected_line, max_length, ellipsis)\n      end\n\n      position = prompt.size + column\n      position -= 1\n\n      show_context = context_lines > 0\n\n      if show_context\n        pre_context, post_context =\n          context(lines, lineno, context_lines)\n      else\n        affected_line_size, affected_line =\n          affected_line.size, affected_line.lstrip\n\n        indent_size_diff = affected_line_size - affected_line.size\n        if column > indent_size_diff\n          position -= indent_size_diff\n        end\n      end\n\n      String.build do |str|\n        if show_context\n          pre_context.try &.each do |line|\n            line = trim(line, max_length, ellipsis)\n            str << prompt\n            str.puts(line.colorize(:dark_gray))\n          end\n        end\n\n        str << prompt\n        str.puts(affected_line.colorize(:white))\n\n        str << (\" \" * position)\n        str << \"^\".colorize(:yellow)\n\n        if end_location\n          end_lineno = end_location.line_number\n          end_column = end_location.column_number\n\n          if end_lineno == lineno && end_column > column\n            if column < max_length\n              end_column = {end_column, max_length}.min\n            end\n\n            length = end_column - column\n            length -= 1\n\n            str << (\"-\" * length).colorize(:dark_gray)\n            str << \"^\".colorize(:yellow)\n          end\n        end\n\n        str.puts\n\n        if show_context\n          post_context.try &.each do |line|\n            line = trim(line, max_length, ellipsis)\n            str << prompt\n            str.puts(line.colorize(:dark_gray))\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/glob_utils.cr",
    "content": "module Ameba\n  # Helper module that is utilizes helpers for working with globs.\n  module GlobUtils\n    extend self\n\n    # Returns all files that match specified globs.\n    # Globs can have wildcards or be rejected:\n    #\n    # ```\n    # find_files_by_globs([\"**/*.cr\", \"!lib\"])\n    # ```\n    def find_files_by_globs(globs, root = Dir.current)\n      rejected = rejected_globs(globs, root)\n      selected = globs - rejected\n\n      expand(selected, root) - expand(rejected.map!(&.lchop), root)\n    end\n\n    # Expands globs. Globs can point to files or even directories.\n    #\n    # ```\n    # expand([\"spec/*.cr\", \"src\"]) # => all files in src folder + first level specs\n    # ```\n    def expand(globs, root = Dir.current)\n      globs\n        .flat_map do |glob|\n          glob = Path[glob].expand(root).relative_to(Dir.current).to_posix\n\n          if File.directory?(glob)\n            glob = glob / \"**\" / \"*.{cr,ecr}\"\n          end\n\n          glob = glob.to_s.lchop(\"./\")\n\n          Dir[glob]\n        end\n        .uniq!\n        .select! { |path| File.file?(path) }\n    end\n\n    private def rejected_globs(globs, root = Dir.current)\n      globs.select do |glob|\n        glob.starts_with?('!') && !File.exists?(Path[glob].expand(root))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/inline_comments.cr",
    "content": "module Ameba\n  # A module that utilizes inline comments parsing and processing logic.\n  module InlineComments\n    COMMENT_DIRECTIVE_REGEX =\n      /# ameba:(?<action>\\w+) (?<rules>\\w+(?:\\/\\w+)?(?:,? \\w+(?:\\/\\w+)?)*)/\n\n    # Available actions in the inline comments\n    enum Action\n      Disable\n      Enable\n    end\n\n    # Returns `true` if current location is disabled for a particular rule,\n    # `false` otherwise.\n    #\n    # Location is disabled in two cases:\n    #   1. The line of the location ends with a comment directive.\n    #   2. The line above the location is a comment directive.\n    #\n    # For example, here are two examples of disabled location:\n    #\n    # ```\n    # # ameba:disable Style/LargeNumbers\n    # Time.epoch(1483859302)\n    #\n    # Time.epoch(1483859302) # ameba:disable Style/LargeNumbers\n    # ```\n    #\n    # But here are examples which are not considered as disabled location:\n    #\n    # ```\n    # # ameba:disable Style/LargeNumbers\n    # #\n    # Time.epoch(1483859302)\n    #\n    # if use_epoch? # ameba:disable Style/LargeNumbers\n    #   Time.epoch(1483859302)\n    # end\n    # ```\n    def location_disabled?(location : Crystal::Location?, rule)\n      return false if rule.name.in?(Rule::SPECIAL)\n      return false unless line_number = location.try &.line_number.try &.- 1\n      return false unless line = lines[line_number]?\n\n      line_disabled?(line, rule) ||\n        (line_number > 0 &&\n          (prev_line = lines[line_number - 1]) &&\n          comment?(prev_line) &&\n          line_disabled?(prev_line, rule))\n    end\n\n    # Parses inline comment directive. Returns a tuple that consists of\n    # an action and parsed rules if directive found, nil otherwise.\n    #\n    # ```\n    # line = \"# ameba:disable Rule1, Rule2\"\n    # directive = parse_inline_directive(line)\n    # directive[:action] # => \"disable\"\n    # directive[:rules]  # => [\"Rule1\", \"Rule2\"]\n    # ```\n    #\n    # It ignores the directive if it is commented out.\n    #\n    # ```\n    # line = \"# # ameba:disable Rule1, Rule2\"\n    # parse_inline_directive(line) # => nil\n    # ```\n    def parse_inline_directive(line)\n      return unless directive = COMMENT_DIRECTIVE_REGEX.match(line)\n      return if commented_out?(line.gsub(directive[0], \"\"))\n      {\n        action: directive[\"action\"],\n        rules:  directive[\"rules\"].split(/[\\s,]/, remove_empty: true),\n      }\n    end\n\n    # Returns `true` if the line at the given `line_number` is a comment.\n    def comment?(line_number : Int32)\n      return unless line = lines[line_number]?\n      comment?(line)\n    end\n\n    private def comment?(line : String)\n      line.lstrip.starts_with? '#'\n    end\n\n    private def line_disabled?(line, rule)\n      return false unless directive = parse_inline_directive(line)\n      return false unless Action.parse?(directive[:action]).try(&.disable?)\n\n      rules = directive[:rules]\n      rules.includes?(rule.name) || rules.includes?(rule.group)\n    end\n\n    private def commented_out?(line)\n      commented = false\n\n      lexer = Crystal::Lexer.new(line).tap(&.comments_enabled = true)\n      Tokenizer.new(lexer).run do |token|\n        commented = true if token.type.comment?\n      end\n      commented\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/issue.cr",
    "content": "module Ameba\n  # Represents an issue reported by Ameba.\n  struct Issue\n    enum Status\n      Enabled\n      Disabled\n    end\n\n    # The source code that triggered this issue.\n    getter code : String\n\n    # A rule that triggers this issue.\n    getter rule : Rule::Base\n\n    # Location of the issue.\n    getter location : Crystal::Location?\n\n    # End location of the issue.\n    getter end_location : Crystal::Location?\n\n    # Issue message.\n    getter message : String\n\n    # Issue status.\n    getter status : Status\n\n    delegate :enabled?, :disabled?,\n      to: status\n\n    def initialize(@code, @rule, @location, @end_location, @message, status : Status? = nil, @block : (Source::Corrector ->)? = nil)\n      @status = status || Status::Enabled\n    end\n\n    def syntax?\n      rule.is_a?(Rule::Lint::Syntax)\n    end\n\n    def correctable?\n      !@block.nil?\n    end\n\n    def correct(corrector)\n      @block.try &.call(corrector)\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/json_schema/builder.cr",
    "content": "module Ameba::JSONSchema::Builder\n  extend self\n\n  def build(indent = 2) : String\n    String.build do |str|\n      build(str, indent)\n    end\n  end\n\n  def build(path : Path, indent = 2) : Nil\n    File.open(path, \"w\") do |file|\n      build(file, indent)\n    end\n  end\n\n  def build(io : IO, indent = 2) : Nil\n    JSON.build(io, indent: indent) do |builder|\n      builder.object do\n        builder.field(\"$schema\", \"https://json-schema.org/draft/2020-12/schema\")\n        builder.field(\"$id\", \"https://crystal-ameba.github.io/.ameba.yml.schema.json\")\n        builder.field(\"title\", \".ameba.yml\")\n        builder.field(\"description\", \"Configuration rules for the Crystal language Ameba linter\")\n        builder.field(\"type\", \"object\")\n        builder.field(\"additionalProperties\", false)\n\n        builder.string(\"$defs\")\n        builder.object do\n          builder.string(\"Severity\")\n          builder.object do\n            builder.field(\"type\", \"string\")\n\n            builder.string(\"enum\")\n            builder.array do\n              Severity.values.each do |value|\n                builder.string(value.to_s)\n              end\n            end\n          end\n\n          builder.string(\"Globs\")\n          builder.object do\n            builder.field(\"type\", \"array\")\n            builder.field(\"title\", \"Globbed files and paths\")\n            builder.field(\"description\",\n              \"An array of wildcards (or paths) to include to the inspection\")\n\n            builder.string(\"items\")\n            builder.object do\n              builder.field(\"type\", \"string\")\n\n              builder.string(\"examples\")\n              builder.array do\n                builder.string(\"src/**/*.{cr,ecr}\")\n                builder.string(\"!lib\")\n              end\n            end\n          end\n\n          builder.string(\"Excluded\")\n          builder.object do\n            builder.field(\"type\", \"array\")\n            builder.field(\"title\", \"Excluded files and paths\")\n            builder.field(\"description\",\n              \"An array of wildcards (or paths) to exclude from the source list\")\n\n            builder.string(\"items\")\n            builder.object do\n              builder.field(\"type\", \"string\")\n\n              builder.string(\"examples\")\n              builder.array do\n                builder.string(\"spec/fixtures/**\")\n                builder.string(\"spec/**/*.manual_spec.cr\")\n              end\n            end\n          end\n\n          builder.string(\"BaseRule\")\n          builder.object do\n            builder.field(\"type\", \"object\")\n\n            builder.string(\"properties\")\n            builder.object do\n              builder.string(\"SinceVersion\")\n              builder.object do\n                builder.field(\"type\", \"string\")\n              end\n\n              builder.string(\"Enabled\")\n              builder.object do\n                builder.field(\"type\", \"boolean\")\n                builder.field(\"default\", true)\n              end\n\n              builder.string(\"Severity\")\n              builder.object do\n                builder.field(\"$ref\", \"#/$defs/Severity\")\n                builder.field(\"default\", Rule::Base.default_severity.to_s)\n              end\n\n              builder.string(\"Excluded\")\n              builder.object do\n                builder.field(\"$ref\", \"#/$defs/Excluded\")\n              end\n            end\n          end\n        end\n\n        builder.string(\"properties\")\n        builder.object do\n          builder.string(\"Version\")\n          builder.object do\n            builder.field(\"type\", \"string\")\n            builder.field(\"description\", \"The version of Ameba to limit rules to\")\n\n            builder.string(\"examples\")\n            builder.array do\n              builder.string(\"1.7.0\")\n              builder.string(\"1.6.4\")\n            end\n          end\n\n          builder.string(\"Formatter\")\n          builder.object do\n            builder.field(\"type\", \"object\")\n            builder.field(\"description\", \"The formatter to use for Ameba\")\n\n            builder.string(\"properties\")\n            builder.object do\n              builder.string(\"Name\")\n              builder.object do\n                builder.field(\"type\", \"string\")\n\n                builder.string(\"enum\")\n                builder.array do\n                  Config::AVAILABLE_FORMATTERS.each_key do |key|\n                    builder.string(key)\n                  end\n                end\n              end\n            end\n          end\n\n          builder.string(\"Globs\")\n          builder.object do\n            builder.field(\"$ref\", \"#/$defs/Globs\")\n          end\n\n          builder.string(\"Excluded\")\n          builder.object do\n            builder.field(\"$ref\", \"#/$defs/Excluded\")\n          end\n\n          Rule.rules.each do |rule|\n            rule.to_json_schema(builder)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/presenter/base_presenter.cr",
    "content": "module Ameba::Presenter\n  private ENABLED_MARK  = \"✓\".colorize(:green)\n  private DISABLED_MARK = \"x\".colorize(:red)\n\n  class BasePresenter\n    # TODO: allow other IOs\n    getter output : IO::FileDescriptor | IO::Memory\n\n    def initialize(@output = STDOUT)\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/presenter/rule_collection_presenter.cr",
    "content": "module Ameba::Presenter\n  class RuleCollectionPresenter < BasePresenter\n    def run(rules) : Nil\n      rules = rules.to_h do |rule|\n        name = rule.name.split('/')\n        name = \"%s/%s\" % {\n          name[0...-1].join('/').colorize(:light_gray),\n          name.last.colorize(:white),\n        }\n        {name, rule}\n      end\n      longest_name = rules.max_of(&.first.size)\n\n      rules.group_by(&.last.group).each do |group, group_rules|\n        output.puts \"— %s\" % group.colorize(:light_blue).underline\n        output.puts\n        group_rules.each do |name, rule|\n          output.puts \"  %s  [%s]    %s    %s\" % {\n            rule.enabled? ? ENABLED_MARK : DISABLED_MARK,\n            rule.severity.symbol.to_s.colorize(:green),\n            name.ljust(longest_name),\n            rule.description.colorize(:dark_gray),\n          }\n        end\n        output.puts\n      end\n\n      output.puts \"Total rules: %s / %s enabled\" % {\n        rules.size.to_s.colorize(:light_blue),\n        rules.count(&.last.enabled?).to_s.colorize(:light_blue),\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/presenter/rule_presenter.cr",
    "content": "module Ameba::Presenter\n  class RulePresenter < BasePresenter\n    include Formatter::Util\n\n    def run(rule) : Nil\n      output_title \"Rule info\"\n\n      info = <<-INFO\n        Name:           %s\n        Severity:       %s\n        Enabled:        %s\n        Since version:  %s\n        INFO\n\n      output_paragraph info % {\n        rule.name.colorize(:magenta),\n        rule.severity.to_s.colorize(rule.severity.color),\n        rule.enabled? ? ENABLED_MARK : DISABLED_MARK,\n        (rule.since_version.try(&.to_s) || \"N/A\").colorize(:white),\n      }\n\n      if rule_description = rule.description\n        output_title \"Description\"\n        output_paragraph colorize_markdown(rule_description)\n      end\n\n      if rule_doc = rule.class.parsed_doc\n        output_title \"Detailed description\"\n        output_paragraph colorize_markdown(rule_doc)\n      end\n    end\n\n    private def output_title(title)\n      output.print \"### %s\\n\\n\" % title.upcase.colorize(:yellow)\n    end\n\n    private def output_paragraph(paragraph : String)\n      output_paragraph(paragraph.lines)\n    end\n\n    private def output_paragraph(paragraph : Array)\n      paragraph.each do |line|\n        output.puts \"    #{line}\"\n      end\n      output.puts\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/presenter/rule_versions_presenter.cr",
    "content": "module Ameba::Presenter\n  class RuleVersionsPresenter < BasePresenter\n    def run(rules, verbose = true)\n      missing_version = SemanticVersion.new(0, 0, 0)\n      versions = rules\n        .sort_by { |rule| rule.since_version || missing_version }\n        .group_by(&.since_version)\n\n      if verbose\n        versions.each_with_index do |(version, version_rules), idx|\n          output.puts if idx.positive?\n          if version\n            output.puts \"- %s\" % version.to_s.colorize(:green)\n          else\n            output.puts \"- %s\" % \"N/A\".colorize(:dark_gray)\n          end\n          version_rules.map(&.name).sort!.each do |name|\n            output.puts \"  - %s\" % name.colorize(:dark_gray)\n          end\n        end\n      else\n        versions.each_key do |version|\n          if version\n            output.puts \"- %s\" % version.to_s.colorize(:green)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/reportable.cr",
    "content": "require \"./ast/util\"\n\nmodule Ameba\n  # Represents a module used to report issues.\n  module Reportable\n    include AST::Util\n\n    # List of reported issues.\n    getter issues = [] of Issue\n\n    # Adds a new issue to the list of issues.\n    def add_issue(rule,\n                  location : Crystal::Location?,\n                  end_location : Crystal::Location?,\n                  message : String,\n                  status : Issue::Status? = nil,\n                  block : (Source::Corrector ->)? = nil) : Issue\n      status ||=\n        Issue::Status::Disabled if location_disabled?(location, rule)\n\n      Issue.new(code, rule, location, end_location, message, status, block).tap do |issue|\n        issues << issue\n      end\n    end\n\n    # :ditto:\n    def add_issue(rule,\n                  location : Crystal::Location?,\n                  end_location : Crystal::Location?,\n                  message : String,\n                  status : Issue::Status? = nil,\n                  &block : Source::Corrector ->) : Issue\n      add_issue rule, location, end_location, message, status, block\n    end\n\n    # Adds a new issue for *location* defined by line and column numbers.\n    def add_issue(rule,\n                  location : {Crystal::Location, Crystal::Location},\n                  message : String,\n                  status : Issue::Status? = nil,\n                  block : (Source::Corrector ->)? = nil) : Issue\n      add_issue rule, *location, message, status, block\n    end\n\n    # Adds a new issue for Crystal AST *node*.\n    def add_issue(rule,\n                  node : Crystal::ASTNode,\n                  message : String,\n                  status : Issue::Status? = nil,\n                  block : (Source::Corrector ->)? = nil,\n                  *,\n                  prefer_name_location = false) : Issue\n      if prefer_name_location\n        case node_location = name_location_or(node)\n        when Tuple(Crystal::Location, Crystal::Location)\n          location, end_location = node_location\n        else\n          location, end_location =\n            name_location(node), name_end_location(node)\n        end\n      end\n\n      location ||= node.location\n      end_location ||= node.end_location\n\n      add_issue rule, location, end_location, message, status, block\n    end\n\n    # :ditto:\n    def add_issue(rule,\n                  node : Crystal::ASTNode,\n                  message : String,\n                  status : Issue::Status? = nil,\n                  *,\n                  prefer_name_location = false,\n                  &block : Source::Corrector ->) : Issue\n      add_issue rule, node, message, status, block, prefer_name_location: prefer_name_location\n    end\n\n    # Adds a new issue for Crystal *token*.\n    def add_issue(rule,\n                  token : Crystal::Token,\n                  message : String,\n                  status : Issue::Status? = nil,\n                  block : (Source::Corrector ->)? = nil) : Issue\n      add_issue rule, token.location, nil, message, status, block\n    end\n\n    # :ditto:\n    def add_issue(rule,\n                  token : Crystal::Token,\n                  message : String,\n                  status : Issue::Status? = nil,\n                  &block : Source::Corrector ->) : Issue\n      add_issue rule, token, message, status, block\n    end\n\n    # Adds a new issue for *location* defined by line and column numbers.\n    def add_issue(rule,\n                  location : {Int32, Int32},\n                  message : String,\n                  status : Issue::Status? = nil,\n                  block : (Source::Corrector ->)? = nil) : Issue\n      location =\n        Crystal::Location.new(path, *location)\n\n      add_issue rule, location, nil, message, status, block\n    end\n\n    # :ditto:\n    def add_issue(rule,\n                  location : {Int32, Int32},\n                  message : String,\n                  status : Issue::Status? = nil,\n                  &block : Source::Corrector ->) : Issue\n      add_issue rule, location, message, status, block\n    end\n\n    # Adds a new issue for *location* and *end_location* defined by line and column numbers.\n    def add_issue(rule,\n                  location : {Int32, Int32},\n                  end_location : {Int32, Int32},\n                  message : String,\n                  status : Issue::Status? = nil,\n                  block : (Source::Corrector ->)? = nil) : Issue\n      location =\n        Crystal::Location.new(path, *location)\n      end_location =\n        Crystal::Location.new(path, *end_location)\n\n      add_issue rule, location, end_location, message, status, block\n    end\n\n    # :ditto:\n    def add_issue(rule,\n                  location : {Int32, Int32},\n                  end_location : {Int32, Int32},\n                  message : String,\n                  status : Issue::Status? = nil,\n                  &block : Source::Corrector ->) : Issue\n      add_issue rule, location, end_location, message, status, block\n    end\n\n    # Returns `true` if the list of not disabled issues is empty, `false` otherwise.\n    def valid?\n      issues.none?(&.enabled?)\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/base.cr",
    "content": "module Ameba::Rule\n  # List of names of the special rules, which\n  # behave differently than usual rules.\n  SPECIAL = {\n    Lint::Syntax.rule_name,\n    Lint::UnneededDisableDirective.rule_name,\n  }\n\n  # Represents a base of all rules. In other words, all rules\n  # inherits from this class:\n  #\n  # ```\n  # class Ameba::Rule::MyRule < Ameba::Rule::Base\n  #   def test(source)\n  #     if invalid?(source)\n  #       issue_for line, column, \"Something wrong\"\n  #     end\n  #   end\n  #\n  #   private def invalid?(source)\n  #     # ...\n  #   end\n  # end\n  # ```\n  #\n  # Enforces rules to implement an abstract `#test` method which\n  # is designed to test the source passed in. If source has issues\n  # that are tested by this rule, it should add an issue.\n  abstract class Base\n    include Config::RuleConfig\n\n    # This method is designed to test the source passed in. If source has issues\n    # that are tested by this rule, it should add an issue.\n    #\n    # By default it uses a node visitor to traverse all the nodes in the source.\n    #\n    # NOTE: Must be overridden for other type of rules.\n    def test(source : Source)\n      AST::NodeVisitor.new self, source\n    end\n\n    # NOTE: Can't be abstract\n    def test(source : Source, node : Crystal::ASTNode, *opts)\n    end\n\n    # A convenient addition to `#test` method that does the same\n    # but returns a passed in `source` as an addition.\n    #\n    # ```\n    # source = MyRule.new.catch(source)\n    # source.valid?\n    # ```\n    def catch(source : Source)\n      source.tap { test source }\n    end\n\n    # Returns a name of this rule, which is basically a class name.\n    #\n    # ```\n    # module Ameba\n    #   class Rule::MyRule < Rule::Base\n    #     def test(source)\n    #     end\n    #   end\n    # end\n    #\n    # Ameba::Rule::MyRule.new.name # => \"MyRule\"\n    # ```\n    def name\n      {{ @type }}.rule_name\n    end\n\n    # Returns a group this rule belong to.\n    #\n    # ```\n    # module Ameba\n    #   class Rule::MyGroup::MyRule < Rule::Base\n    #     # ...\n    #   end\n    # end\n    #\n    # Ameba::Rule::MyGroup::MyRule.new.group # => \"MyGroup\"\n    # ```\n    def group\n      {{ @type }}.group_name\n    end\n\n    # Checks whether the source is excluded from this rule.\n    # It searches for a path in `excluded` property which matches\n    # the one of the given source.\n    #\n    # ```\n    # my_rule.excluded?(source) # => true or false\n    # ```\n    def excluded?(source, root = Dir.current)\n      !!excluded.try &.any? do |path|\n        path = Path.posix(path).to_native.expand(root)\n\n        source.fullpath == path.to_s ||\n          Dir.glob(path.to_posix).includes?(source.fullpath)\n      end\n    end\n\n    # Returns `true` if this rule is special and behaves differently than\n    # usual rules.\n    #\n    # ```\n    # my_rule.special? # => true or false\n    # ```\n    def special?\n      name.in?(SPECIAL)\n    end\n\n    def_equals_and_hash name\n\n    # Adds an issue to the *source*\n    macro issue_for(*args, **kwargs, &block)\n      source.add_issue(self, {{ args.splat(\", \") }}{{ kwargs.double_splat }}) {{ block }}\n    end\n\n    # Returns the name for this rule.\n    #\n    # ```\n    # Ameba::Rule::Lint::Syntax.rule_name # => \"Lint/Syntax\"\n    # ```\n    def self.rule_name\n      name.lchop(\"Ameba::Rule::\").gsub(\"::\", '/')\n    end\n\n    # Returns the group name for this rule.\n    #\n    # ```\n    # Ameba::Rule::Lint::Syntax.group_name # => \"Lint\"\n    # ```\n    def self.group_name\n      rule_name.split('/')[0...-1].join('/')\n    end\n\n    protected def self.subclasses\n      {{ @type.subclasses }}\n    end\n\n    protected def self.abstract?\n      {{ @type.abstract? }}\n    end\n\n    protected def self.inherited_rules\n      subclasses.each_with_object([] of Base.class) do |klass, obj|\n        klass.abstract? ? obj.concat(klass.inherited_rules) : (obj << klass)\n      end\n    end\n\n    private macro read_type_doc(filepath = __FILE__)\n      {{ run(\"../../contrib/read_type_doc\",\n           @type.name.split(\"::\").last,\n           filepath\n         ).chomp.stringify }}.presence\n    end\n\n    macro inherited\n      # Returns the documentation URL for this rule.\n      #\n      # ```\n      # Ameba::Rule::Lint::Syntax.documentation_url\n      # # => \"https://crystal-ameba.github.io/ameba/master/Ameba/Rule/Lint/Syntax.html\"\n      # ```\n      class_getter documentation_url : String do\n        \"https://crystal-ameba.github.io/ameba/%s/Ameba/Rule/%s.html\" % {\n          Ameba.version.for_docs, rule_name,\n        }\n      end\n\n      # Returns documentation for this rule, if there is any.\n      #\n      # ```\n      # module Ameba\n      #   # This is a test rule.\n      #   # Does nothing.\n      #   class Rule::MyRule < Rule::Base\n      #     def test(source)\n      #     end\n      #   end\n      # end\n      #\n      # Ameba::Rule::MyRule.parsed_doc # => \"This is a test rule.\\nDoes nothing.\"\n      # ```\n      class_getter parsed_doc : String? = read_type_doc\n    end\n  end\n\n  # Returns a list of all available rules.\n  #\n  # ```\n  # Ameba::Rule.rules # => [Rule1, Rule2, ....]\n  # ```\n  def self.rules\n    Base.inherited_rules\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/documentation/admonition.cr",
    "content": "module Ameba::Rule::Documentation\n  # A rule that reports documentation admonitions.\n  #\n  # Optionally, these can fail at an appropriate time.\n  #\n  # ```\n  # def get_user(id)\n  #   # TODO(2024-04-24) Fix this hack when the database migration is complete\n  #   if id < 1_000_000\n  #     v1_api_call(id)\n  #   else\n  #     v2_api_call(id)\n  #   end\n  # end\n  # ```\n  #\n  # `TODO` comments are used to remind yourself of source code related things.\n  #\n  # The premise here is that `TODO` should be dealt with in the near future\n  # and are therefore reported by Ameba.\n  #\n  # `FIXME` comments are used to indicate places where source code needs fixing.\n  #\n  # The premise here is that `FIXME` should indeed be fixed as soon as possible\n  # and are therefore reported by Ameba.\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Documentation/Admonition:\n  #   Enabled: true\n  #   Admonitions: [TODO, FIXME, BUG]\n  #   Timezone: UTC\n  # ```\n  class Admonition < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.6.0\"\n      enabled false\n      description \"Reports documentation admonitions\"\n      severity :warning\n      admonitions %w[TODO FIXME BUG]\n      timezone \"UTC\"\n    end\n\n    MSG      = \"Found a %s admonition in a comment\"\n    MSG_LATE = \"Found a %s admonition in a comment (%s)\"\n    MSG_ERR  = \"%s admonition error: %s\"\n\n    @[YAML::Field(ignore: true)]\n    private getter location : Time::Location do\n      Time::Location.load(timezone)\n    end\n\n    def test(source)\n      Tokenizer.new(source).run do |token|\n        next unless token.type.comment?\n        next unless doc = token.value.to_s\n\n        pattern =\n          /^#\\s*(?<admonition>#{Regex.union(admonitions)})(?:\\((?<context>.+?)\\))?(?:\\W+|$)/m\n\n        matches = doc.scan(pattern)\n        matches.each do |match|\n          admonition = match[\"admonition\"]\n\n          token_location = name_location_or token, admonition,\n            adjust_location_column_number: {{ \"# \".size }}\n\n          begin\n            case expr = match[\"context\"]?.presence\n            when /\\A\\d{4}-\\d{2}-\\d{2}\\Z/ # date\n              date = Time.parse($0, \"%F\", location)\n              issue_for_date source, token_location, admonition, date\n            when /\\A\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}(:\\d{2})?\\Z/ # date + time (no tz)\n              date = Time.parse($0, \"%F #{$1?.presence ? \"%T\" : \"%R\"}\", location)\n              issue_for_date source, token_location, admonition, date\n            else\n              issue_for *token_location, MSG % admonition\n            end\n          rescue ex\n            issue_for *token_location, MSG_ERR % {admonition, \"#{ex}: #{expr.inspect}\"}\n          end\n        end\n      end\n    end\n\n    private def issue_for_date(source, token_location, admonition, date)\n      diff = Time.utc - date.to_utc\n\n      return if diff.negative?\n\n      past = case diff\n             when 0.seconds..1.day then \"today is the day!\"\n             when 1.day..2.days    then \"1 day past\"\n             else                       \"#{diff.total_days.to_i} days past\"\n             end\n\n      issue_for *token_location, MSG_LATE % {admonition, past}\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/documentation/documentation.cr",
    "content": "module Ameba::Rule::Documentation\n  # A rule that enforces documentation for public types:\n  # modules, classes, enums, methods and macros.\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Documentation/Documentation:\n  #   Enabled: true\n  #   IgnoreClasses: false\n  #   IgnoreModules: true\n  #   IgnoreEnums: false\n  #   IgnoreDefs: true\n  #   IgnoreMacros: false\n  #   IgnoreMacroHooks: true\n  #   RequireExample: false\n  # ```\n  class Documentation < Base\n    properties do\n      since_version \"1.5.0\"\n      enabled false\n      description \"Enforces public types to be documented\"\n\n      ignore_classes false\n      ignore_modules true\n      ignore_enums false\n      ignore_defs true\n      ignore_macros false\n      ignore_macro_hooks true\n      require_example false\n    end\n\n    MSG         = \"Missing documentation\"\n    MSG_EXAMPLE = \"Missing documentation example\"\n\n    MACRO_HOOK_NAMES = %w[\n      inherited\n      included extended\n      method_missing method_added\n      finished\n    ]\n\n    def test(source)\n      AST::ScopeVisitor.new self, source\n    end\n\n    def test(source, node : Crystal::ClassDef, scope : AST::Scope)\n      ignore_classes? || check_missing_doc(source, node, scope)\n    end\n\n    def test(source, node : Crystal::ModuleDef, scope : AST::Scope)\n      ignore_modules? || check_missing_doc(source, node, scope)\n    end\n\n    def test(source, node : Crystal::EnumDef, scope : AST::Scope)\n      ignore_enums? || check_missing_doc(source, node, scope)\n    end\n\n    def test(source, node : Crystal::Def, scope : AST::Scope)\n      ignore_defs? || check_missing_doc(source, node, scope)\n    end\n\n    def test(source, node : Crystal::Macro, scope : AST::Scope)\n      return if ignore_macro_hooks? && node.name.in?(MACRO_HOOK_NAMES)\n\n      ignore_macros? || check_missing_doc(source, node, scope)\n    end\n\n    private def check_missing_doc(source, node, scope)\n      # bail out if the node is not public,\n      # i.e. `private def foo`\n      return if !node.visibility.public?\n\n      # bail out if the scope is not public,\n      # i.e. `def bar` inside `private class Foo`\n      return if (visibility = scope.visibility) && !visibility.public?\n\n      if doc = node.doc.presence\n        issue_for node, MSG_EXAMPLE unless valid_example?(doc)\n      else\n        issue_for node, MSG\n      end\n    end\n\n    private def valid_example?(doc : String)\n      !require_example? || doc.matches?(/^\\s*```\\n/m)\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/layout/line_length.cr",
    "content": "module Ameba::Rule::Layout\n  # A rule that disallows lines longer than `max_length` number of symbols.\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Layout/LineLength:\n  #   Enabled: true\n  #   MaxLength: 100\n  # ```\n  class LineLength < Base\n    properties do\n      since_version \"0.1.0\"\n      enabled false\n      description \"Disallows lines longer than `MaxLength` number of symbols\"\n      max_length 140\n    end\n\n    MSG = \"Line too long\"\n\n    def test(source)\n      source.lines.each_with_index do |line, index|\n        issue_for({index + 1, max_length + 1}, MSG) if line.size > max_length\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/layout/trailing_blank_lines.cr",
    "content": "module Ameba::Rule::Layout\n  # A rule that disallows trailing blank lines at the end of the source file.\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Layout/TrailingBlankLines:\n  #   Enabled: true\n  # ```\n  class TrailingBlankLines < Base\n    properties do\n      since_version \"0.1.0\"\n      description \"Disallows trailing blank lines\"\n    end\n\n    MSG               = \"Excessive trailing newline detected\"\n    MSG_FINAL_NEWLINE = \"Trailing newline missing\"\n\n    def test(source)\n      source_lines = source.lines\n      return if source_lines.empty?\n\n      last_source_line = source_lines.last\n      source_lines_size = source_lines.size\n      return if source_lines_size == 1 && last_source_line.empty?\n\n      last_line_empty = last_source_line.empty?\n      return if source_lines_size.zero? ||\n                (source_lines.last(2).join.presence && last_line_empty)\n\n      location = {source_lines_size, 1}\n\n      if last_line_empty\n        issue_for location, MSG\n      else\n        issue_for location, MSG_FINAL_NEWLINE do |corrector|\n          corrector.insert_before({source_lines_size + 1, 1}, '\\n')\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/layout/trailing_whitespace.cr",
    "content": "module Ameba::Rule::Layout\n  # A rule that disallows trailing whitespace.\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Layout/TrailingWhitespace:\n  #   Enabled: true\n  # ```\n  class TrailingWhitespace < Base\n    properties do\n      since_version \"0.1.0\"\n      description \"Disallows trailing whitespace\"\n    end\n\n    MSG = \"Trailing whitespace detected\"\n\n    def test(source)\n      source.lines.each_with_index do |line, index|\n        next unless ws_index = line =~ /\\s+$/\n\n        location = {index + 1, ws_index + 1}\n        end_location = {index + 1, line.size}\n\n        issue_for location, end_location, MSG do |corrector|\n          corrector.remove(location, end_location)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/ambiguous_assignment.cr",
    "content": "module Ameba::Rule::Lint\n  # This rule checks for mistyped shorthand assignments.\n  #\n  # This is considered invalid:\n  #\n  #     x =- y\n  #     x =+ y\n  #     x =! y\n  #\n  # And this is valid:\n  #\n  #     x -= y # or x = -y\n  #     x += y # or x = +y\n  #     x != y # or x = !y\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/AmbiguousAssignment:\n  #   Enabled: true\n  # ```\n  class AmbiguousAssignment < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.0.0\"\n      description \"Disallows ambiguous `=-/=+/=!`\"\n    end\n\n    MSG = \"Suspicious assignment detected. Did you mean `%s`?\"\n\n    MISTAKES = {\n      \"=-\": \"-=\",\n      \"=+\": \"+=\",\n      \"=!\": \"!=\",\n    }\n\n    def test(source, node : Crystal::Assign)\n      return unless op_end_location = node.value.location\n\n      op_location = op_end_location.adjust(column_number: -1)\n      op_text = source_between(op_location, op_end_location, source.lines)\n\n      return unless op_text\n      return unless suggestion = MISTAKES[op_text]?\n\n      issue_for op_location, op_end_location, MSG % suggestion\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/assignment_in_call_argument.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows assignments in call arguments.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # foo a = 1\n  # ```\n  #\n  # And has to be written as the following:\n  #\n  # ```\n  # a = 1\n  #\n  # foo a\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/AssignmentInCallArgument:\n  #   Enabled: true\n  # ```\n  class AssignmentInCallArgument < Base\n    properties do\n      since_version \"1.7.0\"\n      description \"Disallows variable assignment in call arguments\"\n    end\n\n    MSG = \"Assignment within a call argument detected\"\n\n    def test(source)\n      AssignmentInCallArgumentVisitor.new(self, source) do |node|\n        issue_for node, MSG\n      end\n    end\n  end\n\n  private class AssignmentInCallArgumentVisitor < AST::ScopeVisitor\n    include AST::Util\n\n    getter? in_call_args = false\n\n    def initialize(rule, source, &@on_assign : Crystal::ASTNode ->)\n      super(rule, source)\n    end\n\n    private def in_call_args(value = true, &)\n      prev_value = @in_call_args\n      begin\n        @in_call_args = value\n        yield\n      ensure\n        @in_call_args = prev_value\n      end\n    end\n\n    def visit(node : Crystal::Def)\n      return super unless node.name == \"->\"\n\n      in_call_args(false) do\n        node.accept_children(self)\n      end\n      false\n    end\n\n    def visit(node : Crystal::Block)\n      super\n\n      in_call_args(false) do\n        node.accept_children(self)\n      end\n      false\n    end\n\n    def visit(node : Crystal::Call)\n      return false if setter_method?(node) || operator_method?(node)\n      return false unless super\n\n      node.obj.try &.accept(self)\n      in_call_args do\n        node.args.each &.accept(self)\n        node.named_args.try &.each &.accept(self)\n      end\n      node.block_arg.try &.accept(self)\n      node.block.try &.accept(self)\n\n      false\n    end\n\n    def visit(node : Crystal::Assign | Crystal::OpAssign | Crystal::MultiAssign)\n      super.tap do\n        @on_assign.call(node) if in_call_args?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/bad_directive.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that reports incorrect comment directives for Ameba.\n  #\n  # ```\n  # # ameba:off Lint/NotNil\n  # def foo\n  #   :bar\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/BadDirective:\n  #   Enabled: true\n  # ```\n  class BadDirective < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.13.0\"\n      description \"Reports bad comment directives\"\n    end\n\n    MSG = \"Bad action in comment directive: `%s`. Possible values: %s\"\n\n    AVAILABLE_ACTIONS = InlineComments::Action\n      .names\n      .map!(&.underscore.gsub('_', '-'))\n\n    def test(source)\n      Tokenizer.new(source).run do |token|\n        next unless token.type.comment?\n        next unless directive = source.parse_inline_directive(token.value.to_s)\n\n        check_action source, token, directive[:action]\n      end\n    end\n\n    private def check_action(source, token, action)\n      return if InlineComments::Action.parse?(action)\n\n      # See `InlineComments::COMMENT_DIRECTIVE_REGEX`\n      prefix_size = {{ \"# ameba:\".size }}\n\n      issue_for name_location_or(token, action, adjust_location_column_number: prefix_size),\n        MSG % {action, AVAILABLE_ACTIONS.map { |name| \"`#{name}`\" }.join(\", \")}\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/comparison_to_boolean.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows comparison to booleans.\n  #\n  # For example, these are considered invalid:\n  #\n  # ```\n  # foo == true\n  # bar != false\n  # false === baz\n  # ```\n  #\n  # This is because these expressions evaluate to `true` or `false`, so you\n  # could get the same result by using either the variable directly,\n  # or negating the variable.\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/ComparisonToBoolean:\n  #   Enabled: true\n  # ```\n  class ComparisonToBoolean < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.1.0\"\n      enabled false\n      description \"Disallows comparison to booleans\"\n    end\n\n    MSG      = \"Comparison to a boolean is pointless\"\n    OP_NAMES = %w[== != ===]\n\n    def test(source, node : Crystal::Call)\n      return unless node.name.in?(OP_NAMES)\n      return unless node.args.size == 1\n\n      arg, obj = node.args.first, node.obj\n      case\n      when arg.is_a?(Crystal::BoolLiteral)\n        bool, exp = arg, obj\n      when obj.is_a?(Crystal::BoolLiteral)\n        bool, exp = obj, arg\n      end\n\n      return unless bool && exp\n      return unless exp_code = node_source(exp, source.lines)\n\n      not =\n        case node.name\n        when \"==\", \"===\" then !bool.value # foo == false\n        when \"!=\"        then bool.value  # foo != true\n        end\n\n      exp_code = \"!#{exp_code}\" if not\n\n      issue_for node, MSG do |corrector|\n        corrector.replace(node, exp_code)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/debug_calls.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows calls to debug-related methods.\n  #\n  # This is because we don't want debug calls accidentally being\n  # committed into our codebase.\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/DebugCalls:\n  #   Enabled: true\n  #   MethodNames:\n  #     - p\n  #     - p!\n  #     - pp\n  #     - pp!\n  # ```\n  class DebugCalls < Base\n    properties do\n      since_version \"1.0.0\"\n      description \"Disallows debug-related calls\"\n      method_names %w[p p! pp pp!]\n    end\n\n    MSG = \"Possibly forgotten debug-related `%s` call detected\"\n\n    def test(source, node : Crystal::Call)\n      return unless node.name.in?(method_names) && node.obj.nil?\n\n      issue_for node, MSG % node.name\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/debugger_statement.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows calls to `debugger`.\n  #\n  # This is because we don't want debugger breakpoints accidentally being\n  # committed into our codebase.\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/DebuggerStatement:\n  #   Enabled: true\n  # ```\n  class DebuggerStatement < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.1.0\"\n      description \"Disallows calls to `debugger`\"\n    end\n\n    MSG = \"Possible forgotten `debugger` statement detected\"\n\n    def test(source, node : Crystal::Call)\n      return unless node.name == \"debugger\" && node.obj.nil?\n      return if has_arguments?(node)\n\n      issue_for node, MSG do |corrector|\n        corrector.remove(node)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/duplicate_branch.cr",
    "content": "module Ameba::Rule::Lint\n  # Checks that there are no repeated bodies within `if/unless`,\n  # `case-when`, `case-in` and `rescue` constructs.\n  #\n  # This is considered invalid:\n  #\n  # ```\n  # if foo\n  #   do_foo\n  #   do_something_else\n  # elsif bar\n  #   do_foo\n  #   do_something_else\n  # end\n  # ```\n  #\n  # And this is valid:\n  #\n  # ```\n  # if foo || bar\n  #   do_foo\n  #   do_something_else\n  # end\n  # ```\n  #\n  # With `IgnoreLiteralBranches: true`, branches are not registered\n  # as offenses if they return a basic literal value (string, symbol,\n  # integer, float, `true`, `false`, or `nil`), or return an array,\n  # hash, regexp or range that only contains one of the above basic\n  # literal values.\n  #\n  # With `IgnoreConstantBranches: true`, branches are not registered\n  # as offenses if they return a constant value.\n  #\n  # With `IgnoreDuplicateElseBranch: true`, in conditionals with multiple branches,\n  # duplicate 'else' branches are not registered as offenses.\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/DuplicateBranch:\n  #   Enabled: true\n  #   IgnoreLiteralBranches: false\n  #   IgnoreConstantBranches: false\n  #   IgnoreDuplicateElseBranch: false\n  # ```\n  class DuplicateBranch < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.7.0\"\n      description \"Reports duplicated branch bodies\"\n      enabled false\n\n      ignore_literal_branches false\n      ignore_constant_branches false\n      ignore_duplicate_else_branch false\n    end\n\n    MSG = \"Duplicate branch body detected\"\n\n    def test(source)\n      AST::ElseIfAwareNodeVisitor.new self, source, skip: :macro\n    end\n\n    def test(\n      source,\n      node : Crystal::If | Crystal::Unless | Crystal::Case | Crystal::ExceptionHandler,\n      ifs : Enumerable(Crystal::If)? = nil,\n    )\n      found_bodies = Set(String).new\n\n      each_branch(ifs || node) do |body_node|\n        next if ignore_literal_branches? && static_literal?(body_node)\n        next if ignore_constant_branches? && body_node.is_a?(Crystal::Path)\n        next if found_bodies.add?(body_node.to_s)\n\n        issue_for body_node, MSG\n      end\n    end\n\n    private def each_branch(ifs : Enumerable(Crystal::If), &)\n      ifs.each do |if_node|\n        yield if_node.then\n      end\n      if !ignore_duplicate_else_branch? && (else_node = ifs.last.else)\n        yield else_node\n      end\n    end\n\n    private def each_branch(node : Crystal::If | Crystal::Unless, &)\n      if !ignore_duplicate_else_branch? && (else_node = node.else)\n        yield node.then\n        yield else_node\n      end\n    end\n\n    private def each_branch(node : Crystal::Case, &)\n      node.whens.each do |when_node|\n        yield when_node.body\n      end\n      if !ignore_duplicate_else_branch? && (else_node = node.else)\n        yield else_node\n      end\n    end\n\n    private def each_branch(node : Crystal::ExceptionHandler, &)\n      node.rescues.try &.each do |rescue_node|\n        yield rescue_node.body\n      end\n      if !ignore_duplicate_else_branch? && (else_node = node.else)\n        yield else_node\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/duplicate_enum_value.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that reports duplicated `enum` member values.\n  #\n  # ```\n  # enum Foo\n  #   Foo = 1\n  #   Bar = 2\n  #   Baz = 2 # duplicate value\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/DuplicateEnumValue:\n  #   Enabled: true\n  # ```\n  class DuplicateEnumValue < Base\n    properties do\n      since_version \"1.7.0\"\n      description \"Reports duplicated `enum` member values\"\n    end\n\n    MSG = \"Duplicate enum member value detected\"\n\n    def test(source, node : Crystal::EnumDef)\n      found_values = Set(String).new\n\n      node.members.each do |member|\n        next unless member.is_a?(Crystal::Arg)\n\n        next unless value = member.default_value\n        next if found_values.add?(value.to_s)\n\n        issue_for value, MSG\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/duplicate_method_signature.cr",
    "content": "module Ameba::Rule::Lint\n  # Reports repeated class or module method signatures.\n  #\n  # Only methods of the same signature are considered duplicates,\n  # regardless of their bodies, except for ones including `previous_def`.\n  #\n  # ```\n  # class Foo\n  #   def greet(name)\n  #     puts \"Hello #{name}!\"\n  #   end\n  #\n  #   def greet(name) # duplicated method signature\n  #     puts \"¡Hola! #{name}\"\n  #   end\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/DuplicateMethodSignature:\n  #   Enabled: true\n  # ```\n  class DuplicateMethodSignature < Base\n    properties do\n      since_version \"1.7.0\"\n      description \"Reports repeated method signatures\"\n    end\n\n    MSG = \"Duplicate method signature detected\"\n\n    def test(source)\n      AST::ScopeVisitor.new self, source\n    end\n\n    def test(source, node : Crystal::ClassDef | Crystal::ModuleDef, scope : AST::Scope)\n      found_defs = Set(String).new\n\n      each_def_node(node) do |def_node|\n        def_node_to_s = def_node.to_s\n\n        next if def_node_to_s.matches?(/\\Wprevious_def\\W/)\n        next if found_defs.add?(def_node_to_s.lines.first)\n\n        issue_for def_node, MSG\n      end\n    end\n\n    private def each_def_node(node, &)\n      case body = node.body\n      when Crystal::Def\n        yield body\n      when Crystal::VisibilityModifier\n        yield body.exp\n      when Crystal::Expressions\n        body.expressions.each do |exp|\n          case exp\n          when Crystal::Def                then yield exp\n          when Crystal::VisibilityModifier then yield exp.exp\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/duplicate_when_condition.cr",
    "content": "module Ameba::Rule::Lint\n  # Reports repeated conditions used in case `when` expressions.\n  #\n  # This is considered invalid:\n  #\n  # ```\n  # case x\n  # when .nil?\n  #   do_something\n  # when .nil?\n  #   do_something_else\n  # end\n  # ```\n  #\n  # And this is valid:\n  #\n  # ```\n  # case x\n  # when .nil?\n  #   do_something\n  # when Symbol\n  #   do_something_else\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/DuplicateWhenCondition:\n  #   Enabled: true\n  # ```\n  class DuplicateWhenCondition < Base\n    properties do\n      since_version \"1.7.0\"\n      description \"Reports repeated conditions used in case `when` expressions\"\n    end\n\n    MSG = \"Duplicate `when` condition detected\"\n\n    def test(source, node : Crystal::Case | Crystal::Select)\n      found_conditions = Set(String).new\n\n      node.whens.each &.conds.each do |cond|\n        next if found_conditions.add?(cond.to_s)\n\n        issue_for cond, MSG\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/duplicated_require.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that reports duplicated `require` statements.\n  #\n  # ```\n  # require \"./thing\"\n  # require \"./stuff\"\n  # require \"./thing\" # duplicated require\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/DuplicatedRequire:\n  #   Enabled: true\n  # ```\n  class DuplicatedRequire < Base\n    properties do\n      since_version \"0.14.0\"\n      description \"Reports duplicated `require` statements\"\n    end\n\n    MSG = \"Duplicated require of `%s`\"\n\n    def test(source)\n      found_requires = Set(String).new\n\n      nodes = AST::TopLevelNodesVisitor.new(source.ast).require_nodes\n      nodes.each do |node|\n        next if found_requires.add?(node.string)\n\n        issue_for node, MSG % node.string\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/else_nil.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows `else` blocks with `nil` as their body, as they\n  # have no effect and can be safely removed.\n  #\n  # This is considered invalid:\n  #\n  # ```\n  # if foo\n  #   do_foo\n  # else\n  #   nil\n  # end\n  # ```\n  #\n  # And this is valid:\n  #\n  # ```\n  # if foo\n  #   do_foo\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/ElseNil:\n  #   Enabled: true\n  # ```\n  class ElseNil < Base\n    properties do\n      since_version \"1.7.0\"\n      description \"Disallows `else` blocks with `nil` as their body\"\n    end\n\n    MSG = \"Avoid `else` blocks with `nil` as their body\"\n\n    def test(source, node : Crystal::Case)\n      check_issue(source, node) unless node.exhaustive?\n    end\n\n    def test(source, node : Crystal::If)\n      check_issue(source, node) unless node.ternary?\n    end\n\n    def test(source, node : Crystal::Unless)\n      check_issue(source, node)\n    end\n\n    private def check_issue(source, node)\n      return unless node_else = node.else\n      return unless node_else.is_a?(Crystal::NilLiteral)\n\n      if node.responds_to?(:else_location) &&\n         (else_location = node.else_location) &&\n         (end_location = node.end_location)\n        issue_for node_else, MSG do |corrector|\n          corrector.remove(\n            else_location,\n            end_location.adjust(column_number: -{{ \"end\".size }})\n          )\n        end\n      else\n        issue_for node_else, MSG\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/empty_ensure.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows empty `ensure` statement.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # def some_method\n  #   do_some_stuff\n  # ensure\n  # end\n  #\n  # begin\n  #   do_some_stuff\n  # ensure\n  # end\n  # ```\n  #\n  # And it should be written as this:\n  #\n  # ```\n  # def some_method\n  #   do_some_stuff\n  # ensure\n  #   do_something_else\n  # end\n  #\n  # begin\n  #   do_some_stuff\n  # ensure\n  #   do_something_else\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/EmptyEnsure:\n  #   Enabled: true\n  # ```\n  class EmptyEnsure < Base\n    properties do\n      since_version \"0.3.0\"\n      description \"Disallows empty `ensure` statement\"\n    end\n\n    MSG = \"Empty `ensure` block detected\"\n\n    def test(source, node : Crystal::ExceptionHandler)\n      node_ensure = node.ensure\n      return if node_ensure.nil? || !node_ensure.nop?\n\n      ensure_location = node.ensure_location\n      end_location = node.end_location\n      return unless ensure_location && end_location\n\n      issue_for ensure_location, end_location, MSG do |corrector|\n        corrector.remove(\n          ensure_location,\n          end_location.adjust(column_number: -{{ \"end\".size }})\n        )\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/empty_expression.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows empty expressions.\n  #\n  # This is considered invalid:\n  #\n  # ```\n  # foo = ()\n  #\n  # if ()\n  #   bar\n  # end\n  # ```\n  #\n  # And this is valid:\n  #\n  # ```\n  # foo = (some_expression)\n  #\n  # if (some_expression)\n  #   bar\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/EmptyExpression:\n  #   Enabled: true\n  # ```\n  class EmptyExpression < Base\n    properties do\n      since_version \"0.2.0\"\n      description \"Disallows empty expressions\"\n    end\n\n    MSG = \"Avoid empty expressions\"\n\n    def test(source, node : Crystal::Expressions)\n      return unless node.expressions.size == 1 &&\n                    node.expressions.first.nop?\n\n      issue_for node, MSG\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/empty_loop.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows empty loops.\n  #\n  # This is considered invalid:\n  #\n  # ```\n  # while false\n  # end\n  #\n  # until 10\n  # end\n  #\n  # loop do\n  #   # nothing here\n  # end\n  # ```\n  #\n  # And this is valid:\n  #\n  # ```\n  # a = 1\n  # while a < 10\n  #   a += 1\n  # end\n  #\n  # until socket_opened?\n  # end\n  #\n  # loop do\n  #   do_something_here\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/EmptyLoop:\n  #   Enabled: true\n  # ```\n  class EmptyLoop < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.12.0\"\n      description \"Disallows empty loops\"\n    end\n\n    MSG = \"Empty loop detected\"\n\n    def test(source, node : Crystal::Call)\n      check_node(source, node, node.block) if loop?(node)\n    end\n\n    def test(source, node : Crystal::While | Crystal::Until)\n      check_node(source, node, node.body) if literal?(node.cond)\n    end\n\n    private def check_node(source, node, loop_body)\n      body =\n        loop_body.is_a?(Crystal::Block) ? loop_body.body : loop_body\n\n      return unless body.nil? || body.nop?\n\n      issue_for node, MSG\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/enum_member_name_conflict.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that reports conflicting enum member names.\n  #\n  # Since Crystal will parse enum member names using `String#camelcase` and\n  # `String#downcase`, it is important to ensure that each member has a name\n  # that stays unique after the transformation.\n  #\n  # ```\n  # enum Foo\n  #   Bar\n  #   BAR\n  # end\n  #\n  # Foo.parse(\"bar\") # => Foo::Bar\n  # Foo.parse(\"Bar\") # => Foo::Bar\n  # Foo.parse(\"BAR\") # => Foo::Bar\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/EnumMemberNameConflict:\n  #   Enabled: true\n  # ```\n  class EnumMemberNameConflict < Base\n    properties do\n      since_version \"1.7.0\"\n      description \"Reports conflicting enum member names\"\n    end\n\n    MSG = \"Enum member name conflict detected\"\n\n    def test(source, node : Crystal::EnumDef)\n      found_names = Set(String).new\n\n      node.members.each do |member|\n        next unless member.is_a?(Crystal::Arg)\n        next if found_names.add?(member.name.camelcase.downcase)\n\n        issue_for member, MSG, prefer_name_location: true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/formatting.cr",
    "content": "require \"compiler/crystal/formatter\"\n\nmodule Ameba::Rule::Lint\n  # A rule that verifies syntax formatting according to the\n  # Crystal's built-in formatter.\n  #\n  # For example, this syntax is invalid:\n  #\n  #     def foo(a,b,c=0)\n  #       #foobar\n  #       a+b+c\n  #     end\n  #\n  # And should be properly written:\n  #\n  #     def foo(a, b, c = 0)\n  #       # foobar\n  #       a + b + c\n  #     end\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/Formatting:\n  #   Enabled: true\n  #   FailOnError: false\n  # ```\n  class Formatting < Base\n    properties do\n      since_version \"1.4.0\"\n      description \"Reports not formatted sources\"\n      fail_on_error false\n    end\n\n    MSG       = \"Use built-in formatter to format this source\"\n    MSG_ERROR = \"Error while formatting: %s\"\n\n    private LOCATION = {1, 1}\n\n    def test(source)\n      source_code = source.code\n\n      source_lines = source_code.lines\n      return if source_lines.empty?\n\n      result = Crystal.format(source_code, source.path)\n      return if result == source_code\n\n      end_location = {\n        source_lines.size,\n        source_lines.last.size + 1,\n      }\n\n      issue_for LOCATION, MSG do |corrector|\n        corrector.replace(LOCATION, end_location, result)\n      end\n    rescue ex : Crystal::SyntaxException\n      if fail_on_error?\n        issue_for({ex.line_number, ex.column_number}, MSG_ERROR % ex.message)\n      end\n    rescue ex\n      if fail_on_error?\n        issue_for(LOCATION, MSG_ERROR % ex.message)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/hash_duplicated_key.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows duplicated keys in hash literals.\n  #\n  # This is considered invalid:\n  #\n  # ```\n  # h = {\"foo\" => 1, \"bar\" => 2, \"foo\" => 3}\n  # ```\n  #\n  # And it has to written as this instead:\n  #\n  # ```\n  # h = {\"foo\" => 1, \"bar\" => 2}\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/HashDuplicatedKey:\n  #   Enabled: true\n  # ```\n  class HashDuplicatedKey < Base\n    properties do\n      since_version \"0.3.0\"\n      description \"Disallows duplicated keys in hash literals\"\n    end\n\n    MSG = \"Duplicated keys in hash literal: %s\"\n\n    def test(source, node : Crystal::HashLiteral)\n      return if (keys = duplicated_keys(node.entries)).empty?\n\n      issue_for node, MSG % keys.map { |key| \"`#{key}`\" }.join(\", \")\n    end\n\n    private def duplicated_keys(entries)\n      entries.map(&.key)\n        .group_by(&.itself)\n        .select! { |_, v| v.size > 1 }\n        .keys\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/literal_assignments_in_expressions.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows assignments with literal values\n  # in control expressions.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # if foo = 42\n  #   do_something\n  # end\n  # ```\n  #\n  # And most likely should be replaced by the following:\n  #\n  # ```\n  # if foo == 42\n  #   do_something\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/LiteralAssignmentsInExpressions:\n  #   Enabled: true\n  # ```\n  class LiteralAssignmentsInExpressions < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.4.0\"\n      description \"Disallows assignments with literal values in control expressions\"\n    end\n\n    MSG = \"Detected assignment with a literal value in control expression\"\n\n    def test(source, node : Crystal::If | Crystal::Unless | Crystal::Case | Crystal::While | Crystal::Until)\n      return unless (cond = node.cond).is_a?(Crystal::Assign)\n      return unless literal?(cond.value)\n\n      issue_for cond, MSG\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/literal_in_condition.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows useless conditional statements that contain a literal\n  # in place of a variable or predicate function.\n  #\n  # This is because a conditional construct with a literal predicate will\n  # always result in the same behavior at run time, meaning it can be\n  # replaced with either the body of the construct, or deleted entirely.\n  #\n  # This is considered invalid:\n  #\n  # ```\n  # if \"something\"\n  #   :ok\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/LiteralInCondition:\n  #   Enabled: true\n  # ```\n  class LiteralInCondition < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.1.0\"\n      description \"Disallows useless conditional statements that contain \\\n        a literal in place of a variable or predicate function\"\n    end\n\n    MSG = \"Literal value found in conditional\"\n\n    def test(source, node : Crystal::If | Crystal::Unless | Crystal::Until)\n      issue_for node.cond, MSG if literal?(node.cond)\n    end\n\n    def test(source, node : Crystal::Case)\n      return unless cond = node.cond\n      return unless static_literal?(cond)\n\n      issue_for cond, MSG\n    end\n\n    def test(source, node : Crystal::While)\n      return unless cond = node.cond\n      return unless literal?(cond)\n\n      # allow `while true`\n      return if cond.is_a?(Crystal::BoolLiteral) && cond.value\n\n      issue_for cond, MSG\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/literal_in_interpolation.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows useless string interpolations\n  # that contain a literal value instead of a variable or function.\n  #\n  # For example:\n  #\n  # ```\n  # \"Hello, #{:Ary}\"\n  # \"There are #{4} cats\"\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/LiteralInInterpolation:\n  #   Enabled: true\n  # ```\n  class LiteralInInterpolation < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.1.0\"\n      description \"Disallows useless string interpolations\"\n    end\n\n    MSG = \"Literal value found in interpolation\"\n\n    MAGIC_CONSTANTS = %w[__LINE__ __FILE__ __DIR__]\n\n    def test(source, node : Crystal::StringInterpolation)\n      each_literal_node(source, node) do |exp|\n        issue_for exp, MSG\n      end\n    end\n\n    private def each_literal_node(source, node, &)\n      source_lines = source.lines\n\n      node.expressions.each do |exp|\n        next if exp.is_a?(Crystal::StringLiteral)\n        next unless static_literal?(exp)\n        next unless code = node_source(exp, source_lines)\n        next if code.in?(MAGIC_CONSTANTS)\n\n        yield exp\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/literals_comparison.cr",
    "content": "module Ameba::Rule::Lint\n  # This rule is used to identify comparisons between two literals.\n  #\n  # They usually have the same result - except for non-primitive\n  # types like containers, range or regex.\n  #\n  # For example, this will be always false:\n  #\n  # ```\n  # \"foo\" == 42\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/LiteralsComparison:\n  #   Enabled: true\n  # ```\n  class LiteralsComparison < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.3.0\"\n      description \"Identifies comparisons between literals\"\n    end\n\n    OP_NAMES = %w[=== == != =~ !~ < <= > >= <=>]\n\n    MSG = \"Comparison always evaluates to %s\"\n\n    def test(source, node : Crystal::Call)\n      return unless node.name.in?(OP_NAMES)\n      return unless (obj = node.obj) && (arg = node.args.first?)\n\n      return unless static_literal?(obj)\n      return unless static_literal?(arg)\n\n      what =\n        case node.name\n        when \"==\"\n          \"`#{obj.to_s == arg.to_s}`\"\n        when \"!=\"\n          \"`#{obj.to_s != arg.to_s}`\"\n        else\n          \"the same\"\n        end\n\n      issue_for node, MSG % what\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/missing_block_argument.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows yielding method definitions without block argument.\n  #\n  # For example, this is considered invalid:\n  #\n  #     def foo\n  #       yield 42\n  #     end\n  #\n  # And has to be written as the following:\n  #\n  #     def foo(&)\n  #       yield 42\n  #     end\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/MissingBlockArgument:\n  #   Enabled: true\n  # ```\n  class MissingBlockArgument < Base\n    properties do\n      since_version \"1.4.0\"\n      description \"Disallows yielding method definitions without block argument\"\n    end\n\n    MSG = \"Missing anonymous block argument. Use `&` as an argument \" \\\n          \"name to indicate yielding method.\"\n\n    def test(source)\n      AST::ScopeVisitor.new self, source\n    end\n\n    def test(source, node : Crystal::Def, scope : AST::Scope)\n      return if !scope.yields? || node.block_arg\n\n      issue_for node, MSG, prefer_name_location: true\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/non_existent_rule.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that reports non-existent rules in comment directives.\n  #\n  # For example, the user can mistakenly add a directive\n  # to disable a rule that even doesn't exist:\n  #\n  # ```\n  # # ameba:disable BadRuleName\n  # def foo\n  #   :bar\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/NonExistentRule:\n  #   Enabled: true\n  # ```\n  class NonExistentRule < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.13.0\"\n      description \"Reports non-existent rules in comment directives\"\n    end\n\n    MSG = \"Such rules do not exist: %s\"\n\n    ALL_RULE_NAMES  = Rule.rules.map(&.rule_name)\n    ALL_GROUP_NAMES = Rule.rules.map(&.group_name).uniq!\n\n    def test(source)\n      Tokenizer.new(source).run do |token|\n        next unless token.type.comment?\n        next unless directive = source.parse_inline_directive(token.value.to_s)\n\n        check_rules source, token, directive[:action], directive[:rules]\n      end\n    end\n\n    private def check_rules(source, token, action, rules)\n      bad_names = rules - ALL_RULE_NAMES - ALL_GROUP_NAMES\n      return if bad_names.empty?\n\n      # See `InlineComments::COMMENT_DIRECTIVE_REGEX`\n      prefix_size = \"# ameba:#{action} \".size\n      token_value = token.value.to_s[prefix_size - 1...-1]?\n\n      issue_for name_location_or(token, token_value, adjust_location_column_number: prefix_size),\n        MSG % bad_names.map { |name| \"`#{name}`\" }.join(\", \")\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/not_nil.cr",
    "content": "module Ameba::Rule::Lint\n  # This rule is used to identify usages of `not_nil!` calls.\n  #\n  # For example, this is considered a code smell:\n  #\n  # ```\n  # names = %w[Alice Bob]\n  # alice = names.find { |name| name == \"Alice\" }.not_nil!\n  # ```\n  #\n  # And can be written as this:\n  #\n  # ```\n  # names = %w[Alice Bob]\n  # alice = names.find { |name| name == \"Alice\" }\n  #\n  # if alice\n  #   # ...\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/NotNil:\n  #   Enabled: true\n  # ```\n  class NotNil < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.3.0\"\n      description \"Identifies usage of `not_nil!` calls\"\n    end\n\n    MSG = \"Avoid using `not_nil!`\"\n\n    def test(source)\n      AST::NodeVisitor.new self, source, skip: :macro\n    end\n\n    def test(source, node : Crystal::Call)\n      return unless node.name == \"not_nil!\" && node.obj\n      return if has_arguments?(node)\n\n      issue_for node, MSG, prefer_name_location: true\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/not_nil_after_no_bang.cr",
    "content": "module Ameba::Rule::Lint\n  # This rule is used to identify usage of `index/rindex/find/match` calls\n  # followed by a call to `not_nil!`.\n  #\n  # For example, this is considered a code smell:\n  #\n  # ```\n  # %w[Alice Bob].find(&.chars.any?(&.in?('o', 'b'))).not_nil!\n  # ```\n  #\n  # And can be written as this:\n  #\n  # ```\n  # %w[Alice Bob].find!(&.chars.any?(&.in?('o', 'b')))\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/NotNilAfterNoBang:\n  #   Enabled: true\n  # ```\n  class NotNilAfterNoBang < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.3.0\"\n      description \"Identifies usage of `index/rindex/find/match` calls followed by `not_nil!`\"\n    end\n\n    MSG = \"Use `%s! {...}` instead of `%s {...}.not_nil!`\"\n\n    BLOCK_CALL_NAMES = %w[index rindex find]\n    CALL_NAMES       = %w[index rindex match]\n\n    def test(source)\n      AST::NodeVisitor.new self, source, skip: :macro\n    end\n\n    def test(source, node : Crystal::Call)\n      return unless node.name == \"not_nil!\"\n      return if has_arguments?(node)\n\n      return unless (obj = node.obj).is_a?(Crystal::Call)\n      return unless obj.name.in?(has_block?(obj) ? BLOCK_CALL_NAMES : CALL_NAMES)\n\n      return unless name_location = name_location(obj)\n      return unless name_location_end = name_end_location(obj)\n      return unless end_location = name_end_location(node)\n\n      msg = MSG % {obj.name, obj.name}\n\n      issue_for name_location, end_location, msg do |corrector|\n        corrector.insert_after(name_location_end, '!')\n        corrector.remove_trailing(node, {{ \".not_nil!\".size }})\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/percent_arrays.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows some unwanted symbols in percent string and symbol array literals.\n  #\n  # For example, this is usually written by mistake:\n  #\n  # ```\n  # %w[\"one\", \"two\"]\n  # %i[:one, :two]\n  # ```\n  #\n  # And the expected example is:\n  #\n  # ```\n  # %w[one two]\n  # %i[one two]\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/PercentArrays:\n  #   Enabled: true\n  #   StringArrayUnwantedSymbols: ',\"'\n  #   SymbolArrayUnwantedSymbols: ',:'\n  # ```\n  class PercentArrays < Base\n    properties do\n      since_version \"0.3.0\"\n      description \"Disallows some unwanted symbols in percent array literals\"\n\n      string_array_unwanted_symbols %(,\")\n      symbol_array_unwanted_symbols %(,:)\n    end\n\n    MSG = \"Symbols `%s` may be unwanted in `%s` array literals\"\n\n    def test(source)\n      issue = start_token = nil\n\n      Tokenizer.new(source).run do |token|\n        case token.type\n        when .string_array_start?, .symbol_array_start?\n          start_token = token.dup\n        when .string?\n          if (_start = start_token) && !issue\n            issue = array_entry_invalid?(token.value.to_s, _start.raw)\n          end\n        when .string_array_end?\n          if (_start = start_token) && (_issue = issue)\n            issue_for _start.location, _start.location.adjust(column_number: 1), _issue\n          end\n          issue = start_token = nil\n        end\n      end\n    end\n\n    private def array_entry_invalid?(entry, array_type)\n      case array_type\n      when .starts_with? \"%w\"\n        check_array_entry entry, string_array_unwanted_symbols, \"%w\"\n      when .starts_with? \"%i\"\n        check_array_entry entry, symbol_array_unwanted_symbols, \"%i\"\n      end\n    end\n\n    private def check_array_entry(entry, symbols, literal)\n      MSG % {symbols, literal} if entry.matches?(/[#{Regex.escape(symbols)}]/)\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/rand_zero.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows `rand(0)` and `rand(1)` calls.\n  # Such calls always return `0`.\n  #\n  # For example:\n  #\n  # ```\n  # rand(1)\n  # ```\n  #\n  # Should be written as:\n  #\n  # ```\n  # rand\n  # # or\n  # rand(2)\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/RandZero:\n  #   Enabled: true\n  # ```\n  class RandZero < Base\n    properties do\n      since_version \"0.5.1\"\n      description \"Disallows `rand` zero calls\"\n    end\n\n    MSG = \"`%s` always returns `0`\"\n\n    def test(source, node : Crystal::Call)\n      return unless node.name == \"rand\" &&\n                    node.args.size == 1 &&\n                    (arg = node.args.first).is_a?(Crystal::NumberLiteral) &&\n                    arg.value.in?(\"0\", \"1\")\n\n      issue_for node, MSG % node\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/redundant_string_coercion.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows string conversion in string interpolation,\n  # which is redundant.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # \"Hello, #{name.to_s}\"\n  # ```\n  #\n  # And this is valid:\n  #\n  # ```\n  # \"Hello, #{name}\"\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/RedundantStringCoercion:\n  #   Enabled: true\n  # ```\n  class RedundantStringCoercion < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.12.0\"\n      description \"Disallows redundant string conversions in interpolation\"\n    end\n\n    MSG = \"Redundant use of `Object#to_s` in interpolation\"\n\n    def test(source, node : Crystal::StringInterpolation)\n      each_string_coercion_node(node) do |expr|\n        issue_for name_location(expr), expr.end_location, MSG\n      end\n    end\n\n    private def each_string_coercion_node(node, &)\n      node.expressions.each do |exp|\n        yield exp if exp.is_a?(Crystal::Call) &&\n                     exp.name == \"to_s\" &&\n                     exp.obj &&\n                     !has_arguments?(exp)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/redundant_with_index.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows redundant `with_index` calls.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # collection.each.with_index do |e|\n  #   # ...\n  # end\n  #\n  # collection.each_with_index do |e, _|\n  #   # ...\n  # end\n  # ```\n  #\n  # and it should be written as follows:\n  #\n  # ```\n  # collection.each do |e|\n  #   # ...\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/RedundantWithIndex:\n  #   Enabled: true\n  # ```\n  class RedundantWithIndex < Base\n    properties do\n      since_version \"0.11.0\"\n      description \"Disallows redundant `with_index` calls\"\n    end\n\n    MSG_WITH_INDEX      = \"Remove redundant `with_index`\"\n    MSG_EACH_WITH_INDEX = \"Use `each` instead of `each_with_index`\"\n\n    def test(source, node : Crystal::Call)\n      args, block = node.args, node.block\n\n      return if block.nil? || args.size > 1\n      return if with_index_arg?(block)\n\n      case node.name\n      when \"with_index\"\n        report source, node, MSG_WITH_INDEX\n      when \"each_with_index\"\n        report source, node, MSG_EACH_WITH_INDEX\n      end\n    end\n\n    private def with_index_arg?(block : Crystal::Block)\n      block.args.size >= 2 && block.args.last.name != \"_\"\n    end\n\n    private def report(source, node, msg)\n      issue_for node, msg, prefer_name_location: true\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/redundant_with_object.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows redundant `each_with_object` calls.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # collection.each_with_object(0) do |e|\n  #   # ...\n  # end\n  #\n  # collection.each_with_object(0) do |e, _|\n  #   # ...\n  # end\n  # ```\n  #\n  # and it should be written as follows:\n  #\n  # ```\n  # collection.each do |e|\n  #   # ...\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/RedundantWithObject:\n  #   Enabled: true\n  # ```\n  class RedundantWithObject < Base\n    properties do\n      since_version \"0.11.0\"\n      description \"Disallows redundant `with_object` calls\"\n    end\n\n    MSG = \"Use `each` instead of `each_with_object`\"\n\n    def test(source, node : Crystal::Call)\n      return if node.name != \"each_with_object\" ||\n                node.args.size != 1 ||\n                !(block = node.block) ||\n                with_index_arg?(block)\n\n      issue_for node, MSG, prefer_name_location: true\n    end\n\n    private def with_index_arg?(block : Crystal::Block)\n      block.args.size >= 2 && block.args.last.name != \"_\"\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/require_parentheses.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows method calls with at least one argument, where no\n  # parentheses are used around the argument list, and a logical operator\n  # (`&&` or `||`) is used within the argument list.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # if foo.includes? \"bar\" || foo.includes? \"baz\"\n  #   # ...\n  # end\n  # ```\n  #\n  # And need to be written as:\n  #\n  # ```\n  # if foo.includes?(\"bar\") || foo.includes?(\"baz\")\n  #   # ...\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/RequireParentheses:\n  #   Enabled: true\n  # ```\n  class RequireParentheses < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.7.0\"\n      description \"Disallows method calls with no parentheses and a logical operator in the argument list\"\n    end\n\n    MSG = \"Use parentheses in the method call to avoid confusion about precedence\"\n\n    ALLOWED_CALL_NAMES = %w[[]? []]\n\n    def test(source, node : Crystal::Call)\n      return if node.has_parentheses? ||\n                !has_arguments?(node) ||\n                setter_method?(node) ||\n                node.name.in?(ALLOWED_CALL_NAMES)\n\n      node.args.each do |arg|\n        next unless arg.is_a?(Crystal::BinaryOp)\n        next unless (right = arg.right).is_a?(Crystal::Call)\n        next unless has_arguments?(right)\n\n        issue_for node, MSG\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/self_initialize_definition.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that reports usage of `initialize` method definition with a `self` receiver.\n  # Such definitions are almost always a typo.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # class Foo\n  #   def self.initialize\n  #   end\n  # end\n  # ```\n  #\n  # And should be written as:\n  #\n  # ```\n  # class Foo\n  #   def initialize\n  #   end\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/SelfInitializeDefinition:\n  #   Enabled: true\n  # ```\n  class SelfInitializeDefinition < Base\n    properties do\n      since_version \"1.7.0\"\n      description \"Reports `initialize` method definitions with a `self` receiver\"\n    end\n\n    MSG = \"`initialize` method definition should not have a receiver\"\n\n    def test(source : Source)\n      AST::NodeVisitor.new self, source, skip: [\n        Crystal::EnumDef,\n        Crystal::ModuleDef,\n      ]\n    end\n\n    def test(source, node : Crystal::Def)\n      return unless node.name == \"initialize\"\n      return unless (receiver = node.receiver).is_a?(Crystal::Var)\n      return unless receiver.name == \"self\"\n\n      if (location = receiver.location) && (end_location = receiver.end_location)\n        issue_for node, MSG do |corrector|\n          corrector.remove(location, end_location.adjust(column_number: 1))\n        end\n      else\n        issue_for node, MSG\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/shadowed_argument.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows shadowed arguments.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # do_something do |foo|\n  #   foo = 1 # shadows block argument\n  #   foo\n  # end\n  #\n  # def do_something(foo)\n  #   foo = 1 # shadows method argument\n  #   foo\n  # end\n  # ```\n  #\n  # and it should be written as follows:\n  #\n  # ```\n  # do_something do |foo|\n  #   foo = foo + 42\n  #   foo\n  # end\n  #\n  # def do_something(foo)\n  #   foo = foo + 42\n  #   foo\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/ShadowedArgument:\n  #   Enabled: true\n  # ```\n  class ShadowedArgument < Base\n    properties do\n      since_version \"0.7.0\"\n      description \"Disallows shadowed arguments\"\n    end\n\n    MSG = \"Argument `%s` is assigned before it is used\"\n\n    def test(source)\n      AST::ScopeVisitor.new self, source\n    end\n\n    def test(source, node, scope : AST::Scope)\n      return unless scope.def? || scope.block?\n\n      args = scope.arguments.reject(&.ignored?)\n      return if args.empty?\n\n      # Skip liveness analysis if no argument is ever reassigned\n      return unless args.any?(&.variable.assignments.present?)\n\n      result = AST::LivenessAnalyzer.new(scope).analyze\n      dead_store_ids = nil\n\n      args.each do |arg|\n        next if result.entry_live_set.includes?(arg.name)\n        next if arg.variable.captured_by_block?\n        next if arg.variable.used_in_macro?\n        next if scope.inner_scopes.any?(&.references?(arg.variable))\n\n        assigns = arg.variable.assignments\n        # Prefer the first non-dead-store assignment (the one whose value\n        # actually gets used), falling back to the first assignment.\n        dead_store_ids ||= result.dead_stores.map(&.node.object_id).to_set\n\n        target =\n          assigns.find { |a| !a.node.object_id.in?(dead_store_ids) } ||\n            assigns.first?\n        next unless target\n\n        issue_for target.node, MSG % arg.name\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/shadowed_exception.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows a rescued exception that get shadowed by a\n  # less specific exception being rescued before a more specific\n  # exception is rescued.\n  #\n  # For example, this is invalid:\n  #\n  # ```\n  # begin\n  #   do_something\n  # rescue Exception\n  #   handle_exception\n  # rescue ArgumentError\n  #   handle_argument_error_exception\n  # end\n  # ```\n  #\n  # And it has to be written as follows:\n  #\n  # ```\n  # begin\n  #   do_something\n  # rescue ArgumentError\n  #   handle_argument_error_exception\n  # rescue Exception\n  #   handle_exception\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/ShadowedException:\n  #   Enabled: true\n  # ```\n  class ShadowedException < Base\n    properties do\n      since_version \"0.3.0\"\n      description \"Disallows rescued exception that get shadowed\"\n    end\n\n    MSG = \"Shadowed exception found: `%s`\"\n\n    def test(source, node : Crystal::ExceptionHandler)\n      return unless rescues = node.rescues\n\n      shadowed(rescues).each do |path|\n        issue_for path, MSG % path.names.join(\"::\")\n      end\n    end\n\n    private def shadowed(rescues, catch_all = false)\n      traversed_types = Set(String).new\n\n      rescues = filter_rescues(rescues)\n      rescues.each_with_object([] of Crystal::Path) do |types, shadowed|\n        case\n        when catch_all\n          shadowed.concat(types)\n        when types.any?(&.single?(\"Exception\"))\n          nodes = types.reject(&.single?(\"Exception\"))\n          shadowed.concat(nodes) unless nodes.empty?\n          catch_all = true\n        else\n          nodes = types.select { |path| traverse(path.to_s, traversed_types) }\n          shadowed.concat(nodes) unless nodes.empty?\n        end\n      end\n    end\n\n    private def filter_rescues(rescues)\n      rescues.compact_map(&.types.try &.select(Crystal::Path))\n    end\n\n    private def traverse(path, traversed_types)\n      dup = traversed_types.includes?(path)\n      dup || (traversed_types << path)\n      dup\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/shadowing_outer_local_var.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows the usage of the same name as outer local variables\n  # for block or proc arguments.\n  #\n  # For example, this is considered incorrect:\n  #\n  # ```\n  # def some_method\n  #   foo = 1\n  #\n  #   3.times do |foo| # shadowing outer `foo`\n  #   end\n  # end\n  # ```\n  #\n  # and should be written as:\n  #\n  # ```\n  # def some_method\n  #   foo = 1\n  #\n  #   3.times do |bar|\n  #   end\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/ShadowingOuterLocalVar:\n  #   Enabled: true\n  # ```\n  class ShadowingOuterLocalVar < Base\n    properties do\n      since_version \"0.7.0\"\n      description \"Disallows the usage of the same name as outer local variables \" \\\n                  \"for block or proc arguments\"\n    end\n\n    MSG = \"Shadowing outer local variable `%s`\"\n\n    def test(source)\n      AST::ScopeVisitor.new self, source, skip: [\n        Crystal::Macro,\n        Crystal::MacroFor,\n      ]\n    end\n\n    def test(source, node : Crystal::ProcLiteral | Crystal::Block, scope : AST::Scope)\n      find_shadowing source, scope\n    end\n\n    private def find_shadowing(source, scope)\n      return unless outer_scope = scope.outer_scope\n\n      each_argument_node(scope) do |arg|\n        # TODO: handle unpacked variables from `Block#unpacks`\n        next unless name = arg.name.presence\n\n        variable = outer_scope.find_variable(name)\n\n        next if variable.nil? || !variable.declared_before?(arg)\n        next if outer_scope.assigns_ivar?(name)\n        next if outer_scope.assigns_type_dec?(name)\n\n        issue_for arg.node, MSG % name, prefer_name_location: true\n      end\n    end\n\n    private def each_argument_node(scope, &)\n      scope.arguments.each do |arg|\n        yield arg unless arg.ignored?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/shared_var_in_fiber.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows using shared variables in fibers,\n  # which are mutated during iterations.\n  #\n  # In most cases it leads to unexpected behaviour and is undesired.\n  #\n  # For example, having this example:\n  #\n  # ```\n  # n = 0\n  # channel = Channel(Int32).new\n  #\n  # while n < 3\n  #   n = n + 1\n  #   spawn { channel.send n }\n  # end\n  #\n  # 3.times { puts channel.receive } # => # 3, 3, 3\n  # ```\n  #\n  # The problem is there is only one shared between fibers variable `n`\n  # and when `channel.receive` is executed its value is `3`.\n  #\n  # To solve this, the code above needs to be rewritten to the following:\n  #\n  # ```\n  # n = 0\n  # channel = Channel(Int32).new\n  #\n  # while n < 3\n  #   n = n + 1\n  #   m = n\n  #   spawn do { channel.send m }\n  # end\n  #\n  # 3.times { puts channel.receive } # => # 1, 2, 3\n  # ```\n  #\n  # This rule is able to find the shared variables between fibers, which are mutated\n  # during iterations. So it reports the issue on the first sample and passes on\n  # the second one.\n  #\n  # There are also other techniques to solve the problem above which are\n  # [officially documented](https://crystal-lang.org/reference/guides/concurrency.html)\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/SharedVarInFiber:\n  #   Enabled: true\n  # ```\n  class SharedVarInFiber < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.12.0\"\n      description \"Disallows shared variables in fibers\"\n    end\n\n    MSG = \"Shared variable `%s` is used in fiber\"\n\n    def test(source)\n      AST::ScopeVisitor.new self, source\n    end\n\n    def test(source, node, scope : AST::Scope)\n      return unless scope.spawn_block?\n\n      scope.references.each do |ref|\n        next if (variable = scope.find_variable(ref.name)).nil?\n        next if variable.scope == scope || !mutated_in_loop?(variable)\n\n        issue_for ref.node, MSG % variable.name\n      end\n    end\n\n    # Variable is mutated in loop if it was declared above the loop and assigned inside.\n    private def mutated_in_loop?(variable)\n      first_assign_node = variable.assignments.first?.try(&.node)\n\n      targets = Set(UInt64).new\n      variable.assignments.each do |assign|\n        next if assign.scope.spawn_block?\n        next if assign.node == first_assign_node\n        targets << assign.node.object_id\n      end\n\n      targets.present? &&\n        LoopAncestorVisitor.new(targets, variable.scope.node).any_in_loop?\n    end\n\n    # Checks whether any of the target nodes are inside a loop within the boundary.\n    # Single traversal for all targets instead of one traversal per target.\n    private class LoopAncestorVisitor < Crystal::Visitor\n      include AST::Util\n\n      getter? any_in_loop = false\n\n      def initialize(@targets : Set(UInt64), boundary : Crystal::ASTNode)\n        @inside_loop = false\n        boundary.accept(self)\n      end\n\n      def visit(node : Crystal::ASTNode)\n        return false if any_in_loop?\n\n        if @targets.includes?(node.object_id)\n          @any_in_loop = @inside_loop\n          return false\n        end\n\n        true\n      end\n\n      def visit(node : Crystal::While | Crystal::Until)\n        return false if any_in_loop?\n\n        prev = @inside_loop\n        @inside_loop = true\n        node.accept_children(self)\n        @inside_loop = prev unless any_in_loop?\n        false\n      end\n\n      def visit(node : Crystal::Call)\n        return false if any_in_loop?\n\n        if loop?(node) && (block = node.block)\n          prev = @inside_loop\n          @inside_loop = true\n          block.body.accept(self)\n          @inside_loop = prev unless any_in_loop?\n        else\n          node.accept_children(self)\n        end\n        false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/signal_trap.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that reports when `Signal::INT/HUP/TERM.trap` is used,\n  # which should be replaced with `Process.on_terminate` instead -\n  # a more portable alternative.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # Signal::INT.trap do\n  #   shutdown\n  # end\n  # ```\n  #\n  # And it should be written as this:\n  #\n  # ```\n  # Process.on_terminate do\n  #   shutdown\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/SignalTrap:\n  #   Enabled: true\n  # ```\n  class SignalTrap < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.7.0\"\n      description \"Disallows `Signal::INT/HUP/TERM.trap` in favor of `Process.on_terminate`\"\n    end\n\n    MSG = \"Use `Process.on_terminate` instead of `%s.trap`\"\n\n    def test(source, node : Crystal::Call)\n      return unless (obj = node.obj).is_a?(Crystal::Path)\n      return unless path_named?(obj, \"Signal::INT\", \"Signal::HUP\", \"Signal::TERM\")\n      return unless node.name == \"trap\"\n\n      if (name_location = name_location(node)) && (name_end_location = name_end_location(node))\n        issue_for node.location, name_end_location, MSG % obj do |corrector|\n          corrector.replace obj, \"Process\"\n          corrector.replace name_location, name_end_location, \"on_terminate\"\n        end\n      else\n        issue_for node, MSG % obj\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/spec_eq_with_bool_or_nil_literal.cr",
    "content": "module Ameba::Rule::Lint\n  # Reports `eq(true|false|nil)` expectations in specs.\n  #\n  # This is considered bad:\n  #\n  # ```\n  # it \"works\" do\n  #   foo.is_a?(String).should eq true\n  #   foo.is_a?(Int32).should eq false\n  #   foo.as?(Symbol).should eq nil\n  # end\n  # ```\n  #\n  # And it should be written as the following:\n  #\n  # ```\n  # it \"works\" do\n  #   foo.is_a?(String).should be_true\n  #   foo.is_a?(Int32).should be_false\n  #   foo.as?(Symbol).should be_nil\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/SpecEqWithBoolOrNilLiteral:\n  #   Enabled: true\n  # ```\n  class SpecEqWithBoolOrNilLiteral < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.7.0\"\n      description \"Reports `eq(true|false|nil)` expectations in specs\"\n    end\n\n    MSG = \"Use `%s` instead of `%s` expectation\"\n\n    def test(source)\n      return super if source.spec?\n    end\n\n    def test(source, node : Crystal::Call)\n      return unless node.name.in?(\"should\", \"should_not\") && node.args.size == 1\n      return if has_block?(node)\n\n      return unless (matcher = node.args.first).is_a?(Crystal::Call)\n      return unless matcher.name == \"eq\" && matcher.args.size == 1\n      return if has_block?(matcher)\n\n      replacement =\n        case arg = matcher.args.first\n        when Crystal::BoolLiteral then arg.value ? \"be_true\" : \"be_false\"\n        when Crystal::NilLiteral  then \"be_nil\"\n        end\n      return unless replacement\n\n      issue_for matcher, MSG % {replacement, matcher.to_s} do |corrector|\n        corrector.replace(matcher, replacement)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/spec_filename.cr",
    "content": "require \"file_utils\"\n\nmodule Ameba::Rule::Lint\n  # A rule that enforces spec filenames to have `_spec` suffix.\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/SpecFilename:\n  #   Enabled: true\n  #   IgnoredDirs: [spec/support spec/fixtures spec/data]\n  #   IgnoredFilenames: [spec_helper]\n  # ```\n  class SpecFilename < Base\n    properties do\n      since_version \"1.6.0\"\n      description \"Enforces spec filenames to have `_spec` suffix\"\n      ignored_dirs %w[spec/support spec/fixtures spec/data]\n      ignored_filenames %w[spec_helper]\n    end\n\n    MSG = \"Spec filename should have `_spec` suffix: `%s.cr`, not `%s.cr`\"\n\n    private LOCATION = {1, 1}\n\n    # TODO: fix the assumption that *source.path* contains relative path\n    def test(source : Source)\n      path_ = Path[source.path].to_posix\n      name = path_.stem\n      path = path_.to_s\n\n      # check only files within spec/ directory\n      return unless path.starts_with?(\"spec/\")\n      # check only files with `.cr` extension\n      return unless path.ends_with?(\".cr\")\n      # ignore files having `_spec` suffix\n      return if name.ends_with?(\"_spec\")\n\n      # ignore known false-positives\n      ignored_dirs.each do |substr|\n        return if path.starts_with?(\"#{substr}/\")\n      end\n      return if name.in?(ignored_filenames)\n\n      expected = \"#{name}_spec\"\n\n      issue_for LOCATION, MSG % {expected, name} do\n        new_path =\n          path_.sibling(expected + path_.extension)\n\n        FileUtils.mv(path, new_path)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/spec_focus.cr",
    "content": "module Ameba::Rule::Lint\n  # Checks if specs are focused.\n  #\n  # In specs `focus: true` is mainly used to focus on a spec\n  # item locally during development. However, if such change\n  # is committed, it silently runs only focused spec on all\n  # other environment, which is undesired.\n  #\n  # This is considered bad:\n  #\n  # ```\n  # describe MyClass, focus: true do\n  # end\n  #\n  # describe \".new\", focus: true do\n  # end\n  #\n  # context \"my context\", focus: true do\n  # end\n  #\n  # it \"works\", focus: true do\n  # end\n  # ```\n  #\n  # And it should be written as the following:\n  #\n  # ```\n  # describe MyClass do\n  # end\n  #\n  # describe \".new\" do\n  # end\n  #\n  # context \"my context\" do\n  # end\n  #\n  # it \"works\" do\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/SpecFocus:\n  #   Enabled: true\n  # ```\n  class SpecFocus < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.14.0\"\n      description \"Reports focused spec items\"\n    end\n\n    MSG = \"Focused spec item detected\"\n\n    SPEC_ITEM_NAMES = %w[describe context it pending]\n\n    def test(source)\n      return super if source.spec?\n    end\n\n    def test(source, node : Crystal::Call)\n      return unless node.name.in?(SPEC_ITEM_NAMES)\n      return unless has_block?(node)\n\n      arg = node.named_args.try &.find(&.name.== \"focus\")\n      return if arg.nil? ||\n                arg.value.is_a?(Crystal::Call) ||\n                arg.value.is_a?(Crystal::Var)\n\n      issue_for arg, MSG\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/syntax.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that reports invalid Crystal syntax.\n  #\n  # For example, this syntax is invalid:\n  #\n  # ```\n  # def hello\n  #   do_something\n  # rescue Exception => e\n  # end\n  # ```\n  #\n  # And should be properly written:\n  #\n  # ```\n  # def hello\n  #   do_something\n  # rescue ex : Exception\n  # end\n  # ```\n  class Syntax < Base\n    properties do\n      since_version \"0.4.2\"\n      description \"Reports invalid Crystal syntax\"\n      severity :error\n    end\n\n    def test(source)\n      source.ast\n    rescue ex : Crystal::SyntaxException\n      issue_for({ex.line_number, ex.column_number}, ex.message.to_s)\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/top_level_operator_definition.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows top level operator method definitions, since these cannot be called.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # def +(other)\n  # end\n  # ```\n  #\n  # And has to be written within a class, struct, or module:\n  #\n  # ```\n  # class Foo\n  #   def +(other)\n  #   end\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/TopLevelOperatorDefinition:\n  #   Enabled: true\n  # ```\n  class TopLevelOperatorDefinition < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.7.0\"\n      description \"Disallows top level operator method definitions\"\n    end\n\n    MSG = \"Top level operator method definitions cannot be called\"\n\n    def test(source)\n      AST::NodeVisitor.new self, source, skip: [\n        Crystal::ClassDef,\n        Crystal::EnumDef,\n        Crystal::ModuleDef,\n        Crystal::Call,\n      ]\n    end\n\n    def test(source, node : Crystal::Def)\n      return if node.receiver || !operator_method?(node)\n\n      issue_for node, MSG\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/trailing_rescue_exception.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that prohibits the misconception about how trailing `rescue` statements work,\n  # preventing Paths (exception class names or otherwise) from being used as the\n  # trailing value. The value after the trailing `rescue` statement is the value\n  # to use if an exception occurs, not the exception class to rescue from.\n  #\n  # For example, this is considered invalid - if an exception occurs,\n  # `response` will be assigned with the value of `IO::Error` instead of `nil`:\n  #\n  # ```\n  # response = HTTP::Client.get(\"http://www.example.com\") rescue IO::Error\n  # ```\n  #\n  # And should instead be written as this in order to capture only `IO::Error` exceptions:\n  #\n  # ```\n  # response = begin\n  #   HTTP::Client.get(\"http://www.example.com\")\n  # rescue IO::Error\n  #   \"default value\"\n  # end\n  # ```\n  #\n  # Or to rescue all exceptions (instead of just `IO::Error`):\n  #\n  # ```\n  # response = HTTP::Client.get(\"http://www.example.com\") rescue \"default value\"\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/TrailingRescueException:\n  #   Enabled: true\n  # ```\n  class TrailingRescueException < Base\n    properties do\n      since_version \"1.7.0\"\n      description \"Disallows trailing `rescue` with a path\"\n    end\n\n    MSG = \"Use a block variant of `rescue` to filter by the exception type\"\n\n    def test(source, node : Crystal::ExceptionHandler)\n      return unless node.suffix &&\n                    (rescues = node.rescues) &&\n                    (resc = rescues.first?) &&\n                    resc.body.is_a?(Crystal::Path)\n\n      issue_for resc.body, MSG, prefer_name_location: true\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/typos.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that reports typos found in source files.\n  #\n  # NOTE: Needs [typos](https://github.com/crate-ci/typos) CLI tool.\n  # NOTE: See the chapter on [false positives](https://github.com/crate-ci/typos#false-positives).\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/Typos:\n  #   Enabled: true\n  #   BinPath: ~\n  #   FailOnMissingBin: false\n  #   FailOnError: true\n  # ```\n  class Typos < Base\n    properties do\n      since_version \"1.6.0\"\n      description \"Reports typos found in source files\"\n      enabled false\n\n      bin_path nil, as: String?\n      fail_on_missing_bin false\n      fail_on_error true\n    end\n\n    MSG = \"Typo found: `%s` -> %s\"\n\n    BIN_PATH = Process.find_executable(\"typos\") rescue nil\n\n    @@mutex = Mutex.new\n\n    private record Typo,\n      typo : String,\n      corrections : Array(String),\n      location : {Int32, Int32},\n      end_location : {Int32, Int32} do\n      def self.parse(str) : self?\n        issue =\n          JSON.parse(str)\n\n        return unless issue[\"type\"] == \"typo\"\n\n        typo = issue[\"typo\"].as_s\n        corrections = issue[\"corrections\"].as_a.map(&.as_s)\n\n        return if typo.empty? || corrections.empty?\n\n        line_no = issue[\"line_num\"].as_i\n        col_no = issue[\"byte_offset\"].as_i + 1\n        end_col_no = col_no + (typo.size - 1)\n\n        new(typo, corrections,\n          {line_no, col_no},\n          {line_no, end_col_no})\n      end\n    end\n\n    protected def self.typos_from(bin_path : String, source : Source) : Array(Typo)?\n      input, output =\n        IO::Memory.new(source.code), IO::Memory.new\n\n      result = @@mutex.synchronize do\n        status = Process.run bin_path, args: %w[--format json -],\n          input: input,\n          output: output\n\n        output.to_s.presence unless status.success?\n      end\n      return unless result\n\n      ([] of Typo).tap do |typos|\n        # NOTE: `--format json` is actually JSON Lines (`jsonl`)\n        result.each_line do |line|\n          Typo.parse(line).try { |typo| typos << typo }\n        end\n      end\n    end\n\n    def bin_path : String?\n      @bin_path || BIN_PATH\n    end\n\n    def test(source : Source)\n      typos = typos_from(source)\n      typos.try &.each do |typo|\n        corrections = typo.corrections\n        message = MSG % {\n          typo.typo, corrections.map { |correction| \"`#{correction}`\" }.join(\" | \"),\n        }\n        if corrections.size == 1\n          issue_for typo.location, typo.end_location, message do |corrector|\n            corrector.replace(typo.location, typo.end_location, corrections.first)\n          end\n        else\n          issue_for typo.location, typo.end_location, message\n        end\n      end\n    rescue ex\n      raise ex if fail_on_error?\n    end\n\n    protected def typos_from(source : Source) : Array(Typo)?\n      if bin_path = self.bin_path\n        return Typos.typos_from(bin_path, source)\n      end\n      if fail_on_missing_bin?\n        raise RuntimeError.new \"Could not find `typos` executable\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/unneeded_disable_directive.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that reports unneeded disable directives.\n  # For example, this is considered invalid:\n  #\n  # ```\n  # # ameba:disable Style/PredicateName\n  # def comment?\n  #   do_something\n  # end\n  # ```\n  #\n  # As the predicate name is correct and the comment directive does not\n  # have any effect, the snippet should be written as the following:\n  #\n  # ```\n  # def comment?\n  #   do_something\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/UnneededDisableDirective:\n  #   Enabled: true\n  # ```\n  class UnneededDisableDirective < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.5.0\"\n      description \"Reports unneeded disable directives in comments\"\n    end\n\n    MSG = \"Unnecessary disabling of %s\"\n\n    def test(source)\n      test(source, Set(String).new)\n    end\n\n    def test(source, excluded_rules : Set(String))\n      Tokenizer.new(source).run do |token|\n        next unless token.type.comment?\n        next unless directive = source.parse_inline_directive(token.value.to_s)\n        next unless names = unneeded_disables(source, directive, token.location, excluded_rules)\n        next if names.empty?\n\n        issue_for name_location_or(token, token.value),\n          MSG % names.map { |name| \"`#{name}`\" }.join(\", \")\n      end\n    end\n\n    private def unneeded_disables(source, directive, location, excluded_rules)\n      return unless directive[:action] == \"disable\"\n\n      directive[:rules].reject do |rule_name|\n        next if rule_name == name\n        next true if rule_name.in?(excluded_rules)\n        # skip non-existent rules\n        next true unless Rule.rules.any?(&.rule_name.== rule_name)\n\n        source.issues.any? do |issue|\n          issue.rule.name == rule_name &&\n            issue.disabled? &&\n            issue_at_location?(source, issue, location)\n        end\n      end\n    end\n\n    private def issue_at_location?(source, issue, location)\n      return false unless issue_line_number = issue.location.try(&.line_number)\n\n      issue_line_number == location.line_number ||\n        ((prev_line_number = issue_line_number - 1) &&\n          prev_line_number == location.line_number &&\n          source.comment?(prev_line_number - 1))\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/unreachable_code.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that reports unreachable code.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # def method(a)\n  #   return 42\n  #   a + 1\n  # end\n  # ```\n  #\n  # ```\n  # a = 1\n  # loop do\n  #   break\n  #   a += 1\n  # end\n  # ```\n  #\n  # And has to be written as the following:\n  #\n  # ```\n  # def method(a)\n  #   return 42 if a == 0\n  #   a + 1\n  # end\n  # ```\n  #\n  # ```\n  # a = 1\n  # loop do\n  #   break a > 3\n  #   a += 1\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/UnreachableCode:\n  #   Enabled: true\n  # ```\n  class UnreachableCode < Base\n    properties do\n      since_version \"0.9.0\"\n      description \"Reports unreachable code\"\n    end\n\n    MSG = \"Unreachable code detected\"\n\n    def test(source)\n      AST::FlowExpressionVisitor.new self, source\n    end\n\n    def test(source, node, flow_expression : AST::FlowExpression)\n      return unless unreachable_node = flow_expression.unreachable_nodes.first?\n      issue_for unreachable_node, MSG\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/unused_argument.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that reports unused arguments.\n  # For example, this is considered invalid:\n  #\n  # ```\n  # def method(a, b, c)\n  #   a + b\n  # end\n  # ```\n  #\n  # and should be written as:\n  #\n  # ```\n  # def method(a, b)\n  #   a + b\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/UnusedArgument:\n  #   Enabled: true\n  #   IgnoreDefs: true\n  #   IgnoreBlocks: false\n  #   IgnoreProcs: false\n  # ```\n  class UnusedArgument < Base\n    properties do\n      since_version \"0.6.0\"\n      description \"Disallows unused arguments\"\n\n      ignore_defs true\n      ignore_blocks false\n      ignore_procs false\n    end\n\n    MSG = \"Unused argument `%s`. If it's necessary, use `%s` \" \\\n          \"as an argument name to indicate that it won't be used.\"\n\n    def test(source)\n      AST::ScopeVisitor.new self, source\n    end\n\n    def test(source, node : Crystal::ProcLiteral, scope : AST::Scope)\n      ignore_procs? || find_unused_arguments(source, scope)\n    end\n\n    def test(source, node : Crystal::Block, scope : AST::Scope)\n      ignore_blocks? || find_unused_arguments(source, scope)\n    end\n\n    def test(source, node : Crystal::Def, scope : AST::Scope)\n      arguments = scope.arguments.dup\n\n      # `Lint/UnusedBlockArgument` rule covers this case explicitly\n      if block_arg = node.block_arg\n        arguments.reject!(&.node.== block_arg)\n      end\n\n      ignore_defs? || find_unused_arguments(source, scope, arguments)\n    end\n\n    private def find_unused_arguments(source, scope, arguments = scope.arguments)\n      arguments.each do |argument|\n        next if argument.anonymous? || argument.ignored?\n        next if scope.references?(argument.variable)\n\n        name_suggestion = scope.node.is_a?(Crystal::Block) ? '_' : \"_#{argument.name}\"\n        message = MSG % {argument.name, name_suggestion}\n\n        node = argument.node\n\n        location = node.location\n        name_end_location = location.try &.adjust(column_number: argument.name.size - 1)\n\n        if node.responds_to?(:restriction)\n          end_location = node.restriction.try(&.end_location)\n        end\n        end_location ||= name_end_location\n\n        if location && name_end_location && end_location\n          issue_for location, end_location, message do |corrector|\n            corrector.replace(location, name_end_location, name_suggestion)\n          end\n        else\n          issue_for node, message\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/unused_block_argument.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that reports unused block arguments.\n  # For example, this is considered invalid:\n  #\n  # ```\n  # def foo(a, b, &block)\n  #   a + b\n  # end\n  #\n  # def bar(&block)\n  #   yield 42\n  # end\n  # ```\n  #\n  # and should be written as:\n  #\n  # ```\n  # def foo(a, b, &_block)\n  #   a + b\n  # end\n  #\n  # def bar(&)\n  #   yield 42\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/UnusedBlockArgument:\n  #   Enabled: true\n  # ```\n  class UnusedBlockArgument < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.4.0\"\n      description \"Disallows unused block arguments\"\n    end\n\n    MSG_UNUSED = \"Unused block argument `%1$s`. If it's necessary, use `_%1$s` \" \\\n                 \"as an argument name to indicate that it won't be used.\"\n\n    MSG_YIELDED = \"Use `&` as an argument name to indicate that it won't be referenced\"\n\n    def test(source)\n      AST::ScopeVisitor.new self, source\n    end\n\n    def test(source, node : Crystal::Def, scope : AST::Scope)\n      return if node.abstract?\n\n      return unless block_arg = node.block_arg\n      return unless block_arg = scope.arguments.find(&.node.== block_arg)\n\n      return if block_arg.anonymous?\n      return if scope.references?(block_arg.variable)\n\n      location = name_location_or(block_arg.node)\n\n      case\n      when scope.yields?\n        case location\n        when Tuple\n          issue_for *location, MSG_YIELDED do |corrector|\n            corrector.remove(*location)\n          end\n        else\n          issue_for location, MSG_YIELDED\n        end\n      when !block_arg.ignored?\n        case location\n        when Tuple\n          issue_for *location, MSG_UNUSED % block_arg.name do |corrector|\n            corrector.insert_before(location[0], '_')\n          end\n        else\n          issue_for location, MSG_UNUSED % block_arg.name\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/unused_expression.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows unused expressions.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # a = obj.method do |x|\n  #   x == 1 # => Comparison operation has no effect\n  #   puts x\n  # end\n  #\n  # Float64 | StaticArray(Float64, 10)\n  #\n  # pointerof(foo)\n  # ```\n  #\n  # And these are considered valid:\n  #\n  # ```\n  # a = obj.method do |x|\n  #   x == 1\n  # end\n  #\n  # foo : Float64 | StaticArray(Float64, 10) = 0.1\n  #\n  # bar = pointerof(foo)\n  # ```\n  #\n  # This rule currently supports checking for unused:\n  # - comparison operators: `<`, `>=`, etc.\n  # - generics and unions: `String?`, `Int32 | Float64`, etc.\n  # - literals: strings, bools, chars, hashes, arrays, range, etc.\n  # - pseudo-method calls: `sizeof`, `is_a?` etc.\n  # - variable access: local, `@ivar`, `@@cvar` and `self`\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/UnusedExpression:\n  #   Enabled: true\n  # ```\n  class UnusedExpression < Base\n    properties do\n      since_version \"1.7.0\"\n      description \"Disallows unused expressions\"\n    end\n\n    COMPARISON_OPERATORS = %w[== != < <= > >= <=>]\n\n    MSG_CLASS_VAR     = \"Class variable access is unused\"\n    MSG_COMPARISON    = \"Comparison operation is unused\"\n    MSG_GENERIC       = \"Generic type is unused\"\n    MSG_UNION         = \"Union type is unused\"\n    MSG_INSTANCE_VAR  = \"Instance variable access is unused\"\n    MSG_LITERAL       = \"Literal value is unused\"\n    MSG_LOCAL_VAR     = \"Local variable access is unused\"\n    MSG_SELF          = \"`self` access is unused\"\n    MSG_PSEUDO_METHOD = \"Pseudo-method call is unused\"\n\n    def test(source : Source)\n      AST::ImplicitReturnVisitor.new(self, source)\n    end\n\n    def test(source, node : Crystal::ClassVar, in_macro : Bool)\n      # Class variables aren't supported in macros\n      return if in_macro\n\n      issue_for node, MSG_CLASS_VAR\n    end\n\n    def test(source, node : Crystal::Call, in_macro : Bool)\n      if node.name.in?(COMPARISON_OPERATORS) && node.args.size == 1\n        issue_for node, MSG_COMPARISON\n      end\n\n      if path_or_generic_union?(node)\n        issue_for node, MSG_UNION\n      end\n    end\n\n    def test(source, node : Crystal::Generic, in_macro : Bool)\n      issue_for node, MSG_GENERIC\n    end\n\n    def test(source, node : Crystal::InstanceVar, in_macro : Bool)\n      # Handle special case when using `@type` within a method body has side-effects\n      return if in_macro && node.name == \"@type\"\n\n      issue_for node, MSG_INSTANCE_VAR\n    end\n\n    def test(source, node : Crystal::RegexLiteral, in_macro : Bool)\n      issue_for node, MSG_LITERAL\n    end\n\n    def test(\n      source,\n      node : Crystal::BoolLiteral | Crystal::CharLiteral | Crystal::HashLiteral |\n             Crystal::ProcLiteral | Crystal::ArrayLiteral | Crystal::RangeLiteral |\n             Crystal::TupleLiteral | Crystal::NumberLiteral |\n             Crystal::StringLiteral | Crystal::SymbolLiteral |\n             Crystal::NamedTupleLiteral | Crystal::StringInterpolation,\n      in_macro : Bool,\n    )\n      issue_for node, MSG_LITERAL\n    end\n\n    def test(source, node : Crystal::Var, in_macro : Bool)\n      if node.name == \"self\"\n        issue_for node, MSG_SELF\n        return\n      end\n\n      # Ignore `debug` and `skip_file` macro methods\n      return if in_macro && node.name.in?(\"debug\", \"skip_file\")\n\n      issue_for node, MSG_LOCAL_VAR\n    end\n\n    def test(\n      source,\n      node : Crystal::PointerOf | Crystal::SizeOf | Crystal::InstanceSizeOf |\n             Crystal::AlignOf | Crystal::InstanceAlignOf | Crystal::OffsetOf |\n             Crystal::IsA | Crystal::NilableCast | Crystal::RespondsTo | Crystal::Not,\n      in_macro : Bool,\n    )\n      issue_for node, MSG_PSEUDO_METHOD\n    end\n\n    private def path_or_generic_union?(node : Crystal::Call) : Bool\n      node.name == \"|\" && node.args.size == 1 && !!(obj = node.obj) &&\n        valid_type_node?(obj) && valid_type_node?(node.args.first)\n    end\n\n    private def valid_type_node?(node : Crystal::ASTNode) : Bool\n      case node\n      when Crystal::Path, Crystal::Generic, Crystal::Self, Crystal::TypeOf, Crystal::Underscore\n        true\n      when Crystal::Var\n        node.name == \"self\"\n      when Crystal::Call\n        path_or_generic_union?(node)\n      else\n        false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/unused_rescue_variable.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows unused `rescue` variables.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # begin\n  #   raise MyException.new(\"OH NO!\")\n  # rescue ex : MyException\n  #   puts \"Rescued MyException\"\n  # end\n  # ```\n  #\n  # and should be written as:\n  #\n  # ```\n  # begin\n  #   raise MyException.new(\"OH NO!\")\n  # rescue MyException\n  #   puts \"Rescued MyException\"\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/UnusedRescueVariable:\n  #   Enabled: true\n  # ```\n  class UnusedRescueVariable < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.7.0\"\n      description \"Disallows unused `rescue` variables\"\n    end\n\n    MSG = \"Unused `rescue` variable `%s`\"\n\n    def test(source, node : Crystal::Rescue)\n      return unless name = node.name\n\n      visitor = VariableReferenceVisitor.new(node.body, name)\n      return if visitor.referenced?\n\n      issue_for name_location_or(node, adjust_location_column_number: {{ \"rescue \".size }}),\n        MSG % name\n    end\n  end\n\n  private class VariableReferenceVisitor < Crystal::Visitor\n    getter variable_name : String\n    getter? referenced = false\n\n    def initialize(node : Crystal::ASTNode, @variable_name)\n      node.accept(self)\n    end\n\n    def visit(node : Crystal::Var)\n      @referenced ||= true if node.name == variable_name\n      true\n    end\n\n    def visit(node : Crystal::MacroIf | Crystal::MacroFor)\n      if AST::MacroReferenceFinder.new(node, variable_name).references?\n        @referenced ||= true\n      end\n      false\n    end\n\n    # Shadowed variable usage check\n    def visit(node : Crystal::Block)\n      node.args.all? { |arg| should_visit?(arg) }\n    end\n\n    # Shadowed variable usage check\n    def visit(node : Crystal::ProcLiteral)\n      node.def.args.all? { |arg| should_visit?(arg) }\n    end\n\n    # Shadowed variable usage check\n    def visit(node : Crystal::UninitializedVar)\n      should_visit?(node.var)\n    end\n\n    # Shadowed variable usage check\n    def visit(node : Crystal::Assign | Crystal::OpAssign)\n      should_visit?(node.target)\n    end\n\n    # Shadowed variable usage check\n    def visit(node : Crystal::MultiAssign)\n      node.targets.all? { |target| should_visit?(target) }\n    end\n\n    def visit(node : Crystal::ASTNode)\n      true\n    end\n\n    private def should_visit?(node : Crystal::Var | Crystal::Arg)\n      node.name != variable_name\n    end\n\n    private def should_visit?(node : Crystal::ASTNode)\n      true\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/useless_assign.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows useless assignments.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # def method\n  #   var = 1\n  #   do_something\n  # end\n  # ```\n  #\n  # And has to be written as the following:\n  #\n  # ```\n  # def method\n  #   var = 1\n  #   do_something(var)\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/UselessAssign:\n  #   Enabled: true\n  # ```\n  class UselessAssign < Base\n    properties do\n      since_version \"0.6.0\"\n      description \"Disallows useless variable assignments\"\n    end\n\n    MSG = \"Useless assignment to variable `%s`\"\n\n    def test(source)\n      UselessAssignScopeVisitor.new self, source\n    end\n\n    def test(source, node, scope : AST::Scope)\n      return if scope.lib_def?(check_outer_scopes: true)\n\n      analyzer = AST::LivenessAnalyzer.new(scope)\n      dead_stores = analyzer.dead_stores\n\n      dead_stores.each do |assign|\n        var = assign.variable\n        next if var.special? || var.ignored? || var.used_in_macro? || var.captured_by_block?\n        next if referenced_in_inner_scope?(scope, var)\n\n        report_issue(source, assign, var)\n      end\n    end\n\n    private def referenced_in_inner_scope?(scope, var)\n      scope.inner_scopes.any?(&.references?(var))\n    end\n\n    private def report_issue(source, assign, var)\n      case target_node = assign.target_node\n      when Crystal::TypeDeclaration\n        issue_for target_node.var, MSG % var.name\n      else\n        issue_for target_node, MSG % var.name\n      end\n    end\n  end\n\n  private class UselessAssignScopeVisitor < AST::ScopeVisitor\n    getter? in_call_args = false\n\n    private def in_call_args(value = true, &)\n      prev_value = @in_call_args\n      begin\n        @in_call_args = value\n        yield\n      ensure\n        @in_call_args = prev_value\n      end\n    end\n\n    def visit(node : Crystal::Def)\n      return super unless node.name == \"->\"\n\n      in_call_args(false) do\n        node.accept_children(self)\n      end\n      false\n    end\n\n    def visit(node : Crystal::Block)\n      super\n\n      in_call_args(false) do\n        node.accept_children(self)\n      end\n      false\n    end\n\n    def visit(node : Crystal::Call)\n      return false unless super\n\n      node.obj.try &.accept(self)\n      in_call_args do\n        node.args.each &.accept(self)\n        node.named_args.try &.each &.accept(self)\n      end\n      node.block_arg.try &.accept(self)\n      node.block.try &.accept(self)\n\n      false\n    end\n\n    def visit(node : Crystal::TypeDeclaration)\n      super unless in_call_args?\n    end\n\n    private def on_assign_end(target, node)\n      super unless in_call_args?\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/useless_condition_in_when.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows useless conditions in `when` clause\n  # where it is guaranteed to always return the same result.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # case\n  # when utc?\n  #   io << \" UTC\"\n  # when local?\n  #   Format.new(\" %:z\").format(self, io) if local?\n  # end\n  # ```\n  #\n  # And has to be written as the following:\n  #\n  # ```\n  # case\n  # when utc?\n  #   io << \" UTC\"\n  # when local?\n  #   Format.new(\" %:z\").format(self, io)\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/UselessConditionInWhen:\n  #   Enabled: true\n  # ```\n  class UselessConditionInWhen < Base\n    properties do\n      since_version \"0.3.0\"\n      description \"Disallows useless conditions in `when`\"\n    end\n\n    MSG = \"Useless condition in `when` detected\"\n\n    # TODO: condition *cond* may be a complex ASTNode with\n    # useless inner conditions. We might need to improve this\n    # simple implementation in future.\n    protected def check_node(source, when_node, cond)\n      return unless cond_s = cond.to_s.presence\n      return if when_node.conds.none?(&.to_s.==(cond_s))\n\n      issue_for cond, MSG\n    end\n\n    def test(source, node : Crystal::When)\n      ConditionInWhenVisitor.new self, source, node\n    end\n\n    private class ConditionInWhenVisitor < Crystal::Visitor\n      @source : Source\n      @rule : UselessConditionInWhen\n      @parent : Crystal::When\n\n      def initialize(@rule, @source, @parent)\n        @parent.accept self\n      end\n\n      def visit(node : Crystal::If | Crystal::Unless)\n        @rule.check_node(@source, @parent, node.cond)\n        true\n      end\n\n      def visit(node : Crystal::ASTNode)\n        true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/useless_visibility_modifier.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows top level `protected` method visibility modifier,\n  # since it has no effect.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # protected def foo\n  # end\n  # ```\n  #\n  # And has to be written as follows:\n  #\n  # ```\n  # def foo\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/UselessVisibilityModifier:\n  #   Enabled: true\n  # ```\n  class UselessVisibilityModifier < Base\n    properties do\n      since_version \"1.7.0\"\n      description \"Disallows top level `protected` method visibility modifier\"\n    end\n\n    MSG = \"Useless visibility modifier\"\n\n    def test(source)\n      AST::ScopeVisitor.new self, source, skip: [\n        Crystal::ClassDef,\n        Crystal::EnumDef,\n        Crystal::ModuleDef,\n      ]\n    end\n\n    def test(source, node : Crystal::Def, scope : AST::Scope)\n      return if node.receiver || node.name == \"->\"\n      return unless node.visibility.protected?\n\n      issue_for node, MSG do |corrector|\n        corrector.remove_preceding(node, {{ \"protected \".size }})\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/void_outside_lib.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that disallows uses of `Void` outside C lib bindings.\n  # Usages of these outside of C lib bindings don't make sense,\n  # and can sometimes break the compiler. `Nil` should be used instead in these cases.\n  # `Pointer(Void)` is the only case that's allowed per this rule.\n  #\n  # These are considered invalid:\n  #\n  # ```\n  # def foo(bar : Void) : Slice(Void)?\n  # end\n  #\n  # alias Baz = Void\n  #\n  # struct Qux < Void\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/VoidOutsideLib:\n  #   Enabled: true\n  # ```\n  class VoidOutsideLib < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.7.0\"\n      description \"Disallows use of `Void` outside C lib bindings and `Pointer(Void)`\"\n    end\n\n    MSG = \"`Void` is not allowed in this context\"\n\n    def test(source)\n      PathGenericUnionVisitor.new self, source, skip: [Crystal::LibDef]\n    end\n\n    def test(source, node : Crystal::Path)\n      return unless path_named?(node, \"Void\")\n\n      issue_for node, MSG\n    end\n\n    def test(source, node : Crystal::Generic)\n      # Specifically only allow `Pointer(Void)`\n      return if path_named?(node.name, \"Pointer\") &&\n                node.type_vars.size == 1 &&\n                path_named?(node.type_vars.first, \"Void\")\n\n      if path_named?(node.name, \"Void\")\n        issue_for node, MSG, prefer_name_location: true\n      end\n\n      node.type_vars.each do |type_var|\n        test(source, type_var)\n      end\n    end\n\n    def test(source, node : Crystal::Union)\n      node.types.each do |type|\n        test(source, type)\n      end\n    end\n\n    private class PathGenericUnionVisitor < AST::NodeVisitor\n      def visit(node : Crystal::Generic | Crystal::Path | Crystal::Union)\n        return false if skip?(node)\n\n        @rule.test @source, node\n        false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/lint/whitespace_around_macro_expression.cr",
    "content": "module Ameba::Rule::Lint\n  # A rule that checks for whitespace around macro expressions.\n  #\n  # This is considered invalid:\n  #\n  # ```\n  # {{foo}}\n  # ```\n  #\n  # And it has to written as this instead:\n  #\n  # ```\n  # {{ foo }}\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Lint/WhitespaceAroundMacroExpression:\n  #   Enabled: true\n  # ```\n  class WhitespaceAroundMacroExpression < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.7.0\"\n      description \"Reports missing spaces around macro expressions\"\n    end\n\n    MSG = \"Missing spaces around macro expression\"\n\n    def test(source, node : Crystal::MacroExpression)\n      return unless node.output?\n      return unless (location = node.location) && location.same_line?(node.end_location)\n      return unless code = node_source(node, source.lines)\n      return if code.starts_with?(\"{{ \") && code.ends_with?(\" }}\")\n\n      issue_for node, MSG do |corrector|\n        corrected_code =\n          \"{{ #{code[2...-2].strip} }}\"\n\n        corrector.replace(node, corrected_code)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/metrics/cyclomatic_complexity.cr",
    "content": "module Ameba::Rule::Metrics\n  # A rule that disallows methods with a cyclomatic complexity higher than `MaxComplexity`\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Metrics/CyclomaticComplexity:\n  #   Enabled: true\n  #   MaxComplexity: 12\n  # ```\n  class CyclomaticComplexity < Base\n    properties do\n      since_version \"0.9.1\"\n      description \"Disallows methods with a cyclomatic complexity higher than `MaxComplexity`\"\n      max_complexity 12\n    end\n\n    MSG = \"Cyclomatic complexity too high [%d/%d]\"\n\n    def test(source, node : Crystal::Def)\n      complexity = AST::CountingVisitor.new(node).count\n      return unless complexity > max_complexity\n\n      issue_for node, MSG % {complexity, max_complexity}, prefer_name_location: true\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/naming/accessor_method_name.cr",
    "content": "module Ameba::Rule::Naming\n  # A rule that makes sure that accessor methods are named properly.\n  #\n  # Favour this:\n  #\n  # ```\n  # class Foo\n  #   def user\n  #     @user\n  #   end\n  #\n  #   def user=(value)\n  #     @user = value\n  #   end\n  # end\n  # ```\n  #\n  # Over this:\n  #\n  # ```\n  # class Foo\n  #   def get_user\n  #     @user\n  #   end\n  #\n  #   def set_user(value)\n  #     @user = value\n  #   end\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Naming/AccessorMethodName:\n  #   Enabled: true\n  # ```\n  class AccessorMethodName < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.6.0\"\n      description \"Makes sure that accessor methods are named properly\"\n    end\n\n    MSG = \"Favour method name `%s` over `%s`\"\n\n    def test(source, node : Crystal::ClassDef | Crystal::ModuleDef)\n      each_def_node(node) do |def_node|\n        # skip defs with explicit receiver, as they'll be handled\n        # by the `test(source, node : Crystal::Def)` overload\n        check_issue(source, def_node) unless def_node.receiver\n      end\n    end\n\n    def test(source, node : Crystal::Def)\n      # check only defs with explicit receiver (`def self.foo`)\n      check_issue(source, node) if node.receiver\n    end\n\n    private def each_def_node(node, &)\n      case body = node.body\n      when Crystal::Def\n        yield body\n      when Crystal::Expressions\n        body.expressions.each do |exp|\n          yield exp if exp.is_a?(Crystal::Def)\n        end\n      end\n    end\n\n    private def check_issue(source, node : Crystal::Def)\n      case node.name\n      when /^get_([a-z]\\w*)$/\n        return if node.block_arg || takes_arguments?(node)\n        issue_for node, MSG % {$1, node.name}, prefer_name_location: true\n      when /^set_([a-z]\\w*)$/\n        return unless node.args.size == 1\n        issue_for node, MSG % {\"#{$1}=\", node.name}, prefer_name_location: true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/naming/ascii_identifiers.cr",
    "content": "module Ameba::Rule::Naming\n  # A rule that reports non-ascii characters in identifiers.\n  #\n  # Favour this:\n  #\n  # ```\n  # class BigAwesomeWolf\n  # end\n  # ```\n  #\n  # Over this:\n  #\n  # ```\n  # class BigAwesome🐺\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Naming/AsciiIdentifiers:\n  #   Enabled: true\n  #   IgnoreSymbols: false\n  # ```\n  class AsciiIdentifiers < Base\n    properties do\n      since_version \"1.6.0\"\n      description \"Disallows non-ascii characters in identifiers\"\n      ignore_symbols false\n    end\n\n    MSG = \"Identifier contains non-ascii characters\"\n\n    def test(source, node : Crystal::Assign)\n      if (target = node.target).is_a?(Crystal::Path)\n        check_issue(source, target, target)\n      end\n      check_symbol_literal(source, node.value)\n    end\n\n    def test(source, node : Crystal::MultiAssign)\n      node.values.each do |value|\n        check_symbol_literal(source, value)\n      end\n    end\n\n    def test(source, node : Crystal::Call)\n      node.args.each do |arg|\n        check_symbol_literal(source, arg)\n      end\n      node.named_args.try &.each do |arg|\n        check_symbol_literal(source, arg.value)\n      end\n    end\n\n    def test(source, node : Crystal::Def)\n      check_issue(source, node, prefer_name_location: true)\n\n      node.args.each do |arg|\n        check_issue(source, arg, prefer_name_location: true)\n        check_symbol_literal(source, arg.default_value)\n      end\n    end\n\n    def test(source, node : Crystal::ClassVar | Crystal::InstanceVar | Crystal::Var | Crystal::Alias)\n      check_issue(source, node, prefer_name_location: true)\n    end\n\n    def test(source, node : Crystal::ClassDef | Crystal::ModuleDef | Crystal::EnumDef | Crystal::LibDef)\n      check_issue(source, node.name, node.name)\n    end\n\n    private def check_symbol_literal(source, node)\n      return if ignore_symbols?\n      return unless node.is_a?(Crystal::SymbolLiteral)\n\n      check_issue(source, node, node.value)\n    end\n\n    private def check_issue(source, location, end_location, name)\n      issue_for location, end_location, MSG unless name.to_s.ascii_only?\n    end\n\n    private def check_issue(source, node, name = node.name, *, prefer_name_location = false)\n      issue_for node, MSG, prefer_name_location: prefer_name_location unless name.to_s.ascii_only?\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/naming/binary_operator_parameter_name.cr",
    "content": "module Ameba::Rule::Naming\n  # A rule that enforces that certain binary operator methods have\n  # standardized parameter names - by default `other`.\n  #\n  # For example, this is considered valid:\n  #\n  # ```\n  # class Money\n  #   def +(other)\n  #   end\n  # end\n  # ```\n  #\n  # And this is invalid parameter name:\n  #\n  # ```\n  # class Money\n  #   def +(amount)\n  #   end\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Naming/BinaryOperatorParameterName:\n  #   Enabled: true\n  #   ExcludedOperators: [\"[]\", \"[]?\", \"[]=\", \"<<\", \">>\", \"=~\", \"!~\"]\n  #   AllowedNames: [other]\n  # ```\n  class BinaryOperatorParameterName < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.6.0\"\n      description \"Enforces that certain binary operator methods have \" \\\n                  \"their sole parameter name standardized\"\n      excluded_operators %w[[] []? []= << >> ` =~ !~]\n      allowed_names %w[other]\n    end\n\n    MSG = \"When defining the `%s` operator, name its argument %s\"\n\n    def test(source, node : Crystal::Def)\n      name = node.name\n\n      return if !operator_method?(node) || name.in?(excluded_operators)\n      return unless node.args.size == 1\n      return if (arg = node.args.first).name.in?(allowed_names)\n\n      opts =\n        allowed_names.map { |val| \"`#{val}`\" }.join(\" or \")\n\n      issue_for arg, MSG % {name, opts}, prefer_name_location: true\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/naming/block_parameter_name.cr",
    "content": "module Ameba::Rule::Naming\n  # A rule that reports non-descriptive block parameter names.\n  #\n  # Favour this:\n  #\n  # ```\n  # tokens.each { |token| token.last_accessed_at = Time.utc }\n  # ```\n  #\n  # Over this:\n  #\n  # ```\n  # tokens.each { |t| t.last_accessed_at = Time.utc }\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Naming/BlockParameterName:\n  #   Enabled: true\n  #   MinNameLength: 3\n  #   AllowNamesEndingInNumbers: true\n  #   AllowedNames: [a, b, e, i, j, k, v, x, y, k1, k2, v1, v2, db, ex, id, io, ip, op, tx, wg, ws]\n  #   ForbiddenNames: []\n  # ```\n  class BlockParameterName < Base\n    properties do\n      since_version \"1.6.0\"\n      description \"Disallows non-descriptive block parameter names\"\n      min_name_length 3\n      allow_names_ending_in_numbers true\n      allowed_names %w[a b e i j k v x y k1 k2 v1 v2 db ex id io ip op tx wg ws]\n      forbidden_names %w[]\n    end\n\n    MSG = \"Disallowed block parameter name found\"\n\n    def test(source, node : Crystal::Call)\n      node.try(&.block).try(&.args).try &.each do |arg|\n        next if valid_name?(arg.name)\n\n        issue_for arg, MSG, prefer_name_location: true\n      end\n    end\n\n    private def valid_name?(name)\n      return true if name.blank? # TODO: handle unpacked variables\n      return true if name.starts_with?('_') || name.in?(allowed_names)\n\n      return false if name.in?(forbidden_names)\n      return false if name.size < min_name_length\n      return false if name[-1].ascii_number? && !allow_names_ending_in_numbers?\n\n      true\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/naming/constant_names.cr",
    "content": "module Ameba::Rule::Naming\n  # A rule that enforces constant names to be in screaming case.\n  #\n  # For example, these constant names are considered valid:\n  #\n  # ```\n  # LUCKY_NUMBERS     = [3, 7, 11]\n  # DOCUMENTATION_URL = \"http://crystal-lang.org/docs\"\n  # ```\n  #\n  # And these are invalid names:\n  #\n  # ```\n  # myBadConstant = 1\n  # Wrong_NAME = 2\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Naming/ConstantNames:\n  #   Enabled: true\n  # ```\n  class ConstantNames < Base\n    properties do\n      since_version \"0.2.0\"\n      description \"Enforces constant names to be in screaming case\"\n    end\n\n    MSG = \"Constant name should be screaming-cased: `%s`, not `%s`\"\n\n    def test(source, node : Crystal::Assign)\n      return unless (target = node.target).is_a?(Crystal::Path)\n\n      name = target.names.last\n      expected = name.upcase\n\n      return if name.in?(expected, name.camelcase)\n\n      issue_for target, MSG % {expected, name}\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/naming/filename.cr",
    "content": "module Ameba::Rule::Naming\n  # A rule that enforces file names to be in underscored case.\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Naming/Filename:\n  #   Enabled: true\n  # ```\n  class Filename < Base\n    properties do\n      since_version \"1.6.0\"\n      description \"Enforces file names to be in underscored case\"\n    end\n\n    MSG = \"Filename should be underscore-cased: `%s`, not `%s`\"\n\n    private LOCATION = {1, 1}\n\n    def test(source : Source)\n      path = Path[source.path]\n      name = path.basename\n\n      return if (expected = name.underscore) == name\n\n      issue_for LOCATION, MSG % {expected, name}\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/naming/method_names.cr",
    "content": "module Ameba::Rule::Naming\n  # A rule that enforces method names to be in underscored case.\n  #\n  # For example, these are considered valid:\n  #\n  # ```\n  # class Person\n  #   def first_name\n  #   end\n  #\n  #   def date_of_birth\n  #   end\n  #\n  #   def homepage_url\n  #   end\n  # end\n  # ```\n  #\n  # And these are invalid method names:\n  #\n  # ```\n  # class Person\n  #   def firstName\n  #   end\n  #\n  #   def date_of_Birth\n  #   end\n  #\n  #   def homepageURL\n  #   end\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Naming/MethodNames:\n  #   Enabled: true\n  # ```\n  class MethodNames < Base\n    properties do\n      since_version \"0.2.0\"\n      description \"Enforces method names to be in underscored case\"\n    end\n\n    MSG = \"Method name should be underscore-cased: `%s`, not `%s`\"\n\n    def test(source, node : Crystal::Def)\n      name = node.name.to_s\n\n      return if (expected = name.underscore) == name\n\n      issue_for node, MSG % {expected, name}, prefer_name_location: true\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/naming/predicate_name.cr",
    "content": "module Ameba::Rule::Naming\n  # A rule that disallows tautological predicate names -\n  # meaning those that start with the prefix `is_`, except for\n  # the ones that are not valid Crystal code (e.g. `is_404?`).\n  #\n  # Favour this:\n  #\n  # ```\n  # def valid?(x)\n  # end\n  # ```\n  #\n  # Over this:\n  #\n  # ```\n  # def is_valid?(x)\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Naming/PredicateName:\n  #   Enabled: true\n  # ```\n  class PredicateName < Base\n    properties do\n      since_version \"0.2.0\"\n      description \"Disallows tautological predicate names\"\n    end\n\n    MSG = \"Favour method name `%s?` over `%s`\"\n\n    def test(source, node : Crystal::Def)\n      return unless node.name =~ /^is_([a-z]\\w*)\\??$/\n      alternative = $1\n\n      issue_for node, MSG % {alternative, node.name}, prefer_name_location: true\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/naming/query_bool_methods.cr",
    "content": "module Ameba::Rule::Naming\n  # A rule that disallows boolean properties without the `?` suffix - defined\n  # using `Object#(class_)property` or `Object#(class_)getter` macros.\n  #\n  # Favour this:\n  #\n  # ```\n  # class Person\n  #   property? deceased = false\n  #   getter? witty = true\n  # end\n  # ```\n  #\n  # Over this:\n  #\n  # ```\n  # class Person\n  #   property deceased = false\n  #   getter witty = true\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Naming/QueryBoolMethods:\n  #   Enabled: true\n  # ```\n  class QueryBoolMethods < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.4.0\"\n      description \"Reports boolean properties without the `?` suffix\"\n    end\n\n    MSG = \"Consider using `%s?` for `%s`\"\n\n    CALL_NAMES = %w[getter class_getter property class_property]\n\n    def test(source, node : Crystal::ClassDef | Crystal::ModuleDef)\n      each_call_node(node) do |exp|\n        next unless exp.name.in?(CALL_NAMES)\n\n        exp.args.each do |arg|\n          name_node, is_bool =\n            case arg\n            when Crystal::Assign\n              {arg.target, arg.value.is_a?(Crystal::BoolLiteral)}\n            when Crystal::TypeDeclaration\n              {arg.var, path_named?(arg.declared_type, \"Bool\")}\n            else\n              {nil, false}\n            end\n\n          if name_node && is_bool\n            issue_for name_node, MSG % {exp.name, name_node}\n          end\n        end\n      end\n    end\n\n    private def each_call_node(node, &)\n      case body = node.body\n      when Crystal::Call\n        yield body\n      when Crystal::Expressions\n        body.expressions.each do |exp|\n          yield exp if exp.is_a?(Crystal::Call)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/naming/rescued_exceptions_variable_name.cr",
    "content": "module Ameba::Rule::Naming\n  # A rule that makes sure that rescued exceptions variables are named as expected.\n  #\n  # For example, these are considered valid:\n  #\n  #     def foo\n  #       # potentially raising computations\n  #     rescue ex\n  #       Log.error(exception: ex) { \"Error\" }\n  #     end\n  #\n  # And these are invalid variable names:\n  #\n  #     def foo\n  #       # potentially raising computations\n  #     rescue wtf\n  #       Log.error(exception: wtf) { \"Error\" }\n  #     end\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Naming/RescuedExceptionsVariableName:\n  #   Enabled: true\n  #   AllowedNames: [e, ex, exception, err, error]\n  # ```\n  class RescuedExceptionsVariableName < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.6.0\"\n      description \"Makes sure that rescued exceptions variables are named as expected\"\n      allowed_names %w[e ex exception err error]\n    end\n\n    MSG          = \"Disallowed variable name, use one of these instead: %s\"\n    MSG_SINGULAR = \"Disallowed variable name, use %s instead\"\n\n    def test(source, node : Crystal::Rescue)\n      return unless name = node.name\n      return if name.in?(allowed_names)\n\n      message =\n        allowed_names.size == 1 ? MSG_SINGULAR : MSG\n\n      issue_for name_location_or(node, adjust_location_column_number: {{ \"rescue \".size }}),\n        message % allowed_names.map { |val| \"`#{val}`\" }.join(\", \")\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/naming/type_names.cr",
    "content": "module Ameba::Rule::Naming\n  # A rule that enforces type names in camelcase manner.\n  #\n  # For example, these are considered valid:\n  #\n  # ```\n  # class ParseError < Exception\n  # end\n  #\n  # module HTTP\n  #   class RequestHandler\n  #   end\n  # end\n  #\n  # alias NumericValue = Float32 | Float64 | Int32 | Int64\n  #\n  # lib LibYAML\n  # end\n  #\n  # struct TagDirective\n  # end\n  #\n  # enum Time::DayOfWeek\n  # end\n  # ```\n  #\n  # And these are invalid type names\n  #\n  # ```\n  # class My_class\n  # end\n  #\n  # module HTT_p\n  # end\n  #\n  # alias Numeric_value = Int32\n  #\n  # lib Lib_YAML\n  # end\n  #\n  # struct Tag_directive\n  # end\n  #\n  # enum Time_enum::Day_of_week\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Naming/TypeNames:\n  #   Enabled: true\n  # ```\n  class TypeNames < Base\n    properties do\n      since_version \"0.2.0\"\n      description \"Enforces type names in camelcase manner\"\n    end\n\n    MSG = \"Type name should be camelcased: `%s`, not `%s`\"\n\n    def test(source, node : Crystal::Alias | Crystal::ClassDef | Crystal::ModuleDef | Crystal::LibDef | Crystal::EnumDef)\n      name = node.name.to_s\n\n      return if (expected = name.camelcase) == name\n\n      issue_for node.name, MSG % {expected, name}\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/naming/variable_names.cr",
    "content": "module Ameba::Rule::Naming\n  # A rule that enforces variable names to be in underscored case.\n  #\n  # For example, these variable names are considered valid:\n  #\n  # ```\n  # var_name = 1\n  # name = 2\n  # _another_good_name = 3\n  # ```\n  #\n  # And these are invalid variable names:\n  #\n  # ```\n  # myBadNamedVar = 1\n  # wrong_Name = 2\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Naming/VariableNames:\n  #   Enabled: true\n  # ```\n  class VariableNames < Base\n    properties do\n      since_version \"0.2.0\"\n      description \"Enforces variable names to be in underscored case\"\n    end\n\n    MSG = \"Variable name should be underscore-cased: `%s`, not `%s`\"\n\n    def test(source : Source)\n      VarVisitor.new self, source\n    end\n\n    def test(source, node : Crystal::Var | Crystal::InstanceVar | Crystal::ClassVar)\n      name = node.name.to_s\n\n      return if (expected = name.underscore) == name\n\n      issue_for node, MSG % {expected, name}, prefer_name_location: true\n    end\n\n    private class VarVisitor < AST::NodeVisitor\n      private getter var_locations = [] of Crystal::Location\n\n      def visit(node : Crystal::Var)\n        !node.location.in?(var_locations) && super\n      end\n\n      def visit(node : Crystal::InstanceVar | Crystal::ClassVar)\n        if location = node.location\n          var_locations << location\n        end\n        super\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/performance/any_after_filter.cr",
    "content": "require \"./base\"\n\nmodule Ameba::Rule::Performance\n  # This rule is used to identify usage of `any?` calls that follow filters.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # [1, 2, 3].select { |e| e > 2 }.any?\n  # [1, 2, 3].reject { |e| e >= 2 }.any?\n  # ```\n  #\n  # And it should be written as this:\n  #\n  # ```\n  # [1, 2, 3].any? { |e| e > 2 }\n  # [1, 2, 3].any? { |e| e < 2 }\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Performance/AnyAfterFilter:\n  #   Enabled: true\n  #   FilterNames:\n  #     - select\n  #     - reject\n  # ```\n  class AnyAfterFilter < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.8.1\"\n      description \"Identifies usage of `any?` calls that follow filters\"\n      filter_names %w[select reject]\n    end\n\n    MSG = \"Use `any? {...}` instead of `%s {...}.any?`\"\n\n    def test(source, node : Crystal::Call)\n      return unless node.name == \"any?\" && (obj = node.obj)\n      return if has_block?(node)\n\n      return unless obj.is_a?(Crystal::Call) && has_block?(obj)\n      return unless obj.name.in?(filter_names)\n\n      issue_for name_location(obj), name_end_location(node), MSG % obj.name\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/performance/any_instead_of_present.cr",
    "content": "require \"./base\"\n\nmodule Ameba::Rule::Performance\n  # This rule is used to identify usage of arg-less `Enumerable#any?` calls.\n  #\n  # Using `Enumerable#any?` instead of `Enumerable#present?` might lead to an\n  # unexpected results (like `[nil, false].any? # => false`). In some cases\n  # it also might be less efficient, since it iterates until the block will\n  # return a _truthy_ value, instead of just checking if there's at least\n  # one value present.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # [1, 2, 3].any?\n  # ```\n  #\n  # And it should be written as this:\n  #\n  # ```\n  # [1, 2, 3].present?\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Performance/AnyInsteadOfPresent:\n  #   Enabled: true\n  # ```\n  class AnyInsteadOfPresent < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.7.0\"\n      description \"Identifies usage of arg-less `any?` calls\"\n    end\n\n    MSG = \"Use `{...}.present?` instead of `{...}.any?`\"\n\n    def test(source)\n      AST::NodeVisitor.new self, source, skip: :macro\n    end\n\n    def test(source, node : Crystal::Call)\n      return unless node.name == \"any?\" && (obj = node.obj)\n      return if has_arguments?(node) || has_block?(node)\n\n      issue_for node, MSG, prefer_name_location: true do |corrector|\n        obj_code =\n          node_source(obj, source.lines)\n\n        corrector.replace(node, \"#{obj_code}.present?\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/performance/base.cr",
    "content": "require \"../base\"\n\nmodule Ameba::Rule::Performance\n  # A general base class for performance rules.\n  abstract class Base < Ameba::Rule::Base\n    def catch(source : Source)\n      source.spec? ? source : super\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/performance/chained_call_with_no_bang.cr",
    "content": "require \"./base\"\n\nmodule Ameba::Rule::Performance\n  # This rule is used to identify usage of chained calls not utilizing\n  # the bang method variants.\n  #\n  # For example, this is considered inefficient:\n  #\n  # ```\n  # names = %w[Alice Bob]\n  # chars = names\n  #   .flat_map(&.chars)\n  #   .uniq\n  #   .sort\n  # ```\n  #\n  # And can be written as this:\n  #\n  # ```\n  # names = %w[Alice Bob]\n  # chars = names\n  #   .flat_map(&.chars)\n  #   .uniq!\n  #   .sort!\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Performance/ChainedCallWithNoBang:\n  #   Enabled: true\n  #   CallNames:\n  #     - uniq\n  #     - unstable_sort\n  #     - sort\n  #     - sort_by\n  #     - shuffle\n  #     - reverse\n  # ```\n  class ChainedCallWithNoBang < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.14.0\"\n      description \"Identifies usage of chained calls not utilizing the bang method variants\"\n\n      # All of those have bang method variants returning `self`\n      # and are not modifying the receiver type (like `compact` does),\n      # thus are safe to switch to the bang variant.\n      call_names %w[uniq unstable_sort sort sort_by shuffle reverse]\n    end\n\n    MSG = \"Use bang method variant `%s!` after chained `%s` call\"\n\n    # All these methods allocate a new object\n    ALLOCATING_METHOD_NAMES = %w[\n      keys values values_at map map_with_index flat_map compact_map\n      flatten compact select reject sample group_by chunks tally merge\n      combinations repeated_combinations permutations repeated_permutations\n      transpose invert split chars lines captures named_captures clone\n    ]\n\n    def test(source)\n      AST::NodeVisitor.new self, source, skip: :macro\n    end\n\n    def test(source, node : Crystal::Call)\n      return unless (obj = node.obj).is_a?(Crystal::Call)\n      return unless node.name.in?(call_names)\n      return unless obj.name.in?(call_names) || obj.name.in?(ALLOCATING_METHOD_NAMES)\n\n      if end_location = name_end_location(node)\n        issue_for node, MSG % {node.name, obj.name}, prefer_name_location: true do |corrector|\n          corrector.insert_after(end_location, '!')\n        end\n      else\n        issue_for node, MSG % {node.name, obj.name}, prefer_name_location: true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/performance/compact_after_map.cr",
    "content": "require \"./base\"\n\nmodule Ameba::Rule::Performance\n  # This rule is used to identify usage of `compact` calls that follow `map`.\n  #\n  # For example, this is considered inefficient:\n  #\n  # ```\n  # %w[Alice Bob].map(&.match(/^A./)).compact\n  # ```\n  #\n  # And can be written as this:\n  #\n  # ```\n  # %w[Alice Bob].compact_map(&.match(/^A./))\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Performance/CompactAfterMap:\n  #   Enabled: true\n  # ```\n  class CompactAfterMap < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.14.0\"\n      description \"Identifies usage of `compact` calls that follow `map`\"\n    end\n\n    MSG = \"Use `compact_map {...}` instead of `map {...}.compact`\"\n\n    def test(source)\n      AST::NodeVisitor.new self, source, skip: :macro\n    end\n\n    def test(source, node : Crystal::Call)\n      return unless node.name == \"compact\" && (obj = node.obj)\n      return unless obj.is_a?(Crystal::Call) && has_block?(obj)\n      return unless obj.name == \"map\"\n\n      issue_for name_location(obj), name_end_location(node), MSG\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/performance/excessive_allocations.cr",
    "content": "require \"./base\"\n\nmodule Ameba::Rule::Performance\n  # This rule is used to identify excessive collection allocations,\n  # that can be avoided by using `each_<member>` instead of `<collection>.each`.\n  #\n  # For example, this is considered inefficient:\n  #\n  # ```\n  # \"Alice\".chars.each { |c| puts c }\n  # \"Alice\\nBob\".lines.each { |l| puts l }\n  # ```\n  #\n  # And can be written as this:\n  #\n  # ```\n  # \"Alice\".each_char { |c| puts c }\n  # \"Alice\\nBob\".each_line { |l| puts l }\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Performance/ExcessiveAllocations:\n  #   Enabled: true\n  #   CallNames:\n  #     codepoints: each_codepoint\n  #     graphemes: each_grapheme\n  #     chars: each_char\n  #     lines: each_line\n  # ```\n  class ExcessiveAllocations < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.5.0\"\n      description \"Identifies usage of excessive collection allocations\"\n      call_names({\n        \"codepoints\" => \"each_codepoint\",\n        \"graphemes\"  => \"each_grapheme\",\n        \"chars\"      => \"each_char\",\n        \"lines\"      => \"each_line\",\n        # \"keys\"       => \"each_key\",\n        # \"values\"     => \"each_value\",\n        # \"children\"   => \"each_child\",\n      })\n    end\n\n    MSG = \"Use `%s {...}` instead of `%s.each {...}` to avoid excessive allocation\"\n\n    def test(source)\n      AST::NodeVisitor.new self, source, skip: :macro\n    end\n\n    def test(source, node : Crystal::Call)\n      return unless node.name == \"each\"\n      return if has_arguments?(node)\n\n      return unless (obj = node.obj).is_a?(Crystal::Call)\n      return if has_arguments?(obj) || has_block?(obj)\n\n      return unless method = call_names[obj.name]?\n\n      return unless name_location = name_location(obj)\n      return unless end_location = name_end_location(node)\n\n      msg = MSG % {method, obj.name}\n\n      issue_for name_location, end_location, msg do |corrector|\n        corrector.replace(name_location, end_location, method)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/performance/first_last_after_filter.cr",
    "content": "require \"./base\"\n\nmodule Ameba::Rule::Performance\n  # This rule is used to identify usage of `first/last/first?/last?` calls that follow filters.\n  #\n  # For example, this is considered inefficient:\n  #\n  # ```\n  # [-1, 0, 1, 2].select { |e| e > 0 }.first?\n  # [-1, 0, 1, 2].select { |e| e > 0 }.last?\n  # ```\n  #\n  # And can be written as this:\n  #\n  # ```\n  # [-1, 0, 1, 2].find { |e| e > 0 }\n  # [-1, 0, 1, 2].reverse_each.find { |e| e > 0 }\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Performance/FirstLastAfterFilter:\n  #   Enabled: true\n  #   FilterNames:\n  #     - select\n  # ```\n  class FirstLastAfterFilter < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.8.1\"\n      description \"Identifies usage of `first/last/first?/last?` calls that follow filters\"\n      filter_names %w[select]\n    end\n\n    MSG         = \"Use `find {...}` instead of `%s {...}.%s`\"\n    MSG_REVERSE = \"Use `reverse_each.find {...}` instead of `%s {...}.%s`\"\n\n    CALL_NAMES = %w[first last first? last?]\n\n    def test(source)\n      AST::NodeVisitor.new self, source, skip: :macro\n    end\n\n    def test(source, node : Crystal::Call)\n      return unless node.name.in?(CALL_NAMES)\n      return if has_arguments?(node) || has_block?(node)\n\n      return unless (obj = node.obj).is_a?(Crystal::Call)\n      return unless obj.name.in?(filter_names) && has_block?(obj)\n\n      message = node.name.includes?(CALL_NAMES.first) ? MSG : MSG_REVERSE\n\n      issue_for name_location(obj), name_end_location(node),\n        message % {obj.name, node.name}\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/performance/flatten_after_map.cr",
    "content": "require \"./base\"\n\nmodule Ameba::Rule::Performance\n  # This rule is used to identify usage of `flatten` calls that follow `map`.\n  #\n  # For example, this is considered inefficient:\n  #\n  # ```\n  # %w[Alice Bob].map(&.chars).flatten\n  # ```\n  #\n  # And can be written as this:\n  #\n  # ```\n  # %w[Alice Bob].flat_map(&.chars)\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Performance/FlattenAfterMap:\n  #   Enabled: true\n  # ```\n  class FlattenAfterMap < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.14.0\"\n      description \"Identifies usage of `flatten` calls that follow `map`\"\n    end\n\n    MSG = \"Use `flat_map {...}` instead of `map {...}.flatten`\"\n\n    def test(source)\n      AST::NodeVisitor.new self, source, skip: :macro\n    end\n\n    def test(source, node : Crystal::Call)\n      return unless node.name == \"flatten\" && (obj = node.obj)\n      return unless obj.is_a?(Crystal::Call) && has_block?(obj)\n      return unless obj.name == \"map\"\n\n      issue_for name_location(obj), name_end_location(node), MSG\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/performance/map_instead_of_block.cr",
    "content": "require \"./base\"\n\nmodule Ameba::Rule::Performance\n  # This rule is used to identify usage of `sum/product` calls\n  # that follow `map`.\n  #\n  # For example, this is considered inefficient:\n  #\n  # ```\n  # (1..3).map(&.*(2)).sum\n  # ```\n  #\n  # And can be written as this:\n  #\n  # ```\n  # (1..3).sum(&.*(2))\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Performance/MapInsteadOfBlock:\n  #   Enabled: true\n  # ```\n  class MapInsteadOfBlock < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.14.0\"\n      description \"Identifies usage of `sum/product` calls that follow `map`\"\n    end\n\n    MSG = \"Use `%s {...}` instead of `map {...}.%s`\"\n\n    CALL_NAMES = %w[sum product]\n\n    def test(source)\n      AST::NodeVisitor.new self, source, skip: :macro\n    end\n\n    def test(source, node : Crystal::Call)\n      return unless node.name.in?(CALL_NAMES) && (obj = node.obj)\n      return unless obj.is_a?(Crystal::Call) && obj.block\n      return unless obj.name == \"map\"\n\n      issue_for name_location(obj), name_end_location(node),\n        MSG % {node.name, node.name}\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/performance/minmax_after_map.cr",
    "content": "require \"./base\"\n\nmodule Ameba::Rule::Performance\n  # This rule is used to identify usage of `min/max/minmax` calls that follow `map`.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # %w[Alice Bob].map(&.size).min\n  # %w[Alice Bob].map(&.size).max\n  # %w[Alice Bob].map(&.size).minmax\n  # ```\n  #\n  # And it should be written as this:\n  #\n  # ```\n  # %w[Alice Bob].min_of(&.size)\n  # %w[Alice Bob].max_of(&.size)\n  # %w[Alice Bob].minmax_of(&.size)\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Performance/MinMaxAfterMap:\n  #   Enabled: true\n  # ```\n  class MinMaxAfterMap < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.5.0\"\n      description \"Identifies usage of `min/max/minmax` calls that follow `map`\"\n    end\n\n    MSG        = \"Use `%s {...}` instead of `map {...}.%s`\"\n    CALL_NAMES = %w[min min? max max? minmax minmax?]\n\n    def test(source)\n      AST::NodeVisitor.new self, source, skip: :macro\n    end\n\n    def test(source, node : Crystal::Call)\n      return unless node.name.in?(CALL_NAMES)\n      return if has_arguments?(node) || has_block?(node)\n\n      return unless (obj = node.obj).is_a?(Crystal::Call)\n      return unless obj.name == \"map\" && has_block?(obj)\n      return if has_arguments?(obj)\n\n      return unless name_location = name_location(obj)\n      return unless end_location = name_end_location(node)\n\n      of_name = node.name.sub(/(.+?)(\\?)?$/, \"\\\\1_of\\\\2\")\n      message = MSG % {of_name, node.name}\n\n      issue_for name_location, end_location, message do |corrector|\n        next unless node_name_location = name_location(node)\n\n        # TODO: switching the order of the below calls breaks the corrector\n        corrector.replace(\n          name_location,\n          name_location.adjust(column_number: {{ \"map\".size - 1 }}),\n          of_name\n        )\n        corrector.remove(\n          node_name_location.adjust(column_number: -1),\n          end_location\n        )\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/performance/size_after_filter.cr",
    "content": "require \"./base\"\n\nmodule Ameba::Rule::Performance\n  # This rule is used to identify usage of `size` calls that follow filter.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # [1, 2, 3].select { |e| e > 2 }.size\n  # [1, 2, 3].reject { |e| e < 2 }.size\n  # [1, 2, 3].select(&.< 2).size\n  # [0, 1, 2].select(&.zero?).size\n  # [0, 1, 2].reject(&.zero?).size\n  # ```\n  #\n  # And it should be written as this:\n  #\n  # ```\n  # [1, 2, 3].count { |e| e > 2 }\n  # [1, 2, 3].count { |e| e >= 2 }\n  # [1, 2, 3].count(&.< 2)\n  # [0, 1, 2].count(&.zero?)\n  # [0, 1, 2].count(&.!= 0)\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Performance/SizeAfterFilter:\n  #   Enabled: true\n  #   FilterNames:\n  #     - select\n  #     - reject\n  # ```\n  class SizeAfterFilter < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.8.1\"\n      description \"Identifies usage of `size` calls that follow filter\"\n      filter_names %w[select reject]\n    end\n\n    MSG = \"Use `count {...}` instead of `%s {...}.size`\"\n\n    def test(source)\n      AST::NodeVisitor.new self, source, skip: :macro\n    end\n\n    def test(source, node : Crystal::Call)\n      return unless node.name == \"size\" && (obj = node.obj)\n      return unless obj.is_a?(Crystal::Call) && has_block?(obj)\n      return unless obj.name.in?(filter_names)\n\n      issue_for name_location(obj), name_end_location(node), MSG % obj.name\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/performance/times_map.cr",
    "content": "require \"./base\"\n\nmodule Ameba::Rule::Performance\n  # This rule is used to identify usage of `times.map { ... }.to_a` calls.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # 5.times.map { |i| i * 2 }.to_a\n  # ```\n  #\n  # And it should be written as this:\n  #\n  # ```\n  # Array.new(5) { |i| i * 2 }\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Performance/TimesMap:\n  #   Enabled: true\n  # ```\n  class TimesMap < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.7.0\"\n      description \"Identifies usage of `times.map { ... }.to_a` calls\"\n    end\n\n    MSG = \"Use `Array.new(%1$s) {...}` instead of `%1$s.times.map {...}.to_a`\"\n\n    def test(source)\n      AST::NodeVisitor.new self, source, skip: :macro\n    end\n\n    # ameba:disable Metrics/CyclomaticComplexity\n    def test(source, node : Crystal::Call)\n      return if has_block?(node)\n      return unless node.name == \"to_a\" && (obj = node.obj)\n\n      return unless obj.is_a?(Crystal::Call) && has_block?(obj)\n      return unless obj.name == \"map\" && (obj2 = obj.obj)\n\n      return if !obj2.is_a?(Crystal::Call) || has_block?(obj2)\n      return unless obj2.name == \"times\" && (obj3 = obj2.obj)\n\n      return unless location = obj3.location\n      return unless end_location = name_end_location(node)\n\n      issue_for location, end_location, MSG % obj3 do |corrector|\n        corrected_code =\n          case\n          when block = obj.block\n            block_code =\n              node_source(block, source.lines)\n\n            \"Array.new(%s) %s\" % {obj3, block_code}\n          when block_arg = obj.block_arg\n            \"Array.new(%s, &%s)\" % {obj3, block_arg}\n          end\n        next unless corrected_code\n\n        corrector.replace(location, end_location, corrected_code)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/array_literal_syntax.cr",
    "content": "module Ameba::Rule::Style\n  # Encourages the use of `Array(T).new` syntax for creating an array over `[] of T`.\n  #\n  # Favour this:\n  #\n  # ```\n  # Array(Int32 | String?).new\n  # ```\n  #\n  # Over this:\n  #\n  # ```\n  # [] of Int32 | String?\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/ArrayLiteralSyntax:\n  #   Enabled: true\n  # ```\n  class ArrayLiteralSyntax < Base\n    properties do\n      since_version \"1.7.0\"\n      enabled false\n      description \"Encourages the use of `Array(T).new` over `[] of T`\"\n    end\n\n    MSG = \"Use `Array(%s).new` for creating an empty array\"\n\n    def test(source)\n      AST::NodeVisitor.new self, source, skip: :macro\n    end\n\n    def test(source, node : Crystal::ArrayLiteral)\n      return unless node.elements.empty? && (array_type = node.of)\n\n      issue_for node, MSG % array_type do |corrector|\n        corrector.replace(node, \"Array(#{array_type}).new\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/call_parentheses.cr",
    "content": "module Ameba::Rule::Style\n  # A rule that enforces usage of parentheses in method or macro calls.\n  #\n  # For example, this (and all of its variants) is considered invalid:\n  #\n  # ```\n  # user.update name: \"John\", age: 30\n  # ```\n  #\n  # And should be replaced by the following:\n  #\n  # ```\n  # user.update(name: \"John\", age: 30)\n  # ```\n  #\n  # ### Options\n  #\n  # - `ExcludeTypeDeclarations` — controls whether calls with type declarations should be checked.\n  # - `ExcludeHeredocs` — controls whether calls with heredoc arguments should be checked.\n  # - `ExcludedToplevelCallNames` — contains a list of top-level method names that should not be checked.\n  # - `ExcludedCallNames` — contains a list of non-top-level method names that should not be checked.\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/CallParentheses:\n  #   Enabled: true\n  #   ExcludeTypeDeclarations: true\n  #   ExcludeHeredocs: false\n  #   ExcludedToplevelCallNames: [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]\n  #   ExcludedCallNames: [should, should_not]\n  # ```\n  class CallParentheses < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.7.0\"\n      description \"Enforces usage of parentheses in method calls\"\n      enabled false\n      exclude_type_declarations true\n      exclude_heredocs false\n      excluded_toplevel_call_names %w[\n        spawn raise super previous_def exit abort sleep\n        print printf puts p p! pp pp! record\n        class_getter class_getter? class_getter!\n        class_property class_property? class_property!\n        class_setter getter getter? getter!\n        property property? property! setter\n        def_equals_and_hash def_equals def_hash\n        delegate forward_missing_to\n        describe context it pending fail\n        use_json_discriminator\n      ]\n      excluded_call_names %w[should should_not]\n    end\n\n    MSG = \"Missing parentheses in method call\"\n\n    def test(source)\n      super unless source.ecr?\n    end\n\n    # ameba:disable Metrics/CyclomaticComplexity\n    def test(source, node : Crystal::Call)\n      return if node.args_in_brackets? ||\n                node.has_parentheses? ||\n                node.expansion?\n\n      return if setter_method?(node) ||\n                operator_method?(node)\n\n      return if exclude_type_declarations? &&\n                node.args.any?(Crystal::TypeDeclaration)\n\n      heredoc_arg = find_heredoc_arg(node, source)\n      return if exclude_heredocs? && heredoc_arg\n\n      return if !node.obj && node.name.in?(excluded_toplevel_call_names)\n      return if node.obj && node.name.in?(excluded_call_names)\n\n      return unless node.block_arg ||\n                    has_arguments?(node) ||\n                    has_short_block?(node, source.lines)\n\n      location, end_location =\n        replacement_locations(node, heredoc_arg, source.lines)\n\n      if location && end_location\n        line = source.lines[location.line_number - 1]\n        rest = line[(location.column_number - 1)..-1]\n\n        location_end = location\n        location_end = location.with(column_number: line.size) if rest.strip == \"\\\\\"\n\n        issue_for node, MSG do |corrector|\n          corrector.replace(location, location_end, \"(\")\n          corrector.insert_before(end_location, \")\")\n        end\n      else\n        issue_for node, MSG\n      end\n    end\n\n    # Returns the replacement start and end locations for the call *node*.\n    #\n    #     foo.bar baz: 42 do |what, is|\n    #            ^--- x  ^--- y\n    #       # ...\n    #     end\n    #\n    private def replacement_locations(node, heredoc_arg, source_lines)\n      location = name_end_location(node)\n\n      end_location =\n        case\n        when block = node.block\n          if short_block?(block, source_lines)\n            block.body.end_location\n          else\n            block.location.try(&.adjust(column_number: -2))\n          end\n        when heredoc_arg\n          if arg_location = heredoc_arg.location\n            if line = source_lines[arg_location.line_number - 1]?\n              if line.rstrip.ends_with?(',')\n                node.end_location\n              else\n                arg_location.with(column_number: line.size)\n              end\n            end\n          end\n        else\n          if node_end_location = node.end_location\n            # handle edge-cases in which the end location is not valid\n            node_end_location if node_end_location.line_number.positive? &&\n                                 node_end_location.column_number.positive?\n          end\n        end\n\n      location &&= location.adjust(column_number: 1)\n      end_location &&= end_location.adjust(column_number: 1)\n\n      {location, end_location}\n    end\n\n    private def find_heredoc_arg(node : Crystal::Call, source)\n      node.named_args.try &.reverse_each.find { |arg| find_heredoc_arg(arg.value, source) } ||\n        node.args.reverse_each.find { |arg| find_heredoc_arg(arg, source) }\n    end\n\n    private def find_heredoc_arg(node, source)\n      heredoc?(node, source)\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/elsif.cr",
    "content": "module Ameba::Rule::Style\n  # A rule that encourages the use of `case/when` syntax over `if/elsif`.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # if foo\n  #   do_something_foo\n  # elsif bar\n  #   do_something_bar\n  # end\n  # ```\n  #\n  # And should be replaced by the following:\n  #\n  # ```\n  # case\n  # when foo\n  #   do_something_foo\n  # when bar\n  #   do_something_bar\n  # end\n  # ```\n  #\n  # If `IgnoreSuffix` option is set to `true` (which is the default),\n  # the suffix `if` nodes will be ignored, i.e., considered valid.\n  #\n  # ```\n  # if foo\n  #   do_something\n  # else\n  #   do_something_else if bar # <- suffix if node\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/Elsif:\n  #   Enabled: true\n  #   IgnoreSuffix: true\n  #   MaxBranches: 0\n  # ```\n  class Elsif < Base\n    properties do\n      since_version \"1.7.0\"\n      description \"Encourages the use of `case/when` syntax over `if/elsif`\"\n      enabled false\n      ignore_suffix true\n      max_branches 0\n    end\n\n    MSG = \"Prefer `case/when` over `if/elsif`\"\n\n    def test(source)\n      AST::ElseIfAwareNodeVisitor.new self, source, skip: :macro,\n        exclude_suffix: ignore_suffix?\n    end\n\n    def test(source, node : Crystal::If, ifs : Enumerable(Crystal::If))\n      return if valid_branches_amount?(ifs)\n      issue_for node, MSG\n    end\n\n    private def valid_branches_amount?(ifs)\n      # 1st item is always the `if` branch\n      ifs.size - 1 <= max_branches\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/guard_clause.cr",
    "content": "module Ameba::Rule::Style\n  # Use a guard clause instead of wrapping the code inside a conditional\n  # expression\n  #\n  # ```\n  # # bad\n  # def test\n  #   if something\n  #     work\n  #   end\n  # end\n  #\n  # # good\n  # def test\n  #   return unless something\n  #\n  #   work\n  # end\n  #\n  # # also good\n  # def test\n  #   work if something\n  # end\n  #\n  # # bad\n  # if something\n  #   raise \"exception\"\n  # else\n  #   ok\n  # end\n  #\n  # # good\n  # raise \"exception\" if something\n  # ok\n  #\n  # # bad\n  # if something\n  #   foo || raise(\"exception\")\n  # else\n  #   ok\n  # end\n  #\n  # # good\n  # foo || raise(\"exception\") if something\n  # ok\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/GuardClause:\n  #   Enabled: true\n  # ```\n  class GuardClause < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.0.0\"\n      enabled false\n      description \"Check for conditionals that can be replaced with guard clauses\"\n    end\n\n    MSG = \"Use a guard clause (`%s`) instead of wrapping the \" \\\n          \"code inside a conditional expression\"\n\n    def test(source)\n      AST::NodeVisitor.new self, source, skip: [\n        Crystal::Assign,\n      ]\n    end\n\n    def test(source, node : Crystal::Def)\n      final_expression =\n        if (body = node.body).is_a?(Crystal::Expressions)\n          body.last\n        else\n          body\n        end\n\n      case final_expression\n      when Crystal::If, Crystal::Unless\n        check_ending_if(source, final_expression)\n      end\n    end\n\n    def test(source, node : Crystal::If | Crystal::Unless)\n      return if accepted_form?(source, node, ending: false)\n\n      case\n      when guard_clause = guard_clause(node.then)\n        parent, conditional_keyword = node.then, keyword(node)\n      when guard_clause = guard_clause(node.else)\n        parent, conditional_keyword = node.else, opposite_keyword(node)\n      end\n\n      return unless guard_clause && parent && conditional_keyword\n\n      guard_clause_source = guard_clause_source(source, guard_clause, parent)\n      report_issue(source, node, guard_clause_source, conditional_keyword)\n    end\n\n    private def check_ending_if(source, node)\n      return if accepted_form?(source, node, ending: true)\n\n      report_issue(source, node, \"return\", opposite_keyword(node))\n    end\n\n    private def report_issue(source, node, scope_exiting_keyword, conditional_keyword)\n      return unless keyword_loc = node.location\n      return unless cond_code = node_source(node.cond, source.lines)\n\n      keyword_end_loc = keyword_loc.adjust(column_number: keyword(node).size - 1)\n\n      example = \"#{scope_exiting_keyword} #{conditional_keyword} #{cond_code}\"\n      # TODO: check if example is too long for single line\n\n      if node.else.is_a?(Crystal::Nop)\n        return unless end_end_loc = node.end_location\n\n        end_loc = end_end_loc.adjust(column_number: {{ 1 - \"end\".size }})\n\n        issue_for keyword_loc, keyword_end_loc, MSG % example do |corrector|\n          replacement = \"#{scope_exiting_keyword} #{conditional_keyword}\"\n\n          corrector.replace(keyword_loc, keyword_end_loc, replacement)\n          corrector.remove(end_loc, end_end_loc)\n        end\n      else\n        issue_for keyword_loc, keyword_end_loc, MSG % example\n      end\n    end\n\n    private def keyword(node : Crystal::If)\n      \"if\"\n    end\n\n    private def keyword(node : Crystal::Unless)\n      \"unless\"\n    end\n\n    private def opposite_keyword(node : Crystal::If)\n      \"unless\"\n    end\n\n    private def opposite_keyword(node : Crystal::Unless)\n      \"if\"\n    end\n\n    private def accepted_form?(source, node, ending)\n      return true if node.is_a?(Crystal::If) && node.ternary?\n      return true unless cond_loc = node.cond.location\n      return true unless cond_loc.same_line?(node.cond.end_location)\n      return true unless (then_loc = node.then.location).nil? || cond_loc < then_loc\n\n      if ending\n        !node.else.is_a?(Crystal::Nop)\n      else\n        return true if node.else.is_a?(Crystal::Nop)\n        return true unless code = node_source(node, source.lines)\n\n        code.starts_with?(\"elsif\")\n      end\n    end\n\n    private def guard_clause(node)\n      node = node.right if node.is_a?(Crystal::BinaryOp)\n\n      return unless location = node.location\n      return unless location.same_line?(node.end_location)\n\n      case node\n      when Crystal::Call\n        node if node.obj.nil? && node.name == \"raise\"\n      when Crystal::Return, Crystal::Break, Crystal::Next\n        node\n      end\n    end\n\n    private def guard_clause_source(source, guard_clause, parent)\n      node = parent.is_a?(Crystal::BinaryOp) ? parent : guard_clause\n\n      node_source(node, source.lines)\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/hash_literal_syntax.cr",
    "content": "module Ameba::Rule::Style\n  # Encourages the use of `Hash(K, V).new` syntax for creating a hash over `{} of K => V`.\n  #\n  # Favour this:\n  #\n  # ```\n  # Hash(Int32, String?).new\n  # ```\n  #\n  # Over this:\n  #\n  # ```\n  # {} of Int32 => String?\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/HashLiteralSyntax:\n  #   Enabled: true\n  # ```\n  class HashLiteralSyntax < Base\n    properties do\n      since_version \"1.7.0\"\n      enabled false\n      description \"Encourages the use of `Hash(K, V).new` over `{} of K => V`\"\n    end\n\n    MSG = \"Use `Hash(%s, %s).new` for creating an empty hash\"\n\n    def test(source)\n      AST::NodeVisitor.new self, source, skip: :macro\n    end\n\n    def test(source, node : Crystal::HashLiteral)\n      return unless node.entries.empty? && (hash_type = node.of)\n\n      issue_for node, MSG % {hash_type.key, hash_type.value} do |corrector|\n        corrector.replace(node, \"Hash(#{hash_type.key}, #{hash_type.value}).new\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/heredoc_escape.cr",
    "content": "module Ameba::Rule::Style\n  # A rule that enforces heredoc variant that escapes interpolation or control\n  # chars in a heredoc body. The opposite is enforced too - i.e. regular heredoc\n  # variant that doesn't escape interpolation or control chars in a heredoc body,\n  # when there is no need to escape it.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # <<-DOC\n  #   This is an escaped \\#{:interpolated} string \\\\n\n  #   DOC\n  # ```\n  #\n  # And should be written as:\n  #\n  # ```\n  # <<-'DOC'\n  #   This is an escaped #{:interpolated} string \\n\n  #   DOC\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/HeredocEscape:\n  #   Enabled: true\n  # ```\n  class HeredocEscape < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.7.0\"\n      description \"Recommends using the heredoc variant that escapes interpolation or control chars in a heredoc body\"\n    end\n\n    MSG_ESCAPE_NEEDED     = \"Use an escaped heredoc marker: `<<-'%s'`\"\n    MSG_ESCAPE_NOT_NEEDED = \"Use an unescaped heredoc marker: `<<-%s`\"\n\n    ESCAPE_SEQUENCE_PATTERN =\n      /\\\\(?:[abefnrtv]|[CdDhHRsSvVwWX]|[0-7]{1,3}|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|u\\{[0-9a-fA-F]{1,6}\\})/\n\n    def test(source, node : Crystal::StringInterpolation)\n      # Heredocs without interpolations have always size of 1\n      return unless node.expressions.size == 1\n      return unless expr = node.expressions.first.as?(Crystal::StringLiteral)\n\n      return unless code = node_source(node, source.lines)\n      return unless code.starts_with?(\"<<-\")\n\n      body = code.lines[1..-2].join('\\n')\n\n      if code.starts_with?(\"<<-'\")\n        return if has_escape_sequence?(expr.value) || has_escaped_escape_sequence?(body)\n\n        marker = code.lchop(\"<<-'\").match!(/^(\\w+)/)[1]\n        msg = MSG_ESCAPE_NOT_NEEDED % marker\n      else\n        return if !has_escape_sequence?(expr.value) || has_escape_sequence?(body)\n\n        marker = code.lchop(\"<<-\").match!(/^(\\w+)/)[1]\n        msg = MSG_ESCAPE_NEEDED % marker\n      end\n\n      issue_for node, msg\n    end\n\n    private def has_escape_sequence?(value : String)\n      value.matches? /(?<!\\\\)(?:#\\{|#{ESCAPE_SEQUENCE_PATTERN})/,\n        options: :no_utf_check\n    end\n\n    private def has_escaped_escape_sequence?(value : String)\n      value.matches? /(?<!\\\\)(?:\\\\)+(?:#\\{|#{ESCAPE_SEQUENCE_PATTERN})/,\n        options: :no_utf_check\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/heredoc_indent.cr",
    "content": "module Ameba::Rule::Style\n  # A rule that enforces _heredoc_ bodies be indented one level above the\n  # indentation of the line they're used on.\n  #\n  # For example, this is considered invalid:\n  #\n  #     <<-HEREDOC\n  #       hello world\n  #     HEREDOC\n  #\n  #       <<-HEREDOC\n  #     hello world\n  #     HEREDOC\n  #\n  # And should be written as:\n  #\n  #     <<-HEREDOC\n  #         hello world\n  #       HEREDOC\n  #\n  #     <<-HEREDOC\n  #       hello world\n  #       HEREDOC\n  #\n  # The `IndentBy` configuration option changes the enforced indentation level\n  # of the _heredoc_.\n  #\n  # If `BodyAutoDedent` is enabled (default), the body of the _heredoc_ will be\n  # automatically dedented to the minimum indentation level of the body lines.\n  #\n  # For example:\n  #\n  # ```\n  # <<-HEREDOC\n  #   <article>\n  #     ...\n  #   </article>\n  # HEREDOC\n  # ```\n  #\n  # Will be automatically dedented to:\n  #\n  # ```\n  # <<-HEREDOC\n  #   <article>\n  #     ...\n  #   </article>\n  #   HEREDOC\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/HeredocIndent:\n  #   Enabled: true\n  #   IndentBy: 2\n  #   BodyAutoDedent: true\n  # ```\n  class HeredocIndent < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.7.0\"\n      description \"Recommends heredoc bodies are indented consistently\"\n      indent_by 2\n      body_auto_dedent true\n    end\n\n    MSG = \"Heredoc body should be indented by %d spaces\"\n\n    def test(source, node : Crystal::StringInterpolation)\n      return unless location = node.location\n\n      return unless node_source = node_source(node, source.lines)\n      return unless node_source.starts_with?(\"<<-\")\n\n      correct_indent = line_indent(source, location) + indent_by\n      heredoc_indent = node.heredoc_indent\n\n      return if heredoc_indent == correct_indent\n\n      issue_for node, MSG % indent_by do |corrector|\n        source_lines = node_source.lines\n        if body_auto_dedent?\n          body_dedent =\n            source_lines[1...-1]\n              .reject!(&.empty?)\n              .min_of?(&.each_char.take_while(&.whitespace?).size)\n        end\n        body_dedent ||= heredoc_indent\n\n        corrected_code = source_lines\n          .map_with_index! do |line, idx|\n            # ignore 1st line containing the marker\n            next line if idx.zero? || line.empty?\n\n            dedent =\n              idx == source_lines.size - 1 ? heredoc_indent : body_dedent\n\n            \"#{\" \" * correct_indent}#{line[dedent..]}\"\n          end\n          .join('\\n')\n\n        corrector.replace(node, corrected_code)\n      end\n    end\n\n    private def line_indent(source, location) : Int32\n      line = source.lines[location.line_number - 1]\n      line.size - line.lstrip.size\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/is_a_filter.cr",
    "content": "module Ameba::Rule::Style\n  # This rule is used to identify usage of `is_a?/nil?` calls within filters.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # matches = %w[Alice Bob].map(&.match(/^A./))\n  #\n  # matches.any?(&.is_a?(Regex::MatchData)) # => true\n  # matches.one?(&.nil?)                    # => true\n  #\n  # typeof(matches.reject(&.nil?))                    # => Array(Regex::MatchData | Nil)\n  # typeof(matches.select(&.is_a?(Regex::MatchData))) # => Array(Regex::MatchData | Nil)\n  # ```\n  #\n  # And it should be written as this:\n  #\n  # ```\n  # matches = %w[Alice Bob].map(&.match(/^A./))\n  #\n  # matches.any?(Regex::MatchData) # => true\n  # matches.one?(Nil)              # => true\n  #\n  # typeof(matches.reject(Nil))              # => Array(Regex::MatchData)\n  # typeof(matches.select(Regex::MatchData)) # => Array(Regex::MatchData)\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/IsAFilter:\n  #   Enabled: true\n  #   FilterNames:\n  #     - select\n  #     - reject\n  #     - any?\n  #     - all?\n  #     - none?\n  #     - one?\n  # ```\n  class IsAFilter < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.14.0\"\n      description \"Identifies usage of `is_a?/nil?` calls within filters\"\n      filter_names %w[select reject any? all? none? one?]\n    end\n\n    MSG = \"Use `%s` instead of `%s`\"\n\n    OLD = \"%s {...}\"\n    NEW = \"%s(%s)\"\n\n    def test(source)\n      AST::NodeVisitor.new self, source, skip: :macro\n    end\n\n    def test(source, node : Crystal::Call)\n      return unless node.name.in?(filter_names)\n      return unless location = name_location(node)\n      return unless (block = node.block) && block.args.size == 1\n      return unless (body = block.body).is_a?(Crystal::IsA)\n      return unless (path = body.const).is_a?(Crystal::Path)\n      return unless body.obj.is_a?(Crystal::Var)\n\n      name = path.names.join(\"::\")\n      name = \"::#{name}\" if path.global? && !body.nil_check?\n\n      old = OLD % node.name\n      new = NEW % {node.name, name}\n      msg = MSG % {new, old}\n\n      if end_location = node.end_location\n        issue_for location, end_location, msg do |corrector|\n          corrector.replace(location, end_location, new)\n        end\n      else\n        issue_for location, nil, msg\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/is_a_nil.cr",
    "content": "module Ameba::Rule::Style\n  # A rule that disallows calls to `is_a?(Nil)` in favor of `nil?`.\n  #\n  # This is considered bad:\n  #\n  # ```\n  # var.is_a?(Nil)\n  # ```\n  #\n  # And needs to be written as:\n  #\n  # ```\n  # var.nil?\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/IsANil:\n  #   Enabled: true\n  # ```\n  class IsANil < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.13.0\"\n      description \"Disallows calls to `is_a?(Nil)` in favor of `nil?`\"\n    end\n\n    MSG = \"Use `nil?` instead of `is_a?(Nil)`\"\n\n    def test(source, node : Crystal::IsA)\n      return if node.nil_check?\n\n      const = node.const\n      return unless path_named?(const, \"Nil\")\n\n      issue_for const, MSG do |corrector|\n        corrector.replace(node, \"#{node.obj}.nil?\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/large_numbers.cr",
    "content": "module Ameba::Rule::Style\n  # A rule that disallows usage of large numbers without underscore.\n  # These do not affect the value of the number, but can help read\n  # large numbers more easily.\n  #\n  # For example, these are considered invalid:\n  #\n  # ```\n  # 100000\n  # 141592654\n  # 5.123456\n  # ```\n  #\n  # And has to be rewritten as the following:\n  #\n  # ```\n  # 100_000\n  # 141_592_654\n  # 5.123_456\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/LargeNumbers:\n  #   Enabled: true\n  #   IntMinDigits: 6 # i.e. integers higher than 99999\n  # ```\n  class LargeNumbers < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.2.0\"\n      enabled false\n      description \"Disallows usage of large numbers without underscore\"\n      int_min_digits 6\n    end\n\n    MSG = \"Large numbers should be written with underscores: `%s`\"\n\n    def test(source)\n      Tokenizer.new(source).run do |token|\n        next unless token.type.number? && decimal?(token.raw)\n\n        parsed = parse_number(token.raw)\n\n        if allowed?(*parsed) && (expected = underscored *parsed) != token.raw\n          location = name_location_or(token, token.raw)\n\n          issue_for *location, MSG % expected do |corrector|\n            corrector.replace(*location, expected)\n          end\n        end\n      end\n    end\n\n    private def decimal?(value)\n      value !~ /^0(x|b|o)/\n    end\n\n    private def allowed?(_sign, value, fraction, _suffix)\n      return true if fraction && fraction.size > 3\n\n      digits = value.chars.select!(&.number?)\n      digits.size >= int_min_digits\n    end\n\n    private def underscored(sign, value, fraction, suffix)\n      value = slice_digits(value.reverse).reverse\n      fraction = \".#{slice_digits(fraction)}\" if fraction\n\n      \"#{sign}#{value}#{fraction}#{suffix}\"\n    end\n\n    private def slice_digits(value, by = 3)\n      %w[].tap do |slices|\n        value.chars.reject!(&.== '_').each_slice(by) do |slice|\n          slices << slice.join\n        end\n      end.join('_')\n    end\n\n    private def parse_number(value)\n      value, sign = parse_sign(value)\n      value, suffix = parse_suffix(value)\n      value, fraction = parse_fraction(value)\n\n      {sign, value, fraction, suffix}\n    end\n\n    private def parse_sign(value)\n      if value[0].in?('+', '-')\n        sign = value[0]\n        value = value[1..-1]\n      end\n      {value, sign}\n    end\n\n    private def parse_suffix(value)\n      if pos = (value =~ /(e|_?(i|u|f))/)\n        suffix = value[pos..-1]\n        value = value[0..pos - 1]\n      end\n      {value, suffix}\n    end\n\n    private def parse_fraction(value)\n      if comma = value.index('.')\n        fraction = value[comma + 1..-1]\n        value = value[0..comma - 1]\n      end\n      {value, fraction}\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/multiline_curly_block.cr",
    "content": "module Ameba::Rule::Style\n  # A rule that disallows multi-line blocks that use curly brackets\n  # instead of `do`...`end`.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # (0..10).map { |i|\n  #   i * 2\n  # }\n  # ```\n  #\n  # And should be rewritten to the following:\n  #\n  # ```\n  # (0..10).map do |i|\n  #   i * 2\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/MultilineCurlyBlock:\n  #   Enabled: true\n  # ```\n  class MultilineCurlyBlock < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.7.0\"\n      description \"Disallows multi-line blocks using curly block syntax\"\n    end\n\n    MSG = \"Use `do`...`end` instead of curly brackets for multi-line blocks\"\n\n    def test(source, node : Crystal::Block)\n      return unless location = node.location\n      return if location.same_line?(node.end_location)\n      return unless source.code[source.pos(location)]? == '{'\n\n      issue_for node, MSG\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/multiline_string_literal.cr",
    "content": "module Ameba::Rule::Style\n  # A rule that disallows multiline string literals not using\n  # `<<-HEREDOC` markers.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # %(\n  #   foo\n  #   bar\n  # )\n  # ```\n  #\n  # And should be rewritten to the following:\n  #\n  # ```\n  # <<-HEREDOC\n  #   foo\n  #   bar\n  # HEREDOC\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/MultilineStringLiteral:\n  #   Enabled: true\n  #   AllowBackslashSplitStrings: true\n  # ```\n  class MultilineStringLiteral < Base\n    properties do\n      since_version \"1.7.0\"\n      description \"Disallows multiline string literals not using `<<-HEREDOC` markers\"\n      allow_backslash_split_strings true\n    end\n\n    MSG = \"Use `<<-HEREDOC` markers for multiline strings\"\n\n    def test(source, node : Crystal::StringLiteral | Crystal::StringInterpolation)\n      return unless location = node.location\n      return if location.same_line?(node.end_location)\n\n      location_pos = source.pos(location)\n\n      # ignore regex and command literals\n      return if source.code[location_pos]?.in?('/', '`')\n      return if source.code[location_pos..(location_pos + 1)]?.in?(\"%r\", \"%x\")\n\n      # ignore heredoc string literals\n      return if source.code[location_pos..(location_pos + 2)]? == \"<<-\"\n\n      # ignore string literals split by \\\n      return if allow_backslash_split_strings? &&\n                source.code.lines[location.line_number - 1].ends_with?('\\\\')\n\n      issue_for node, MSG\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/negated_conditions_in_unless.cr",
    "content": "module Ameba::Rule::Style\n  # A rule that disallows negated conditions in `unless`.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # unless !s.empty?\n  #   :ok\n  # end\n  # ```\n  #\n  # And should be rewritten to the following:\n  #\n  # ```\n  # if s.empty?\n  #   :ok\n  # end\n  # ```\n  #\n  # It is pretty difficult to wrap your head around a block of code\n  # that is executed if a negated condition is NOT met.\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/NegatedConditionsInUnless:\n  #   Enabled: true\n  # ```\n  class NegatedConditionsInUnless < Base\n    properties do\n      since_version \"0.2.0\"\n      description \"Disallows negated conditions in `unless`\"\n    end\n\n    MSG = \"Avoid negated conditions in unless blocks\"\n\n    def test(source, node : Crystal::Unless)\n      issue_for node, MSG if negated_condition?(node.cond)\n    end\n\n    private def negated_condition?(node)\n      case node\n      when Crystal::BinaryOp\n        negated_condition?(node.left) || negated_condition?(node.right)\n      when Crystal::Expressions\n        node.expressions.any? { |exp| negated_condition?(exp) }\n      when Crystal::Not\n        true\n      else\n        false\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/parentheses_around_condition.cr",
    "content": "module Ameba::Rule::Style\n  # A rule that checks for the presence of superfluous parentheses\n  # around the condition of `if`, `unless`, `case`, `while` and `until`.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # if (foo == 42)\n  #   do_something\n  # end\n  # ```\n  #\n  # And should be replaced by the following:\n  #\n  # ```\n  # if foo == 42\n  #   do_something\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/ParenthesesAroundCondition:\n  #   Enabled: true\n  #   ExcludeTernary: false\n  #   ExcludeMultiline: false\n  #   AllowSafeAssignment: false\n  # ```\n  class ParenthesesAroundCondition < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.4.0\"\n      description \"Disallows redundant parentheses around control expressions\"\n\n      exclude_ternary false\n      exclude_multiline false\n      allow_safe_assignment false\n    end\n\n    MSG_REDUNDANT = \"Redundant parentheses\"\n    MSG_MISSING   = \"Missing parentheses\"\n\n    def test(source, node : Crystal::If | Crystal::Unless | Crystal::Case | Crystal::While | Crystal::Until)\n      return unless cond = node.cond\n\n      if cond.is_a?(Crystal::Assign) && allow_safe_assignment?\n        issue_for cond, MSG_MISSING do |corrector|\n          corrector.wrap(cond, '(', ')')\n        end\n        return\n      end\n\n      return unless redundant_parentheses?(node, cond)\n\n      issue_for cond, MSG_REDUNDANT do |corrector|\n        corrector.remove_trailing(cond, 1)\n        corrector.remove_leading(cond, 1)\n      end\n    end\n\n    private def redundant_parentheses?(node, cond) : Bool\n      is_ternary = node.is_a?(Crystal::If) && node.ternary?\n\n      return false if is_ternary && exclude_ternary?\n\n      return false unless cond.is_a?(Crystal::Expressions)\n      return false unless cond.keyword.paren?\n\n      return false unless exp = cond.single_expression?\n      return false unless strip_parentheses?(exp, is_ternary)\n\n      if exclude_multiline?\n        if (location = node.location) && (end_location = node.end_location)\n          return false unless location.same_line?(end_location)\n        end\n      end\n\n      true\n    end\n\n    private def strip_parentheses?(node, in_ternary) : Bool\n      case node\n      when Crystal::BinaryOp\n        !in_ternary\n      when Crystal::Call\n        !in_ternary || node.has_parentheses? || !has_arguments?(node)\n      when Crystal::ExceptionHandler, Crystal::If, Crystal::Unless\n        false\n      when Crystal::Yield\n        !in_ternary || node.has_parentheses? || node.exps.empty?\n      when Crystal::Assign, Crystal::OpAssign, Crystal::MultiAssign\n        !in_ternary && !allow_safe_assignment?\n      else\n        true\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/percent_literal_delimiters.cr",
    "content": "module Ameba::Rule::Style\n  # A rule that enforces the consistent usage of `%`-literal delimiters.\n  #\n  # Specifying `DefaultDelimiters` option will set all preferred delimiters at once. You\n  # can continue to specify individual preferred delimiters via `PreferredDelimiters`\n  # setting to override the default. In both cases the delimiters should be specified\n  # as a string of two characters, or `nil` to ignore a particular `%`-literal / default.\n  #\n  # Setting `IgnoreLiteralsContainingDelimiters` to `true` will ignore `%`-literals that\n  # contain one or both delimiters.\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/PercentLiteralDelimiters:\n  #   Enabled: true\n  #   DefaultDelimiters: '()'\n  #   PreferredDelimiters:\n  #     '%w': '[]'\n  #     '%i': '[]'\n  #     '%r': '{}'\n  #   IgnoreLiteralsContainingDelimiters: false\n  # ```\n  class PercentLiteralDelimiters < Base\n    properties do\n      since_version \"1.7.0\"\n      description \"Enforces the consistent usage of `%`-literal delimiters\"\n\n      default_delimiters \"()\", as: String?\n      preferred_delimiters({\n        \"%w\" => \"[]\",\n        \"%i\" => \"[]\",\n        \"%r\" => \"{}\",\n      } of String => String?)\n      ignore_literals_containing_delimiters false\n    end\n\n    MSG = \"`%s`-literals should be delimited by `%s` and `%s`\"\n\n    def test(source)\n      processor = TokenProcessor.new(source, self)\n      processor.run do |state|\n        msg = MSG % {state.literal, state.opening_delimiter, state.closing_delimiter}\n\n        x = state.start_token.location.adjust(column_number: state.literal.size)\n        y = state.end_token.location\n\n        issue_for state.location, state.end_location, msg do |corrector|\n          corrector.replace(x, x, state.opening_delimiter)\n          corrector.replace(y, y, state.closing_delimiter)\n        end\n      end\n    end\n\n    private class TokenProcessor\n      def initialize(source, @rule : PercentLiteralDelimiters)\n        @tokenizer = Tokenizer.new(source)\n      end\n\n      def run(&on_literal : LiteralState -> Nil) : Nil\n        current_state = nil\n\n        @tokenizer.run do |token|\n          case token.type\n          when .string_array_start?, .symbol_array_start?, .delimiter_start?\n            if literal = extract_percent_literal(token.raw)\n              if delimiters = delimiters_for_literal(literal)\n                current_state = LiteralState.new(token.dup, literal, delimiters)\n              end\n            end\n          when .string?\n            if (state = current_state) && @rule.ignore_literals_containing_delimiters?\n              current_state = nil if state.includes_delimiters?(token.raw)\n            end\n          when .string_array_end?, .delimiter_end?\n            if state = current_state\n              unless state.correct_delimiters?\n                state.end_token = token\n                on_literal.call(state)\n              end\n              current_state = nil\n            end\n          end\n        end\n      end\n\n      private def extract_percent_literal(string)\n        string.match(/^(%\\w*)\\W/).try &.[1]\n      end\n\n      private def delimiters_for_literal(literal)\n        @rule.preferred_delimiters.fetch(literal) { @rule.default_delimiters }\n      end\n\n      struct LiteralState\n        getter start_token : Crystal::Token\n        property! end_token : Crystal::Token\n        getter literal : String\n        getter opening_delimiter : Char\n        getter closing_delimiter : Char\n\n        def initialize(@start_token, @literal, delimiters)\n          @opening_delimiter = delimiters[0]\n          @closing_delimiter = delimiters[1]\n        end\n\n        def includes_delimiters?(string)\n          string.includes?(opening_delimiter) ||\n            string.includes?(closing_delimiter)\n        end\n\n        def correct_delimiters?\n          start_token.delimiter_state.nest == opening_delimiter &&\n            start_token.delimiter_state.end == closing_delimiter\n        end\n\n        def location\n          start_token.location\n        end\n\n        def end_location\n          location.adjust(column_number: literal.size - 1)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/redundant_begin.cr",
    "content": "module Ameba::Rule::Style\n  # A rule that disallows redundant `begin` blocks.\n  #\n  # Currently it is able to detect:\n  #\n  # 1. Exception handler block that can be used as a part of the method.\n  #\n  # For example, this:\n  #\n  # ```\n  # def method\n  #   begin\n  #     read_content\n  #   rescue\n  #     close_file\n  #   end\n  # end\n  # ```\n  #\n  # should be rewritten as:\n  #\n  # ```\n  # def method\n  #   read_content\n  # rescue\n  #   close_file\n  # end\n  # ```\n  #\n  # 2. `begin`..`end` block as a top level block in a method.\n  #\n  # For example this is considered invalid:\n  #\n  # ```\n  # def method\n  #   begin\n  #     a = 1\n  #     b = 2\n  #   end\n  # end\n  # ```\n  #\n  # and has to be written as the following:\n  #\n  # ```\n  # def method\n  #   a = 1\n  #   b = 2\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/RedundantBegin:\n  #   Enabled: true\n  # ```\n  class RedundantBegin < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.3.0\"\n      description \"Disallows redundant `begin` blocks\"\n    end\n\n    MSG = \"Redundant `begin` block detected\"\n\n    def test(source, node : Crystal::Def)\n      return unless def_loc = node.location\n\n      case body = node.body\n      when Crystal::ExceptionHandler\n        return if begin_exprs_in_handler?(body) || inner_handler?(body)\n      when Crystal::Expressions\n        return unless redundant_begin_in_expressions?(body)\n      else\n        return\n      end\n\n      return unless begin_range = def_redundant_begin_range(source, node)\n\n      begin_loc, end_loc = begin_range\n      begin_loc, end_loc = def_loc.seek(begin_loc), def_loc.seek(end_loc)\n      begin_end_loc = begin_loc.adjust(column_number: {{ \"begin\".size - 1 }})\n      end_end_loc = end_loc.adjust(column_number: {{ \"end\".size - 1 }})\n\n      issue_for begin_loc, begin_end_loc, MSG do |corrector|\n        corrector.remove(begin_loc, begin_end_loc)\n        corrector.remove(end_loc, end_end_loc)\n      end\n    end\n\n    private def redundant_begin_in_expressions?(node)\n      !!node.keyword.try(&.begin?)\n    end\n\n    private def inner_handler?(handler)\n      handler.body.is_a?(Crystal::ExceptionHandler)\n    end\n\n    private def begin_exprs_in_handler?(handler)\n      return unless (body = handler.body).is_a?(Crystal::Expressions)\n      body.expressions.first?.is_a?(Crystal::ExceptionHandler)\n    end\n\n    private def def_redundant_begin_range(source, node)\n      return unless code = node_source(node, source.lines)\n\n      lexer = Crystal::Lexer.new code\n      return unless begin_loc = def_redundant_begin_loc(lexer)\n      return unless end_loc = def_redundant_end_loc(lexer)\n\n      {begin_loc, end_loc}\n    end\n\n    private def def_redundant_begin_loc(lexer)\n      in_body = in_argument_list = false\n\n      loop do\n        token = lexer.next_token\n\n        case token.type\n        when .eof?, .op_minus_gt?\n          break\n        when .ident?\n          next unless in_body\n          return unless token.value == Crystal::Keyword::BEGIN\n          return token.location\n        when .op_lparen?\n          in_argument_list = true\n        when .op_rparen?\n          in_argument_list = false\n        when .newline?\n          in_body = true unless in_argument_list\n        when .space?\n          # ignore\n        else\n          return if in_body\n        end\n      end\n    end\n\n    private def def_redundant_end_loc(lexer)\n      end_loc = def_end_loc = nil\n\n      Tokenizer.new(lexer).run do |token|\n        next unless token.value == Crystal::Keyword::END\n\n        end_loc, def_end_loc = def_end_loc, token.location\n      end\n\n      end_loc\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/redundant_next.cr",
    "content": "module Ameba::Rule::Style\n  # A rule that disallows redundant `next` expressions. A `next` keyword allows\n  # a block to skip to the next iteration early, however, it is considered\n  # redundant in cases where it is the last expression in a block or combines\n  # into the node which is the last in a block.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # block do |v|\n  #   next v + 1\n  # end\n  # ```\n  #\n  # ```\n  # block do |v|\n  #   case v\n  #   when .nil?\n  #     next \"nil\"\n  #   when .blank?\n  #     next \"blank\"\n  #   else\n  #     next \"empty\"\n  #   end\n  # end\n  # ```\n  #\n  # And has to be written as the following:\n  #\n  # ```\n  # block do |v|\n  #   v + 1\n  # end\n  # ```\n  #\n  # ```\n  # block do |v|\n  #   case arg\n  #   when .nil?\n  #     \"nil\"\n  #   when .blank?\n  #     \"blank\"\n  #   else\n  #     \"empty\"\n  #   end\n  # end\n  # ```\n  #\n  # ### Configuration params\n  #\n  # 1. *allow_multi_next*, default: `true`\n  #\n  # Allows end-user to configure whether to report or not the `next` statements\n  # which yield tuple literals i.e.\n  #\n  # ```\n  # block do\n  #   next a, b\n  # end\n  # ```\n  #\n  # If this param equals to `false`, the block above will be forced to be written as:\n  #\n  # ```\n  # block do\n  #   {a, b}\n  # end\n  # ```\n  #\n  # 2. *allow_empty_next*, default: `true`\n  #\n  # Allows end-user to configure whether to report or not the `next` statements\n  # without arguments. Sometimes such statements are used to yield the `nil` value explicitly.\n  #\n  # ```\n  # block do\n  #   @foo = :empty\n  #   next\n  # end\n  # ```\n  #\n  # If this param equals to `false`, the block above will be forced to be written as:\n  #\n  # ```\n  # block do\n  #   @foo = :empty\n  #   nil\n  # end\n  # ```\n  #\n  # ### YAML config example\n  #\n  # ```\n  # Style/RedundantNext:\n  #   Enabled: true\n  #   AllowMultiNext: true\n  #   AllowEmptyNext: true\n  # ```\n  class RedundantNext < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.12.0\"\n      description \"Reports redundant `next` expressions\"\n\n      allow_multi_next true\n      allow_empty_next true\n    end\n\n    MSG = \"Redundant `next` detected\"\n\n    def test(source, node : Crystal::Block)\n      AST::RedundantControlExpressionVisitor.new(self, source, node.body)\n    end\n\n    def test(source, node : Crystal::Next, visitor : AST::RedundantControlExpressionVisitor)\n      return if allow_multi_next? && node.exp.is_a?(Crystal::TupleLiteral)\n      return if allow_empty_next? && (node.exp.nil? || node.exp.try(&.nop?))\n\n      if exp_code = control_exp_code(node, source.lines)\n        issue_for node, MSG do |corrector|\n          corrector.replace(node, exp_code)\n        end\n      else\n        issue_for node, MSG\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/redundant_nil_in_control_expression.cr",
    "content": "module Ameba::Rule::Style\n  # A rule that disallows control expressions (`return`, `break` and `next`)\n  # with `nil` argument.\n  #\n  # This is considered invalid:\n  #\n  # ```\n  # def greeting(name)\n  #   return nil unless name\n  #\n  #   \"Hello, #{name}\"\n  # end\n  # ```\n  #\n  # And this is valid:\n  #\n  # ```\n  # def greeting(name)\n  #   return unless name\n  #\n  #   \"Hello, #{name}\"\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/RedundantNilInControlExpression:\n  #   Enabled: true\n  # ```\n  class RedundantNilInControlExpression < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.7.0\"\n      description \"Disallows control expressions with `nil` argument\"\n    end\n\n    MSG = \"Redundant `nil` detected\"\n\n    def test(source, node : Crystal::ControlExpression)\n      return unless (exp = node.exp).is_a?(Crystal::NilLiteral)\n\n      node_code =\n        node_source(node, source.lines) || node.to_s\n\n      # `return(nil)`\n      if node_code.includes?('(')\n        issue_for exp, MSG\n      else\n        issue_for exp, MSG do |corrector|\n          corrector.replace(node, node_code.sub(/\\s*\\(?nil\\)?$/, \"\"))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/redundant_return.cr",
    "content": "module Ameba::Rule::Style\n  # A rule that disallows redundant `return` expressions.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # def foo\n  #   return :bar\n  # end\n  # ```\n  #\n  # ```\n  # def bar(arg)\n  #   case arg\n  #   when .nil?\n  #     return \"nil\"\n  #   when .blank?\n  #     return \"blank\"\n  #   else\n  #     return \"empty\"\n  #   end\n  # end\n  # ```\n  #\n  # And has to be written as the following:\n  #\n  # ```\n  # def foo\n  #   :bar\n  # end\n  # ```\n  #\n  # ```\n  # def bar(arg)\n  #   case arg\n  #   when .nil?\n  #     \"nil\"\n  #   when .blank?\n  #     \"blank\"\n  #   else\n  #     \"empty\"\n  #   end\n  # end\n  # ```\n  #\n  # ### Configuration params\n  #\n  # 1. *allow_multi_return*, default: `true`\n  #\n  # Allows end-user to configure whether to report or not the `return` statements\n  # which return tuple literals i.e.\n  #\n  # ```\n  # def method(a, b)\n  #   return a, b\n  # end\n  # ```\n  #\n  # If this param equals to `false`, the method above has to be written as:\n  #\n  # ```\n  # def method(a, b)\n  #   {a, b}\n  # end\n  # ```\n  #\n  # 2. *allow_empty_return*, default: `true`\n  #\n  # Allows end-user to configure whether to report or not the `return` statements\n  # without arguments. Sometimes such returns are used to return the `nil` value explicitly.\n  #\n  # ```\n  # def method\n  #   @foo = :empty\n  #   return\n  # end\n  # ```\n  #\n  # If this param equals to `false`, the method above has to be written as:\n  #\n  # ```\n  # def method\n  #   @foo = :empty\n  #   nil\n  # end\n  # ```\n  #\n  # ### YAML config example\n  #\n  # ```\n  # Style/RedundantReturn:\n  #   Enabled: true\n  #   AllowMultiReturn: true\n  #   AllowEmptyReturn: true\n  # ```\n  class RedundantReturn < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.9.0\"\n      description \"Reports redundant `return` expressions\"\n\n      allow_multi_return true\n      allow_empty_return true\n    end\n\n    MSG = \"Redundant `return` detected\"\n\n    def test(source, node : Crystal::Def)\n      AST::RedundantControlExpressionVisitor.new(self, source, node.body)\n    end\n\n    def test(source, node : Crystal::Return, visitor : AST::RedundantControlExpressionVisitor)\n      return if allow_multi_return? && node.exp.is_a?(Crystal::TupleLiteral)\n      return if allow_empty_return? && (node.exp.nil? || node.exp.try(&.nop?))\n\n      if exp_code = control_exp_code(node, source.lines)\n        issue_for node, MSG do |corrector|\n          corrector.replace(node, exp_code)\n        end\n      else\n        issue_for node, MSG\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/redundant_self.cr",
    "content": "require \"compiler/crystal/syntax/token\"\n\nmodule Ameba::Rule::Style\n  # A rule that disallows redundant uses of `self`.\n  #\n  # This is considered bad:\n  #\n  # ```\n  # class Greeter\n  #   getter name : String\n  #\n  #   def self.init\n  #     self.new(\"Crystal\").greet\n  #   end\n  #\n  #   def initialize(@name)\n  #   end\n  #\n  #   def greet\n  #     puts \"Hello, my name is #{self.name}\"\n  #   end\n  #\n  #   self.init\n  # end\n  # ```\n  #\n  # And needs to be written as:\n  #\n  # ```\n  # class Greeter\n  #   getter name : String\n  #\n  #   def self.init\n  #     new(\"Crystal\").greet\n  #   end\n  #\n  #   def initialize(@name)\n  #   end\n  #\n  #   def greet\n  #     puts \"Hello, my name is #{name}\"\n  #   end\n  #\n  #   init\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/RedundantSelf:\n  #   Enabled: true\n  #   AllowedMethodNames:\n  #     - in?\n  #     - inspect\n  #     - not_nil!\n  # ```\n  class RedundantSelf < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.7.0\"\n      description \"Disallows redundant uses of `self`\"\n      allowed_method_names %w[in? inspect not_nil!]\n    end\n\n    MSG = \"Redundant `self` detected\"\n\n    CRYSTAL_KEYWORDS = Crystal::Keyword.values.map(&.to_s)\n\n    def test(source)\n      AST::ScopeCallsWithSelfReceiverVisitor.new self, source\n    end\n\n    def test(source, node : Crystal::Call, scope : AST::Scope)\n      return if setter_method?(node) || operator_method?(node)\n\n      # Guard against auto-expanded `OpAssign` nodes, i.e.\n      # `self.a += b` is expanded to `self.a = self.a + b`.\n      return unless node.location && node.end_location\n      return unless (obj = node.obj).is_a?(Crystal::Var)\n\n      name = node.name\n\n      return if name.in?(CRYSTAL_KEYWORDS)\n      return if name.in?(allowed_method_names)\n\n      vars = Set(String).new\n\n      while scope\n        break if scope.type_definition?\n\n        scope.arguments.each do |arg|\n          vars << arg.name\n        end\n        scope.variables.each do |var|\n          var.assignments.each do |assign|\n            vars << assign.variable.name\n          end\n        end\n        scope = scope.outer_scope\n      end\n\n      return if name.in?(vars)\n      return unless node_source = node_source(node, source.lines)\n\n      issue_for obj, MSG do |corrector|\n        corrector.replace(node, node_source.sub(/\\Aself\\s*\\./, \"\"))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/unless_else.cr",
    "content": "module Ameba::Rule::Style\n  # A rule that disallows the use of an `else` block with the `unless`.\n  #\n  # For example, the rule considers these valid:\n  #\n  # ```\n  # unless something\n  #   :ok\n  # end\n  #\n  # if something\n  #   :one\n  # else\n  #   :two\n  # end\n  # ```\n  #\n  # But it considers this one invalid as it is an `unless` with an `else`:\n  #\n  # ```\n  # unless something\n  #   :one\n  # else\n  #   :two\n  # end\n  # ```\n  #\n  # The solution is to swap the order of the blocks, and change the `unless` to\n  # an `if`, so the previous invalid example would become this:\n  #\n  # ```\n  # if something\n  #   :two\n  # else\n  #   :one\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/UnlessElse:\n  #   Enabled: true\n  # ```\n  class UnlessElse < Base\n    properties do\n      since_version \"0.1.0\"\n      description \"Disallows the use of an `else` block with the `unless`\"\n    end\n\n    MSG = \"Favour `if` over `unless` with `else`\"\n\n    def test(source, node : Crystal::Unless)\n      return if node.else.nop?\n\n      location = node.location\n      cond_end_location = node.cond.end_location\n      else_location = node.else_location\n      end_location = node.end_location\n\n      unless location && cond_end_location && else_location && end_location\n        issue_for node, MSG\n        return\n      end\n\n      issue_for location, cond_end_location, MSG do |corrector|\n        keyword_begin_pos = source.pos(location)\n        keyword_end_pos = keyword_begin_pos + {{ \"unless\".size }}\n        keyword_range = keyword_begin_pos...keyword_end_pos\n\n        cond_end_pos = source.pos(cond_end_location, end: true)\n        else_begin_pos = source.pos(else_location)\n        body_range = cond_end_pos...else_begin_pos\n\n        else_end_pos = else_begin_pos + {{ \"else\".size }}\n        end_end_pos = source.pos(end_location, end: true)\n        end_begin_pos = end_end_pos - {{ \"end\".size }}\n        else_range = else_end_pos...end_begin_pos\n\n        corrector.replace(keyword_range, \"if\")\n        corrector.replace(body_range, source.code[else_range])\n        corrector.replace(else_range, source.code[body_range])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/verbose_block.cr",
    "content": "module Ameba::Rule::Style\n  # This rule is used to identify usage of single expression blocks with\n  # argument as a receiver, that can be collapsed into a short form.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # (1..3).any? { |i| i.odd? }\n  # ```\n  #\n  # And it should be written as this:\n  #\n  # ```\n  # (1..3).any?(&.odd?)\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/VerboseBlock:\n  #   Enabled: true\n  #   ExcludeMultipleLineBlocks: true\n  #   ExcludeCallsWithBlock: true\n  #   ExcludePrefixOperators: true\n  #   ExcludeOperators: true\n  #   ExcludeSetters: false\n  #   MaxLineLength: ~\n  #   MaxLength: 50 # use ~ to disable\n  # ```\n  class VerboseBlock < Base\n    include AST::Util\n\n    properties do\n      since_version \"0.14.0\"\n      description \"Identifies usage of collapsible single expression blocks\"\n\n      exclude_multiple_line_blocks true\n      exclude_calls_with_block true\n      exclude_prefix_operators true\n      exclude_operators true\n      exclude_setters false\n\n      max_line_length nil, as: Int32?\n      max_length 50, as: Int32?\n    end\n\n    MSG          = \"Use short block notation instead: `%s`\"\n    CALL_PATTERN = \"%s(%s&.%s)\"\n\n    private PREFIX_OPERATORS = {\"+\", \"-\", \"~\"}\n\n    def test(source, node : Crystal::Call)\n      # we are interested only in calls with block taking a single argument\n      #\n      # ```\n      # (1..3).any? { |i| i.to_i64.odd? }\n      #        ^---    ^  ^------------\n      #        block  arg  body\n      # ```\n      return unless (block = node.block) && block.args.size == 1\n\n      arg = block.args.first\n\n      # we filter out the blocks that are of call type - `i.to_i64.odd?`\n      return unless (body = block.body).is_a?(Crystal::Call)\n\n      # we need to \"unwind\" the call chain, so the final receiver object\n      # ends up being a variable - `i`\n      obj = body.obj\n      while obj.is_a?(Crystal::Call)\n        obj = obj.obj\n      end\n\n      # only calls with a first argument used as a receiver are the valid game\n      return unless obj == arg\n\n      # we bail out if the block node include the block argument\n      return if reference_count(body, arg) > 1\n\n      # add issue if the given nodes pass all of the checks\n      issue_for_valid source, node, block, body\n    end\n\n    # ameba:disable Metrics/CyclomaticComplexity\n    private def issue_for_valid(source, call : Crystal::Call, block : Crystal::Block, body : Crystal::Call)\n      return if exclude_calls_with_block? && body.block\n      return if exclude_multiple_line_blocks? && !same_location_lines?(call, body)\n      return if exclude_prefix_operators? && prefix_operator?(body)\n      return if exclude_operators? && operator_method?(body)\n      return if exclude_setters? && setter_method?(body)\n\n      call_code =\n        call_code(source, call, body)\n\n      return unless valid_line_length?(call, call_code)\n      return unless valid_length?(call_code)\n\n      return unless location = name_location(call)\n      return unless end_location = block.end_location\n\n      if call_code.includes?(\"{...}\")\n        issue_for location, end_location, MSG % call_code\n      else\n        issue_for location, end_location, MSG % call_code do |corrector|\n          corrector.replace(location, end_location, call_code)\n        end\n      end\n    end\n\n    private def call_code(source, call, body)\n      args = String.build { |io| args_to_s(io, call) }.presence\n      args += \", \" if args\n\n      call_chain = %w[].tap do |arr|\n        obj = body.obj\n        while obj.is_a?(Crystal::Call)\n          arr << node_to_s(source, obj)\n          obj = obj.obj\n        end\n        arr.reverse!\n        arr << node_to_s(source, body)\n      end\n\n      name =\n        call_chain.join('.')\n\n      CALL_PATTERN % {call.name, args, name}\n    end\n\n    private def node_to_s(source, node : Crystal::Call)\n      String.build do |str|\n        case name = node.name\n        when \"[]\"\n          str << '['\n          args_to_s(str, node)\n          str << ']'\n        when \"[]?\"\n          str << '['\n          args_to_s(str, node)\n          str << \"]?\"\n        when \"[]=\"\n          str << '['\n          args_to_s(str, node, skip_last_arg: true)\n          str << \"]=(\" << node.args.last? << ')'\n        else\n          short_block = short_block_code(source, node)\n          str << name\n          if has_arguments?(node) || short_block\n            str << '('\n            args_to_s(str, node, short_block)\n            str << ')'\n          end\n          str << \" {...}\" if node.block && short_block.nil?\n        end\n      end\n    end\n\n    private def args_to_s(io : IO, node : Crystal::Call, short_block = nil, skip_last_arg = false) : Nil\n      args = node.args.dup\n      args.pop? if skip_last_arg\n      args.join io, \", \"\n\n      named_args = node.named_args\n      if named_args\n        io << \", \" unless args.empty? || named_args.empty?\n        named_args.join io, \", \" do |arg, inner_io|\n          inner_io << arg.name << \": \" << arg.value\n        end\n      end\n\n      if short_block\n        io << \", \" unless args.empty? && (named_args.nil? || named_args.empty?)\n        io << short_block\n      end\n    end\n\n    private def short_block_code(source, node : Crystal::Call)\n      return unless block = node.block\n      return unless block_location = block.location\n      return unless block_end_location = block.body.end_location\n\n      block_code = source_between(block_location, block_end_location, source.lines)\n      block_code if block_code.try(&.starts_with?(\"&.\"))\n    end\n\n    private def same_location_lines?(a, b)\n      name_location(a).try &.same_line?(b.location)\n    end\n\n    private def prefix_operator?(node)\n      node.name.in?(PREFIX_OPERATORS) && !has_arguments?(node)\n    end\n\n    private def valid_length?(code)\n      if max_length = self.max_length\n        return code.size <= max_length\n      end\n      true\n    end\n\n    private def valid_line_length?(node, code)\n      if max_line_length = self.max_line_length\n        if location = name_location(node)\n          final_line_length = location.column_number + code.size\n          return final_line_length <= max_line_length\n        end\n      end\n      true\n    end\n\n    private def reference_count(node, obj : Crystal::Var)\n      i = 0\n      case node\n      when Crystal::Call\n        i += reference_count(node.obj, obj)\n        i += reference_count(node.block, obj)\n\n        node.args.each do |arg|\n          i += reference_count(arg, obj)\n        end\n        node.named_args.try &.each do |arg|\n          i += reference_count(arg.value, obj)\n        end\n      when Crystal::BinaryOp\n        i += reference_count(node.left, obj)\n        i += reference_count(node.right, obj)\n      when Crystal::Block\n        i += reference_count(node.body, obj)\n      when Crystal::Var\n        i += 1 if node == obj\n      end\n      i\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/verbose_nil_type.cr",
    "content": "module Ameba::Rule::Style\n  # A rule that enforces consistent naming of `Nil` in type unions.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # foo : String | Nil = nil\n  # ```\n  #\n  # And should be replaced by the following:\n  #\n  # ```\n  # foo : String? = nil\n  # ```\n  #\n  # Enable the `ExplicitNil` option to enforce the opposite behavior.\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/VerboseNilType:\n  #   Enabled: true\n  #   ExplicitNil: false\n  # ```\n  class VerboseNilType < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.7.0\"\n      description \"Enforces consistent naming of `Nil` in type unions\"\n      explicit_nil false\n    end\n\n    MSG_VERBOSE = \"Prefer `?` instead of `| Nil` in unions\"\n    MSG_SHORT   = \"Prefer `| Nil` instead of `?` in unions\"\n\n    private NIL_TYPE_PATTERN    = /(\\s*\\|\\s*Nil(?=\\W|$))|((?<=\\W|^)Nil\\s*\\|\\s*)/\n    private SINGLE_TYPE_PATTERN = /\\((\\w+)\\)/\n\n    def test(source, node : Crystal::Union)\n      return unless has_nil?(node)\n      return unless node_source = node_source(node, source.lines)\n\n      # https://github.com/crystal-lang/crystal/issues/11071\n      return if node_source.includes?(\".class\")\n\n      # `String?` -> `String | Nil`\n      if explicit_nil?\n        return unless node_source.ends_with?('?')\n\n        issue_for node, MSG_SHORT do |corrector|\n          corrector.replace(node, \"%s | Nil\" % node_source.rstrip('?'))\n        end\n        return\n      end\n\n      # `String | Nil` -> `String?`\n      return unless node_source.matches?(NIL_TYPE_PATTERN)\n\n      # Unions that _do not_ contain generic types are safe to modify using\n      # simple find-and-replace, due to the fact that their types are being\n      # flattened in the end, so removing `Nil` type from anywhere in the\n      # union and appending `?` should be semantically the same and should\n      # not affect the type of the union.\n      #\n      # If union contains generic type however, we need to be careful, as\n      # simple find-and-replace might change the type of the union -\n      # and that's why we skip the auto-correction of those.\n      if has_generic?(node)\n        issue_for node, MSG_VERBOSE\n        return\n      end\n\n      issue_for node, MSG_VERBOSE do |corrector|\n        corrected_code = \"%s?\" % node_source\n          .gsub(NIL_TYPE_PATTERN, \"\")\n          .gsub('?', \"\")\n\n        # handle `(String | ((Symbol)))` cases\n        while corrected_code.matches?(SINGLE_TYPE_PATTERN)\n          corrected_code = corrected_code\n            .gsub(SINGLE_TYPE_PATTERN, \"\\\\1\")\n        end\n        corrector.replace(node, corrected_code)\n      end\n    end\n\n    private def has_generic?(node : Crystal::Union)\n      node.types.any? { |type| has_generic?(type) }\n    end\n\n    private def has_generic?(node)\n      node.is_a?(Crystal::Generic)\n    end\n\n    private def has_nil?(node : Crystal::Union)\n      node.types.any? { |type| has_nil?(type) }\n    end\n\n    private def has_nil?(node)\n      path_named?(node, \"Nil\")\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/style/while_true.cr",
    "content": "module Ameba::Rule::Style\n  # A rule that disallows the use of `while true` instead of using the idiomatic `loop`\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # while true\n  #   do_something\n  #   break if some_condition\n  # end\n  # ```\n  #\n  # And should be replaced by the following:\n  #\n  # ```\n  # loop do\n  #   do_something\n  #   break if some_condition\n  # end\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Style/WhileTrue:\n  #   Enabled: true\n  # ```\n  class WhileTrue < Base\n    properties do\n      since_version \"0.3.0\"\n      description \"Disallows `while` statements with a `true` literal as condition\"\n    end\n\n    MSG = \"While statement using `true` literal as condition\"\n\n    def test(source, node : Crystal::While)\n      return unless node.cond.true_literal?\n\n      return unless location = node.location\n      return unless end_location = node.cond.end_location\n\n      issue_for node, MSG do |corrector|\n        corrector.replace(location, end_location, \"loop do\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/typing/macro_call_argument_type_restriction.cr",
    "content": "module Ameba::Rule::Typing\n  # A rule that enforces call arguments to specific macros have a type restriction.\n  # By default these macros are: `(class_)getter/setter/property(?/!)` and `record`.\n  #\n  # For example, these are considered invalid:\n  #\n  # ```\n  # class Greeter\n  #   getter name\n  #   getter age = 0.days\n  #   getter :height\n  # end\n  #\n  # record Task,\n  #   cmd = \"\",\n  #   args = %w[]\n  # ```\n  #\n  # And these are considered valid:\n  #\n  # ```\n  # class Greeter\n  #   getter name : String?\n  #   getter age : Time::Span = 0.days\n  #   getter height : Float64?\n  # end\n  #\n  # record Task,\n  #   cmd : String = \"\",\n  #   args : Array(String) = %w[]\n  # ```\n  #\n  # The `DefaultValue` configuration option controls whether this rule applies to\n  # call arguments that have a default value.\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Typing/MacroCallArgumentTypeRestriction:\n  #   Enabled: true\n  #   DefaultValue: false\n  #   MacroNames:\n  #    - getter\n  #    - getter?\n  #    - getter!\n  #    - class_getter\n  #    - class_getter?\n  #    - class_getter!\n  #    - setter\n  #    - setter?\n  #    - setter!\n  #    - class_setter\n  #    - class_setter?\n  #    - class_setter!\n  #    - property\n  #    - property?\n  #    - property!\n  #    - class_property\n  #    - class_property?\n  #    - class_property!\n  #    - record\n  # ```\n  class MacroCallArgumentTypeRestriction < Base\n    properties do\n      since_version \"1.7.0\"\n      description \"Recommends that call arguments to certain macros have type restrictions\"\n      enabled false\n      default_value false\n      macro_names %w[\n        getter getter? getter! class_getter class_getter? class_getter!\n        setter setter? setter! class_setter class_setter? class_setter!\n        property property? property! class_property class_property? class_property!\n        record\n      ]\n    end\n\n    MSG = \"Argument should have a type restriction\"\n\n    def test(source, node : Crystal::Call)\n      return unless node.name.in?(macro_names)\n\n      node.args.each do |arg|\n        case arg\n        when Crystal::Assign\n          issue_for arg.target, MSG if default_value?\n        when Crystal::Var, Crystal::Call, Crystal::StringLiteral, Crystal::SymbolLiteral\n          issue_for arg, MSG\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/typing/method_parameter_type_restriction.cr",
    "content": "module Ameba::Rule::Typing\n  # A rule that enforces method parameters have type restrictions, with optional enforcement of block parameters.\n  #\n  # For example, this is considered invalid:\n  #\n  # ```\n  # def add(a, b)\n  #   a + b\n  # end\n  # ```\n  #\n  # And this is considered valid:\n  #\n  # ```\n  # def add(a : String, b : String)\n  #   a + b\n  # end\n  # ```\n  #\n  # When the config options `PrivateMethods` and `ProtectedMethods`\n  # are true, this rule is also applied to private and protected methods, respectively.\n  #\n  # The `NodocMethods` configuration option controls whether this rule applies to\n  # methods with a `:nodoc:` directive.\n  #\n  # The `BlockParameters` configuration option will extend this to block parameters, where these are invalid:\n  #\n  # ```\n  # def exec(&)\n  # end\n  #\n  # def exec(&block)\n  # end\n  # ```\n  #\n  # And this is valid:\n  #\n  # ```\n  # def exec(&block : String -> String)\n  #   yield \"cmd\"\n  # end\n  # ```\n  #\n  # The config option `DefaultValue` controls whether this rule applies to parameters that have a default value.\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Typing/MethodParameterTypeRestriction:\n  #   Enabled: true\n  #   DefaultValue: false\n  #   BlockParameters: false\n  #   PrivateMethods: false\n  #   ProtectedMethods: false\n  #   NodocMethods: false\n  # ```\n  class MethodParameterTypeRestriction < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.7.0\"\n      description \"Recommends that method parameters have type restrictions\"\n      enabled false\n      default_value false\n      block_parameters false\n      private_methods false\n      protected_methods false\n      nodoc_methods false\n    end\n\n    MSG = \"Method parameter should have a type restriction\"\n\n    def test(source, node : Crystal::Def)\n      return if valid_visibility?(node)\n\n      node.args.each do |arg|\n        next if arg.restriction || arg.name.empty?\n        next if !default_value? && arg.default_value\n\n        issue_for arg, MSG, prefer_name_location: true\n      end\n\n      if block_parameters? && (block_arg = node.block_arg) && !block_arg.restriction\n        issue_for block_arg, MSG, prefer_name_location: true\n      end\n    end\n\n    private def valid_visibility?(node : Crystal::ASTNode) : Bool\n      (!private_methods? && node.visibility.private?) ||\n        (!protected_methods? && node.visibility.protected?) ||\n        (!nodoc_methods? && nodoc?(node))\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/typing/method_return_type_restriction.cr",
    "content": "module Ameba::Rule::Typing\n  # A rule that enforces method definitions have a return type restriction.\n  #\n  # For example, this are considered invalid:\n  #\n  # ```\n  # def hello(name = \"World\")\n  #   \"Hello #{name}\"\n  # end\n  # ```\n  #\n  # And this is valid:\n  #\n  # ```\n  # def hello(name = \"World\") : String\n  #   \"Hello #{name}\"\n  # end\n  # ```\n  #\n  # When the config options `PrivateMethods` and `ProtectedMethods`\n  # are true, this rule is also applied to private and protected methods, respectively.\n  #\n  # The `NodocMethods` configuration option controls whether this rule applies to\n  # methods with a `:nodoc:` directive.\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Typing/MethodReturnTypeRestriction:\n  #   Enabled: true\n  #   PrivateMethods: false\n  #   ProtectedMethods: false\n  #   NodocMethods: false\n  # ```\n  class MethodReturnTypeRestriction < Base\n    include AST::Util\n\n    properties do\n      since_version \"1.7.0\"\n      description \"Recommends that methods have a return type restriction\"\n      enabled false\n      private_methods false\n      protected_methods false\n      nodoc_methods false\n    end\n\n    MSG = \"Method should have a return type restriction\"\n\n    def test(source, node : Crystal::Def)\n      issue_for node, MSG unless valid_return_type?(node)\n    end\n\n    private def valid_return_type?(node : Crystal::ASTNode) : Bool\n      !!node.return_type ||\n        (node.visibility.private? && !private_methods?) ||\n        (node.visibility.protected? && !protected_methods?) ||\n        (!nodoc_methods? && nodoc?(node))\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/rule/typing/proc_literal_return_type_restriction.cr",
    "content": "module Ameba::Rule::Typing\n  # A rule that enforces that `Proc` literals have a return type.\n  #\n  # For example, these are considered invalid:\n  #\n  # ```\n  # greeter = ->(name : String) { \"Hello #{name}\" }\n  # ```\n  #\n  # ```\n  # task = -> { Task.new(\"execute this command\") }\n  # ```\n  #\n  # And these are valid:\n  #\n  # ```\n  # greeter = ->(name : String) : String { \"Hello #{name}\" }\n  # ```\n  #\n  # ```\n  # task = -> : Task { Task.new(\"execute this command\") }\n  # ```\n  #\n  # YAML configuration example:\n  #\n  # ```\n  # Typing/ProcLiteralReturnTypeRestriction:\n  #   Enabled: true\n  # ```\n  class ProcLiteralReturnTypeRestriction < Base\n    properties do\n      since_version \"1.7.0\"\n      description \"Disallows proc literals without return type restriction\"\n      enabled false\n    end\n\n    MSG = \"Proc literal should have a return type restriction\"\n\n    def test(source, node : Crystal::ProcLiteral)\n      issue_for node, MSG unless node.def.return_type\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/runner.cr",
    "content": "module Ameba\n  # Represents a runner for inspecting sources files.\n  # Holds a list of rules to do inspection based on,\n  # list of sources to run inspection on and a formatter\n  # to prepare a report.\n  #\n  # ```\n  # config = Ameba::Config.load\n  # runner = Ameba::Runner.new config\n  # runner.run.success? # => true or false\n  # ```\n  class Runner\n    # An error indicating that the inspection loop got stuck correcting\n    # issues back and forth.\n    class InfiniteCorrectionLoopError < RuntimeError\n      def initialize(path, issues_by_iteration, loop_start = -1)\n        root_cause =\n          issues_by_iteration[loop_start..-1]\n            .join(\" -> \", &.map(&.rule.name).uniq!.join(\", \"))\n\n        message = String.build do |io|\n          io << \"Infinite loop\"\n          io << \" in \" << path unless path.empty?\n          io << \" caused by \" << root_cause\n        end\n\n        super message\n      end\n    end\n\n    # A list of rules to do inspection based on.\n    @rules : Array(Rule::Base)\n\n    # Project root path.\n    getter root : Path\n\n    # A list of sources to run inspection on.\n    getter sources : Array(Source)\n\n    # A level of severity to be reported.\n    @severity : Severity\n\n    # A formatter to prepare report.\n    @formatter : Formatter::BaseFormatter\n\n    # A syntax rule which always inspects a source first\n    @syntax_rule = Rule::Lint::Syntax.new\n\n    # Checks for unneeded disable directives. Always inspects a source last\n    @unneeded_disable_directive_rule : Rule::Lint::UnneededDisableDirective?\n\n    # Returns `true` if correctable issues should be autocorrected.\n    private getter? autocorrect : Bool\n\n    # Returns an ameba version up to which the rules should be ran.\n    property version : SemanticVersion?\n\n    # Instantiates a runner using a `config`.\n    #\n    # ```\n    # config = Ameba::Config.load\n    # config.files = files\n    # config.formatter = formatter\n    #\n    # Ameba::Runner.new config\n    # ```\n    def initialize(config : Config)\n      initialize(\n        config.rules,\n        config.sources,\n        config.formatter,\n        config.severity,\n        config.autocorrect?,\n        config.version,\n        config.root,\n      )\n    end\n\n    protected def initialize(rules, sources, @formatter, @severity, @autocorrect = false, @version = nil, @root = Path[Dir.current])\n      @sources = sources.sort_by(&.path)\n      @rules =\n        rules.select(&->rule_runnable?(Rule::Base))\n      @unneeded_disable_directive_rule =\n        rules.find(&.class.==(Rule::Lint::UnneededDisableDirective))\n          .as?(Rule::Lint::UnneededDisableDirective)\n    end\n\n    protected def rule_runnable?(rule)\n      rule.enabled? && !rule.special? &&\n        rule_satisfies_severity?(rule, @severity) &&\n        rule_satisfies_version?(rule, @version)\n    end\n\n    protected def rule_satisfies_severity?(rule, severity)\n      rule.severity <= severity\n    end\n\n    protected def rule_satisfies_version?(rule, version)\n      !version || !(since_version = rule.since_version) ||\n        since_version <= version\n    end\n\n    # Performs the inspection. Iterates through all sources and test it using\n    # list of rules. If a specific rule fails on a specific source, it adds\n    # an issue to that source.\n    #\n    # This action also notifies formatter when inspection is started/finished,\n    # and when a specific source started/finished to be inspected.\n    #\n    # ```\n    # runner = Ameba::Runner.new config\n    # runner.run # => returns runner again\n    # ```\n    def run\n      @formatter.started @sources\n\n      channels = @sources.map { Channel(Exception?).new }\n      @sources.zip(channels).each do |source, channel|\n        spawn do\n          run_source(source)\n        rescue ex\n          channel.send(ex)\n        else\n          channel.send(nil)\n        end\n      end\n\n      channels.each do |chan|\n        chan.receive.try { |ex| raise ex }\n      end\n\n      self\n    ensure\n      @formatter.finished @sources\n    end\n\n    private def run_source(source) : Nil\n      @formatter.source_started source\n\n      # This variable is a 2D array used to track corrected issues after each\n      # inspection iteration. This is used to output meaningful infinite loop\n      # error message.\n      corrected_issues = [] of Array(Issue)\n\n      # When running with --fix, we need to inspect the source until no more\n      # corrections are made (because automatic corrections can introduce new\n      # issues). In the normal case the loop is only executed once.\n      loop_unless_infinite(source, corrected_issues) do\n        # We have to reprocess the source to pick up any changes. Since a\n        # change could (theoretically) introduce syntax errors, we break the\n        # loop if we find any.\n        @syntax_rule.test(source)\n        break unless source.valid?\n\n        excluded_rules = Set(String).new\n\n        @rules.each do |rule|\n          if rule.excluded?(source, root)\n            excluded_rules << rule.name\n            next\n          end\n          rule.test(source)\n        end\n        check_unneeded_directives(source, excluded_rules)\n        break unless autocorrect? && source.correct!\n\n        # The issues that couldn't be corrected will be found again so we\n        # only keep the corrected ones in order to avoid duplicate reporting.\n        corrected_issues << source.issues.select(&.correctable?)\n        source.issues.clear\n      end\n\n      corrected_issues.flatten.reverse_each do |issue|\n        source.issues.unshift(issue)\n      end\n\n      File.write(source.path, source.code) unless corrected_issues.empty?\n    ensure\n      source.issues.sort_by! do |issue|\n        {\n          issue.location.try(&.line_number) || 0,\n          issue.location.try(&.column_number) || 0,\n        }\n      end\n      @formatter.source_finished source\n    end\n\n    # Explains an issue at a specified *location*.\n    #\n    # Runner should perform inspection before doing the explain.\n    # This is necessary to be able to find the issue at a specified location.\n    #\n    # ```\n    # runner = Ameba::Runner.new config\n    # runner.run\n    # runner.explain(Crystal::Location.new(file, line, column))\n    # ```\n    def explain(location, output = STDOUT)\n      Formatter::ExplainFormatter.new(output, location).finished @sources\n    end\n\n    # Indicates whether the last inspection successful or not.\n    # It returns `true` if no issues are found, `false` otherwise.\n    #\n    # ```\n    # runner = Ameba::Runner.new config\n    # runner.run\n    # runner.success? # => true or false\n    # ```\n    def success?\n      @sources.all? &.issues.none? &.enabled?\n    end\n\n    private MAX_ITERATIONS = 200\n\n    private def loop_unless_infinite(source, corrected_issues, &)\n      # Keep track of the state of the source. If a rule modifies the source\n      # and another rule undoes it producing identical source we have an\n      # infinite loop.\n      processed_sources = [] of UInt64\n\n      # It is possible for a rule to keep adding indefinitely to a file,\n      # making it bigger and bigger. If the inspection loop runs for an\n      # excessively high number of iterations, this is likely happening.\n      iterations = 0\n\n      loop do\n        check_for_infinite_loop(source, corrected_issues, processed_sources)\n\n        if (iterations += 1) > MAX_ITERATIONS\n          raise InfiniteCorrectionLoopError.new(source.path, corrected_issues)\n        end\n\n        yield\n      end\n    end\n\n    # Check whether a run created source identical to a previous run, which\n    # means that we definitely have an infinite loop.\n    private def check_for_infinite_loop(source, corrected_issues, processed_sources)\n      checksum = source.code.hash\n\n      if loop_start = processed_sources.index(checksum)\n        raise InfiniteCorrectionLoopError.new(\n          source.path,\n          corrected_issues,\n          loop_start: loop_start\n        )\n      end\n\n      processed_sources << checksum\n    end\n\n    private def check_unneeded_directives(source, excluded_rules = Set(String).new)\n      return unless rule = @unneeded_disable_directive_rule\n      return unless rule.enabled?\n      return if rule.excluded?(source, root)\n\n      rule.test(source, excluded_rules)\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/severity.cr",
    "content": "require \"colorize\"\n\nmodule Ameba\n  enum Severity\n    Error\n    Warning\n    Convention\n\n    # Returns a symbol uniquely indicating severity.\n    #\n    # ```\n    # Severity::Warning.symbol # => 'W'\n    # ```\n    def symbol : Char\n      case self\n      in Error      then 'E'\n      in Warning    then 'W'\n      in Convention then 'C'\n      end\n    end\n\n    # Returns a color uniquely indicating severity.\n    #\n    # ```\n    # Severity::Warning.color # => Colorize::ColorANSI::Red\n    # ```\n    def color : Colorize::Color\n      case self\n      in Error      then Colorize::ColorANSI::Red\n      in Warning    then Colorize::ColorANSI::Red\n      in Convention then Colorize::ColorANSI::Blue\n      end\n    end\n\n    # Creates Severity by the name.\n    #\n    # ```\n    # Severity.parse(\"convention\") # => Severity::Convention\n    # Severity.parse(\"foo-bar\")    # => Exception: Incorrect severity name\n    # ```\n    def self.parse(name : String)\n      super name\n    rescue ArgumentError\n      raise \"Incorrect severity name #{name}. Try one of: #{values.join(\", \")}\"\n    end\n  end\n\n  # Converter for `YAML.mapping` which converts severity enum to and from YAML.\n  class SeverityYamlConverter\n    def self.from_yaml(ctx : YAML::ParseContext, node : YAML::Nodes::Node)\n      unless node.is_a?(YAML::Nodes::Scalar)\n        raise \"Severity must be a scalar, not #{node.class}\"\n      end\n\n      case value = node.value\n      when String then Severity.parse(value)\n      when Nil    then raise \"Missing severity\"\n      else\n        raise \"Incorrect severity: #{value}\"\n      end\n    end\n\n    def self.to_yaml(value : Severity, yaml : YAML::Nodes::Builder)\n      yaml.scalar value\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/source/corrector.cr",
    "content": "require \"./rewriter\"\n\nclass Ameba::Source\n  # This class takes source code and rewrites it based\n  # on the different correction actions supplied.\n  class Corrector\n    @line_sizes = [] of Int32\n\n    def initialize(code : String)\n      code.each_line(chomp: false) do |line|\n        @line_sizes << line.size\n      end\n      @rewriter = Rewriter.new(code)\n    end\n\n    # Replaces the code of the given range with *content*.\n    def replace(location, end_location, content)\n      @rewriter.replace(loc_to_pos(location), loc_to_pos(end_location) + 1, content)\n    end\n\n    # :ditto:\n    def replace(range : Range(Int32, Int32), content)\n      begin_pos, end_pos = range.begin, range.end\n      end_pos -= 1 unless range.excludes_end?\n      @rewriter.replace(begin_pos, end_pos, content)\n    end\n\n    # Inserts the given strings before and after the given range.\n    def wrap(location, end_location, insert_before, insert_after)\n      @rewriter.wrap(loc_to_pos(location), loc_to_pos(end_location) + 1, insert_before, insert_after)\n    end\n\n    # :ditto:\n    def wrap(range : Range(Int32, Int32), insert_before, insert_after)\n      begin_pos, end_pos = range.begin, range.end\n      end_pos -= 1 unless range.excludes_end?\n      @rewriter.wrap(begin_pos, end_pos, insert_before, insert_after)\n    end\n\n    # Shortcut for `replace(location, end_location, \"\")`\n    def remove(location, end_location)\n      @rewriter.remove(loc_to_pos(location), loc_to_pos(end_location) + 1)\n    end\n\n    # Shortcut for `replace(range, \"\")`\n    def remove(range : Range(Int32, Int32))\n      begin_pos, end_pos = range.begin, range.end\n      end_pos -= 1 unless range.excludes_end?\n      @rewriter.remove(begin_pos, end_pos)\n    end\n\n    # Shortcut for `wrap(location, end_location, content, nil)`\n    def insert_before(location, end_location, content)\n      @rewriter.insert_before(loc_to_pos(location), loc_to_pos(end_location) + 1, content)\n    end\n\n    # Shortcut for `wrap(range, content, nil)`\n    def insert_before(range : Range(Int32, Int32), content)\n      begin_pos, end_pos = range.begin, range.end\n      end_pos -= 1 unless range.excludes_end?\n      @rewriter.insert_before(begin_pos, end_pos, content)\n    end\n\n    # Shortcut for `wrap(location, end_location, nil, content)`\n    def insert_after(location, end_location, content)\n      @rewriter.insert_after(loc_to_pos(location), loc_to_pos(end_location) + 1, content)\n    end\n\n    # Shortcut for `wrap(range, nil, content)`\n    def insert_after(range : Range(Int32, Int32), content)\n      begin_pos, end_pos = range.begin, range.end\n      end_pos -= 1 unless range.excludes_end?\n      @rewriter.insert_after(begin_pos, end_pos, content)\n    end\n\n    # Shortcut for `insert_before(location, location, content)`\n    def insert_before(location, content)\n      @rewriter.insert_before(loc_to_pos(location), content)\n    end\n\n    # Shortcut for `insert_before(pos.., content)`\n    def insert_before(pos : Int32, content)\n      @rewriter.insert_before(pos, content)\n    end\n\n    # Shortcut for `insert_after(location, location, content)`\n    def insert_after(location, content)\n      @rewriter.insert_after(loc_to_pos(location) + 1, content)\n    end\n\n    # Shortcut for `insert_after(...pos, content)`\n    def insert_after(pos : Int32, content)\n      @rewriter.insert_after(pos, content)\n    end\n\n    # Removes *size* characters prior to the source range.\n    def remove_preceding(location, end_location, size)\n      @rewriter.remove(loc_to_pos(location) - size, loc_to_pos(location))\n    end\n\n    # :ditto:\n    def remove_preceding(range : Range(Int32, Int32), size)\n      begin_pos = range.begin\n      @rewriter.remove(begin_pos - size, begin_pos)\n    end\n\n    # Removes *size* characters from the beginning of the given range.\n    # If *size* is greater than the size of the range, the removed region can\n    # overrun the end of the range.\n    def remove_leading(location, end_location, size)\n      @rewriter.remove(loc_to_pos(location), loc_to_pos(location) + size)\n    end\n\n    # :ditto:\n    def remove_leading(range : Range(Int32, Int32), size)\n      begin_pos = range.begin\n      @rewriter.remove(begin_pos, begin_pos + size)\n    end\n\n    # Removes *size* characters from the end of the given range.\n    # If *size* is greater than the size of the range, the removed region can\n    # overrun the beginning of the range.\n    def remove_trailing(location, end_location, size)\n      @rewriter.remove(loc_to_pos(end_location) + 1 - size, loc_to_pos(end_location) + 1)\n    end\n\n    # :ditto:\n    def remove_trailing(range : Range(Int32, Int32), size)\n      end_pos = range.end\n      end_pos -= 1 unless range.excludes_end?\n      @rewriter.remove(end_pos - size, end_pos)\n    end\n\n    private def loc_to_pos(location : Crystal::Location | {Int32, Int32})\n      if location.is_a?(Crystal::Location)\n        line, column = location.line_number, location.column_number\n      else\n        line, column = location\n      end\n      @line_sizes[0...line - 1].sum + (column - 1)\n    end\n\n    # Replaces the code of the given node with *content*.\n    def replace(node : Crystal::ASTNode, content)\n      replace(location(node), end_location(node), content)\n    end\n\n    # Inserts the given strings before and after the given node.\n    def wrap(node : Crystal::ASTNode, insert_before, insert_after)\n      wrap(location(node), end_location(node), insert_before, insert_after)\n    end\n\n    # Shortcut for `replace(node, \"\")`\n    def remove(node : Crystal::ASTNode)\n      remove(location(node), end_location(node))\n    end\n\n    # Shortcut for `wrap(node, content, nil)`\n    def insert_before(node : Crystal::ASTNode, content)\n      insert_before(location(node), content)\n    end\n\n    # Shortcut for `wrap(node, nil, content)`\n    def insert_after(node : Crystal::ASTNode, content)\n      insert_after(end_location(node), content)\n    end\n\n    # Removes *size* characters prior to the given node.\n    def remove_preceding(node : Crystal::ASTNode, size)\n      remove_preceding(location(node), end_location(node), size)\n    end\n\n    # Removes *size* characters from the beginning of the given node.\n    # If *size* is greater than the size of the node, the removed region can\n    # overrun the end of the node.\n    def remove_leading(node : Crystal::ASTNode, size)\n      remove_leading(location(node), end_location(node), size)\n    end\n\n    # Removes *size* characters from the end of the given node.\n    # If *size* is greater than the size of the node, the removed region can\n    # overrun the beginning of the node.\n    def remove_trailing(node : Crystal::ASTNode, size)\n      remove_trailing(location(node), end_location(node), size)\n    end\n\n    private def location(node : Crystal::ASTNode)\n      node.location || raise \"Missing location\"\n    end\n\n    private def end_location(node : Crystal::ASTNode)\n      node.end_location || raise \"Missing end location\"\n    end\n\n    # Applies all scheduled changes and returns modified source as a new string.\n    def process\n      @rewriter.process\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/source/rewriter/action.cr",
    "content": "class Ameba::Source::Rewriter\n  # :nodoc:\n  #\n  # Actions are arranged in a tree and get combined so that:\n  # - children are strictly contained by their parent\n  # - siblings all disjoint from one another and ordered\n  # - only actions with `replacement == nil` may have children\n  class Action\n    getter begin_pos : Int32\n    getter end_pos : Int32\n    getter replacement : String?\n    getter insert_before : String\n    getter insert_after : String\n    protected getter children : Array(Action)\n\n    def initialize(@begin_pos,\n                   @end_pos,\n                   @insert_before = \"\",\n                   @replacement = nil,\n                   @insert_after = \"\",\n                   @children = [] of Action)\n    end\n\n    def combine(action)\n      return self if action.empty? # Ignore empty action\n\n      if action.begin_pos == @begin_pos && action.end_pos == @end_pos\n        merge(action)\n      else\n        place_in_hierarchy(action)\n      end\n    end\n\n    def empty?\n      replacement = @replacement\n\n      @insert_before.empty? &&\n        @insert_after.empty? &&\n        @children.empty? &&\n        (replacement.nil? ||\n          (replacement.empty? && @begin_pos == @end_pos))\n    end\n\n    def ordered_replacements\n      replacement = @replacement\n\n      reps = [] of {Int32, Int32, String}\n      reps << {@begin_pos, @begin_pos, @insert_before} unless @insert_before.empty?\n      reps << {@begin_pos, @end_pos, replacement} if replacement\n      reps.concat(@children.flat_map(&.ordered_replacements))\n      reps << {@end_pos, @end_pos, @insert_after} unless @insert_after.empty?\n      reps\n    end\n\n    def insertion?\n      replacement = @replacement\n\n      !@insert_before.empty? ||\n        !@insert_after.empty? ||\n        (replacement && !replacement.empty?)\n    end\n\n    protected def with(*,\n                       begin_pos = @begin_pos,\n                       end_pos = @end_pos,\n                       insert_before = @insert_before,\n                       replacement = @replacement,\n                       insert_after = @insert_after,\n                       children = @children)\n      children = [] of Action if replacement\n      self.class.new(begin_pos, end_pos, insert_before, replacement, insert_after, children)\n    end\n\n    protected def place_in_hierarchy(action)\n      family = analyze_hierarchy(action)\n      sibling_left, sibling_right = family[:sibling_left], family[:sibling_right]\n\n      if fusible = family[:fusible]\n        child = family[:child]\n        child ||= [] of Action\n        fuse_deletions(action, fusible, sibling_left + child + sibling_right)\n      else\n        extra_sibling =\n          case\n          when parent = family[:parent]\n            # action should be a descendant of one of the children\n            parent.combine(action)\n          when child = family[:child]\n            # or it should become the parent of some of the children,\n            action.with(children: child).combine_children(action.children)\n          else\n            # or else it should become an additional child\n            action\n          end\n        self.with(children: sibling_left + [extra_sibling] + sibling_right)\n      end\n    end\n\n    # Assumes *more_children* all contained within `@begin_pos...@end_pos`\n    protected def combine_children(more_children)\n      more_children.reduce(self) do |parent, new_child|\n        parent.place_in_hierarchy(new_child)\n      end\n    end\n\n    protected def fuse_deletions(action, fusible, other_siblings)\n      without_fusible = self.with(children: other_siblings)\n      fusible = [action] + fusible\n      fused_begin_pos = fusible.min_of(&.begin_pos)\n      fused_end_pos = fusible.max_of(&.end_pos)\n      fused_deletion = action.with(begin_pos: fused_begin_pos, end_pos: fused_end_pos)\n      without_fusible.combine(fused_deletion)\n    end\n\n    # Similar to `@children.bsearch_index || size` except allows for a starting point\n    protected def bsearch_child_index(from = 0, &)\n      size = @children.size\n      (from...size).bsearch { |i| yield @children[i] } || size\n    end\n\n    # Returns the children in a hierarchy with respect to *action*:\n    #\n    # - `:sibling_left`, `:sibling_right` (for those that are disjoint from *action*)\n    # - `:parent` (in case one of our children contains *action*)\n    # - `:child` (in case *action* strictly contains some of our children)\n    # - `:fusible` (in case *action* overlaps some children but they can be fused in one deletion)\n    #\n    # In case a child has equal range to *action*, it is returned as `:parent`\n    #\n    # NOTE: an empty range `1...1` is considered disjoint from `1...10`\n    protected def analyze_hierarchy(action)\n      # left_index is the index of the first child that isn't completely to the left of action\n      left_index = bsearch_child_index do |child|\n        child.end_pos > action.begin_pos\n      end\n      # right_index is the index of the first child that is completely on the right of action\n      start = left_index == 0 ? 0 : left_index - 1 # See \"corner case\" below for reason of -1\n      right_index = bsearch_child_index(start) do |child|\n        child.begin_pos >= action.end_pos\n      end\n      center = right_index - left_index\n\n      case center\n      when 0\n        # All children are disjoint from action, nothing else to do\n      when -1\n        # Corner case: if a child has empty range == action's range\n        # then it will appear to be both disjoint and to the left of action,\n        # as well as disjoint and to the right of action.\n        # Since ranges are equal, we return it as parent\n        left_index -= 1  # Fix indices, as otherwise this child would be\n        right_index += 1 # considered as a sibling (both left and right!)\n        parent = @children[left_index]\n      else\n        overlap_left = @children[left_index].begin_pos <=> action.begin_pos\n        overlap_right = @children[right_index - 1].end_pos <=> action.end_pos\n\n        raise \"Unable to compare begin pos\" unless overlap_left\n        raise \"Unable to compare end pos\" unless overlap_right\n\n        # For one child to be the parent of action, we must have:\n        if center == 1 && overlap_left <= 0 && overlap_right >= 0\n          parent = @children[left_index]\n        else\n          # Otherwise consider all non disjoint elements (center) to be contained...\n          contained = @children[left_index...right_index]\n          fusible = [] of Action\n          fusible << contained.shift if overlap_left < 0 # ... but check first and last one\n          fusible << contained.pop if overlap_right > 0  # ... for overlaps\n          fusible = nil if fusible.empty?\n        end\n      end\n\n      {\n        parent:        parent,\n        sibling_left:  @children[0...left_index],\n        sibling_right: @children[right_index...@children.size],\n        fusible:       fusible,\n        child:         contained,\n      }\n    end\n\n    # Assumes *action* has the exact same range and has no children\n    protected def merge(action)\n      self.with(\n        insert_before: \"#{action.insert_before}#{insert_before}\",\n        replacement: action.replacement || @replacement,\n        insert_after: \"#{insert_after}#{action.insert_after}\",\n      ).combine_children(action.children)\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/source/rewriter.cr",
    "content": "class Ameba::Source\n  # This class performs the heavy lifting in the source rewriting process.\n  # It schedules code updates to be performed in the correct order.\n  #\n  # For simple cases, the resulting source will be obvious.\n  #\n  # Examples for more complex cases follow. Assume these examples are acting on\n  # the source `puts(:hello, :world)`. The methods `#wrap`, `#remove`, etc.\n  # receive a range as the first two arguments; for clarity, examples below use\n  # English sentences and a string of raw code instead.\n  #\n  # ## Overlapping deletions:\n  #\n  # * remove `:hello, `\n  # * remove `, :world`\n  #\n  # The overlapping ranges are merged and `:hello, :world` will be removed.\n  #\n  # ## Multiple actions at the same end points:\n  #\n  # Results will always be independent of the order they were given.\n  # Exception: rewriting actions done on exactly the same range (covered next).\n  #\n  # Example:\n  #\n  # * replace `, ` by ` => `\n  # * wrap `:hello, :world` with `{` and `}`\n  # * replace `:world` with `:everybody`\n  # * wrap `:world` with `[`, `]`\n  #\n  # The resulting string will be `puts({:hello => [:everybody]})`\n  # and this result is independent of the order the instructions were given in.\n  #\n  # ## Multiple wraps on same range:\n  #\n  # * wrap `:hello` with `(` and `)`\n  # * wrap `:hello` with `[` and `]`\n  #\n  # The wraps are combined in order given and results would be `puts([(:hello)], :world)`.\n  #\n  # ## Multiple replacements on same range:\n  #\n  # * replace `:hello` by `:hi`, then\n  # * replace `:hello` by `:hey`\n  #\n  # The replacements are made in the order given, so the latter replacement\n  # supersedes the former and `:hello` will be replaced by `:hey`.\n  #\n  # ## Swallowed insertions:\n  #\n  # * wrap `world` by `__`, `__`\n  # * replace `:hello, :world` with `:hi`\n  #\n  # A containing replacement will swallow the contained rewriting actions\n  # and `:hello, :world` will be replaced by `:hi`.\n  #\n  # ## Implementation\n  #\n  # The updates are organized in a tree, according to the ranges they act on\n  # (where children are strictly contained by their parent).\n  class Rewriter\n    getter code : String\n\n    def initialize(@code)\n      @action_root = Rewriter::Action.new(0, code.size)\n    end\n\n    # Returns `true` if no (non trivial) update has been recorded\n    def empty?\n      @action_root.empty?\n    end\n\n    # Replaces the code of the given range with *content*.\n    def replace(begin_pos, end_pos, content)\n      combine begin_pos, end_pos,\n        replacement: content.to_s\n    end\n\n    # Inserts the given strings before and after the given range.\n    def wrap(begin_pos, end_pos, insert_before, insert_after)\n      combine begin_pos, end_pos,\n        insert_before: insert_before.to_s,\n        insert_after: insert_after.to_s\n    end\n\n    # Shortcut for `replace(begin_pos, end_pos, \"\")`\n    def remove(begin_pos, end_pos)\n      replace(begin_pos, end_pos, \"\")\n    end\n\n    # Shortcut for `wrap(begin_pos, end_pos, content, nil)`\n    def insert_before(begin_pos, end_pos, content)\n      wrap(begin_pos, end_pos, content, nil)\n    end\n\n    # Shortcut for `wrap(begin_pos, end_pos, nil, content)`\n    def insert_after(begin_pos, end_pos, content)\n      wrap(begin_pos, end_pos, nil, content)\n    end\n\n    # Shortcut for `insert_before(pos, pos, content)`\n    def insert_before(pos, content)\n      insert_before(pos, pos, content)\n    end\n\n    # Shortcut for `insert_after(pos, pos, content)`\n    def insert_after(pos, content)\n      insert_after(pos, pos, content)\n    end\n\n    # Applies all scheduled changes and returns modified source as a new string.\n    def process\n      String.build do |io|\n        last_end = 0\n        @action_root.ordered_replacements.each do |begin_pos, end_pos, replacement|\n          io << code[last_end...begin_pos] << replacement\n          last_end = end_pos\n        end\n        io << code[last_end...code.size]\n      end\n    end\n\n    protected def combine(begin_pos, end_pos, **attributes)\n      check_range_validity(begin_pos, end_pos)\n      action = Rewriter::Action.new(begin_pos, end_pos, **attributes)\n      @action_root = @action_root.combine(action)\n    end\n\n    private def check_range_validity(begin_pos, end_pos)\n      return unless begin_pos < 0 || end_pos > code.size\n      raise IndexError.new(\n        \"The range #{begin_pos}...#{end_pos} is outside the bounds of \" \\\n        \"the source (0...#{code.size})\"\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/source.cr",
    "content": "module Ameba\n  # An entity that represents a Crystal source file.\n  # Has path, lines of code and issues reported by rules.\n  class Source\n    include InlineComments\n    include Reportable\n\n    # Path to the source file.\n    getter path : String\n\n    # Absolute path to the source file.\n    getter fullpath : String do\n      File.expand_path(path)\n    end\n\n    # Crystal code (content of a source file).\n    getter code : String\n\n    # Creates a new source by `code` and `path`.\n    #\n    # For example:\n    #\n    # ```\n    # path = \"./src/source.cr\"\n    # Ameba::Source.new(File.read(path), path)\n    # ```\n    def initialize(@code = \"\", @path = \"\")\n    end\n\n    # Corrects any correctable issues and updates `code`.\n    # Returns `false` if no issues were corrected.\n    def correct!\n      corrector = Corrector.new(code)\n      issues.each { |issue| issue.correct(corrector) if issue.enabled? }\n\n      corrected_code = corrector.process\n      return false if code == corrected_code\n\n      @code = corrected_code\n      @lines = nil\n      @ast = nil\n\n      true\n    end\n\n    # Returns lines of code split by new line character.\n    # Since `code` is immutable and can't be changed, this\n    # method caches lines in an instance variable, so calling\n    # it second time will not perform a split, but will return\n    # lines instantly.\n    #\n    # ```\n    # source = Ameba::Source.new(\"a = 1\\nb = 2\", path)\n    # source.lines # => [\"a = 1\", \"b = 2\"]\n    # ```\n    getter lines : Array(String) { code.split(/\\r?\\n/) }\n\n    # Returns AST nodes constructed by `Crystal::Parser`.\n    #\n    # ```\n    # source = Ameba::Source.new(code, path)\n    # source.ast\n    # ```\n    getter ast : Crystal::ASTNode do\n      code = @code\n\n      if ecr?\n        begin\n          code = ECR.process_string(code, path)\n        rescue ex : ECR::Lexer::SyntaxException\n          # Need to rescue to add the filename\n          raise Crystal::SyntaxException.new(\n            ex.message,\n            ex.line_number,\n            ex.column_number,\n            path\n          )\n        end\n      end\n\n      Crystal::Parser.new(code)\n        .tap(&.wants_doc = true)\n        .tap(&.filename = path)\n        .parse\n    end\n\n    # Returns `true` if the source is a spec file, `false` otherwise.\n    def spec?\n      path.ends_with?(\"_spec.cr\")\n    end\n\n    # Returns `true` if the source is an ECR template, `false` otherwise.\n    def ecr?\n      path.ends_with?(\".ecr\")\n    end\n\n    # Converts an AST location to a string position.\n    def pos(location : Crystal::Location, end end_pos = false) : Int32\n      line, column = location.line_number, location.column_number\n      pos = lines[0...line - 1].sum(&.size) + line + column - 2\n      pos += 1 if end_pos\n      pos\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/spec/annotated_source.cr",
    "content": "# Parsed representation of code annotated with the `# ^^^ error: Message` style\nclass Ameba::Spec::AnnotatedSource\n  ANNOTATION_PATTERN_1 = /\\A\\s*(# )?(\\^+|\\^{})( error:)? /\n  ANNOTATION_PATTERN_2 = \" # error: \"\n\n  ABBREV = \"[...]\"\n\n  getter lines : Array(String)\n\n  # Each entry is the line number, annotation prefix, and message.\n  # The prefix is empty if the annotation is at the end of a code line.\n  getter annotations : Array({Int32, String, String})\n\n  # Separates annotation lines from code lines. Tracks the real\n  # code line number that each annotation corresponds to.\n  def self.parse(annotated_code)\n    lines = [] of String\n    annotations = [] of {Int32, String, String}\n\n    code_lines = annotated_code.split('\\n') # must preserve trailing newline\n    code_lines.each do |code_line|\n      case\n      when annotation_match = ANNOTATION_PATTERN_1.match(code_line)\n        message_index = annotation_match.end\n        prefix = code_line[0...message_index]\n        message = code_line[message_index...]\n        annotations << {lines.size, prefix, message}\n      when annotation_index = code_line.index(ANNOTATION_PATTERN_2)\n        lines << code_line[...annotation_index]\n        message_index = annotation_index + ANNOTATION_PATTERN_2.size\n        message = code_line[message_index...]\n        annotations << {lines.size, \"\", message}\n      else\n        lines << code_line\n      end\n    end\n    annotations.map! { |_, prefix, message| {1, prefix, message} } if lines.empty?\n    new(lines, annotations)\n  end\n\n  # NOTE: Annotations are sorted so that reconstructing the annotation\n  #       text via `#to_s` is deterministic.\n  def initialize(@lines, annotations : Enumerable({Int32, String, String}))\n    @annotations = annotations.to_a.sort_by do |line, _, message|\n      {line, message}\n    end\n  end\n\n  # Annotates the source code with the Ameba issues provided.\n  #\n  # NOTE: Annotations are sorted so that reconstructing the annotation\n  #       text via `#to_s` is deterministic.\n  def initialize(@lines, issues : Enumerable(Issue))\n    @annotations = issues_to_annotations(issues).sort_by do |line, _, message|\n      {line, message}\n    end\n  end\n\n  def ==(other)\n    other.is_a?(AnnotatedSource) && other.lines == lines && match_annotations?(other)\n  end\n\n  private def match_annotations?(other)\n    return false unless annotations.size == other.annotations.size\n\n    annotations.zip(other.annotations) do |(actual_line, actual_prefix, actual_message), (expected_line, expected_prefix, expected_message)|\n      return false unless actual_line == expected_line\n      return false unless expected_prefix.empty? || actual_prefix == expected_prefix\n      next if actual_message == expected_message\n      return false unless expected_message.includes?(ABBREV)\n\n      regex = /\\A#{message_to_regex(expected_message)}\\Z/\n      return false unless actual_message.matches?(regex)\n    end\n\n    true\n  end\n\n  private def message_to_regex(expected_annotation)\n    String.build do |io|\n      offset = 0\n      while index = expected_annotation.index(ABBREV, offset)\n        io << Regex.escape(expected_annotation[offset...index])\n        io << \".*?\"\n        offset = index + ABBREV.size\n      end\n      io << Regex.escape(expected_annotation[offset..])\n    end\n  end\n\n  # Constructs an annotated source string (like what we parse).\n  #\n  # Reconstructs a deterministic annotated source string. This is\n  # useful for eliminating semantically irrelevant annotation\n  # ordering differences.\n  #\n  #     source1 = AnnotatedSource.parse(<<-CRYSTAL)\n  #     line1\n  #     ^ Annotation 1\n  #      ^^ Annotation 2\n  #     CRYSTAL\n  #\n  #     source2 = AnnotatedSource.parse(<<-CRYSTAL)\n  #     line1\n  #      ^^ Annotation 2\n  #     ^ Annotation 1\n  #     CRYSTAL\n  #\n  #     source1.to_s == source2.to_s # => true\n  def to_s(io)\n    reconstructed = lines.dup\n    annotations.reverse_each do |line_number, prefix, message|\n      if prefix.empty?\n        reconstructed[line_number - 1] += \"#{ANNOTATION_PATTERN_2}#{message}\"\n      else\n        line_number = 0 if lines.empty?\n        reconstructed.insert(line_number, \"#{prefix}#{message}\")\n      end\n    end\n    io << reconstructed.join('\\n')\n  end\n\n  private def issues_to_annotations(issues)\n    issues.map do |issue|\n      line, column, end_line, end_column = validate_location(issue)\n      indent_count = column - 3\n      indent = if indent_count < 0\n                 \"\"\n               else\n                 \" \" * indent_count\n               end\n      caret_count = column_length(line, column, end_line, end_column)\n      caret_count += indent_count if indent_count < 0\n      carets = if caret_count <= 0\n                 \"^{}\"\n               else\n                 \"^\" * caret_count\n               end\n      {line, \"#{indent}# #{carets} error: \", issue.message}\n    end\n  end\n\n  private def validate_location(issue)\n    loc, end_loc = issue.location, issue.end_location\n    raise \"Missing location for issue '#{issue.message}'\" unless loc\n\n    line, column = loc.line_number, loc.column_number\n    if line > lines.size || line < 1 || column < 1\n      raise \"Invalid issue location: #{loc}\"\n    end\n\n    if end_loc\n      if end_loc < loc\n        raise <<-MSG\n          Invalid issue location\n            start: #{loc}\n            end:   #{end_loc}\n          MSG\n      end\n\n      end_line, end_column = end_loc.line_number, end_loc.column_number\n\n      if end_line > lines.size || end_line < 1 || end_column < 1\n        raise \"Invalid issue end location: #{end_loc}\"\n      end\n    end\n\n    {line, column, end_line, end_column}\n  end\n\n  private def column_length(line, column, end_line, end_column)\n    return 1 unless end_line && end_column\n\n    if line < end_line\n      code_line = lines[line - 1]\n      end_column = code_line.size\n    end\n\n    end_column - column + 1\n  end\nend\n"
  },
  {
    "path": "src/ameba/spec/be_valid.cr",
    "content": "module Ameba::Spec\n  module BeValid\n    def be_valid\n      BeValidExpectation.new\n    end\n  end\n\n  struct BeValidExpectation\n    def match(source)\n      source.valid?\n    end\n\n    def failure_message(source)\n      String.build do |str|\n        str << \"Source expected to be valid, but there are issues: \\n\\n\"\n        source.issues.reject(&.disabled?).each do |issue|\n          str << \"  * #{issue.rule.name}: #{issue.message}\\n\"\n        end\n      end\n    end\n\n    def negative_failure_message(source)\n      \"Source expected to be invalid, but it is valid.\"\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/spec/expect_issue.cr",
    "content": "require \"./annotated_source\"\nrequire \"./util\"\n\n# Mixin for `expect_issue` and `expect_no_issues`\n#\n# This mixin makes it easier to specify strict issue expectations\n# in a declarative and visual fashion. Just type out the code that\n# should generate an issue, annotate code by writing '^'s\n# underneath each character that should be highlighted, and follow\n# the carets with a string (separated by a space) that is the\n# message of the issue. You can include multiple issues in\n# one code snippet.\n#\n# Usage:\n#\n#     expect_issue subject, <<-CRYSTAL\n#       a do\n#         b\n#       end.c\n#       # ^^^ error: Avoid chaining a method call on a do...end block.\n#       CRYSTAL\n#\n# Equivalent assertion without `expect_issue`:\n#\n#     source = Source.new <<-CRYSTAL, \"source.cr\"\n#       a do\n#         b\n#       end.c\n#       CRYSTAL\n#     subject.catch(source).should_not be_valid\n#     source.issues.size.should be(1)\n#\n#     issue = source.issues.first\n#     issue.location.to_s.should eq \"source.cr:3:1\"\n#     issue.end_location.to_s.should eq \"source.cr:3:5\"\n#     issue.message.should eq(\n#       \"Avoid chaining a method call on a do...end block.\"\n#     )\n#\n# Autocorrection can be tested using `expect_correction` after\n# `expect_issue`.\n#\n#     source = expect_issue subject, <<-CRYSTAL\n#       x % 2 == 0\n#       # ^^^^^^^^ error: Replace with `Int#even?`.\n#       CRYSTAL\n#\n#     expect_correction source, <<-CRYSTAL\n#       x.even?\n#       CRYSTAL\n#\n# If you do not want to specify an issue then use the\n# companion method `expect_no_issues`. This method is a much\n# simpler assertion since it just inspects the code and checks\n# that there were no issues. The `expect_issue` method has\n# to do more work by parsing out lines that contain carets.\n#\n# If the code produces an issue that could not be auto-corrected, you can\n# use `expect_no_corrections` after `expect_issue`.\n#\n#     source = expect_issue subject, <<-CRYSTAL\n#       a do\n#         b\n#       end.c\n#       # ^^^ error: Avoid chaining a method call on a do...end block.\n#       CRYSTAL\n#\n#     expect_no_corrections source\n#\n# If your code has variables of different lengths, you can use `%{foo}`,\n# `^{foo}`, and `_{foo}` to format your template; you can also abbreviate\n# issue messages with `[...]`:\n#\n#     %w[raise fail].each do |keyword|\n#       expect_issue subject, <<-CRYSTAL, keyword: keyword\n#         %{keyword} Exception.new(msg)\n#         # ^{keyword}^^^^^^^^^^^^^^^^^ error: Redundant `Exception.new` [...]\n#         CRYSTAL\n#\n#     %w[has_one has_many].each do |type|\n#       expect_issue subject, <<-CRYSTAL, type: type\n#         class Book\n#           %{type} :chapter, foreign_key: \"book_id\"\n#           _{type}         # ^^^^^^^^^^^^^^^^^^^^^^ error: Specifying the default [...]\n#         end\n#         CRYSTAL\n#     end\n#\n# If you need to specify an issue on a blank line, use the empty `^{}` marker:\n#\n#     expect_issue subject, <<-CRYSTAL\n#\n#       # ^{} error: Missing frozen string literal comment.\n#       puts 1\n#       CRYSTAL\nmodule Ameba::Spec::ExpectIssue\n  include Spec::Util\n\n  def expect_issue(rules : Rule::Base | Enumerable(Rule::Base),\n                   annotated_code : String,\n                   path = \"\",\n                   *,\n                   file = __FILE__,\n                   line = __LINE__,\n                   **replacements)\n    annotated_code = format_issue(annotated_code, **replacements)\n    expected_annotations = AnnotatedSource.parse(annotated_code)\n    lines = expected_annotations.lines\n    code = lines.join('\\n')\n\n    if code == annotated_code\n      raise \"Use `expect_no_issues` to assert that no issues are found\"\n    end\n\n    source, actual_annotations = actual_annotations(rules, code, path, lines)\n    unless actual_annotations == expected_annotations\n      fail <<-MSG, file, line\n        Expected:\n\n        #{expected_annotations}\n\n        Got:\n\n        #{actual_annotations}\n        MSG\n    end\n\n    source\n  end\n\n  def expect_correction(source, correction, *, file = __FILE__, line = __LINE__)\n    raise \"Use `expect_no_corrections` if the code will not change\" unless source.correct!\n    return if correction == source.code\n\n    fail <<-MSG, file, line\n      Expected correction:\n\n      #{correction}\n\n      Got:\n\n      #{source.code}\n      MSG\n  end\n\n  def expect_no_corrections(source, *, file = __FILE__, line = __LINE__)\n    return unless source.correct!\n\n    fail <<-MSG, file, line\n      Expected no corrections, but got:\n\n      #{source.code}\n      MSG\n  end\n\n  def expect_no_issues(rules : Rule::Base | Enumerable(Rule::Base),\n                       code : String,\n                       path = \"\",\n                       *,\n                       file = __FILE__,\n                       line = __LINE__)\n    lines = code.split('\\n') # must preserve trailing newline\n\n    _, actual_annotations = actual_annotations(rules, code, path, lines)\n    return if actual_annotations.to_s == code\n\n    fail <<-MSG, file, line\n      Expected no issues, but got:\n\n      #{actual_annotations}\n      MSG\n  end\n\n  private def actual_annotations(rules, code, path, lines)\n    source = Source.new(code, path, normalize: false)\n    if rules.is_a?(Enumerable)\n      rules.each(&.catch(source))\n    else\n      rules.catch(source)\n    end\n    {source, AnnotatedSource.new(lines, source.issues)}\n  end\n\n  private def format_issue(code, **replacements)\n    replacements.each do |keyword, value|\n      value = value.to_s\n      code = code\n        .gsub(\"%{#{keyword}}\", value)\n        .gsub(\"^{#{keyword}}\", \"^\" * value.size)\n        .gsub(\"_{#{keyword}}\", \" \" * value.size)\n    end\n    code\n  end\nend\n"
  },
  {
    "path": "src/ameba/spec/support.cr",
    "content": "# Require this file to load code that supports testing Ameba rules.\n\nrequire \"./be_valid\"\nrequire \"./expect_issue\"\nrequire \"./util\"\n\nmodule Ameba\n  class Source\n    include Spec::Util\n\n    def initialize(code : String, @path = \"\", normalize = true)\n      @code = normalize ? normalize_code(code) : code\n    end\n  end\nend\n\ninclude Ameba::Spec::BeValid\ninclude Ameba::Spec::ExpectIssue\n"
  },
  {
    "path": "src/ameba/spec/util.cr",
    "content": "module Ameba::Spec::Util\n  def normalize_code(code, separator = '\\n')\n    lines = code.split(separator)\n\n    # remove unneeded first blank lines if any\n    lines.shift if lines[0].blank? && lines.size > 1\n\n    # find the minimum indentation\n    min_indent = lines.min_of do |line|\n      line.blank? ? code.size : line.size - line.lstrip.size\n    end\n\n    # remove the width of minimum indentation in each line\n    lines.join(separator) do |line|\n      line.blank? ? line : line[min_indent..]\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/tokenizer.cr",
    "content": "require \"compiler/crystal/syntax/*\"\n\nmodule Ameba\n  # Represents Crystal syntax tokenizer based on `Crystal::Lexer`.\n  #\n  # ```\n  # source = Ameba::Source.new(code, path)\n  # tokenizer = Ameba::Tokenizer.new(source)\n  # tokenizer.run do |token|\n  #   puts token\n  # end\n  # ```\n  class Tokenizer\n    # Instantiates Tokenizer using a `source`.\n    #\n    # ```\n    # source = Ameba::Source.new(code, path)\n    # Ameba::Tokenizer.new(source)\n    # ```\n    def initialize(source)\n      @lexer = Crystal::Lexer.new source.code\n      @lexer.count_whitespace = true\n      @lexer.comments_enabled = true\n      @lexer.wants_raw = true\n      @lexer.filename = source.path\n    end\n\n    # Instantiates Tokenizer using a `lexer`.\n    #\n    # ```\n    # lexer = Crystal::Lexer.new(code)\n    # Ameba::Tokenizer.new(lexer)\n    # ```\n    def initialize(@lexer : Crystal::Lexer)\n    end\n\n    # Runs the tokenizer and yields each token as a block argument.\n    #\n    # ```\n    # Ameba::Tokenizer.new(source).run do |token|\n    #   puts token\n    # end\n    # ```\n    def run(&block : Crystal::Token -> _)\n      run_normal_state @lexer, &block\n      true\n    rescue Crystal::SyntaxException\n      false\n    end\n\n    private def run_normal_state(lexer, break_on_rcurly = false, &block : Crystal::Token -> _)\n      loop do\n        token = @lexer.next_token\n        block.call token\n\n        case token.type\n        when .delimiter_start?\n          run_delimiter_state lexer, token, &block\n        when .string_array_start?, .symbol_array_start?\n          run_array_state lexer, token, &block\n        when .eof?\n          break\n        when .op_rcurly?\n          break if break_on_rcurly\n        end\n      end\n    end\n\n    private def run_delimiter_state(lexer, token, &block : Crystal::Token -> _)\n      loop do\n        token = @lexer.next_string_token(token.delimiter_state)\n        block.call token\n\n        case token.type\n        when .interpolation_start?\n          run_normal_state lexer, break_on_rcurly: true, &block\n        when .delimiter_end?, .eof?\n          break\n        end\n      end\n    end\n\n    private def run_array_state(lexer, token, &block : Crystal::Token -> _)\n      loop do\n        # NOTE: Crystal::Token is a class and the lexer modifies @token in place,\n        # so the assignment here is for clarity/consistency with run_delimiter_state,\n        # not for correctness (the behavior is identical without the assignment).\n        token = lexer.next_string_array_token\n        block.call token\n\n        case token.type\n        when .string_array_end?, .eof?\n          break\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba/version.cr",
    "content": "module Ameba\n  VERSION = detect_version\n\n  # Detects Ameba version from several sources.\n  private macro detect_version\n    {%\n      version =\n        env(\"AMEBA_BUILD_VERSION\") ||\n          read_file?(\"#{__DIR__}/../../VERSION\") ||\n          `shards version \"#{__DIR__}\"`.stringify\n    %}\n    {{ version.chomp }}\n  end\n\n  class Version\n    {% if flag?(:windows) %}\n      private GIT_SHA = nil\n    {% else %}\n      private GIT_SHA =\n        {{ `(git rev-parse --short HEAD || true) 2>/dev/null`.chomp.stringify }}.presence\n    {% end %}\n\n    # Cached version object.\n    INSTANCE = begin\n      version = SemanticVersion.parse(VERSION)\n      unless version.build\n        version = version.copy_with \\\n          build: GIT_SHA.try { |commit| \"git.commit.#{commit}\" }\n      end\n      new(version)\n    end\n\n    # Returns the current version as a `SemanticVersion` object.\n    getter version : SemanticVersion\n\n    def initialize(@version)\n    end\n\n    # Appends the version string to the given *io*.\n    def to_s(io : IO) : Nil\n      version.to_s(io)\n    end\n\n    # Returns the `version` without prerelease and build metadata.\n    def for_production : String\n      version.copy_with(prerelease: nil, build: nil).to_s\n    end\n\n    # Returns the `version` without prerelease and build metadata.\n    def for_docs : String\n      dev? ? \"master\" : for_production\n    end\n\n    # Returns `true` if the current `version` is a development version.\n    def dev? : Bool\n      version.prerelease.identifiers.any?(\"dev\")\n    end\n\n    # # Returns `true` if the current `version` is a release candidate version.\n    def release_candidate? : Bool\n      version.prerelease.identifiers.any?(/^rc-?(\\d+)?$/)\n    end\n  end\nend\n"
  },
  {
    "path": "src/ameba.cr",
    "content": "# Ameba's entry module.\n#\n# To run the linter with default parameters:\n#\n# ```\n# Ameba.run\n# ```\n#\n# To configure and run it:\n#\n# ```\n# config = Ameba::Config.load\n# config.formatter = formatter\n# config.files = file_paths\n#\n# Ameba.run config\n# ```\nmodule Ameba\n  extend self\n\n  # Returns the version object of Ameba.\n  def version : Version\n    Version::INSTANCE\n  end\n\n  # Initializes `Ameba::Runner` and runs it.\n  # Can be configured via `config` parameter.\n  #\n  # Examples:\n  #\n  # ```\n  # Ameba.run\n  # Ameba.run config\n  # ```\n  def run(config = Config.load)\n    Runner.new(config).run\n  end\nend\n\nrequire \"./ameba/*\"\nrequire \"./ameba/ast/**\"\nrequire \"./ameba/ext/**\"\nrequire \"./ameba/rule/**\"\nrequire \"./ameba/formatter/*\"\nrequire \"./ameba/presenter/*\"\nrequire \"./ameba/source/**\"\n"
  },
  {
    "path": "src/cli.cr",
    "content": "require \"./ameba/cli/cmd\"\n\nbegin\n  exit Ameba::CLI.run ? 0 : 1\nrescue ex\n  STDERR.puts \"Error: #{ex.message}\"\n  exit 255\nend\n"
  },
  {
    "path": "src/contrib/read_type_doc.cr",
    "content": "require \"compiler/crystal/syntax/*\"\n\nprivate class DocFinder < Crystal::Visitor\n  getter type_name : String\n  getter doc : String?\n\n  def initialize(nodes, @type_name)\n    nodes.accept self\n  end\n\n  def visit(node : Crystal::ASTNode)\n    return false if @doc\n\n    if node.responds_to?(:name) && (name = node.name).is_a?(Crystal::Path)\n      @doc = node.doc if name.names.last? == @type_name\n    end\n\n    true\n  end\nend\n\ntype_name, path_to_source_file = ARGV\n\nsource = File.read(path_to_source_file)\nnodes = Crystal::Parser.new(source)\n  .tap(&.wants_doc = true)\n  .parse\n\nputs DocFinder.new(nodes, type_name).doc\n"
  },
  {
    "path": "src/json-schema-builder.cr",
    "content": "require \"./ameba\"\nrequire \"./ameba/json_schema/*\"\n\nJSON_SCHEMA_FILEPATH =\n  Path[__DIR__, \"..\", \".ameba.yml.schema.json\"].expand\n\nAmeba::JSONSchema::Builder.build(JSON_SCHEMA_FILEPATH)\n"
  }
]