packwerk 1.0.0 → 1.1.2
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.
- checksums.yaml +4 -4
- data/.github/pull_request_template.md +8 -7
- data/.github/workflows/ci.yml +1 -1
- data/.gitignore +1 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +5 -2
- data/README.md +5 -3
- data/TROUBLESHOOT.md +1 -1
- data/USAGE.md +56 -19
- data/exe/packwerk +1 -1
- data/lib/packwerk.rb +3 -3
- data/lib/packwerk/application_load_paths.rb +68 -0
- data/lib/packwerk/application_validator.rb +96 -70
- data/lib/packwerk/association_inspector.rb +50 -20
- data/lib/packwerk/cache_deprecated_references.rb +55 -0
- data/lib/packwerk/checker.rb +23 -0
- data/lib/packwerk/checking_deprecated_references.rb +5 -2
- data/lib/packwerk/cli.rb +65 -56
- data/lib/packwerk/commands/detect_stale_violations_command.rb +60 -0
- data/lib/packwerk/commands/offense_progress_marker.rb +24 -0
- data/lib/packwerk/commands/result.rb +13 -0
- data/lib/packwerk/commands/update_deprecations_command.rb +81 -0
- data/lib/packwerk/configuration.rb +6 -34
- data/lib/packwerk/const_node_inspector.rb +28 -17
- data/lib/packwerk/dependency_checker.rb +16 -5
- data/lib/packwerk/deprecated_references.rb +24 -1
- data/lib/packwerk/detect_stale_deprecated_references.rb +14 -0
- data/lib/packwerk/file_processor.rb +4 -4
- data/lib/packwerk/formatters/offenses_formatter.rb +48 -0
- data/lib/packwerk/formatters/progress_formatter.rb +6 -2
- data/lib/packwerk/generators/application_validation.rb +2 -2
- data/lib/packwerk/generators/templates/package.yml +4 -0
- data/lib/packwerk/generators/templates/packwerk +2 -2
- data/lib/packwerk/generators/templates/packwerk.yml.erb +1 -1
- data/lib/packwerk/inflector.rb +17 -8
- data/lib/packwerk/node.rb +78 -39
- data/lib/packwerk/node_processor.rb +14 -3
- data/lib/packwerk/node_processor_factory.rb +39 -0
- data/lib/packwerk/offense.rb +4 -6
- data/lib/packwerk/output_style.rb +20 -0
- data/lib/packwerk/output_styles/coloured.rb +29 -0
- data/lib/packwerk/output_styles/plain.rb +26 -0
- data/lib/packwerk/package.rb +8 -1
- data/lib/packwerk/package_set.rb +13 -5
- data/lib/packwerk/parsed_constant_definitions.rb +4 -4
- data/lib/packwerk/parsers/erb.rb +4 -0
- data/lib/packwerk/parsers/factory.rb +10 -1
- data/lib/packwerk/privacy_checker.rb +26 -5
- data/lib/packwerk/run_context.rb +70 -46
- data/lib/packwerk/sanity_checker.rb +1 -1
- data/lib/packwerk/spring_command.rb +1 -1
- data/lib/packwerk/updating_deprecated_references.rb +2 -39
- data/lib/packwerk/version.rb +1 -1
- data/packwerk.gemspec +2 -2
- metadata +15 -8
- data/lib/packwerk/output_styles.rb +0 -41
- data/static/packwerk-check-demo.png +0 -0
- data/static/packwerk_check.gif +0 -0
- data/static/packwerk_check_violation.gif +0 -0
- data/static/packwerk_update.gif +0 -0
- data/static/packwerk_validate.gif +0 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Packwerk
|
5
|
+
module OutputStyle
|
6
|
+
extend T::Sig
|
7
|
+
extend T::Helpers
|
8
|
+
|
9
|
+
interface!
|
10
|
+
|
11
|
+
sig { abstract.returns(String) }
|
12
|
+
def reset; end
|
13
|
+
|
14
|
+
sig { abstract.returns(String) }
|
15
|
+
def filename; end
|
16
|
+
|
17
|
+
sig { abstract.returns(String) }
|
18
|
+
def error; end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Packwerk
|
5
|
+
module OutputStyles
|
6
|
+
# See https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit for ANSI escape colour codes
|
7
|
+
class Coloured
|
8
|
+
extend T::Sig
|
9
|
+
include OutputStyle
|
10
|
+
|
11
|
+
sig { override.returns(String) }
|
12
|
+
def reset
|
13
|
+
"\033[m"
|
14
|
+
end
|
15
|
+
|
16
|
+
sig { override.returns(String) }
|
17
|
+
def filename
|
18
|
+
# 36 is foreground cyan
|
19
|
+
"\033[36m"
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { override.returns(String) }
|
23
|
+
def error
|
24
|
+
# 31 is foreground red
|
25
|
+
"\033[31m"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Packwerk
|
5
|
+
module OutputStyles
|
6
|
+
class Plain
|
7
|
+
extend T::Sig
|
8
|
+
include OutputStyle
|
9
|
+
|
10
|
+
sig { override.returns(String) }
|
11
|
+
def reset
|
12
|
+
""
|
13
|
+
end
|
14
|
+
|
15
|
+
sig { override.returns(String) }
|
16
|
+
def filename
|
17
|
+
""
|
18
|
+
end
|
19
|
+
|
20
|
+
sig { override.returns(String) }
|
21
|
+
def error
|
22
|
+
""
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/packwerk/package.rb
CHANGED
@@ -33,13 +33,20 @@ module Packwerk
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def public_path
|
36
|
-
@public_path ||= File.join(@name, "app/public/")
|
36
|
+
@public_path ||= File.join(@name, user_defined_public_path || "app/public/")
|
37
37
|
end
|
38
38
|
|
39
39
|
def public_path?(path)
|
40
40
|
path.start_with?(public_path)
|
41
41
|
end
|
42
42
|
|
43
|
+
def user_defined_public_path
|
44
|
+
return unless @config["public_path"]
|
45
|
+
return @config["public_path"] if @config["public_path"].end_with?("/")
|
46
|
+
|
47
|
+
@config["public_path"] + "/"
|
48
|
+
end
|
49
|
+
|
43
50
|
def <=>(other)
|
44
51
|
return nil unless other.is_a?(self.class)
|
45
52
|
name <=> other.name
|
data/lib/packwerk/package_set.rb
CHANGED
@@ -25,13 +25,20 @@ module Packwerk
|
|
25
25
|
new(packages)
|
26
26
|
end
|
27
27
|
|
28
|
-
private
|
29
|
-
|
30
28
|
def package_paths(root_path, package_pathspec)
|
31
|
-
|
32
|
-
|
29
|
+
bundle_path_match = Bundler.bundle_path.join("**").to_s
|
30
|
+
|
31
|
+
glob_patterns = Array(package_pathspec).map do |pathspec|
|
32
|
+
File.join(root_path, pathspec, PACKAGE_CONFIG_FILENAME)
|
33
|
+
end
|
34
|
+
|
35
|
+
Dir.glob(glob_patterns)
|
36
|
+
.map { |path| Pathname.new(path).cleanpath }
|
37
|
+
.reject { |path| path.realpath.fnmatch(bundle_path_match) }
|
33
38
|
end
|
34
39
|
|
40
|
+
private
|
41
|
+
|
35
42
|
def create_root_package_if_none_in(packages)
|
36
43
|
return if packages.any?(&:root?)
|
37
44
|
packages << Package.new(name: Package::ROOT_PACKAGE_NAME, config: nil)
|
@@ -53,7 +60,8 @@ module Packwerk
|
|
53
60
|
end
|
54
61
|
|
55
62
|
def package_from_path(file_path)
|
56
|
-
|
63
|
+
path_string = file_path.to_s
|
64
|
+
@packages.values.find { |package| package.package_path?(path_string) }
|
57
65
|
end
|
58
66
|
end
|
59
67
|
end
|
@@ -28,8 +28,8 @@ module Packwerk
|
|
28
28
|
|
29
29
|
fully_qualified_constant_name = "::#{constant_name}"
|
30
30
|
|
31
|
-
possible_namespaces = namespace_path.
|
32
|
-
acc << acc.last
|
31
|
+
possible_namespaces = namespace_path.each_with_object([""]) do |current, acc|
|
32
|
+
acc << "#{acc.last}::#{current}" if acc.last && current
|
33
33
|
end
|
34
34
|
|
35
35
|
possible_namespaces.map { |namespace| namespace + fully_qualified_constant_name }
|
@@ -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.
|
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.
|
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
|
|
data/lib/packwerk/parsers/erb.rb
CHANGED
@@ -19,6 +19,10 @@ module Packwerk
|
|
19
19
|
def call(io:, file_path: "<unknown>")
|
20
20
|
buffer = Parser::Source::Buffer.new(file_path)
|
21
21
|
buffer.source = io.read
|
22
|
+
parse_buffer(buffer, file_path: file_path)
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_buffer(buffer, file_path:)
|
22
26
|
parser = @parser_class.new(buffer, template_language: :html)
|
23
27
|
to_ruby_ast(parser.ast, file_path)
|
24
28
|
rescue EncodingError => e
|
@@ -26,9 +26,18 @@ module Packwerk
|
|
26
26
|
when RUBY_REGEX
|
27
27
|
@ruby_parser ||= Ruby.new
|
28
28
|
when ERB_REGEX
|
29
|
-
@erb_parser ||=
|
29
|
+
@erb_parser ||= erb_parser_class.new
|
30
30
|
end
|
31
31
|
end
|
32
|
+
|
33
|
+
def erb_parser_class
|
34
|
+
@erb_parser_class ||= Erb
|
35
|
+
end
|
36
|
+
|
37
|
+
def erb_parser_class=(klass)
|
38
|
+
@erb_parser_class = klass
|
39
|
+
@erb_parser = nil
|
40
|
+
end
|
32
41
|
end
|
33
42
|
end
|
34
43
|
end
|
@@ -1,28 +1,39 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "packwerk/violation_type"
|
5
|
+
require "packwerk/checker"
|
5
6
|
|
6
7
|
module Packwerk
|
7
8
|
class PrivacyChecker
|
9
|
+
extend T::Sig
|
10
|
+
include Checker
|
11
|
+
|
12
|
+
sig { override.returns(Packwerk::ViolationType) }
|
8
13
|
def violation_type
|
9
14
|
ViolationType::Privacy
|
10
15
|
end
|
11
16
|
|
17
|
+
sig do
|
18
|
+
override
|
19
|
+
.params(reference: Packwerk::Reference, reference_lister: Packwerk::ReferenceLister)
|
20
|
+
.returns(T::Boolean)
|
21
|
+
end
|
12
22
|
def invalid_reference?(reference, reference_lister)
|
13
|
-
return if reference.constant.public?
|
23
|
+
return false if reference.constant.public?
|
14
24
|
|
15
25
|
privacy_option = reference.constant.package.enforce_privacy
|
16
|
-
return if enforcement_disabled?(privacy_option)
|
26
|
+
return false if enforcement_disabled?(privacy_option)
|
17
27
|
|
18
|
-
return unless privacy_option == true ||
|
28
|
+
return false unless privacy_option == true ||
|
19
29
|
explicitly_private_constant?(reference.constant, explicitly_private_constants: privacy_option)
|
20
30
|
|
21
|
-
return if reference_lister.listed?(reference, violation_type: violation_type)
|
31
|
+
return false if reference_lister.listed?(reference, violation_type: violation_type)
|
22
32
|
|
23
33
|
true
|
24
34
|
end
|
25
35
|
|
36
|
+
sig { override.params(reference: Packwerk::Reference).returns(String) }
|
26
37
|
def message_for(reference)
|
27
38
|
source_desc = reference.source_package ? "'#{reference.source_package}'" : "here"
|
28
39
|
"Privacy violation: '#{reference.constant.name}' is private to '#{reference.constant.package}' but " \
|
@@ -32,12 +43,22 @@ module Packwerk
|
|
32
43
|
|
33
44
|
private
|
34
45
|
|
46
|
+
sig do
|
47
|
+
params(
|
48
|
+
constant: ConstantDiscovery::ConstantContext,
|
49
|
+
explicitly_private_constants: T::Array[String]
|
50
|
+
).returns(T::Boolean)
|
51
|
+
end
|
35
52
|
def explicitly_private_constant?(constant, explicitly_private_constants:)
|
36
53
|
explicitly_private_constants.include?(constant.name) ||
|
37
54
|
# nested constants
|
38
55
|
explicitly_private_constants.any? { |epc| constant.name.start_with?(epc + "::") }
|
39
56
|
end
|
40
57
|
|
58
|
+
sig do
|
59
|
+
params(privacy_option: T.nilable(T.any(T::Boolean, T::Array[String])))
|
60
|
+
.returns(T::Boolean)
|
61
|
+
end
|
41
62
|
def enforcement_disabled?(privacy_option)
|
42
63
|
[false, nil].include?(privacy_option)
|
43
64
|
end
|
data/lib/packwerk/run_context.rb
CHANGED
@@ -1,47 +1,49 @@
|
|
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/
|
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
|
-
:
|
26
|
-
:
|
27
|
-
:
|
23
|
+
:load_paths,
|
24
|
+
:package_paths,
|
25
|
+
:inflector,
|
26
|
+
:custom_associations,
|
27
|
+
:checker_classes,
|
28
28
|
)
|
29
29
|
|
30
|
+
attr_accessor :reference_lister
|
31
|
+
|
30
32
|
DEFAULT_CHECKERS = [
|
31
33
|
::Packwerk::DependencyChecker,
|
32
34
|
::Packwerk::PrivacyChecker,
|
33
35
|
]
|
34
36
|
|
35
37
|
class << self
|
36
|
-
def from_configuration(configuration, reference_lister:
|
37
|
-
|
38
|
-
::Packwerk::CheckingDeprecatedReferences.new(configuration.root_path)
|
38
|
+
def from_configuration(configuration, reference_lister:)
|
39
|
+
inflector = ::Packwerk::Inflector.from_file(configuration.inflections_file)
|
39
40
|
new(
|
40
41
|
root_path: configuration.root_path,
|
41
42
|
load_paths: configuration.load_paths,
|
42
|
-
|
43
|
+
package_paths: configuration.package_paths,
|
44
|
+
inflector: inflector,
|
43
45
|
custom_associations: configuration.custom_associations,
|
44
|
-
reference_lister:
|
46
|
+
reference_lister: reference_lister,
|
45
47
|
)
|
46
48
|
end
|
47
49
|
end
|
@@ -53,51 +55,73 @@ module Packwerk
|
|
53
55
|
inflector: nil,
|
54
56
|
custom_associations: [],
|
55
57
|
checker_classes: DEFAULT_CHECKERS,
|
56
|
-
|
57
|
-
reference_lister: nil
|
58
|
+
reference_lister:
|
58
59
|
)
|
59
|
-
@root_path =
|
60
|
+
@root_path = root_path
|
61
|
+
@load_paths = load_paths
|
62
|
+
@package_paths = package_paths
|
63
|
+
@inflector = inflector
|
64
|
+
@custom_associations = custom_associations
|
65
|
+
@checker_classes = checker_classes
|
66
|
+
@reference_lister = reference_lister
|
67
|
+
end
|
60
68
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
69
|
+
sig { params(file: String).returns(T::Array[T.nilable(::Packwerk::Offense)]) }
|
70
|
+
def process_file(file:)
|
71
|
+
file_processor.call(file)
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
sig { returns(FileProcessor) }
|
77
|
+
def file_processor
|
78
|
+
@file_processor ||= FileProcessor.new(node_processor_factory: node_processor_factory)
|
79
|
+
end
|
66
80
|
|
67
|
-
|
81
|
+
sig { returns(NodeProcessorFactory) }
|
82
|
+
def node_processor_factory
|
83
|
+
NodeProcessorFactory.new(
|
84
|
+
context_provider: context_provider,
|
85
|
+
checkers: checkers,
|
86
|
+
root_path: root_path,
|
87
|
+
constant_name_inspectors: constant_name_inspectors,
|
88
|
+
reference_lister: reference_lister
|
89
|
+
)
|
90
|
+
end
|
68
91
|
|
69
|
-
|
92
|
+
sig { returns(ConstantDiscovery) }
|
93
|
+
def context_provider
|
94
|
+
::Packwerk::ConstantDiscovery.new(
|
70
95
|
constant_resolver: resolver,
|
71
96
|
packages: package_set
|
72
97
|
)
|
98
|
+
end
|
73
99
|
|
74
|
-
|
100
|
+
sig { returns(ConstantResolver) }
|
101
|
+
def resolver
|
102
|
+
ConstantResolver.new(
|
103
|
+
root_path: root_path,
|
104
|
+
load_paths: load_paths,
|
105
|
+
inflector: inflector,
|
106
|
+
)
|
107
|
+
end
|
75
108
|
|
76
|
-
|
109
|
+
sig { returns(PackageSet) }
|
110
|
+
def package_set
|
111
|
+
::Packwerk::PackageSet.load_all_from(root_path, package_pathspec: package_paths)
|
112
|
+
end
|
77
113
|
|
78
|
-
|
114
|
+
sig { returns(T::Array[Checker]) }
|
115
|
+
def checkers
|
116
|
+
checker_classes.map(&:new)
|
117
|
+
end
|
118
|
+
|
119
|
+
sig { returns(T::Array[ConstantNameInspector]) }
|
120
|
+
def constant_name_inspectors
|
121
|
+
[
|
79
122
|
::Packwerk::ConstNodeInspector.new,
|
80
123
|
::Packwerk::AssociationInspector.new(inflector: inflector, custom_associations: custom_associations),
|
81
124
|
]
|
82
|
-
|
83
|
-
@node_processor_class = node_processor_class
|
84
|
-
@file_processor = FileProcessor.new(run_context: self)
|
85
|
-
end
|
86
|
-
|
87
|
-
def node_processor_for(filename:, ast_node:)
|
88
|
-
reference_extractor = ::Packwerk::ReferenceExtractor.new(
|
89
|
-
context_provider: context_provider,
|
90
|
-
constant_name_inspectors: constant_name_inspectors,
|
91
|
-
root_node: ast_node,
|
92
|
-
root_path: root_path,
|
93
|
-
)
|
94
|
-
|
95
|
-
node_processor_class.new(
|
96
|
-
reference_extractor: reference_extractor,
|
97
|
-
reference_lister: @reference_lister,
|
98
|
-
filename: filename,
|
99
|
-
checkers: checkers,
|
100
|
-
)
|
101
125
|
end
|
102
126
|
end
|
103
127
|
end
|
@@ -1,51 +1,14 @@
|
|
1
1
|
# typed: true
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "
|
5
|
-
|
6
|
-
require "packwerk/deprecated_references"
|
7
|
-
require "packwerk/reference"
|
8
|
-
require "packwerk/reference_lister"
|
9
|
-
require "packwerk/violation_type"
|
4
|
+
require "packwerk/cache_deprecated_references"
|
10
5
|
|
11
6
|
module Packwerk
|
12
|
-
class UpdatingDeprecatedReferences
|
13
|
-
extend T::Sig
|
14
|
-
include ReferenceLister
|
15
|
-
|
16
|
-
def initialize(root_path, deprecated_references = {})
|
17
|
-
@root_path = root_path
|
18
|
-
@deprecated_references = deprecated_references
|
19
|
-
end
|
20
|
-
|
21
|
-
sig do
|
22
|
-
params(reference: Packwerk::Reference, violation_type: ViolationType)
|
23
|
-
.returns(T::Boolean)
|
24
|
-
.override
|
25
|
-
end
|
26
|
-
def listed?(reference, violation_type:)
|
27
|
-
deprecated_references = deprecated_references_for(reference.source_package)
|
28
|
-
deprecated_references.add_entries(reference, violation_type.serialize)
|
29
|
-
true
|
30
|
-
end
|
31
|
-
|
7
|
+
class UpdatingDeprecatedReferences < CacheDeprecatedReferences
|
32
8
|
def dump_deprecated_references_files
|
33
9
|
@deprecated_references.each do |_, deprecated_references_file|
|
34
10
|
deprecated_references_file.dump
|
35
11
|
end
|
36
12
|
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def deprecated_references_for(package)
|
41
|
-
@deprecated_references[package] ||= Packwerk::DeprecatedReferences.new(
|
42
|
-
package,
|
43
|
-
deprecated_references_file_for(package),
|
44
|
-
)
|
45
|
-
end
|
46
|
-
|
47
|
-
def deprecated_references_file_for(package)
|
48
|
-
File.join(@root_path, package.name, "deprecated_references.yml")
|
49
|
-
end
|
50
13
|
end
|
51
14
|
end
|