packwerk 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6adf93154744a85f6f49a1b830710dee4f4c7b8e05373de50aef86926167f23e
4
- data.tar.gz: cfea93c802c34114e63966717e7a643cf1de94f3061b13ed434b04dd12469bed
3
+ metadata.gz: 3b54c31e7c1902a03feb01304c85c9f2363b769fdb5f3c3cea7f38c67ff77af3
4
+ data.tar.gz: efefa11bf554021227ce63a876c891d5466ee7bcd173e319deb086c8ec9732a5
5
5
  SHA512:
6
- metadata.gz: 00dfee149ce8b43ce862a0df57eda5af2583fd4066cd1596ffaae2a1a5f562150e8caf84d29f062e11a039bf08bea3eb360e9a2817b14c2d9229ce77a89dc2e4
7
- data.tar.gz: f74cecc91b2fce594df2c1dae811d469a1f8e490daefbd396ed2fb87cb6438e7286de98f63c425d196af2b7ce4ed6f8ef6e0cfb9ddf8b47d17e38bbccda2a0db
6
+ metadata.gz: 5765e07827c5c5fc6d297f916e80c6cd2a6e09984ba91a1f2186ce1209d7965c7d906b6ff06a6b50fd37c299cdb9f82188d57273faf6e0fe7c654983ff8c0e8d
7
+ data.tar.gz: b44c1ba5e1c5b61dd2d3e4755b5c215f245cb412a05f272513a6eba83e561097d6f6e624d65f5e8fa7021479616433f0e37ca9b818f00363efd11f24f8b6bca6
@@ -7,21 +7,22 @@
7
7
  ## What should reviewers focus on?
8
8
 
9
9
 
10
-
11
10
  ## Type of Change
12
11
 
