diff --git a/spec/ameba/rule/documentation/documentation_spec.cr b/spec/ameba/rule/documentation/documentation_spec.cr index 3be29cea4..72889fc27 100644 --- a/spec/ameba/rule/documentation/documentation_spec.cr +++ b/spec/ameba/rule/documentation/documentation_spec.cr @@ -29,7 +29,7 @@ module Ameba::Rule::Documentation private macro bag end - CRYSTAL + CRYSTAL end it "passes for documented public types" do @@ -59,31 +59,31 @@ module Ameba::Rule::Documentation # bag macro bag end - CRYSTAL + CRYSTAL end it "fails if there is an undocumented public type" do expect_issue subject, <<-CRYSTAL class Foo - # ^^^^^^^^^ error: Missing documentation + # ^^^^^^^ error: Missing documentation end module Bar - # ^^^^^^^^^^ error: Missing documentation + # ^^^^^^^^ error: Missing documentation end enum Baz - # ^^^^^^^^ error: Missing documentation + # ^^^^^^ error: Missing documentation end def bat - # ^^^^^^^ error: Missing documentation + # ^^^^^ error: Missing documentation end macro bag - # ^^^^^^^^^ error: Missing documentation + # ^^^^^^^ error: Missing documentation end - CRYSTAL + CRYSTAL end context "properties" do diff --git a/spec/ameba/rule/lint/empty_ensure_spec.cr b/spec/ameba/rule/lint/empty_ensure_spec.cr index 5b7f11b0e..d2c3ce45c 100644 --- a/spec/ameba/rule/lint/empty_ensure_spec.cr +++ b/spec/ameba/rule/lint/empty_ensure_spec.cr @@ -23,7 +23,7 @@ module Ameba::Rule::Lint ensure nil end - CRYSTAL + CRYSTAL end it "fails if there is an empty ensure in method" do @@ -31,9 +31,9 @@ module Ameba::Rule::Lint def method do_some_stuff ensure - # ^^^^^^ error: Empty `ensure` block detected + # ^^^^ error: Empty `ensure` block detected end - CRYSTAL + CRYSTAL end it "fails if there is an empty ensure in a block" do @@ -43,10 +43,10 @@ module Ameba::Rule::Lint rescue do_some_other_stuff ensure - # ^^^^^^ error: Empty `ensure` block detected + # ^^^^ error: Empty `ensure` block detected # nothing here end - CRYSTAL + CRYSTAL end end end diff --git a/spec/ameba/rule/lint/shadowed_exception_spec.cr b/spec/ameba/rule/lint/shadowed_exception_spec.cr index 016867a71..2d07c407b 100644 --- a/spec/ameba/rule/lint/shadowed_exception_spec.cr +++ b/spec/ameba/rule/lint/shadowed_exception_spec.cr @@ -25,7 +25,7 @@ module Ameba::Rule::Lint rescue e : Exception handle_exception end - CRYSTAL + CRYSTAL end it "fails if there is a shadowed exception" do @@ -38,7 +38,7 @@ module Ameba::Rule::Lint # ^^^^^^^^^^^^^ error: Shadowed exception found: `ArgumentError` handle_argument_error_exception end - CRYSTAL + CRYSTAL end it "fails if there is a custom shadowed exceptions" do @@ -51,7 +51,7 @@ module Ameba::Rule::Lint # ^^^^^^^^^^^^^^^^ error: Shadowed exception found: `MySuperException` 3 end - CRYSTAL + CRYSTAL end it "fails if there is a shadowed exception in a type list" do @@ -60,7 +60,7 @@ module Ameba::Rule::Lint rescue Exception | IndexError # ^^^^^^^^^^ error: Shadowed exception found: `IndexError` end - CRYSTAL + CRYSTAL end it "fails if there is a first shadowed exception in a type list" do @@ -72,7 +72,7 @@ module Ameba::Rule::Lint # ^^^^^^^^^ error: Shadowed exception found: `Exception` rescue end - CRYSTAL + CRYSTAL end it "fails if there is a shadowed duplicated exception" do @@ -83,7 +83,7 @@ module Ameba::Rule::Lint rescue IndexError # ^^^^^^^^^^ error: Shadowed exception found: `IndexError` end - CRYSTAL + CRYSTAL end it "fails if there is a shadowed duplicated exception in a type list" do @@ -93,7 +93,7 @@ module Ameba::Rule::Lint rescue ArgumentError | IndexError # ^^^^^^^^^^ error: Shadowed exception found: `IndexError` end - CRYSTAL + CRYSTAL end it "fails if there is only shadowed duplicated exceptions" do @@ -104,7 +104,7 @@ module Ameba::Rule::Lint # ^^^^^^^^^^ error: Shadowed exception found: `IndexError` rescue Exception end - CRYSTAL + CRYSTAL end it "fails if there is only shadowed duplicated exceptions in a type list" do @@ -113,7 +113,7 @@ module Ameba::Rule::Lint rescue IndexError | IndexError # ^^^^^^^^^^ error: Shadowed exception found: `IndexError` end - CRYSTAL + CRYSTAL end it "fails if all rescues are shadowed and there is a catch-all rescue" do @@ -129,7 +129,7 @@ module Ameba::Rule::Lint # ^^^^^^^^ error: Shadowed exception found: `KeyError` rescue end - CRYSTAL + CRYSTAL end it "fails if there are shadowed exception with args" do @@ -140,7 +140,7 @@ module Ameba::Rule::Lint # ^^^^^^^^^^ error: Shadowed exception found: `IndexError` rescue end - CRYSTAL + CRYSTAL end it "fails if there are multiple shadowed exceptions" do @@ -152,7 +152,7 @@ module Ameba::Rule::Lint rescue IndexError # ^^^^^^^^^^ error: Shadowed exception found: `IndexError` end - CRYSTAL + CRYSTAL end it "fails if there are multiple shadowed exceptions in a type list" do diff --git a/spec/ameba/rule/style/heredoc_indent_spec.cr b/spec/ameba/rule/style/heredoc_indent_spec.cr new file mode 100644 index 000000000..6d7b739cc --- /dev/null +++ b/spec/ameba/rule/style/heredoc_indent_spec.cr @@ -0,0 +1,81 @@ +require "../../../spec_helper" + +module Ameba::Rule::Style + describe HeredocIndent do + subject = HeredocIndent.new + + it "passes if heredoc body indented one level" do + expect_no_issues subject, <<-CRYSTAL + <<-HEREDOC + hello world + HEREDOC + + <<-HEREDOC + hello world + HEREDOC + CRYSTAL + end + + it "fails if the heredoc body is indented incorrectly" do + expect_issue subject, <<-CRYSTAL + <<-ONE + # ^^^^ error: Heredoc body should be indented by 2 spaces + hello world + ONE + + <<-TWO + # ^^^^^^ error: Heredoc body should be indented by 2 spaces + hello world + TWO + + <<-THREE + # ^^^^^^^^ error: Heredoc body should be indented by 2 spaces + hello world + THREE + + <<-FOUR + # ^^^^^^^ error: Heredoc body should be indented by 2 spaces + hello world + FOUR + CRYSTAL + end + + context "properties" do + context "#indent_by" do + rule = HeredocIndent.new + rule.indent_by = 0 + + it "passes if heredoc body has the same indent level" do + expect_no_issues rule, <<-CRYSTAL + <<-HEREDOC + hello world + HEREDOC + + <<-HEREDOC + hello world + HEREDOC + CRYSTAL + end + + it "fails if the heredoc body is indented incorrectly" do + expect_issue rule, <<-CRYSTAL + <<-ONE + # ^^^^ error: Heredoc body should be indented by 0 spaces + hello world + ONE + + <<-TWO + # ^^^^^^ error: Heredoc body should be indented by 0 spaces + hello world + TWO + + <<-FOUR + # ^^^^^^^ error: Heredoc body should be indented by 0 spaces + hello world + FOUR + CRYSTAL + end + end + end + end +end diff --git a/src/ameba/rule/style/heredoc_indent.cr b/src/ameba/rule/style/heredoc_indent.cr new file mode 100644 index 000000000..1c5df6a84 --- /dev/null +++ b/src/ameba/rule/style/heredoc_indent.cr @@ -0,0 +1,66 @@ +module Ameba::Rule::Style + # A rule that enforces _Heredoc_ bodies be indented one level above the indentation of the + # line they're used on. + # + # For example, this is considered invalid: + # + # ``` + # <<-HERERDOC + # hello world + # HEREDOC + # + # <<-HERERDOC + # hello world + # HEREDOC + # ``` + # + # And should be written as: + # + # ``` + # <<-HERERDOC + # hello world + # HEREDOC + # + # <<-HERERDOC + # hello world + # HEREDOC + # ``` + # + # The `IndentBy` configuration option changes the enforced indentation level of the _heredoc_. + # + # ``` + # Style/HeredocIndent: + # Enabled: true + # IndentBy: 2 + # ``` + class HeredocIndent < Base + properties do + since_version "1.7.0" + description "Recommends heredoc bodies are indented consistently" + indent_by 2 + end + + MSG = "Heredoc body should be indented by %s spaces" + + def test(source, node : Crystal::StringInterpolation) + return unless start_location = node.location + + start_location_pos = source.pos(start_location) + return unless source.code[start_location_pos..(start_location_pos + 2)]? == "<<-" + + correct_indent = line_indent(source, start_location) + indent_by + + unless node.heredoc_indent == correct_indent + issue_for node, MSG % indent_by + end + end + + private def line_indent(source, start_location) : Int32 + line_location = Crystal::Location.new(nil, start_location.line_number, 1) + line_location_pos = source.pos(line_location) + line = source.code[line_location_pos..(line_location_pos + start_location.column_number)] + + line.size - line.lstrip.size + end + end +end