From 1a9a58b3cdc83f63e056704ddbab8d8cbf3874a5 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Mon, 29 May 2023 14:17:43 +0200 Subject: [PATCH 01/12] Add `Lint/Documentation` rule --- spec/ameba/rule/lint/documentation_spec.cr | 139 +++++++++++++++++++++ src/ameba/rule/lint/documentation.cr | 42 +++++++ 2 files changed, 181 insertions(+) create mode 100644 spec/ameba/rule/lint/documentation_spec.cr create mode 100644 src/ameba/rule/lint/documentation.cr diff --git a/spec/ameba/rule/lint/documentation_spec.cr b/spec/ameba/rule/lint/documentation_spec.cr new file mode 100644 index 00000000..12d839fc --- /dev/null +++ b/spec/ameba/rule/lint/documentation_spec.cr @@ -0,0 +1,139 @@ +require "../../../spec_helper" + +module Ameba::Rule::Lint + subject = Documentation.new + .tap(&.ignore_classes = false) + .tap(&.ignore_modules = false) + .tap(&.ignore_defs = false) + + describe Documentation do + it "passes for undocumented private types" do + expect_no_issues subject, <<-CRYSTAL + private class Foo + end + + private module Bar + end + + private enum Baz + end + + private def bat + end + + private macro bag + end + CRYSTAL + end + + it "passes for documented public types" do + expect_no_issues subject, <<-CRYSTAL + # Foo + class Foo + end + + # Bar + module Bar + end + + # Baz + enum Baz + end + + # bat + def bat + end + + # bag + macro bag + end + CRYSTAL + end + + it "fails if there is an undocumented public type" do + expect_issue subject, <<-CRYSTAL + class Foo + # ^^^^^^^^^ error: Missing documentation + end + + module Bar + # ^^^^^^^^^^ error: Missing documentation + end + + enum Baz + # ^^^^^^^^ error: Missing documentation + end + + def bat + # ^^^^^^^ error: Missing documentation + end + + macro bag + # ^^^^^^^^^ error: Missing documentation + end + CRYSTAL + end + + context "properties" do + describe "#ignore_classes" do + it "lets the rule to ignore method definitions if true" do + rule = Documentation.new + rule.ignore_classes = true + + expect_no_issues rule, <<-CRYSTAL + class Foo + end + CRYSTAL + end + end + + describe "#ignore_modules" do + it "lets the rule to ignore method definitions if true" do + rule = Documentation.new + rule.ignore_modules = true + + expect_no_issues rule, <<-CRYSTAL + module Bar + end + CRYSTAL + end + end + + describe "#ignore_enums" do + it "lets the rule to ignore method definitions if true" do + rule = Documentation.new + rule.ignore_enums = true + + expect_no_issues rule, <<-CRYSTAL + enum Baz + end + CRYSTAL + end + end + + describe "#ignore_defs" do + it "lets the rule to ignore method definitions if true" do + rule = Documentation.new + rule.ignore_defs = true + + expect_no_issues rule, <<-CRYSTAL + def bat + end + CRYSTAL + end + end + + describe "#ignore_macros" do + it "lets the rule to ignore macros if true" do + rule = Documentation.new + rule.ignore_macros = true + + expect_no_issues rule, <<-CRYSTAL + macro bag + end + CRYSTAL + end + end + end + end +end diff --git a/src/ameba/rule/lint/documentation.cr b/src/ameba/rule/lint/documentation.cr new file mode 100644 index 00000000..efd7d58a --- /dev/null +++ b/src/ameba/rule/lint/documentation.cr @@ -0,0 +1,42 @@ +module Ameba::Rule::Lint + # A rule that enforces documentation for public types: + # modules, classes, enums, methods and macros. + # + # YAML configuration example: + # + # ``` + # Lint/Documentation: + # Enabled: true + # ``` + class Documentation < Base + properties do + description "Enforces public types to be documented" + + ignore_classes true + ignore_modules true + ignore_enums false + ignore_defs true + ignore_macros false + end + + MSG = "Missing documentation" + + def test(source) + AST::ScopeVisitor.new self, source + end + + def test(source, node : Crystal::ClassDef | Crystal::ModuleDef | Crystal::EnumDef | Crystal::Def | Crystal::Macro, scope : AST::Scope) + return unless node.visibility.public? + + case node + when Crystal::ClassDef then return if ignore_classes? + when Crystal::ModuleDef then return if ignore_modules? + when Crystal::EnumDef then return if ignore_enums? + when Crystal::Def then return if ignore_defs? + when Crystal::Macro then return if ignore_macros? + end + + issue_for(node, MSG) unless node.doc.presence + end + end +end From 09fdac6be987fada88b0c1eaf74f63ebeb4d9fe8 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Mon, 29 May 2023 16:33:45 +0200 Subject: [PATCH 02/12] Refactor `Lint::Documentation` rule to use a custom visitor --- src/ameba/rule/lint/documentation.cr | 32 ++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/ameba/rule/lint/documentation.cr b/src/ameba/rule/lint/documentation.cr index efd7d58a..534181a0 100644 --- a/src/ameba/rule/lint/documentation.cr +++ b/src/ameba/rule/lint/documentation.cr @@ -22,10 +22,10 @@ module Ameba::Rule::Lint MSG = "Missing documentation" def test(source) - AST::ScopeVisitor.new self, source + DocumentationVisitor.new self, source end - def test(source, node : Crystal::ClassDef | Crystal::ModuleDef | Crystal::EnumDef | Crystal::Def | Crystal::Macro, scope : AST::Scope) + def test(source, node) return unless node.visibility.public? case node @@ -38,5 +38,33 @@ module Ameba::Rule::Lint issue_for(node, MSG) unless node.doc.presence end + + # :nodoc: + private class DocumentationVisitor < AST::BaseVisitor + NODES = { + ClassDef, + ModuleDef, + EnumDef, + Def, + Macro, + } + + @visibility : Crystal::Visibility = :public + + def visit(node : Crystal::VisibilityModifier) + @visibility = node.modifier + true + end + + {% for name in NODES %} + def visit(node : Crystal::{{ name }}) + node.visibility = @visibility + @visibility = :public + + @rule.test @source, node + true + end + {% end %} + end end end From 6e5a9a60b3cec76d7df9edcceb0800a149900327 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Mon, 29 May 2023 17:09:39 +0200 Subject: [PATCH 03/12] Refactor `Lint::Documentation` and ignore macro hooks --- src/ameba/rule/lint/documentation.cr | 42 +++++++++++++++++++++------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/src/ameba/rule/lint/documentation.cr b/src/ameba/rule/lint/documentation.cr index 534181a0..ac0555a6 100644 --- a/src/ameba/rule/lint/documentation.cr +++ b/src/ameba/rule/lint/documentation.cr @@ -21,22 +21,44 @@ module Ameba::Rule::Lint MSG = "Missing documentation" + HOOK_NAMES = %w[ + inherited + included extended + method_missing method_added + finished + ] + def test(source) DocumentationVisitor.new self, source end - def test(source, node) + def test(source, node : Crystal::ClassDef) + ignore_classes? || check_missing_doc(source, node) + end + + def test(source, node : Crystal::ModuleDef) + ignore_modules? || check_missing_doc(source, node) + end + + def test(source, node : Crystal::EnumDef) + ignore_enums? || check_missing_doc(source, node) + end + + def test(source, node : Crystal::Def) + ignore_defs? || check_missing_doc(source, node) + end + + def test(source, node : Crystal::Macro) + return if node.name.in?(HOOK_NAMES) + + ignore_macros? || check_missing_doc(source, node) + end + + private def check_missing_doc(source, node) return unless node.visibility.public? + return if node.doc.presence - case node - when Crystal::ClassDef then return if ignore_classes? - when Crystal::ModuleDef then return if ignore_modules? - when Crystal::EnumDef then return if ignore_enums? - when Crystal::Def then return if ignore_defs? - when Crystal::Macro then return if ignore_macros? - end - - issue_for(node, MSG) unless node.doc.presence + issue_for(node, MSG) end # :nodoc: From 85c3db4d74b42d079c913c6d596394a28e520666 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Wed, 31 May 2023 13:15:01 +0200 Subject: [PATCH 04/12] Move `NODES` constant into its proper namespace --- src/ameba/ast/visitors/node_visitor.cr | 56 +++++++++++++------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/ameba/ast/visitors/node_visitor.cr b/src/ameba/ast/visitors/node_visitor.cr index 4e6e3618..c537b86c 100644 --- a/src/ameba/ast/visitors/node_visitor.cr +++ b/src/ameba/ast/visitors/node_visitor.cr @@ -1,34 +1,6 @@ require "./base_visitor" module Ameba::AST - # List of nodes to be visited by Ameba's rules. - NODES = { - Alias, - IsA, - Assign, - Call, - Block, - Case, - ClassDef, - ClassVar, - Def, - EnumDef, - ExceptionHandler, - Expressions, - HashLiteral, - If, - InstanceVar, - LibDef, - ModuleDef, - NilLiteral, - StringInterpolation, - Unless, - Var, - When, - While, - Until, - } - # An AST Visitor that traverses the source and allows all nodes # to be inspected by rules. # @@ -36,6 +8,34 @@ module Ameba::AST # visitor = Ameba::AST::NodeVisitor.new(rule, source) # ``` class NodeVisitor < BaseVisitor + # List of nodes to be visited by Ameba's rules. + NODES = { + Alias, + IsA, + Assign, + Call, + Block, + Case, + ClassDef, + ClassVar, + Def, + EnumDef, + ExceptionHandler, + Expressions, + HashLiteral, + If, + InstanceVar, + LibDef, + ModuleDef, + NilLiteral, + StringInterpolation, + Unless, + Var, + When, + While, + Until, + } + @skip : Array(Crystal::ASTNode.class)? def initialize(@rule, @source, skip = nil) From 4c740f394afb66a69093e1b093e19b20a72fffcb Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Thu, 8 Jun 2023 01:58:58 +0200 Subject: [PATCH 05/12] Implement `Scope#visibility` --- spec/ameba/ast/visitors/scope_visitor_spec.cr | 37 +++++++++++++++++++ spec/spec_helper.cr | 3 ++ src/ameba/ast/scope.cr | 8 ++++ src/ameba/ast/visitors/scope_visitor.cr | 15 +++++++- 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/spec/ameba/ast/visitors/scope_visitor_spec.cr b/spec/ameba/ast/visitors/scope_visitor_spec.cr index a15402ee..b4f2432c 100644 --- a/spec/ameba/ast/visitors/scope_visitor_spec.cr +++ b/spec/ameba/ast/visitors/scope_visitor_spec.cr @@ -2,6 +2,17 @@ require "../../../spec_helper" module Ameba::AST describe ScopeVisitor do + {% for type in %w[class module enum].map(&.id) %} + it "creates a scope for the {{ type }} def" do + rule = ScopeRule.new + ScopeVisitor.new rule, Source.new <<-CRYSTAL + {{ type }} Foo + end + CRYSTAL + rule.scopes.size.should eq 1 + end + {% end %} + it "creates a scope for the def" do rule = ScopeRule.new ScopeVisitor.new rule, Source.new <<-CRYSTAL @@ -54,5 +65,31 @@ module Ameba::AST outer_block.outer_scope.should be_nil end end + + context "#visibility" do + it "is being properly set" do + rule = ScopeRule.new + ScopeVisitor.new rule, Source.new <<-CRYSTAL + private class Foo + end + CRYSTAL + rule.scopes.size.should eq 1 + rule.scopes.first.visibility.should eq Crystal::Visibility::Private + end + + it "is being inherited from the outer scope(s)" do + rule = ScopeRule.new + ScopeVisitor.new rule, Source.new <<-CRYSTAL + private class Foo + class Bar + def baz + end + end + end + CRYSTAL + rule.scopes.size.should eq 3 + rule.scopes.each &.visibility.should eq Crystal::Visibility::Private + end + end end end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 4a18746b..365febd1 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -43,6 +43,9 @@ module Ameba description "Internal rule to test scopes" end + def test(source, node : Crystal::VisibilityModifier, scope : AST::Scope) + end + def test(source, node : Crystal::ASTNode, scope : AST::Scope) @scopes << scope end diff --git a/src/ameba/ast/scope.cr b/src/ameba/ast/scope.cr index b74f8b62..3c343c6e 100644 --- a/src/ameba/ast/scope.cr +++ b/src/ameba/ast/scope.cr @@ -7,6 +7,9 @@ module Ameba::AST # Whether the scope yields. setter yields = false + # Scope visibility level + setter visibility : Crystal::Visibility? + # Link to local variables getter variables = [] of Variable @@ -172,6 +175,11 @@ module Ameba::AST false end + # Returns visibility of the current scope (could be inherited from the outer scope). + def visibility + @visibility || outer_scope.try(&.visibility) + end + # Returns `true` if current scope is a def, `false` otherwise. def def? node.is_a?(Crystal::Def) diff --git a/src/ameba/ast/visitors/scope_visitor.cr b/src/ameba/ast/visitors/scope_visitor.cr index a5c578ae..023094a0 100644 --- a/src/ameba/ast/visitors/scope_visitor.cr +++ b/src/ameba/ast/visitors/scope_visitor.cr @@ -25,6 +25,7 @@ module Ameba::AST @scope_queue = [] of Scope @current_scope : Scope @current_assign : Crystal::ASTNode? + @visibility_modifier : Crystal::Visibility? @skip : Array(Crystal::ASTNode.class)? def initialize(@rule, @source, skip = nil) @@ -36,12 +37,18 @@ module Ameba::AST private def on_scope_enter(node) return if skip?(node) - @current_scope = Scope.new(node, @current_scope) + + scope = Scope.new(node, @current_scope) + scope.visibility = @visibility_modifier + + @current_scope = scope end private def on_scope_end(node) @scope_queue << @current_scope + @visibility_modifier = nil + # go up if this is not a top level scope return unless outer_scope = @current_scope.outer_scope @current_scope = outer_scope @@ -64,6 +71,12 @@ module Ameba::AST end {% end %} + # :nodoc: + def visit(node : Crystal::VisibilityModifier) + @visibility_modifier = node.modifier + true + end + # :nodoc: def visit(node : Crystal::Yield) @current_scope.yields = true From 4d8346509e714eee009f93fe96b88c3467eb883e Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Thu, 8 Jun 2023 02:04:06 +0200 Subject: [PATCH 06/12] Implement `Documentation` rule on top of the `ScopeVisitor` --- spec/ameba/rule/lint/documentation_spec.cr | 12 ++++ src/ameba/rule/lint/documentation.cr | 64 +++++++--------------- 2 files changed, 31 insertions(+), 45 deletions(-) diff --git a/spec/ameba/rule/lint/documentation_spec.cr b/spec/ameba/rule/lint/documentation_spec.cr index 12d839fc..442b1444 100644 --- a/spec/ameba/rule/lint/documentation_spec.cr +++ b/spec/ameba/rule/lint/documentation_spec.cr @@ -4,15 +4,21 @@ module Ameba::Rule::Lint subject = Documentation.new .tap(&.ignore_classes = false) .tap(&.ignore_modules = false) + .tap(&.ignore_enums = false) .tap(&.ignore_defs = false) + .tap(&.ignore_macros = false) describe Documentation do it "passes for undocumented private types" do expect_no_issues subject, <<-CRYSTAL private class Foo + def foo + end end private module Bar + def bar + end end private enum Baz @@ -30,10 +36,16 @@ module Ameba::Rule::Lint expect_no_issues subject, <<-CRYSTAL # Foo class Foo + # foo + def foo + end end # Bar module Bar + # bar + def bar + end end # Baz diff --git a/src/ameba/rule/lint/documentation.cr b/src/ameba/rule/lint/documentation.cr index ac0555a6..5e6d134b 100644 --- a/src/ameba/rule/lint/documentation.cr +++ b/src/ameba/rule/lint/documentation.cr @@ -10,9 +10,10 @@ module Ameba::Rule::Lint # ``` class Documentation < Base properties do + enabled false description "Enforces public types to be documented" - ignore_classes true + ignore_classes false ignore_modules true ignore_enums false ignore_defs true @@ -21,7 +22,7 @@ module Ameba::Rule::Lint MSG = "Missing documentation" - HOOK_NAMES = %w[ + MACRO_HOOK_NAMES = %w[ inherited included extended method_missing method_added @@ -29,64 +30,37 @@ module Ameba::Rule::Lint ] def test(source) - DocumentationVisitor.new self, source + AST::ScopeVisitor.new self, source end - def test(source, node : Crystal::ClassDef) - ignore_classes? || check_missing_doc(source, node) + def test(source, node : Crystal::ClassDef, scope : AST::Scope) + ignore_classes? || check_missing_doc(source, node, scope) end - def test(source, node : Crystal::ModuleDef) - ignore_modules? || check_missing_doc(source, node) + def test(source, node : Crystal::ModuleDef, scope : AST::Scope) + ignore_modules? || check_missing_doc(source, node, scope) end - def test(source, node : Crystal::EnumDef) - ignore_enums? || check_missing_doc(source, node) + def test(source, node : Crystal::EnumDef, scope : AST::Scope) + ignore_enums? || check_missing_doc(source, node, scope) end - def test(source, node : Crystal::Def) - ignore_defs? || check_missing_doc(source, node) + def test(source, node : Crystal::Def, scope : AST::Scope) + ignore_defs? || check_missing_doc(source, node, scope) end - def test(source, node : Crystal::Macro) - return if node.name.in?(HOOK_NAMES) - - ignore_macros? || check_missing_doc(source, node) + def test(source, node : Crystal::Macro, scope : AST::Scope) + node.name.in?(MACRO_HOOK_NAMES) || + ignore_macros? || check_missing_doc(source, node, scope) end - private def check_missing_doc(source, node) - return unless node.visibility.public? + private def check_missing_doc(source, node, scope) + visibility = scope.visibility + + return if visibility && !visibility.public? return if node.doc.presence issue_for(node, MSG) end - - # :nodoc: - private class DocumentationVisitor < AST::BaseVisitor - NODES = { - ClassDef, - ModuleDef, - EnumDef, - Def, - Macro, - } - - @visibility : Crystal::Visibility = :public - - def visit(node : Crystal::VisibilityModifier) - @visibility = node.modifier - true - end - - {% for name in NODES %} - def visit(node : Crystal::{{ name }}) - node.visibility = @visibility - @visibility = :public - - @rule.test @source, node - true - end - {% end %} - end end end From 7caa47fb6a1348a30fffb193d02128a2b3d68428 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Thu, 8 Jun 2023 02:04:30 +0200 Subject: [PATCH 07/12] Several small refactors --- src/ameba/ast/scope.cr | 9 ++------- src/ameba/ast/visitors/scope_visitor.cr | 1 + 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/ameba/ast/scope.cr b/src/ameba/ast/scope.cr index 3c343c6e..9d727409 100644 --- a/src/ameba/ast/scope.cr +++ b/src/ameba/ast/scope.cr @@ -124,10 +124,7 @@ module Ameba::AST # end # ``` def spawn_block? - return false unless node.is_a?(Crystal::Block) - - call = node.as(Crystal::Block).call - call.try(&.name) == "spawn" + node.as?(Crystal::Block).try(&.call).try(&.name) == "spawn" end # Returns `true` if current scope sits inside a macro. @@ -170,9 +167,7 @@ module Ameba::AST # Returns `true` if current scope (or any of inner scopes) yields, # `false` otherwise. def yields?(check_inner_scopes = true) - return true if @yields - return inner_scopes.any?(&.yields?) if check_inner_scopes - false + @yields || (check_inner_scopes && inner_scopes.any?(&.yields?)) end # Returns visibility of the current scope (could be inherited from the outer scope). diff --git a/src/ameba/ast/visitors/scope_visitor.cr b/src/ameba/ast/visitors/scope_visitor.cr index 023094a0..cf562d51 100644 --- a/src/ameba/ast/visitors/scope_visitor.cr +++ b/src/ameba/ast/visitors/scope_visitor.cr @@ -122,6 +122,7 @@ module Ameba::AST @current_assign = node.value unless node.value.nil? end + # :nodoc: def end_visit(node : Crystal::TypeDeclaration) return unless (var = node.var).is_a?(Crystal::Var) From c9d25f3409e6a127bbd7188267810657a2c2924e Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Thu, 8 Jun 2023 02:24:12 +0200 Subject: [PATCH 08/12] Do not run disabled rules against the codebase --- .github/workflows/ci.yml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86049975..d51482cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,4 +36,4 @@ jobs: run: shards build -Dpreview_mt - name: Run ameba linter - run: bin/ameba --all + run: bin/ameba diff --git a/Makefile b/Makefile index 8b7772a4..f59b75f3 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ build: .PHONY: lint lint: build - ./bin/ameba --all + ./bin/ameba .PHONY: spec spec: From 262e31c35b7a9851da5ccaf04b5d75808fc35a7d Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Thu, 8 Jun 2023 02:24:30 +0200 Subject: [PATCH 09/12] Cleanup README mention (MT is enabled by default) --- README.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/README.md b/README.md index 34519df5..d09d58cc 100644 --- a/README.md +++ b/README.md @@ -99,15 +99,6 @@ $ ameba --explain crystal/command/format.cr:26:83 # same thing ### Run in parallel -Starting from 0.31.0 Crystal [supports parallelism](https://crystal-lang.org/2019/09/06/parallelism-in-crystal.html). -It allows to run linting in parallel too. -In order to take advantage of this feature you need to build ameba with preview_mt support: - -```sh -$ crystal build src/cli.cr -Dpreview_mt -o bin/ameba -$ make install -``` - Some quick benchmark results measured while running Ameba on Crystal repo: ```sh From e12d72cc88825e7b72c851ab9bda34ae431e72ba Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Thu, 8 Jun 2023 04:28:17 +0200 Subject: [PATCH 10/12] Set the `ASTNode#visibility` as well --- spec/ameba/ast/visitors/scope_visitor_spec.cr | 2 ++ src/ameba/ast/visitors/scope_visitor.cr | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/ameba/ast/visitors/scope_visitor_spec.cr b/spec/ameba/ast/visitors/scope_visitor_spec.cr index b4f2432c..4239e55a 100644 --- a/spec/ameba/ast/visitors/scope_visitor_spec.cr +++ b/spec/ameba/ast/visitors/scope_visitor_spec.cr @@ -89,6 +89,8 @@ module Ameba::AST CRYSTAL rule.scopes.size.should eq 3 rule.scopes.each &.visibility.should eq Crystal::Visibility::Private + rule.scopes.last.node.visibility.should eq Crystal::Visibility::Private + rule.scopes[0...-1].each &.node.visibility.should eq Crystal::Visibility::Public end end end diff --git a/src/ameba/ast/visitors/scope_visitor.cr b/src/ameba/ast/visitors/scope_visitor.cr index cf562d51..28a563ba 100644 --- a/src/ameba/ast/visitors/scope_visitor.cr +++ b/src/ameba/ast/visitors/scope_visitor.cr @@ -73,7 +73,7 @@ module Ameba::AST # :nodoc: def visit(node : Crystal::VisibilityModifier) - @visibility_modifier = node.modifier + @visibility_modifier = node.exp.visibility = node.modifier true end From 94e31d4685a6a3580e6a17a3227adf6f13d5c8c6 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Thu, 8 Jun 2023 04:28:37 +0200 Subject: [PATCH 11/12] Do the same in `NodeVisitor` --- src/ameba/ast/visitors/node_visitor.cr | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ameba/ast/visitors/node_visitor.cr b/src/ameba/ast/visitors/node_visitor.cr index c537b86c..315d1b7b 100644 --- a/src/ameba/ast/visitors/node_visitor.cr +++ b/src/ameba/ast/visitors/node_visitor.cr @@ -43,6 +43,11 @@ module Ameba::AST super @rule, @source end + def visit(node : Crystal::VisibilityModifier) + node.exp.visibility = node.modifier + true + end + {% for name in NODES %} # A visit callback for `Crystal::{{ name }}` node. # From b156a6a6a13fe3e1e389685ebe176cf2059b411e Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Thu, 8 Jun 2023 14:03:35 +0200 Subject: [PATCH 12/12] Add comments to macros --- src/ameba/config.cr | 1 + src/ameba/rule/base.cr | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ameba/config.cr b/src/ameba/config.cr index ff039f28..5b426195 100644 --- a/src/ameba/config.cr +++ b/src/ameba/config.cr @@ -240,6 +240,7 @@ class Ameba::Config # :nodoc: module RuleConfig + # Define rule properties macro properties(&block) {% definitions = [] of NamedTuple %} {% if block.body.is_a? Assign %} diff --git a/src/ameba/rule/base.cr b/src/ameba/rule/base.cr index 675ae99b..7d656d8e 100644 --- a/src/ameba/rule/base.cr +++ b/src/ameba/rule/base.cr @@ -112,6 +112,7 @@ module Ameba::Rule name.hash end + # Adds an issue to the *source* macro issue_for(*args, **kwargs, &block) source.add_issue(self, {{ *args }}, {{ **kwargs }}) {{ block }} end