From 1a9a58b3cdc83f63e056704ddbab8d8cbf3874a5 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Mon, 29 May 2023 14:17:43 +0200 Subject: [PATCH] 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