Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
PATH
remote: .
specs:
query_packwerk (0.1.25)
query_packwerk (0.1.26)
coderay
herb (>= 0.9)
packwerk
parse_packwerk
rubocop
Expand Down Expand Up @@ -52,6 +53,12 @@ GEM
diff-lcs (1.6.0)
drb (2.2.1)
erubi (1.13.1)
herb (0.11.0-aarch64-linux-gnu)
herb (0.11.0-aarch64-linux-musl)
herb (0.11.0-arm64-darwin)
herb (0.11.0-x86_64-darwin)
herb (0.11.0-x86_64-linux-gnu)
herb (0.11.0-x86_64-linux-musl)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
io-console (0.8.0)
Expand Down
1 change: 1 addition & 0 deletions lib/query_packwerk.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# frozen_string_literal: true

require 'sorbet-runtime'
require 'herb'
require 'parse_packwerk'
require 'rubocop'

Expand Down
40 changes: 35 additions & 5 deletions lib/query_packwerk/file_cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ module QueryPackwerk
class FileCache
extend T::Sig

RUBY_FILE = T.let(/\.(rb|rake)\z/, Regexp)
# The files packwerk parses as Ruby, from Packwerk::Parsers::Factory
RUBY_FILE = T.let(/(\.(rb|rake|builder|gemspec|ru)|Gemfile|Rakefile)\z/, Regexp)
ERB_FILE = T.let(/\.erb\z/, Regexp)

sig { void }
def initialize
Expand Down Expand Up @@ -62,12 +64,18 @@ def set(cache_name, key:, value:)

sig { params(file_name: String).returns(RuboCop::AST::Node) }
def get_file_ast(file_name)
@file_ast_cache[file_name] ||= ast_from(get_file(file_name))
@file_ast_cache[file_name] ||= begin
source = processed_source(ruby_source(file_name))
warn_unparseable(file_name, source) unless source.valid_syntax?

# The AST is also nil for source with no code, like a template that's only HTML
source.ast || ast_from('x')
end
end

sig { params(file_name: String).returns(String) }
def get_file(file_name)
@file_cache[file_name] ||= if RUBY_FILE.match?(file_name) && File.exist?(file_name)
@file_cache[file_name] ||= if (RUBY_FILE.match?(file_name) || ERB_FILE.match?(file_name)) && File.exist?(file_name)
File.read(file_name)
else
'x'
Expand All @@ -84,7 +92,7 @@ def get_all_const_occurrences(file_name:, class_name:)
absolute_const_node = ast_from(class_name)
relative_const_node = ast_from(class_name.delete_prefix('::'))

@file_const_cache[const_key] = get_file_ast(file_name).each_descendant.select do |node|
@file_const_cache[const_key] = get_file_ast(file_name).each_node.select do |node|
next false unless node.const_type?

node == absolute_const_node || node == relative_const_node
Expand Down Expand Up @@ -133,10 +141,32 @@ def get_full_anonymous_sources(file_name:, class_name:)

private

# Blanks out everything in an ERB template except its Ruby code, so line numbers still match the template
sig { params(file_name: String).returns(String) }
def ruby_source(file_name)
source = get_file(file_name)
return source unless ERB_FILE.match?(file_name)

Herb.extract_ruby(source, semicolons: true, comments: false, preserve_positions: true)
end

sig { params(string: String).returns(RuboCop::ProcessedSource) }
def processed_source(string)
RuboCop::ProcessedSource.new(string, RUBY_VERSION.to_f)
end

sig { params(file_name: String, source: RuboCop::ProcessedSource).void }
def warn_unparseable(file_name, source)
error = source.diagnostics.find { |diagnostic| diagnostic.level == :error }
location = error ? "#{file_name}:#{error.location.line} (#{error.message})" : file_name

warn "Could not parse #{location}, so its violations are not included"
end

# Turns a string into a Ruby AST
sig { params(string: String).returns(RuboCop::AST::Node) }
def ast_from(string)
RuboCop::ProcessedSource.new(string, RUBY_VERSION.to_f).ast
processed_source(string).ast
end

# We can find a constant, but by going up its parents we can find the full call chain.
Expand Down
2 changes: 1 addition & 1 deletion lib/query_packwerk/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
# frozen_string_literal: true

module QueryPackwerk
VERSION = '0.1.25'
VERSION = '0.1.26'
end
10 changes: 5 additions & 5 deletions lib/query_packwerk/violation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ module QueryPackwerk
class Violation
extend T::Sig

# This does not play nicely with ERB files which may have violations
RUBY_FILE = T.let(/\.(rb|rake)\z/, Regexp)
ALL_CAPS = T.let(/\A[A-Z_]+\z/, Regexp)

sig { returns(QueryPackwerk::Package) }
Expand Down Expand Up @@ -168,13 +166,15 @@ def sources_with_contexts(start_offset: 3, end_offset: 3)
load_cache!

file_sources = files.flat_map do |file_name|
lines = @file_cache.get_file(file_name).lines

@file_cache.get_full_sources(file_name: file_name, class_name: class_name).map do |s|
line_number = s.loc.line
start_pos = line_number - start_offset
end_pos = line_number + end_offset
start_pos = [line_number - start_offset, 1].max
end_pos = [line_number + end_offset, lines.size].min

location = "#{file_name}:#{s.loc.line} (L#{start_pos}..#{end_pos})"
context = @file_cache.get_file(file_name).lines.slice(start_pos..end_pos)
context = lines[(start_pos - 1)...end_pos]
full_context = unindent((context || ['']).join)

[@file_cache.anonymize_arguments(s.source), "> #{location}\n\n#{full_context}"]
Expand Down
4 changes: 3 additions & 1 deletion lib/query_packwerk/violations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,9 @@ def anonymous_sources_with_locations
def sources_with_contexts(start_offset: 3, end_offset: 3)
load_sources!

deep_merge_hash_groups(@original_collection) { |v| [v.class_name, v.sources_with_contexts] }
deep_merge_hash_groups(@original_collection) do |v|
[v.class_name, v.sources_with_contexts(start_offset:, end_offset:)]
end
end

sig { params(start_offset: Integer, end_offset: Integer).returns(String) }
Expand Down
1 change: 1 addition & 0 deletions query_packwerk.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Gem::Specification.new do |spec|
spec.require_paths = ['lib']

spec.add_dependency 'coderay'
spec.add_dependency 'herb', '>= 0.9'
spec.add_dependency 'packwerk'
spec.add_dependency 'parse_packwerk'
spec.add_dependency 'rubocop'
Expand Down
Loading
Loading