diff --git a/lib/erb_lint/linters/allowed_script_type.rb b/lib/erb_lint/linters/allowed_script_type.rb index 64a55c2..dccc47c 100644 --- a/lib/erb_lint/linters/allowed_script_type.rb +++ b/lib/erb_lint/linters/allowed_script_type.rb @@ -21,8 +21,7 @@ class ConfigSchema < LinterConfig self.config_schema = ConfigSchema def run(processed_source) - parser = processed_source.parser - parser.nodes_with_type(:tag).each do |tag_node| + processed_source.tag_nodes.each do |tag_node| tag = BetterHtml::Tree::Tag.from_node(tag_node) next if tag.closing? next unless tag.name == "script" diff --git a/lib/erb_lint/linters/closing_erb_tag_indent.rb b/lib/erb_lint/linters/closing_erb_tag_indent.rb index 504eadd..0fb176a 100644 --- a/lib/erb_lint/linters/closing_erb_tag_indent.rb +++ b/lib/erb_lint/linters/closing_erb_tag_indent.rb @@ -11,7 +11,7 @@ class ClosingErbTagIndent < Linter END_SPACES = /([[:space:]]*)\z/m def run(processed_source) - processed_source.ast.descendants(:erb).each do |erb_node| + processed_source.erb_nodes.each do |erb_node| _, _, code_node, = *erb_node code = code_node.children.first diff --git a/lib/erb_lint/linters/comment_syntax.rb b/lib/erb_lint/linters/comment_syntax.rb index bd7f2b6..3c0b48e 100644 --- a/lib/erb_lint/linters/comment_syntax.rb +++ b/lib/erb_lint/linters/comment_syntax.rb @@ -14,7 +14,7 @@ def run(processed_source) file_content = processed_source.file_content return if file_content.empty? - processed_source.ast.descendants(:erb).each do |erb_node| + processed_source.erb_nodes.each do |erb_node| indicator_node, _, code_node, _ = *erb_node next if code_node.nil? diff --git a/lib/erb_lint/linters/deprecated_classes.rb b/lib/erb_lint/linters/deprecated_classes.rb index 040f4f2..7efb001 100644 --- a/lib/erb_lint/linters/deprecated_classes.rb +++ b/lib/erb_lint/linters/deprecated_classes.rb @@ -89,7 +89,7 @@ def tags(processed_source) end def tag_nodes(processed_source) - processed_source.parser.nodes_with_type(:tag) + processed_source.tag_nodes end def generate_offenses(class_name, range) diff --git a/lib/erb_lint/linters/no_javascript_tag_helper.rb b/lib/erb_lint/linters/no_javascript_tag_helper.rb index 3c25dd6..bdb3242 100644 --- a/lib/erb_lint/linters/no_javascript_tag_helper.rb +++ b/lib/erb_lint/linters/no_javascript_tag_helper.rb @@ -17,8 +17,7 @@ class ConfigSchema < LinterConfig self.config_schema = ConfigSchema def run(processed_source) - parser = processed_source.parser - parser.ast.descendants(:erb).each do |erb_node| + processed_source.erb_nodes.each do |erb_node| indicator_node, _, code_node, _ = *erb_node indicator = indicator_node&.loc&.source next if indicator == "#" diff --git a/lib/erb_lint/linters/require_input_autocomplete.rb b/lib/erb_lint/linters/require_input_autocomplete.rb index 4a779e5..6ec245b 100644 --- a/lib/erb_lint/linters/require_input_autocomplete.rb +++ b/lib/erb_lint/linters/require_input_autocomplete.rb @@ -42,16 +42,14 @@ class RequireInputAutocomplete < Linter ].freeze def run(processed_source) - parser = processed_source.parser - - find_html_input_tags(parser) - find_rails_helper_input_tags(parser) + find_html_input_tags(processed_source) + find_rails_helper_input_tags(processed_source) end private - def find_html_input_tags(parser) - parser.nodes_with_type(:tag).each do |tag_node| + def find_html_input_tags(processed_source) + processed_source.tag_nodes.each do |tag_node| tag = BetterHtml::Tree::Tag.from_node(tag_node) autocomplete_attribute = tag.attributes["autocomplete"] @@ -82,8 +80,8 @@ def html_type_requires_autocomplete_attribute?(type_attribute) type_present && HTML_INPUT_TYPES_REQUIRING_AUTOCOMPLETE.include?(type_attribute.value) end - def find_rails_helper_input_tags(parser) - parser.ast.descendants(:erb).each do |erb_node| + def find_rails_helper_input_tags(processed_source) + processed_source.erb_nodes.each do |erb_node| indicator_node, _, code_node, _ = *erb_node source = code_node.loc.source ruby_node = extract_ruby_node(source) diff --git a/lib/erb_lint/linters/require_script_nonce.rb b/lib/erb_lint/linters/require_script_nonce.rb index e575dfb..340280d 100644 --- a/lib/erb_lint/linters/require_script_nonce.rb +++ b/lib/erb_lint/linters/require_script_nonce.rb @@ -11,16 +11,14 @@ class RequireScriptNonce < Linter include LinterRegistry def run(processed_source) - parser = processed_source.parser - - find_html_script_tags(parser) - find_rails_helper_script_tags(parser) + find_html_script_tags(processed_source) + find_rails_helper_script_tags(processed_source) end private - def find_html_script_tags(parser) - parser.nodes_with_type(:tag).each do |tag_node| + def find_html_script_tags(processed_source) + processed_source.tag_nodes.each do |tag_node| tag = BetterHtml::Tree::Tag.from_node(tag_node) nonce_attribute = tag.attributes["nonce"] @@ -52,8 +50,8 @@ def html_javascript_type_attribute?(tag) type_attribute.value_node.to_a[1] != "application/javascript" end - def find_rails_helper_script_tags(parser) - parser.ast.descendants(:erb).each do |erb_node| + def find_rails_helper_script_tags(processed_source) + processed_source.erb_nodes.each do |erb_node| indicator_node, _, code_node, _ = *erb_node source = code_node.loc.source ruby_node = extract_ruby_node(source) diff --git a/lib/erb_lint/linters/right_trim.rb b/lib/erb_lint/linters/right_trim.rb index c96301f..a995bc7 100644 --- a/lib/erb_lint/linters/right_trim.rb +++ b/lib/erb_lint/linters/right_trim.rb @@ -13,7 +13,7 @@ class ConfigSchema < LinterConfig self.config_schema = ConfigSchema def run(processed_source) - processed_source.ast.descendants(:erb).each do |erb_node| + processed_source.erb_nodes.each do |erb_node| _, _, _, trim_node = *erb_node next if trim_node.nil? || trim_node.loc.source == @config.enforced_style diff --git a/lib/erb_lint/linters/rubocop.rb b/lib/erb_lint/linters/rubocop.rb index abe8e3b..d219c47 100644 --- a/lib/erb_lint/linters/rubocop.rb +++ b/lib/erb_lint/linters/rubocop.rb @@ -49,7 +49,7 @@ def autocorrect(_processed_source, offense) private def descendant_nodes(processed_source) - processed_source.ast.descendants(:erb) + processed_source.erb_nodes end def inspect_content(processed_source, erb_node) diff --git a/lib/erb_lint/linters/self_closing_tag.rb b/lib/erb_lint/linters/self_closing_tag.rb index e5fc4cd..61ac874 100644 --- a/lib/erb_lint/linters/self_closing_tag.rb +++ b/lib/erb_lint/linters/self_closing_tag.rb @@ -32,7 +32,7 @@ class ConfigSchema < LinterConfig ] def run(processed_source) - processed_source.ast.descendants(:tag).each do |tag_node| + processed_source.tag_nodes.each do |tag_node| tag = BetterHtml::Tree::Tag.from_node(tag_node) next unless SELF_CLOSING_TAGS.include?(tag.name) diff --git a/lib/erb_lint/linters/space_around_erb_tag.rb b/lib/erb_lint/linters/space_around_erb_tag.rb index 0a8ce7d..791b9a4 100644 --- a/lib/erb_lint/linters/space_around_erb_tag.rb +++ b/lib/erb_lint/linters/space_around_erb_tag.rb @@ -12,7 +12,7 @@ class SpaceAroundErbTag < Linter END_SPACES = /([[:space:]]*)\z/m def run(processed_source) - processed_source.ast.descendants(:erb).each do |erb_node| + processed_source.erb_nodes.each do |erb_node| indicator_node, ltrim, code_node, rtrim = *erb_node indicator = indicator_node&.loc&.source next if indicator == "#" || indicator == "%" diff --git a/lib/erb_lint/linters/space_in_html_tag.rb b/lib/erb_lint/linters/space_in_html_tag.rb index 44360c9..a2b3972 100644 --- a/lib/erb_lint/linters/space_in_html_tag.rb +++ b/lib/erb_lint/linters/space_in_html_tag.rb @@ -7,7 +7,7 @@ class SpaceInHtmlTag < Linter include LinterRegistry def run(processed_source) - processed_source.ast.descendants(:tag).each do |tag_node| + processed_source.tag_nodes.each do |tag_node| start_solidus, name, attributes, end_solidus = *tag_node next_loc = name&.loc&.begin_pos || attributes&.loc&.begin_pos || diff --git a/lib/erb_lint/linters/strict_locals.rb b/lib/erb_lint/linters/strict_locals.rb index f533a8f..49411e0 100644 --- a/lib/erb_lint/linters/strict_locals.rb +++ b/lib/erb_lint/linters/strict_locals.rb @@ -18,7 +18,7 @@ def run(processed_source) file_content = processed_source.file_content return if file_content.empty? - strict_locals_node = processed_source.ast.descendants(:erb).find do |erb_node| + strict_locals_node = processed_source.erb_nodes.find do |erb_node| indicator_node, _, code_node, _ = *erb_node indicator_node_str = indicator_node&.deconstruct&.last diff --git a/lib/erb_lint/processed_source.rb b/lib/erb_lint/processed_source.rb index 1fcb319..2eb0e6b 100644 --- a/lib/erb_lint/processed_source.rb +++ b/lib/erb_lint/processed_source.rb @@ -14,6 +14,15 @@ def ast @parser.ast end + # Memoized descendant lookups — many linters traverse the same node types + def erb_nodes + @erb_nodes ||= ast.descendants(:erb).to_a + end + + def tag_nodes + @tag_nodes ||= parser.nodes_with_type(:tag).to_a + end + def source_buffer @source_buffer ||= begin buffer = Parser::Source::Buffer.new(filename) diff --git a/spec/erb_lint/processed_source_spec.rb b/spec/erb_lint/processed_source_spec.rb new file mode 100644 index 0000000..cf6eb45 --- /dev/null +++ b/spec/erb_lint/processed_source_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe ERBLint::ProcessedSource do + let(:file) { <<~FILE } +
+ + + <%= helper_method %> + <% if condition %> + <%= value %> + <% end %> +
+ FILE + let(:processed_source) { described_class.new("file.html.erb", file) } + + describe "#erb_nodes" do + it "returns an array of erb nodes" do + expect(processed_source.erb_nodes).to(be_an(Array)) + expect(processed_source.erb_nodes).not_to(be_empty) + end + + it "returns the same object on subsequent calls" do + expect(processed_source.erb_nodes).to(equal(processed_source.erb_nodes)) + end + + it "matches ast.descendants(:erb)" do + expect(processed_source.erb_nodes).to(eq(processed_source.ast.descendants(:erb).to_a)) + end + end + + describe "#tag_nodes" do + it "returns an array of tag nodes" do + expect(processed_source.tag_nodes).to(be_an(Array)) + expect(processed_source.tag_nodes).not_to(be_empty) + end + + it "returns the same object on subsequent calls" do + expect(processed_source.tag_nodes).to(equal(processed_source.tag_nodes)) + end + + it "matches parser.nodes_with_type(:tag)" do + expect(processed_source.tag_nodes).to(eq(processed_source.parser.nodes_with_type(:tag).to_a)) + end + end +end