packwerk 2.1.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,20 +1,40 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
5
5
  class FilesForProcessing
6
+ extend T::Sig
7
+
6
8
  class << self
7
- def fetch(paths:, configuration:, ignore_nested_packages: false)
8
- new(paths, configuration, ignore_nested_packages).files
9
+ extend T::Sig
10
+
11
+ sig do
12
+ params(
13
+ relative_file_paths: T::Array[String],
14
+ configuration: Configuration,
15
+ ignore_nested_packages: T::Boolean
16
+ ).returns(T::Array[String])
17
+ end
18
+ def fetch(relative_file_paths:, configuration:, ignore_nested_packages: false)
19
+ new(relative_file_paths, configuration, ignore_nested_packages).files
9
20
  end
10
21
  end
11
22
 
12
- def initialize(paths, configuration, ignore_nested_packages)
13
- @paths = paths
23
+ sig do
24
+ params(
25
+ relative_file_paths: T::Array[String],
26
+ configuration: Configuration,
27
+ ignore_nested_packages: T::Boolean
28
+ ).void
29
+ end
30
+ def initialize(relative_file_paths, configuration, ignore_nested_packages)
31
+ @relative_file_paths = relative_file_paths
14
32
  @configuration = configuration
15
33
  @ignore_nested_packages = ignore_nested_packages
34
+ @custom_files = T.let(nil, T.nilable(T::Array[String]))
16
35
  end
17
36
 
37
+ sig { returns(T::Array[String]) }
18
38
  def files
19
39
  include_files = if custom_files.empty?
20
40
  configured_included_files
@@ -27,50 +47,57 @@ module Packwerk
27
47
 
28
48
  private
29
49
 
50
+ sig { returns(T::Array[String]) }
30
51
  def custom_files
31
- @custom_files ||= @paths.flat_map do |path|
32
- path = File.expand_path(path, @configuration.root_path)
33
- if File.file?(path)
34
- path
52
+ @custom_files ||= @relative_file_paths.flat_map do |relative_file_path|
53
+ absolute_file_path = File.expand_path(relative_file_path, @configuration.root_path)
54
+ if File.file?(absolute_file_path)
55
+ absolute_file_path
35
56
  else
36
- custom_included_files(path)
57
+ custom_included_files(absolute_file_path)
37
58
  end
38
59
  end
39
60
  end
40
61
 
41
- def custom_included_files(path)
62
+ sig { params(absolute_file_path: String).returns(T::Array[String]) }
63
+ def custom_included_files(absolute_file_path)
42
64
  # Note, assuming include globs are always relative paths
43
65
  absolute_includes = @configuration.include.map do |glob|
44
66
  File.expand_path(glob, @configuration.root_path)
45
67
  end
46
68
 
47
- files = Dir.glob([File.join(path, "**", "*")]).select do |file_path|
69
+ absolute_files = Dir.glob([File.join(absolute_file_path, "**", "*")]).select do |absolute_path|
48
70
  absolute_includes.any? do |pattern|
49
- File.fnmatch?(pattern, file_path, File::FNM_EXTGLOB)
71
+ File.fnmatch?(pattern, absolute_path, File::FNM_EXTGLOB)
50
72
  end
51
73
  end
52
74
 
53
75
  if @ignore_nested_packages
54
- nested_packages_paths = Dir.glob(File.join(path, "*", "**", "package.yml"))
55
- nested_packages_globs = nested_packages_paths.map { |npp| npp.gsub("package.yml", "**/*") }
56
- nested_packages_globs.each do |glob|
57
- files -= Dir.glob(glob)
76
+ nested_packages_absolute_file_paths = Dir.glob(File.join(absolute_file_path, "*", "**", "package.yml"))
77
+ nested_packages_absolute_globs = nested_packages_absolute_file_paths.map do |npp|
78
+ npp.gsub("package.yml", "**/*")
79
+ end
80
+ nested_packages_absolute_globs.each do |absolute_glob|
81
+ absolute_files -= Dir.glob(absolute_glob)
58
82
  end
