Populate Rule::Base+.parsed_doc on compile time

This commit is contained in:
Sijawusz Pur Rahnama 2022-10-29 01:23:26 +02:00
parent cac1ce4138
commit 3d61254739
2 changed files with 54 additions and 50 deletions

View file

@ -138,57 +138,29 @@ module Ameba::Rule
end
end
private macro read_rule_doc(filepath = __FILE__)
{{ run("../../read_rule_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_rule_doc
end
end

32
src/read_rule_doc.cr Normal file
View file

@ -0,0 +1,32 @@
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) &&
name.is_a?(Crystal::Path) &&
name.names.last? == @type_name
@doc = node.doc
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