Skip to content

Commit

Permalink
Add a linter for calls to super in templates (#1153)
Browse files Browse the repository at this point in the history
  • Loading branch information
camertron authored May 13, 2022
1 parent 30a24cf commit 8b21a68
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/popular-vans-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/view-components': patch
---

Add a linter for calls to super in templates
2 changes: 0 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,6 @@ GEM
nokogiri (1.12.5)
mini_portile2 (~> 2.6.1)
racc (~> 1.4)
nokogiri (1.12.5-x86_64-darwin)
racc (~> 1.4)
octicons (17.1.0)
parallel (1.21.0)
parser (3.0.2.0)
Expand Down
65 changes: 65 additions & 0 deletions lib/primer/view_components/linters/super_in_component_templates.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# frozen_string_literal: true

require_relative "helpers/rubocop_helpers"

module ERBLint
module Linters
# Replaces calls to `super` with calls to `render_parent`.
class SuperInComponentTemplates < Linter
include ERBLint::LinterRegistry
include Helpers::RubocopHelpers

def run(processed_source)
processed_source.ast.descendants(:erb).each do |erb_node|
indicator_node, _, code_node = *erb_node
code = code_node.children.first
ast = erb_ast(code)
next unless ast

super_call_nodes = find_super_call_nodes(ast)
next if super_call_nodes.empty?

indicator, = *indicator_node
indicator ||= ""

# +2 to account for the leading "<%" characters
code_start_pos = erb_node.location.begin_pos + indicator.size + 2

super_call_nodes.each do |super_call_node|
orig_loc = code_node.location
super_call_loc = super_call_node.location.expression

new_loc = orig_loc.with(
begin_pos: super_call_loc.begin_pos + code_start_pos,
end_pos: super_call_loc.end_pos + code_start_pos
)

add_offense(
new_loc,
"Avoid calling `super` in component templates. Call `render_parent` instead",
"render_parent"
)
end
end
end

def autocorrect(_, offense)
return unless offense.context

lambda do |corrector|
corrector.replace(offense.source_range, offense.context)
end
end

private

def find_super_call_nodes(ast)
return [ast] if ast.type == :zsuper

ast.each_child_node.flat_map do |child_ast|
find_super_call_nodes(child_ast)
end
end
end
end
end
73 changes: 73 additions & 0 deletions test/linters/super_in_component_templates_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# frozen_string_literal: true

require "linter_test_case"

class SuperInComponentTemplatesTest < LinterTestCase
def test_identifies_super_calls
@file = "<div><% super %></div>"
@linter.run(processed_source)

refute_empty @linter.offenses
end

def test_identifies_echoed_super_calls
@file = "<div><%= super %></div>"
@linter.run(processed_source)

refute_empty @linter.offenses
end

def test_replaces_super_with_render_parent
@file = <<~ERB
<div>
<% super %>
</div>
ERB

expected = <<~ERB
<div>
<% render_parent %>
</div>
ERB

assert_equal expected, corrected_content
end

def test_replaces_echoed_super_with_render_parent
@file = <<~ERB
<div>
<%= super %>
</div>
ERB

expected = <<~ERB
<div>
<%= render_parent %>
</div>
ERB

assert_equal expected, corrected_content
end

def test_replaces_super_when_nested
@file = <<~ERB
<div>
<% 3.times { |_i| super } %>
</div>
ERB

expected = <<~ERB
<div>
<% 3.times { |_i| render_parent } %>
</div>
ERB

assert_equal expected, corrected_content
end

private

def linter_class
ERBLint::Linters::SuperInComponentTemplates
end
end

0 comments on commit 8b21a68

Please sign in to comment.