59
83
  end
60
84
 
61
- files
85
+ absolute_files
62
86
  end
63
87
 
88
+ sig { returns(T::Array[String]) }
64
89
  def configured_included_files
65
- files_for_globs(@configuration.include)
90
+ absolute_files_for_globs(@configuration.include)
66
91
  end
67
92
 
93
+ sig { returns(T::Array[String]) }
68
94
  def configured_excluded_files
69
- files_for_globs(@configuration.exclude)
95
+ absolute_files_for_globs(@configuration.exclude)
70
96
  end
71
97
 
72
- def files_for_globs(globs)
73
- globs
98
+ sig { params(relative_globs: T::Array[String]).returns(T::Array[String]) }
99
+ def absolute_files_for_globs(relative_globs)
100
+ relative_globs
74
101
  .flat_map { |glob| Dir[File.expand_path(glob, @configuration.root_path)] }
75
102
  .uniq
76
103
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
@@ -9,12 +9,12 @@ module Packwerk
9
9
  sig do
10
10
  params(
11
11
  reference_extractor: ReferenceExtractor,
12
- filename: String,
12
+ absolute_file: String,
13
13
  ).void
14
14
  end
15
- def initialize(reference_extractor:, filename:)
15
+ def initialize(reference_extractor:, absolute_file:)
16
16
  @reference_extractor = reference_extractor
17
- @filename = filename
17
+ @absolute_file = absolute_file
18
18
  end
19
19
 
20
20
  sig do
@@ -25,7 +25,7 @@ module Packwerk
25
25
  end
26
26
  def call(node, ancestors)
27
27
  return unless Node.method_call?(node) || Node.constant?(node)
28
- @reference_extractor.reference_from_node(node, ancestors: ancestors, file_path: @filename)
28
+ @reference_extractor.reference_from_node(node, ancestors: ancestors, absolute_file: @absolute_file)
29
29
  end
30
30
  end
31
31
  end
@@ -9,11 +9,11 @@ module Packwerk
9
9
  const :context_provider, Packwerk::ConstantDiscovery
10
10
  const :constant_name_inspectors, T::Array[ConstantNameInspector]
11
11
 
12
- sig { params(filename: String, node: AST::Node).returns(NodeProcessor) }
13
- def for(filename:, node:)
12
+ sig { params(absolute_file: String, node: AST::Node).returns(NodeProcessor) }
13
+ def for(absolute_file:, node:)
14
14
  ::Packwerk::NodeProcessor.new(
15
15
  reference_extractor: reference_extractor(node: node),
16
- filename: filename,
16
+ absolute_file: absolute_file,
17
17
  )
18
18
  end
19
19
 
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "parser/source/map"
@@ -8,7 +8,14 @@ module Packwerk
8
8
  extend T::Sig
9
9
  extend T::Helpers
10
10
 
11
- attr_reader :location, :file, :message
11
+ sig { returns(T.nilable(Node::Location)) }
12
+ attr_reader :location
13
+
14
+ sig { returns(String) }
15
+ attr_reader :file
16
+
17
+ sig { returns(String) }
18
+ attr_reader :message
12
19
 
13
20
  sig do
14
21
  params(file: String, message: String, location: T.nilable(Node::Location))
@@ -22,6 +29,7 @@ module Packwerk
22
29
 
23
30
  sig { params(style: OutputStyle).returns(String) }
24
31
  def to_s(style = OutputStyles::Plain.new)
32
+ location = self.location
25
33
  if location
26
34
  <<~EOS
27
35
  #{style.filename}#{file}#{style.reset}:#{location.line}:#{location.column}
@@ -92,10 +92,10 @@ module Packwerk
92
92
  packages[name]
93
93
  end
94
94
 
