Add Rubocop overfetch cop
This commit is contained in:
Родитель
4b2ef6ce00
Коммит
190c93dc30
|
@ -0,0 +1,63 @@
|
|||
require "active_support/inflector"
|
||||
require "graphql"
|
||||
require "graphql/client/erubis"
|
||||
require "rubocop"
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module GraphQL
|
||||
# Public: Rubocop for catching overfetched fields in ERB templates.
|
||||
class Overfetch < Cop
|
||||
def_node_search :send_methods, "(send ...)"
|
||||
|
||||
def investigate(processed_source)
|
||||
erb = File.read(processed_source.buffer.name)
|
||||
query, = ::GraphQL::Client::Erubis.extract_graphql_section(erb)
|
||||
return unless query
|
||||
|
||||
aliases = {}
|
||||
fields = {}
|
||||
ranges = {}
|
||||
|
||||
# TODO: Use GraphQL client parser
|
||||
document = ::GraphQL.parse(query.gsub(/::/, "__"))
|
||||
|
||||
visitor = ::GraphQL::Language::Visitor.new(document)
|
||||
visitor[::GraphQL::Language::Nodes::Field] << ->(node, _parent) do
|
||||
name = node.alias || node.name
|
||||
fields[name] ||= 0
|
||||
field_aliases(name).each { |n| (aliases[n] ||= []) << name }
|
||||
ranges[name] ||= source_range(processed_source.buffer, node.line, 0)
|
||||
end
|
||||
visitor.visit
|
||||
|
||||
send_methods(processed_source.ast).each do |node|
|
||||
_receiver, method_name, *_args = *node
|
||||
aliases.fetch(method_name.to_s, []).each do |field_name|
|
||||
fields[field_name] += 1
|
||||
end
|
||||
end
|
||||
|
||||
fields.each do |field, count|
|
||||
next if count > 0
|
||||
add_offense(nil, ranges[field], "GraphQL field '#{field}' query but was not used in template.")
|
||||
end
|
||||
end
|
||||
|
||||
def field_aliases(name)
|
||||
names = Set.new
|
||||
|
||||
names << name
|
||||
names << "#{name}?"
|
||||
|
||||
names << underscore_name = ActiveSupport::Inflector.underscore(name)
|
||||
names << "#{underscore_name}?"
|
||||
|
||||
names << "each_node" if name == "edges" || name == "node"
|
||||
|
||||
names
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,35 @@
|
|||
require "graphql/client/erubis"
|
||||
require "rubocop/cop/graphql/overfetch"
|
||||
require "minitest/autorun"
|
||||
|
||||
class TestRubocopOverfetch < MiniTest::Test
|
||||
Root = File.expand_path("..", __FILE__)
|
||||
|
||||
def setup
|
||||
config = RuboCop::Config.new
|
||||
@cop = RuboCop::Cop::GraphQL::Overfetch.new(config)
|
||||
end
|
||||
|
||||
def test_all_fields_used
|
||||
investigate(@cop, "#{Root}/views/users/show.html.erb")
|
||||
|
||||
assert_empty @cop.offenses.map(&:message)
|
||||
end
|
||||
|
||||
def test_field_unused
|
||||
investigate(@cop, "#{Root}/views/users/overfetch.html.erb")
|
||||
|
||||
assert_equal 1, @cop.offenses.count
|
||||
assert_equal "GraphQL field 'birthday' query but was not used in template.", @cop.offenses.first.message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def investigate(cop, path)
|
||||
engine = GraphQL::Client::Erubis.new(File.read(path))
|
||||
processed_source = RuboCop::ProcessedSource.new(engine.src, RUBY_VERSION.to_f, path)
|
||||
commissioner = RuboCop::Cop::Commissioner.new([cop], [], raise_error: true)
|
||||
commissioner.investigate(processed_source)
|
||||
commissioner
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
<%graphql
|
||||
fragment User on User {
|
||||
login
|
||||
birthday
|
||||
}
|
||||
%>
|
||||
<%= user.login %>
|
Загрузка…
Ссылка в новой задаче