Merge pull request #282 from crystal-ameba/Sija/fix-issue-270

Populate `Rule::Base+.parsed_doc` on compile time
This commit is contained in:
Sijawusz Pur Rahnama 2022-10-29 18:12:55 +02:00 committed by GitHub
commit 4700ae7649
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 51 additions and 50 deletions

View file

@ -138,57 +138,29 @@ module Ameba::Rule
end
end
private macro read_type_doc(filepath = __FILE__)
{{ run("../../contrib/read_type_doc",
@type.name.split("::").last,
filepath
).chomp.stringify }}.presence
end
macro inherited
protected def self.path_to_source_file
__FILE__
end
end
# Returns documentation for this rule if any.
#
# ```
# module Ameba
# # This is a test rule.
# # Does nothing.
# class MyRule < Ameba::Rule::Base
# def test(source)
# end
# end
# end
#
# MyRule.parsed_doc # => "This is a test rule.\nDoes nothing."
# ```
def self.parsed_doc
source = File.read(path_to_source_file)
nodes = Crystal::Parser.new(source)
.tap(&.wants_doc = true)
.parse
type_name = rule_name.split('/').last?
DocFinder.new(nodes, type_name).doc
end
# :nodoc:
private class DocFinder < Crystal::Visitor
getter doc : String?
getter type_name : String?
def initialize(nodes, @type_name)
self.accept(nodes)
end
def visit(node : Crystal::ASTNode)
return false if @doc
if node.responds_to?(:name) &&
(name = node.name) &&
name.is_a?(Crystal::Path) &&
name.names.last? == @type_name
@doc = node.doc
end
true
end
# Returns documentation for this rule, if there is any.
#
# ```
# module Ameba
# # This is a test rule.
# # Does nothing.
# class MyRule < Ameba::Rule::Base
# def test(source)
# end
# end
# end
#
# MyRule.parsed_doc # => "This is a test rule.\nDoes nothing."
# ```
class_getter parsed_doc : String? = read_type_doc
end
end

View file

@ -0,0 +1,29 @@
require "compiler/crystal/syntax/*"
private class DocFinder < Crystal::Visitor
getter type_name : String
getter doc : String?
def initialize(nodes, @type_name)
self.accept(nodes)
end
def visit(node : Crystal::ASTNode)
return false if @doc
if node.responds_to?(:name) && (name = node.name).is_a?(Crystal::Path)
@doc = node.doc if name.names.last? == @type_name
end
true
end
end
type_name, path_to_source_file = ARGV
source = File.read(path_to_source_file)
nodes = Crystal::Parser.new(source)
.tap(&.wants_doc = true)
.parse
puts DocFinder.new(nodes, type_name).doc