diff --git a/lib/packwerk.rb b/lib/packwerk.rb index 39023d1ca..765de3efe 100644 --- a/lib/packwerk.rb +++ b/lib/packwerk.rb @@ -32,6 +32,7 @@ module Packwerk autoload :Reference autoload :ReferenceOffense autoload :Validator + autoload :ReferencesFromFile module OutputStyles extend ActiveSupport::Autoload diff --git a/lib/packwerk/references_from_file.rb b/lib/packwerk/references_from_file.rb new file mode 100644 index 000000000..e516304c5 --- /dev/null +++ b/lib/packwerk/references_from_file.rb @@ -0,0 +1,41 @@ +# typed: strict +# frozen_string_literal: true + +module Packwerk + # Extracts all static constant references between Ruby files. + class ReferencesFromFile + extend T::Sig + + class FileParserError < RuntimeError + extend T::Sig + + sig { params(file: String, offenses: T::Array[Packwerk::Offense]).void } + def initialize(file:, offenses:) + super("Errors while parsing #{file}: #{offenses.map(&:to_s).join("\n")}") + end + end + + sig { params(config: Packwerk::Configuration).void } + def initialize(config = Configuration.from_path) + @config = config + @run_context = T.let(RunContext.from_configuration(@config), RunContext) + end + + sig { params(relative_file_paths: T::Array[String]).returns(T::Array[Packwerk::Reference]) } + def list_for_all(relative_file_paths: []) + files = FilesForProcessing.fetch(relative_file_paths: relative_file_paths, configuration: @config).files + files.flat_map { |file| list_for_file(file) } + end + + sig { params(relative_file: String).returns(T::Array[Packwerk::Reference]) } + def list_for_file(relative_file) + references_result = @run_context.references_from_file(relative_file: relative_file) + + if references_result.file_offenses.present? + raise FileParserError.new(file: relative_file, offenses: references_result.file_offenses) + end + + references_result.references + end + end +end diff --git a/lib/packwerk/run_context.rb b/lib/packwerk/run_context.rb index 8dae554a1..636a016ac 100644 --- a/lib/packwerk/run_context.rb +++ b/lib/packwerk/run_context.rb @@ -75,15 +75,29 @@ def initialize( sig { params(relative_file: String).returns(T::Array[Packwerk::Offense]) } def process_file(relative_file:) + reference_checker = ReferenceChecking::ReferenceChecker.new(@checkers) + + references_result = references_from_file(relative_file: relative_file) + + references_result.file_offenses + + references_result.references.flat_map { |reference| reference_checker.call(reference) } + end + + class FileReferencesResult < T::Struct + const :references, T::Array[Packwerk::Reference] + const :file_offenses, T::Array[Packwerk::Offense] + end + + sig { params(relative_file: String).returns(FileReferencesResult) } + def references_from_file(relative_file:) processed_file = file_processor.call(relative_file) references = ReferenceExtractor.get_fully_qualified_references_from( processed_file.unresolved_references, context_provider ) - reference_checker = ReferenceChecking::ReferenceChecker.new(@checkers) - processed_file.offenses + references.flat_map { |reference| reference_checker.call(reference) } + FileReferencesResult.new(references: references, file_offenses: processed_file.offenses) end sig { returns(PackageSet) } diff --git a/test/unit/packwerk/references_from_file_test.rb b/test/unit/packwerk/references_from_file_test.rb new file mode 100644 index 000000000..531fa5b59 --- /dev/null +++ b/test/unit/packwerk/references_from_file_test.rb @@ -0,0 +1,61 @@ +# typed: true +# frozen_string_literal: true + +require "test_helper" + +module Packwerk + class ReferencesFromFileTest < Minitest::Test + setup do + @config = Configuration.new + @config.stubs(:load_paths).returns({}) + @run_context = RunContext.from_configuration(@config) + RunContext.stubs(:from_configuration).with(@config).returns(@run_context) + @referencer = ReferencesFromFile.new(@config) + end + + test "raises on parser error" do + offense = Offense.new(file: "something.rb", message: "yo") + @run_context.stubs(:references_from_file).returns( + RunContext::FileReferencesResult.new(file_offenses: [offense], references: []) + ) + + assert_raises ReferencesFromFile::FileParserError do + @referencer.list_for_file("lib/something.rb") + end + end + + test "fetches violations for all files from run context" do + references = { + "lib/something.rb" => [ + make_fake_reference, + ], + "components/ice_cream_sales/app/models/scoop.rb" => [ + make_fake_reference, + ], + } + ffp_mock = mock("FilesForProcessing instance") + ffp_mock.stubs(:files).returns(references.keys) + FilesForProcessing.stubs(:fetch).with(relative_file_paths: [], configuration: @config).returns(ffp_mock) + + references.each do |file, references| + @run_context.stubs(:references_from_file).with(relative_file: file).returns( + RunContext::FileReferencesResult.new(file_offenses: [], references: references) + ) + end + + assert_equal Set.new(references.values.flatten), Set.new(@referencer.list_for_all) + end + + private + + def make_fake_reference + package_name = Array("ilikeletters".chars.sample(5)).join + Reference.new( + package: Package.new(name: package_name), + relative_path: package_name, + constant: ConstantContext.new, + source_location: nil + ) + end + end +end