13
- - [ ] Bug fix (non-breaking change which fixes an issue)
14
- - [ ] New feature (non-breaking change which adds functionality)
15
- - [ ] Code refactor (non-breaking change that doesn't add functionality)
16
- - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
17
- - [ ] This change requires a documentation update
12
+ - [ ] Bugfix
13
+ - [ ] New feature
14
+ - [ ] Non-breaking change (a change that doesn't alter functionality - i.e., code refactor, configs, etc.)
18
15
 
19
16
  ### Additional Release Notes
20
17
 
18
+ - [ ] Breaking change (fix or feature that would cause existing functionality to change)
19
+
21
20
  Include any notes here to include in the release description. For example, if you selected "breaking change" above, leave notes on how users can transition to this version.
22
21
 
23
22
  If no additional notes are necessary, delete this section or leave it unchanged.
24
23
 
25
24
  ## Checklist
26
25
 
27
- - [ ] It is safe to simply rollback this change.
26
+ - [ ] I have updated the documentation accordingly.
27
+ - [ ] I have added tests to cover my changes.
28
+ - [ ] It is safe to rollback this change.
data/Gemfile CHANGED
@@ -19,4 +19,5 @@ gem("tapioca", require: false)
19
19
 
20
20
  group :development do
21
21
  gem("byebug", require: false)
22
+ gem("minitest-focus", require: false)
22
23
  end
@@ -85,7 +85,7 @@ GIT
85
85
  PATH
86
86
  remote: .
87
87
  specs:
88
- packwerk (1.0.1)
88
+ packwerk (1.0.2)
89
89
  activesupport (>= 5.2)
90
90
  ast
91
91
  better_html
@@ -137,6 +137,8 @@ GEM
137
137
  mini_mime (1.0.2)
138
138
  mini_portile2 (2.4.0)
139
139
  minitest (5.14.0)
140
+ minitest-focus (1.2.1)
141
+ minitest (>= 4, < 6)
140
142
  mocha (1.11.2)
141
143
  nio4r (2.5.2)
142
144
  nokogiri (1.10.9)
@@ -180,7 +182,7 @@ GEM
180
182
  smart_properties (1.15.0)
181
183
  sorbet (0.5.5898)
182
184
  sorbet-static (= 0.5.5898)
183
- sorbet-runtime (0.5.5898)
185
+ sorbet-runtime (0.5.6049)
184
186
  sorbet-static (0.5.5898-universal-darwin-19)
185
187
  spoom (1.0.4)
186
188
  colorize
@@ -220,6 +222,7 @@ DEPENDENCIES
220
222
  byebug
221
223
  constant_resolver
222
224
  m
225
+ minitest-focus
223
226
  mocha
224
227
  packwerk!
225
228
  rails!
data/USAGE.md CHANGED
@@ -70,7 +70,7 @@ Packwerk reads from the `packwerk.yml` configuration file in the root directory.
70
70
 
71
71
  ### Inflections
72
72
 
73
- Packwerk requires custom inflections to be defined in `inflections.yml` instead of the traditional `inflections.rb`. This is because Packwerk accounts for custom inflections, such as acronyms, when resolving constants. Additionally, Packwerk interprets Active Record associations as references to constants. For example, `has_many :birds` is reference to the `Birds` constant.
73
+ Packwerk requires custom inflections to be defined in `inflections.yml` instead of the traditional `inflections.rb`. This is because Packwerk accounts for custom inflections, such as acronyms, when resolving constants. Additionally, Packwerk interprets Active Record associations as references to constants. For example, `has_many :birds` is a reference to the `Bird` constant.
74
74
 
75
75
  In order to make your custom inflections compatible with Active Support and Packwerk, you must create a `config/inflections.yml` file and point `ActiveSupport::Inflector` to that file.
76
76
 
@@ -14,7 +14,6 @@ require "packwerk/cli"
14
14
  require "packwerk/configuration"
15
15
  require "packwerk/const_node_inspector"
16
16
  require "packwerk/constant_discovery"
17
- require "packwerk/constant_name_inspector"
18
17
  require "packwerk/dependency_checker"
19
18
  require "packwerk/deprecated_references"
20
19
  require "packwerk/files_for_processing"
@@ -0,0 +1,67 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler"
5
+
6
+ module Packwerk
7
+ module ApplicationLoadPaths
8
+ class << self
9
+ extend T::Sig
10
+
11
+ sig { returns(T::Array[String]) }
12
+ def extract_relevant_paths
13
+ assert_application_booted
14
+ all_paths = extract_application_autoload_paths
15
+ relevant_paths = filter_relevant_paths(all_paths)
16
+ assert_load_paths_present(relevant_paths)
17
+ relative_path_strings(relevant_paths)
18
+ end
19
+
20
+ sig { void }
21
+ def assert_application_booted
22
+ raise "The application needs to be booted to extract load paths" unless defined?(::Rails)
23
+ end
24
+
25
+ sig { returns(T::Array[String]) }
26
+ def extract_application_autoload_paths
27
+ Rails.application.railties
28
+ .select { |railtie| railtie.is_a?(Rails::Engine) }
29
+ .push(Rails.application)
30
+ .flat_map do |engine|
31
+ (engine.config.autoload_paths + engine.config.eager_load_paths + engine.config.autoload_once_paths).uniq
32
+ end
33
+ end
34
+
35
+ sig do
36
+ params(all_paths: T::Array[String], bundle_path: Pathname, rails_root: Pathname)
37
+ .returns(T::Array[Pathname])
38
+ end
39
+ def filter_relevant_paths(all_paths, bundle_path: Bundler.bundle_path, rails_root: Rails.root)
40
+ bundle_path_match = bundle_path.join("**")
41
+ rails_root_match = rails_root.join("**")
42
+
43
+ all_paths
44
+ .map { |path| Pathname.new(path).expand_path }
45
+ .select { |path| path.fnmatch(rails_root_match.to_s) } # path needs to be in application directory
46
+ .reject { |path| path.fnmatch(bundle_path_match.to_s) } # reject paths from vendored gems
47
+ end
48
+
49
+ sig { params(paths: T::Array[Pathname], rails_root: Pathname).returns(T::Array[String]) }
50
+ def relative_path_strings(paths, rails_root: Rails.root)
51
+ paths
52
+ .map { |path| path.relative_path_from(rails_root).to_s }
53
+ .uniq
54
+ end
55
+
56
+ sig { params(paths: T::Array[T.untyped]).void }
57
+ def assert_load_paths_present(paths)
58
+ if paths.empty?
59
+ raise <<~EOS
60
+ We could not extract autoload paths from your Rails app. This is likely a configuration error.
61
+ Packwerk will not work correctly without any autoload paths.
62
+ EOS
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -9,15 +9,15 @@ require "yaml"
9
9
  require "packwerk/package_set"
10
10
  require "packwerk/graph"
11
11
  require "packwerk/inflector"
12
+ require "packwerk/application_load_paths"
12
13
 
13
14
  module Packwerk
14
15
  class ApplicationValidator
15
- def initialize(config_file_path:, application_load_paths:, configuration:)
16
+ def initialize(config_file_path:, configuration:)
16
17
  @config_file_path = config_file_path
17
18
  @configuration = configuration
18
19
 
19
- # Load paths should be from the application
20
- @application_load_paths = application_load_paths.sort.uniq
20
+ @application_load_paths = ApplicationLoadPaths.extract_relevant_paths
21
21
  end
22
22
 
23
23
  Result = Struct.new(:ok?, :error_value)
@@ -170,14 +170,12 @@ module Packwerk
170
170
  def check_inflection_file
171
171
  inflections_file = @configuration.inflections_file
172
172
 
173
- test_inflections = ActiveSupport::Inflector::Inflections.new
174
-
175
- Packwerk::Inflections::Default.apply_to(test_inflections)
176
- Packwerk::Inflections::Custom.new(inflections_file).apply_to(test_inflections)
173
+ application_inflections = ActiveSupport::Inflector.inflections
174
+ packwerk_inflections = Packwerk::Inflector.from_file(inflections_file).inflections
177
175
 
178
176
  results = %i(plurals singulars uncountables humans acronyms).map do |type|
179
- expected = ActiveSupport::Inflector.inflections.public_send(type).to_set
180
- actual = test_inflections.public_send(type).to_set
177
+ expected = application_inflections.public_send(type).to_set
178
+ actual = packwerk_inflections.public_send(type).to_set
181
179
 
182
180
  if expected == actual
183
181
  Result.new(true)
@@ -331,7 +329,8 @@ module Packwerk
331
329
  end
332
330
 
333
331
  def package_manifests(glob_pattern)
334
- PackageSet.package_paths(@configuration.root_path, glob_pattern).map(&:to_s)
332
+ PackageSet.package_paths(@configuration.root_path, glob_pattern)
333
+ .map { |f| File.realpath(f) }
335
334
  end
336
335
 
337
336
  def relative_paths(paths)
@@ -16,7 +16,7 @@ module Packwerk
16
16
  has_and_belongs_to_many
17
17
  ).to_set
18
18
 
19
- def initialize(inflector: Inflector.new, custom_associations: Set.new)
19
+ def initialize(inflector:, custom_associations: Set.new)
20
20
  @inflector = inflector
21
21
  @associations = RAILS_ASSOCIATIONS + custom_associations
22
22
  end
@@ -11,6 +11,7 @@ require "packwerk/inflector"
11
11
  require "packwerk/output_styles"
12
12
  require "packwerk/run_context"
13
13
  require "packwerk/updating_deprecated_references"
14
+ require "packwerk/checking_deprecated_references"
14
15
 
15
16
  module Packwerk
16
17
  class Cli
@@ -21,7 +22,10 @@ module Packwerk
21
22
  @err_out = err_out
22
23
  @style = style
23
24
  @configuration = configuration || Configuration.from_path
24
- @run_context = run_context || Packwerk::RunContext.from_configuration(@configuration)
25
+ @run_context = run_context || Packwerk::RunContext.from_configuration(
26
+ @configuration,
27
+ reference_lister: ::Packwerk::CheckingDeprecatedReferences.new(@configuration.root_path),
28
+ )
25
29
  @progress_formatter = Formatters::ProgressFormatter.new(@out, style: style)
26
30
  end
27
31
 
@@ -138,7 +142,7 @@ module Packwerk
138
142
  all_offenses = T.let([], T.untyped)
139
143
  execution_time = Benchmark.realtime do
140
144
  all_offenses = files.flat_map do |path|
141
- @run_context.file_processor.call(path).tap { |offenses| mark_progress(offenses) }
145
+ @run_context.process_file(file: path).tap { |offenses| mark_progress(offenses) }
142
146
  end
143
147
 
144
148
  updating_deprecated_references.dump_deprecated_references_files
@@ -160,7 +164,7 @@ module Packwerk
160
164
  all_offenses = T.let([], T.untyped)
161
165
  execution_time = Benchmark.realtime do
162
166
  files.each do |path|
163
- @run_context.file_processor.call(path).tap do |offenses|
167
+ @run_context.process_file(file: path).tap do |offenses|
164
168
  mark_progress(offenses)
165
169
  all_offenses.concat(offenses)
166
170
  end
@@ -200,7 +204,6 @@ module Packwerk
200
204
  @progress_formatter.started_validation do
201
205
  checker = Packwerk::ApplicationValidator.new(
202
206
  config_file_path: @configuration.config_path,
203
- application_load_paths: @configuration.all_application_autoload_paths,
204
207
  configuration: @configuration
205
208
  )
206
209
  result = checker.check_all
@@ -42,45 +42,10 @@ module Packwerk
42
42
  @root_path = File.expand_path(root)
43
43
  @package_paths = configs["package_paths"] || "**/"
44
44
  @custom_associations = configs["custom_associations"] || []
45
- @load_paths = configs["load_paths"] || all_application_autoload_paths
45
+ @load_paths = configs["load_paths"]
46
46
  @inflections_file = File.expand_path(configs["inflections_file"] || "config/inflections.yml", @root_path)
47
47
 
48
48
  @config_path = config_path
49
49
  end
50
-
51
- def all_application_autoload_paths
52
- return [] unless defined?(::Rails)
53
-
54
- all_paths = Rails.application.railties
55
- .select { |railtie| railtie.is_a?(Rails::Engine) }
56
- .push(Rails.application)
57
- .flat_map do |engine|
58
- (engine.config.autoload_paths + engine.config.eager_load_paths + engine.config.autoload_once_paths).uniq
59
- end
60
-
61
- bundle_path_match = Bundler.bundle_path.join("**").to_s
62
-
63
- all_paths = all_paths.each_with_object([]) do |path_string, paths|
64
- # ignore paths inside gems
65
- path = Pathname.new(path_string)
66
-
67
- next unless path.exist?
68
- next if path.realpath.fnmatch(bundle_path_match)
69
-
70
- paths << path.relative_path_from(Rails.root).to_s
71
- end
72
-
73
- all_paths.tap do |paths|
74
- if paths.empty?
75
- raise <<~EOS
76
- No autoload paths have been set up in your Rails app. This is likely a bug, and
77
- packwerk is unlikely to work correctly without any autoload paths.
78
-
79
- You can follow the Rails guides on setting up load paths, or manually configure
80
- them in `packwerk.yml` with `load_paths`.
81
- EOS
82
- end
83
- end
84
- end
85
50
  end
86
51
  end
@@ -9,20 +9,12 @@ module Packwerk
9
9
  include ConstantNameInspector
10
10
 
11
11
  def constant_name_from_node(node, ancestors:)
12
- return nil unless Node.type(node) == Node::CONSTANT
13
-
14
- # Only process the root `const` node for namespaced constant references. For example, in the
15
- # reference `Spam::Eggs::Thing`, we only process the const node associated with `Spam`.
12
+ return nil unless Node.constant?(node)
16
13
  parent = ancestors.first
17
- return nil if parent && Node.type(parent) == Node::CONSTANT
18
-
19
- if constant_in_module_or_class_definition?(node, parent: parent)
20
- # We're defining a class with this name, in which case the constant is implicitly fully qualified by its
21
- # enclosing namespace
22
- name = Node.parent_module_name(ancestors: ancestors)
23
- name ||= Node.enclosing_namespace_path(node, ancestors: ancestors).push(Node.constant_name(node)).join("::")
14
+ return nil unless root_constant?(parent)
24
15
 
25
- "::" + name
16
+ if parent && constant_in_module_or_class_definition?(node, parent: parent)
17
+ fully_qualify_constant(node, ancestors: ancestors)
26
18
  else
27
19
  begin
28
20
  Node.constant_name(node)
@@ -34,11 +26,29 @@ module Packwerk
34
26
 
35
27
  private
36
28
 
29
+ # Only process the root `const` node for namespaced constant references. For example, in the
30
+ # reference `Spam::Eggs::Thing`, we only process the const node associated with `Spam`.
31
+ def root_constant?(parent)
32
+ !(parent && Node.constant?(parent))
33
+ end
34
+
37
35
  def constant_in_module_or_class_definition?(node, parent:)
38
- if parent
39
- parent_name = Node.module_name_from_definition(parent)
40
- parent_name && parent_name == Node.constant_name(node)
41
- end
36
+ parent_name = Node.module_name_from_definition(parent)
37
+ parent_name && parent_name == Node.constant_name(node)
38
+ end
39
+
40
+ def fully_qualify_constant(node, ancestors:)
41
+ # We're defining a class with this name, in which case the constant is implicitly fully qualified by its
42
+ # enclosing namespace
43
+ name = Node.parent_module_name(ancestors: ancestors)
44
+ name ||= generate_qualified_constant(node, ancestors)
45
+ "::" + name
46
+ end
47
+
48
+ def generate_qualified_constant(node, ancestors:)
49
+ namespace_path = Node.enclosing_namespace_path(node, ancestors: ancestors)
50
+ constant_name = Node.constant_name(node)
51
+ namespace_path.push(constant_name).join("::")
42
52
  end
43
53
  end
44
54
  end
@@ -15,8 +15,8 @@ module Packwerk
15
15
  end
16
16
  end
17
17
 
18
- def initialize(run_context:, parser_factory: nil)
19
- @run_context = run_context
18
+ def initialize(node_processor_factory:, parser_factory: nil)
19
+ @node_processor_factory = node_processor_factory
20
20
  @parser_factory = parser_factory || Packwerk::Parsers::Factory.instance
21
21
  end
22
22
 
@@ -32,8 +32,8 @@ module Packwerk
32
32
 
33
33
  result = []
34
34
  if node
35
- @node_processor = @run_context.node_processor_for(filename: file_path, ast_node: node)
36
- node_visitor = Packwerk::NodeVisitor.new(node_processor: @node_processor)
35
+ node_processor = @node_processor_factory.for(filename: file_path, node: node)
36
+ node_visitor = Packwerk::NodeVisitor.new(node_processor: node_processor)
37
37
 
38
38
  node_visitor.visit(node, ancestors: [], result: result)
39
39
  end
@@ -8,20 +8,31 @@ require "packwerk/inflections/custom"
8
8
  module Packwerk
9
9
  class Inflector
10
10
  class << self
11
+ extend T::Sig
12
+
11
13
  def default
12
- @default ||= new
14
+ @default ||= new(custom_inflector: Inflections::Custom.new)
15
+ end
16
+
17
+ sig { params(inflections_file: String).returns(::Packwerk::Inflector) }
18
+ def from_file(inflections_file)
19
+ new(custom_inflector: Inflections::Custom.new(inflections_file))
13
20
  end
14
21
  end
15
22
 
16
- # For #camelize, #classify, #pluralize, #singularize
17
- include ::ActiveSupport::Inflector
23
+ extend T::Sig
24
+ include ::ActiveSupport::Inflector # For #camelize, #classify, #pluralize, #singularize
18
25
 
19
- def initialize(custom_inflection_file: nil)
26
+ sig do
27
+ params(
28
+ custom_inflector: Inflections::Custom
29
+ ).void
30
+ end
31
+ def initialize(custom_inflector:)
20
32
  @inflections = ::ActiveSupport::Inflector::Inflections.new
21
33
 
22
34
  Inflections::Default.apply_to(@inflections)
23
-
24
- Inflections::Custom.new(custom_inflection_file).apply_to(@inflections)
35
+ custom_inflector.apply_to(@inflections)
25
36
  end
26
37
 
27
38
  def pluralize(word, count = nil)
@@ -32,8 +43,6 @@ module Packwerk
32
43
  end
33
44
  end
34
45
 
35
- private
36
-
37
46
  def inflections(_ = nil)
38
47
  @inflections
39
48
  end
@@ -5,24 +5,12 @@ require "parser/ast/node"
5
5
 
6
6
  module Packwerk
7
7
  module Node
8
- BLOCK = :block
9
- CLASS = :class
10
- CONSTANT = :const
11
- CONSTANT_ASSIGNMENT = :casgn
12
- CONSTANT_ROOT_NAMESPACE = :cbase
13
- HASH = :hash
14
- HASH_PAIR = :pair
15
- METHOD_CALL = :send
16
- MODULE = :module
17
- STRING = :str
18
- SYMBOL = :sym
19
-
20
8
  class TypeError < ArgumentError; end
21
9
  Location = Struct.new(:line, :column)
22
10
 
23
11
  class << self
24
12
  def class_or_module_name(class_or_module_node)
25
- case type(class_or_module_node)
13
+ case type_of(class_or_module_node)
26
14
  when CLASS, MODULE
27
15
  # (class (const nil :Foo) (const nil :Bar) (nil))
28
16
  # "class Foo < Bar; end"
@@ -36,7 +24,7 @@ module Packwerk
36
24
  end
37
25
 
38
26
  def constant_name(constant_node)
39
- case type(constant_node)
27
+ case type_of(constant_node)
40
28
  when CONSTANT_ROOT_NAMESPACE
41
29
  ""
42
30
  when CONSTANT, CONSTANT_ASSIGNMENT
@@ -74,19 +62,19 @@ module Packwerk
74
62
  end
75
63
 
76
64
  def enclosing_namespace_path(starting_node, ancestors:)
77
- ancestors.select { |n| [CLASS, MODULE].include?(type(n)) }
65
+ ancestors.select { |n| [CLASS, MODULE].include?(type_of(n)) }
78
66
  .each_with_object([]) do |node, namespace|
79
67
  # when evaluating `class Child < Parent`, the const node for `Parent` is a child of the class
80
68
  # node, so it'll be an ancestor, but `Parent` is not evaluated in the namespace of `Child`, so
81
69
  # we need to skip it here
82
- next if type(node) == CLASS && parent_class(node) == starting_node
70
+ next if type_of(node) == CLASS && parent_class(node) == starting_node
83
71
 
84
72
  namespace.prepend(class_or_module_name(node))
85
73
  end
86
74
  end
87
75
 
88
76
  def literal_value(string_or_symbol_node)
89
- case type(string_or_symbol_node)
77
+ case type_of(string_or_symbol_node)
90
78
  when STRING, SYMBOL
91
79
  # (str "foo")
92
80
  # "'foo'"
@@ -103,20 +91,32 @@ module Packwerk
103
91
  Location.new(location.line, location.column)
104
92
  end
105
93
 
94
+ def constant?(node)
95
+ type_of(node) == CONSTANT
96
+ end
97
+
98
+ def constant_assignment?(node)
99
+ type_of(node) == CONSTANT_ASSIGNMENT
100
+ end
101
+
102
+ def class?(node)
103
+ type_of(node) == CLASS
104
+ end
105
+
106
106
  def method_call?(node)
107
- type(node) == METHOD_CALL
107
+ type_of(node) == METHOD_CALL
108
108
  end
109
109
 
110
110
  def hash?(node)
111
- type(node) == HASH
111
+ type_of(node) == HASH
112
112
  end
113
113
 
114
114
  def string?(node)
115
- type(node) == STRING
115
+ type_of(node) == STRING
116
116
  end
117
117
 
118
118
  def symbol?(node)
119
- type(node) == SYMBOL
119
+ type_of(node) == SYMBOL
120
120
  end
121
121
 
122
122
  def method_arguments(method_call_node)
@@ -136,7 +136,7 @@ module Packwerk
136
136
  end
137
137
 
138
138
  def module_name_from_definition(node)
139
- case type(node)
139
+ case type_of(node)
140
140
  when CLASS, MODULE
141
141
  # "class My::Class; end"
142
142
  # "module My::Module; end"
@@ -146,7 +146,7 @@ module Packwerk
146
146
  # "My::Module = ..."
147
147
  rvalue = node.children.last
148
148
 
149
- case type(rvalue)
149
+ case type_of(rvalue)
150
150
  when METHOD_CALL
151
151
  # "Class.new"
152
152
  # "Module.new"
@@ -169,7 +169,7 @@ module Packwerk
169
169
  end
170
170
 
171
171
  def parent_class(class_node)
172
- raise TypeError unless type(class_node) == CLASS
172
+ raise TypeError unless type_of(class_node) == CLASS
173
173
 
174
174
  # (class (const nil :Foo) (const nil :Bar) (nil))
175
175
  # "class Foo < Bar; end"
@@ -178,7 +178,7 @@ module Packwerk
178
178
 
179
179
  def parent_module_name(ancestors:)
180
180
  definitions = ancestors
181
- .select { |n| [CLASS, MODULE, CONSTANT_ASSIGNMENT, BLOCK].include?(type(n)) }
181
+ .select { |n| [CLASS, MODULE, CONSTANT_ASSIGNMENT, BLOCK].include?(type_of(n)) }
182
182
 
183
183
  names = definitions.map do |definition|
184
184
  name_part_from_definition(definition)
@@ -187,10 +187,6 @@ module Packwerk
187
187
  names.empty? ? "Object" : names.reverse.join("::")
188
188
  end
189
189
 
190
- def type(node)
191
- node.type
192
- end
193
-
194
190
  def value_from_hash(hash_node, key)
195
191
  raise TypeError unless hash?(hash_node)
196
192
  pair = hash_pairs(hash_node).detect { |pair_node| literal_value(hash_pair_key(pair_node)) == key }
@@ -199,8 +195,29 @@ module Packwerk
199
195
 
200
196
  private
201
197
 
198
+ BLOCK = :block
199
+ CLASS = :class
200
+ CONSTANT = :const
201
+ CONSTANT_ASSIGNMENT = :casgn
202
+ CONSTANT_ROOT_NAMESPACE = :cbase
203
+ HASH = :hash
204
+ HASH_PAIR = :pair
205
+ METHOD_CALL = :send
206
+ MODULE = :module
207
+ STRING = :str
208
+ SYMBOL = :sym
209
+
210
+ private_constant(
211
+ :BLOCK, :CLASS, :CONSTANT, :CONSTANT_ASSIGNMENT, :CONSTANT_ROOT_NAMESPACE, :HASH, :HASH_PAIR, :METHOD_CALL,
212
+ :MODULE, :STRING, :SYMBOL,
213
+ )
214
+
215
+ def type_of(node)
216
+ node.type
217
+ end
218
+
202
219
  def hash_pair_key(hash_pair_node)
203
- raise TypeError unless type(hash_pair_node) == HASH_PAIR
220
+ raise TypeError unless type_of(hash_pair_node) == HASH_PAIR
204
221
 
205
222
  # (pair (int 1) (int 2))
206
223
  # "1 => 2"
@@ -210,7 +227,7 @@ module Packwerk
210
227
  end
211
228
 
212
229
  def hash_pair_value(hash_pair_node)
213
- raise TypeError unless type(hash_pair_node) == HASH_PAIR
230
+ raise TypeError unless type_of(hash_pair_node) == HASH_PAIR
214
231
 
215
232
  # (pair (int 1) (int 2))
216
233
  # "1 => 2"
@@ -224,11 +241,11 @@ module Packwerk
224
241
 
225
242
  # (hash (pair (int 1) (int 2)) (pair (int 3) (int 4)))
226
243
  # "{1 => 2, 3 => 4}"
227
- hash_node.children.select { |n| type(n) == HASH_PAIR }
244
+ hash_node.children.select { |n| type_of(n) == HASH_PAIR }
228
245
  end
229
246
 
230
247
  def method_call_node(block_node)
231
- raise TypeError unless type(block_node) == BLOCK
248
+ raise TypeError unless type_of(block_node) == BLOCK
232
249
 
233
250
  # (block (send (lvar :foo) :bar) (args) (int 42))
234
251
  # "foo.bar do 42 end"
@@ -239,7 +256,7 @@ module Packwerk
239
256
  # "Class.new"
240
257
  # "Module.new"
241
258
  method_call?(node) &&
242
- type(receiver(node)) == CONSTANT &&
259
+ constant?(receiver(node)) &&
243
260
  ["Class", "Module"].include?(constant_name(receiver(node))) &&
244
261
  method_name(node) == :new
245
262
  end
@@ -247,12 +264,12 @@ module Packwerk
247
264
  def name_from_block_definition(node)
248
265
  if method_name(method_call_node(node)) == :class_eval
249
266
  receiver = receiver(node)
250
- constant_name(receiver) if receiver && type(receiver) == CONSTANT
267
+ constant_name(receiver) if receiver && constant?(receiver)
251
268
  end
252
269
  end
253
270
 
254
271
  def name_part_from_definition(node)
255
- case type(node)
272
+ case type_of(node)
256
273
  when CLASS, MODULE, CONSTANT_ASSIGNMENT
257
274
  module_name_from_definition(node)
258
275
  when BLOCK
@@ -261,7 +278,7 @@ module Packwerk
261
278
  end
262
279
 
263
280
  def receiver(method_call_or_block_node)
264
- case type(method_call_or_block_node)
281
+ case type_of(method_call_or_block_node)
265
282
  when METHOD_CALL
266
283
  method_call_or_block_node.children[0]
267
284
  when BLOCK
@@ -26,8 +26,7 @@ module Packwerk
26
26
  end
27
27
 
28
28
  def call(node, ancestors:)
29
- case Node.type(node)
30
- when Node::METHOD_CALL, Node::CONSTANT
29
+ if Node.method_call?(node) || Node.constant?(node)
31
30
  reference = @reference_extractor.reference_from_node(node, ancestors: ancestors, file_path: @filename)
32
31
  check_reference(reference, node) if reference
33
32
  end
@@ -42,7 +41,7 @@ module Packwerk
42
41
 
43
42
  Packwerk::Offense.new(
44
43
  location: Node.location(node),
45
- file: @filename,
44
+ file: reference.relative_path,
46
45
  message: <<~EOS
47
46
  #{message}
48
47
  Inference details: this is a reference to #{constant.name} which seems to be defined in #{constant.location}.
@@ -0,0 +1,39 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "packwerk/constant_name_inspector"
5
+ require "packwerk/checker"
6
+
7
+ module Packwerk
8
+ class NodeProcessorFactory < T::Struct
9
+ extend T::Sig
10
+
11
+ const :root_path, String
12
+ const :reference_lister, ReferenceLister
13
+ const :context_provider, Packwerk::ConstantDiscovery
14
+ const :constant_name_inspectors, T::Array[ConstantNameInspector]
15
+ const :checkers, T::Array[Checker]
16
+
17
+ sig { params(filename: String, node: AST::Node).returns(NodeProcessor) }
18
+ def for(filename:, node:)
19
+ ::Packwerk::NodeProcessor.new(
20
+ reference_extractor: reference_extractor(node: node),
21
+ reference_lister: reference_lister,
22
+ filename: filename,
23
+ checkers: checkers,
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ sig { params(node: AST::Node).returns(::Packwerk::ReferenceExtractor) }
30
+ def reference_extractor(node:)
31
+ ::Packwerk::ReferenceExtractor.new(
32
+ context_provider: context_provider,
33
+ constant_name_inspectors: constant_name_inspectors,
34
+ root_node: node,
35
+ root_path: root_path,
36
+ )
37
+ end
38
+ end
39
+ end
@@ -29,7 +29,8 @@ module Packwerk
29
29
  bundle_path_match = Bundler.bundle_path.join("**").to_s
30
30
 
31
31
  Dir.glob(File.join(root_path, package_pathspec, PACKAGE_CONFIG_FILENAME))
32
- .map { |path| Pathname.new(path) }.reject { |path| path.realpath.fnmatch(bundle_path_match) }
32
+ .map { |path| Pathname.new(path) }
33
+ .reject { |path| path.realpath.fnmatch(bundle_path_match) }
33
34
  end
34
35
 
35
36
  private
@@ -38,12 +38,12 @@ module Packwerk
38
38
  private
39
39
 
40
40
  def collect_local_definitions_from_root(node, current_namespace_path = [])
41
- if Node.type(node) == Node::CONSTANT_ASSIGNMENT
41
+ if Node.constant_assignment?(node)
42
42
  add_definition(Node.constant_name(node), current_namespace_path, Node.name_location(node))
43
43
  elsif Node.module_name_from_definition(node)
44
44
  # handle compact constant nesting (e.g. "module Sales::Order")
45
45
  tempnode = node
46
- while (tempnode = Node.each_child(tempnode).find { |n| Node.type(n) == Node::CONSTANT })
46
+ while (tempnode = Node.each_child(tempnode).find { |n| Node.constant?(n) })
47
47
  add_definition(Node.constant_name(tempnode), current_namespace_path, Node.name_location(tempnode))
48
48
  end
49
49
 
@@ -1,30 +1,31 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- require "active_support/inflector"
5
4
  require "constant_resolver"
6
5
 
7
6
  require "packwerk/association_inspector"
8
- require "packwerk/checking_deprecated_references"
9
7
  require "packwerk/constant_discovery"
10
8
  require "packwerk/const_node_inspector"
11
9
  require "packwerk/dependency_checker"
12
10
  require "packwerk/file_processor"
13
- require "packwerk/node_processor"
11
+ require "packwerk/inflector"
14
12
  require "packwerk/package_set"
15
13
  require "packwerk/privacy_checker"
16
14
  require "packwerk/reference_extractor"
15
+ require "packwerk/node_processor_factory"
17
16
 
18
17
  module Packwerk
19
18
  class RunContext
19
+ extend T::Sig
20
+
20
21
  attr_reader(
21
- :checkers,
22
- :constant_name_inspectors,
23
- :context_provider,
24
22
  :root_path,
25
- :file_processor,
26
- :node_processor_class,
27
- :reference_lister
23
+ :load_paths,
24
+ :package_paths,
25
+ :inflector,
26
+ :custom_associations,
27
+ :checker_classes,
28
+ :reference_lister,
28
29
  )
29
30
 
30
31
  DEFAULT_CHECKERS = [
@@ -33,16 +34,15 @@ module Packwerk
33
34
  ]
34
35
 
35
36
  class << self
36
- def from_configuration(configuration, reference_lister: nil)
37
- default_reference_lister = reference_lister ||
38
- ::Packwerk::CheckingDeprecatedReferences.new(configuration.root_path)
37
+ def from_configuration(configuration, reference_lister:)
38
+ inflector = ::Packwerk::Inflector.from_file(configuration.inflections_file)
39
39
  new(
40
40
  root_path: configuration.root_path,
41
41
  load_paths: configuration.load_paths,
42
42
  package_paths: configuration.package_paths,
43
- inflector: ActiveSupport::Inflector,
43
+ inflector: inflector,
44
44
  custom_associations: configuration.custom_associations,
45
- reference_lister: default_reference_lister,
45
+ reference_lister: reference_lister,
46
46
  )
47
47
  end
48
48
  end
@@ -54,51 +54,73 @@ module Packwerk
54
54
  inflector: nil,
55
55
  custom_associations: [],
56
56
  checker_classes: DEFAULT_CHECKERS,
57
- node_processor_class: NodeProcessor,
58
- reference_lister: nil
57
+ reference_lister:
59
58
  )
60
- @root_path = File.expand_path(root_path)
59
+ @root_path = root_path
60
+ @load_paths = load_paths
61
+ @package_paths = package_paths
62
+ @inflector = inflector
63
+ @custom_associations = custom_associations
64
+ @checker_classes = checker_classes
65
+ @reference_lister = reference_lister
66
+ end
61
67
 
62
- resolver = ConstantResolver.new(
63
- root_path: @root_path,
64
- load_paths: load_paths,
65
- inflector: inflector,
66
- )
68
+ sig { params(file: String).returns(T::Array[T.nilable(::Packwerk::Offense)]) }
69
+ def process_file(file:)
70
+ file_processor.call(file)
71
+ end
72
+
73
+ private
67
74
 
68
- package_set = ::Packwerk::PackageSet.load_all_from(@root_path, package_pathspec: package_paths)
75
+ sig { returns(FileProcessor) }
76
+ def file_processor
77
+ @file_processor ||= FileProcessor.new(node_processor_factory: node_processor_factory)
78
+ end
69
79
 
70
- @context_provider = ::Packwerk::ConstantDiscovery.new(
80
+ sig { returns(NodeProcessorFactory) }
81
+ def node_processor_factory
82
+ NodeProcessorFactory.new(
83
+ context_provider: context_provider,
84
+ checkers: checkers,
85
+ root_path: root_path,
86
+ constant_name_inspectors: constant_name_inspectors,
87
+ reference_lister: reference_lister
88
+ )
89
+ end
90
+
91
+ sig { returns(ConstantDiscovery) }
92
+ def context_provider
93
+ ::Packwerk::ConstantDiscovery.new(
71
94
  constant_resolver: resolver,
72
95
  packages: package_set
73
96
  )
97
+ end
98
+
99
+ sig { returns(ConstantResolver) }
100
+ def resolver
101
+ ConstantResolver.new(
102
+ root_path: root_path,
103
+ load_paths: load_paths,
104
+ inflector: inflector,
105
+ )
106
+ end
74
107
 
75
- @reference_lister = reference_lister || ::Packwerk::CheckingDeprecatedReferences.new(@root_path)
108
+ sig { returns(PackageSet) }
109
+ def package_set
110
+ ::Packwerk::PackageSet.load_all_from(root_path, package_pathspec: package_paths)
111
+ end
76
112
 
77
- @checkers = checker_classes.map(&:new)
113
+ sig { returns(T::Array[Checker]) }
114
+ def checkers
115
+ checker_classes.map(&:new)
116
+ end
78
117
 
79
- @constant_name_inspectors = [
118
+ sig { returns(T::Array[ConstantNameInspector]) }
119
+ def constant_name_inspectors
120
+ [
80
121
  ::Packwerk::ConstNodeInspector.new,
81
122
  ::Packwerk::AssociationInspector.new(inflector: inflector, custom_associations: custom_associations),
82
123
  ]
83
-
84
- @node_processor_class = node_processor_class
85
- @file_processor = FileProcessor.new(run_context: self)
86
- end
87
-
88
- def node_processor_for(filename:, ast_node:)
89
- reference_extractor = ::Packwerk::ReferenceExtractor.new(
90
- context_provider: context_provider,
91
- constant_name_inspectors: constant_name_inspectors,
92
- root_node: ast_node,
93
- root_path: root_path,
94
- )
95
-
96
- node_processor_class.new(
97
- reference_extractor: reference_extractor,
98
- reference_lister: @reference_lister,
99
- filename: filename,
100
- checkers: checkers,
101
- )
102
124
  end
103
125
  end
104
126
  end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Packwerk
5
- VERSION = "1.0.1"
5
+ VERSION = "1.0.2"
6
6
  end
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
34
34
  spec.executables << "packwerk"
35
35
 
36
36
  spec.files = Dir.chdir(__dir__) do
37
- %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
37
+ %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{^(test|spec|features|static)/}) }
38
38
  end
39
39
  spec.require_paths = %w(lib)
40
40
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: packwerk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-30 00:00:00.000000000 Z
11
+ date: 2020-11-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -183,6 +183,7 @@ files:
183
183
  - docs/cohesion.png
184
184
  - exe/packwerk
185
185
  - lib/packwerk.rb
186
+ - lib/packwerk/application_load_paths.rb
186
187
  - lib/packwerk/application_validator.rb
187
188
  - lib/packwerk/association_inspector.rb
188
189
  - lib/packwerk/checker.rb
@@ -212,6 +213,7 @@ files:
212
213
  - lib/packwerk/inflector.rb
213
214
  - lib/packwerk/node.rb
214
215
  - lib/packwerk/node_processor.rb
216
+ - lib/packwerk/node_processor_factory.rb
215
217
  - lib/packwerk/node_visitor.rb
216
218
  - lib/packwerk/offense.rb
217
219
  - lib/packwerk/output_styles.rb
@@ -307,11 +309,6 @@ files:
307
309
  - sorbet/rbi/gems/websocket-extensions@0.1.4.rbi
308
310
  - sorbet/rbi/gems/zeitwerk@2.3.0.rbi
309
311
  - sorbet/tapioca/require.rb
310
- - static/packwerk-check-demo.png
311
- - static/packwerk_check.gif
312
- - static/packwerk_check_violation.gif
313
- - static/packwerk_update.gif
314
- - static/packwerk_validate.gif
315
312
  homepage: https://github.com/Shopify/packwerk
316
313
  licenses:
317
314
  - MIT
Binary file
Binary file
Binary file