95
- sig { params(file_path: T.any(Pathname, String)).returns(T.nilable(Package)) }
95
+ sig { params(file_path: T.any(Pathname, String)).returns(Package) }
96
96
  def package_from_path(file_path)
97
97
  path_string = file_path.to_s
98
- @package_from_path[path_string] ||= packages.values.find { |package| package.package_path?(path_string) }
98
+ @package_from_path[path_string] ||= T.must(packages.values.find { |package| package.package_path?(path_string) })
99
99
  end
100
100
  end
101
101
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "benchmark"
@@ -8,8 +8,20 @@ module Packwerk
8
8
  class ParseRun
9
9
  extend T::Sig
10
10
 
11
+ ProcessFileProc = T.type_alias do
12
+ T.proc.params(path: String).returns(T::Array[Offense])
13
+ end
14
+
15
+ sig do
16
+ params(
17
+ absolute_files: T::Array[String],
18
+ configuration: Configuration,
19
+ progress_formatter: Formatters::ProgressFormatter,
20
+ offenses_formatter: OffensesFormatter,
21
+ ).void
22
+ end
11
23
  def initialize(
12
- files:,
24
+ absolute_files:,
13
25
  configuration:,
14
26
  progress_formatter: Formatters::ProgressFormatter.new(StringIO.new),
15
27
  offenses_formatter: Formatters::OffensesFormatter.new
@@ -17,9 +29,10 @@ module Packwerk
17
29
  @configuration = configuration
18
30
  @progress_formatter = progress_formatter
19
31
  @offenses_formatter = offenses_formatter
20
- @files = files
32
+ @absolute_files = absolute_files
21
33
  end
22
34
 
35
+ sig { returns(Result) }
23
36
  def detect_stale_violations
24
37
  offense_collection = find_offenses
25
38
 
@@ -29,6 +42,7 @@ module Packwerk
29
42
  Result.new(message: message, status: result_status)
30
43
  end
31
44
 
45
+ sig { returns(Result) }
32
46
  def update_deprecations
33
47
  offense_collection = find_offenses
34
48
  offense_collection.dump_deprecated_references_files
@@ -41,6 +55,7 @@ module Packwerk
41
55
  Result.new(message: message, status: offense_collection.errors.empty?)
42
56
  end
43
57
 
58
+ sig { returns(Result) }
44
59
  def check
45
60
  offense_collection = find_offenses(show_errors: true)
46
61
 
@@ -55,23 +70,24 @@ module Packwerk
55
70
 
56
71
  private
57
72
 
73
+ sig { params(show_errors: T::Boolean).returns(OffenseCollection) }
58
74
  def find_offenses(show_errors: false)
59
75
  offense_collection = OffenseCollection.new(@configuration.root_path)
60
- @progress_formatter.started(@files)
76
+ @progress_formatter.started(@absolute_files)
61
77
 
62
78
  run_context = Packwerk::RunContext.from_configuration(@configuration)
63
- all_offenses = T.let([], T.untyped)
79
+ all_offenses = T.let([], T::Array[Offense])
64
80
 
65
- process_file = -> (path) do
66
- run_context.process_file(file: path).tap do |offenses|
81
+ process_file = T.let(-> (absolute_file) do
82
+ run_context.process_file(absolute_file: absolute_file).tap do |offenses|
67
83
  failed = show_errors && offenses.any? { |offense| !offense_collection.listed?(offense) }
68
84
  update_progress(failed: failed)
69
85
  end
70
- end
86
+ end, ProcessFileProc)
71
87
 
72
88
  execution_time = Benchmark.realtime do
73
89
  all_offenses = if @configuration.parallel?
74
- Parallel.flat_map(@files, &process_file)
90
+ Parallel.flat_map(@absolute_files, &process_file)
75
91
  else
76
92
  serial_find_offenses(&process_file)
77
93
  end
@@ -83,18 +99,22 @@ module Packwerk
83
99
  offense_collection
84
100
  end
85
101
 
86
- def serial_find_offenses
87
- all_offenses = T.let([], T.untyped)
88
- @files.each do |path|
89
- offenses = yield path
90
- all_offenses.concat(offenses)
102
+ sig { params(block: ProcessFileProc).returns(T::Array[Offense]) }
103
+ def serial_find_offenses(&block)
104
+ all_offenses = T.let([], T::Array[Offense])
105
+ begin
106
+ @absolute_files.each do |absolute_file|
107
+ offenses = block.call(absolute_file)
108
+ all_offenses.concat(offenses)
109
+ end
110
+ rescue Interrupt
111
+ @progress_formatter.interrupted
112
+ all_offenses
91
113
  end
92
114
  all_offenses
93
- rescue Interrupt
94
- @progress_formatter.interrupted
95
- all_offenses
96
115
  end
97
116
 
117
+ sig { params(failed: T::Boolean).void }
98
118
  def update_progress(failed: false)
99
119
  if failed
100
120
  @progress_formatter.mark_as_failed
@@ -9,6 +9,8 @@ require "parser/source/buffer"
9
9
  module Packwerk
10
10
  module Parsers
11
11
  class Erb
12
+ include ParserInterface
13
+
12
14
  def initialize(parser_class: BetterHtml::Parser, ruby_parser: Ruby.new)
13
15
  @parser_class = parser_class
14
16
  @ruby_parser = ruby_parser
@@ -6,6 +6,7 @@ require "singleton"
6
6
  module Packwerk
7
7
  module Parsers
8
8
  class Factory
9
+ extend T::Sig
9
10
  include Singleton
10
11
 
11
12
  RUBY_REGEX = %r{
@@ -19,6 +20,7 @@ module Packwerk
19
20
  ERB_REGEX = /\.erb\Z/
20
21
  private_constant :ERB_REGEX
21
22
 
23
+ sig { params(path: String).returns(T.nilable(ParserInterface)) }
22
24
  def for_path(path)
23
25
  case path
24
26
  when RUBY_REGEX
@@ -0,0 +1,17 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Parsers
6
+ module ParserInterface
7
+ extend T::Helpers
8
+ extend T::Sig
9
+
10
+ interface!
11
+
12
+ sig { abstract.params(io: File, file_path: String).returns(T.untyped) }
13
+ def call(io:, file_path:)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -7,6 +7,8 @@ require "parser/current"
7
7
  module Packwerk
8
8
  module Parsers
9
9
  class Ruby
10
+ include ParserInterface
11
+
10
12
  class RaiseExceptionsParser < Parser::CurrentRuby
11
13
  def initialize(builder)
12
14
  super(builder)
@@ -5,6 +5,7 @@ module Packwerk
5
5
  module Parsers
6
6
  autoload :Erb, "packwerk/parsers/erb"
7
7
  autoload :Factory, "packwerk/parsers/factory"
8
+ autoload :ParserInterface, "packwerk/parsers/parser_interface"
8
9
  autoload :Ruby, "packwerk/parsers/ruby"
9
10
 
10
11
  class ParseResult < Offense; end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
@@ -6,6 +6,7 @@ module Packwerk
6
6
  class ReferenceChecker
7
7
  extend T::Sig
8
8
 
9
+ sig { params(checkers: T::Array[Checkers::Checker]).void }
9
10
  def initialize(checkers)
10
11
  @checkers = checkers
11
12
  end
@@ -27,10 +27,10 @@ module Packwerk
27
27
  params(
28
28
  node: Parser::AST::Node,
29
29
  ancestors: T::Array[Parser::AST::Node],
30
- file_path: String
30
+ absolute_file: String
31
31
  ).returns(T.nilable(UnresolvedReference))
32
32
  end
33
- def reference_from_node(node, ancestors:, file_path:)
33
+ def reference_from_node(node, ancestors:, absolute_file:)
34
34
  constant_name = T.let(nil, T.nilable(String))
35
35
 
36
36
  @constant_name_inspectors.each do |inspector|
@@ -39,7 +39,14 @@ module Packwerk
39
39
  break if constant_name
40
40
  end
41
41
 
42
- reference_from_constant(constant_name, node: node, ancestors: ancestors, file_path: file_path) if constant_name
42
+ if constant_name
43
+ reference_from_constant(
44
+ constant_name,
45
+ node: node,
46
+ ancestors: ancestors,
47
+ absolute_file: absolute_file
48
+ )
49
+ end
43
50
  end
44
51
 
45
52
  sig do
@@ -66,11 +73,15 @@ module Packwerk
66
73
  current_namespace_path: unresolved_reference.namespace_path
67
74
  )
68
75
 
69
- next if constant&.package.nil?
76
+ next if constant.nil?
77
+
78
+ package_for_constant = constant.package
79
+
80
+ next if package_for_constant.nil?
70
81
 
71
82
  source_package = context_provider.package_from_path(unresolved_reference.relative_path)
72
83
 
73
- next if source_package == constant.package
84
+ next if source_package == package_for_constant
74
85
 
75
86
  fully_qualified_references_and_offenses << Reference.new(
76
87
  source_package,
@@ -90,21 +101,21 @@ module Packwerk
90
101
  constant_name: String,
91
102
  node: Parser::AST::Node,
92
103
  ancestors: T::Array[Parser::AST::Node],
93
- file_path: String
104
+ absolute_file: String
94
105
  ).returns(T.nilable(UnresolvedReference))
95
106
  end
96
- def reference_from_constant(constant_name, node:, ancestors:, file_path:)
107
+ def reference_from_constant(constant_name, node:, ancestors:, absolute_file:)
97
108
  namespace_path = Node.enclosing_namespace_path(node, ancestors: ancestors)
98
109
 
99
110
  return if local_reference?(constant_name, Node.name_location(node), namespace_path)
100
111
 
101
- relative_path = Pathname.new(file_path).relative_path_from(@root_path).to_s
112
+ relative_file = Pathname.new(absolute_file).relative_path_from(@root_path).to_s
102
113
  location = Node.location(node)
103
114
 
104
115
  UnresolvedReference.new(
105
116
  constant_name,
106
117
  namespace_path,
107
- relative_path,
118
+ relative_file,
108
119
  location
109
120
  )
110
121
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
@@ -7,7 +7,11 @@ module Packwerk
7
7
  extend T::Sig
8
8
  extend T::Helpers
9
9
 
10
- attr_reader :reference, :violation_type
10
+ sig { returns(Reference) }
11
+ attr_reader :reference
12
+
13
+ sig { returns(ViolationType) }
14
+ attr_reader :violation_type
11
15
 
12
16
  sig do
13
17
  params(
@@ -25,10 +29,11 @@ module Packwerk
25
29
 
26
30
  private
27
31
 
32
+ sig { params(reference: Reference, violation_type: ViolationType).returns(String) }
28
33
  def build_message(reference, violation_type)
29
34
  violation_message = case violation_type
30
35
  when ViolationType::Privacy
31
- source_desc = reference.source_package ? "'#{reference.source_package}'" : "here"
36
+ source_desc = "'#{reference.source_package}'"
32
37
  "Privacy violation: '#{reference.constant.name}' is private to '#{reference.constant.package}' but " \
33
38
  "referenced from #{source_desc}.\n" \
34
39
  "Is there a public entrypoint in '#{reference.constant.package.public_path}' that you can use instead?"
@@ -3,7 +3,7 @@
3
3
 
4
4
  module Packwerk
5
5
  class Result < T::Struct
6
- prop :message, String
7
- prop :status, T::Boolean
6
+ const :message, String
7
+ const :status, T::Boolean
8
8
  end
9
9
  end