rubocop-flexport 0.1.0 → 0.2.0

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: a599585e57397ed31a361c5267c82ebe7ac4d7e0f673712e31369f135ec0fded
4
- data.tar.gz: c9d92a894a7940d3cea6bec075b5e8934d0a79003e59fe58da9925caa74ab46c
3
+ metadata.gz: 21c9588d65a1cc1b11a85277a94b02caf3dd37bb067b8a2ae09b53da39052bb0
4
+ data.tar.gz: 2ca05dbe05457065dcfd40e703e6a9c29340fd44a20862df21364ebc1c31e76f
5
5
  SHA512:
6
- metadata.gz: c596496c5e30b66d43f587a115d948ccea67549e116f4661337a101132a0776909be6329f2af13a5cf2ea115321787fbd589f00f6e20713cd012a48275d7db5e
7
- data.tar.gz: 88aadf6198c2e703e2bf016d81059ec6e2309889ccaba9be6d10b9a458a8072a72baef8df08d0b0bbac66e2059a4a05b8af365439e76b30c8b2c498ad8c4815a
6
+ metadata.gz: 375ce90040f7a4185de7bc785867b46574dbc04a24ac0f17d8d3213075b9f689c60aec9e127830aea48005f76ca3f8a8023948a792284f45460fc80ce704ff87
7
+ data.tar.gz: 560c5d8655494e752fae1db186c5c7ee1d514fa938fb977c07d09a1c87b9edcffc17d9bec31aa6b42a25436df480f63e70caee00bc3b52f3cdd786cb18962aaf
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Rubocop::Flexport
1
+ # RuboCop::Flexport
2
2
 
3
3
  This repo is for cops developed at Flexport that don't make sense to upstream
4
4
  into any of the existing RuboCop repos. When possible, we prefer upstreaming.
data/config/default.yml CHANGED
@@ -1,3 +1,15 @@
1
+ Flexport/GlobalModelAccessFromEngine:
2
+ Description: 'Do not directly access global models from within Rails Engines.'
3
+ Enabled: false
4
+ VersionAdded: '0.2.0'
5
+ AutoCorrect: false
6
+ EnginesPath: engines/
7
+ GlobalModelsPath: app/models/
8
+ DisabledEngines: []
9
+ AllowedGlobalModels: []
10
+ Include:
11
+ - '**/*.rb'
12
+
1
13
  Flexport/NewGlobalModel:
2
14
  Description: 'Disallows addition of new global models to `app/models`. Prefer Rails Engines or namespaces.'
3
15
  Enabled: true
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/inflector'
4
+ require 'digest/sha1'
5
+
6
+ module RuboCop
7
+ module Cop
8
+ module Flexport
9
+ # This cop checks for engines reaching directly into app/ models.
10
+ #
11
+ # With an ActiveRecord object, engine code can perform arbitrary
12
+ # reads and arbitrary writes to models located in the main `app/`
13
+ # directory. This cop helps isolate Rails Engine code to ensure
14
+ # that modular boundaries are respected.
15
+ #
16
+ # Checks for both access via `MyGlobalModel.foo` and associations.
17
+ #
18
+ # @example
19
+ #
20
+ # # bad
21
+ #
22
+ # class MyEngine::MyService
23
+ # m = SomeGlobalModel.find(123)
24
+ # m.any_random_attribute = "whatever i want"
25
+ # m.save
26
+ # end
27
+ #
28
+ # # good
29
+ #
30
+ # class MyEngine::MyService
31
+ # ApiServiceForGlobalModels.perform_a_supported_operation("foo")
32
+ # end
33
+ #
34
+ # @example
35
+ #
36
+ # # bad
37
+ #
38
+ # class MyEngine::MyModel < ApplicationModel
39
+ # has_one :some_global_model, class_name: "SomeGlobalModel"
40
+ # end
41
+ #
42
+ # # good
43
+ #
44
+ # class MyEngine::MyModel < ApplicationModel
45
+ # # No direct association to global models.
46
+ # end
47
+ #
48
+ class GlobalModelAccessFromEngine < Cop
49
+ MSG = 'Direct access of global model from within Rails Engine.'
50
+
51
+ def_node_matcher :rails_association_hash_args, <<-PATTERN
52
+ (send _ {:belongs_to :has_one :has_many} sym $hash)
53
+ PATTERN
54
+
55
+ def on_const(node)
56
+ return unless in_enforced_engine_file?
57
+ return unless global_model_const?(node)
58
+ # The cop allows access to e.g. MyGlobalModel::MY_CONST.
59
+ return if child_of_const?(node)
60
+
61
+ add_offense(node)
62
+ end
63
+
64
+ def on_send(node)
65
+ return unless in_enforced_engine_file?
66
+
67
+ rails_association_hash_args(node) do |assocation_hash_args|
68
+ class_name_node = extract_class_name_node(assocation_hash_args)
69
+ class_name = class_name_node&.value
70
+ next unless global_model?(class_name)
71
+
72
+ add_offense(class_name_node)
73
+ end
74
+ end
75
+
76
+ # Because this cop's behavior depends on the state of external files,
77
+ # we override this method to bust the RuboCop cache when those files
78
+ # change.
79
+ def external_dependency_checksum
80
+ Digest::SHA1.hexdigest(model_dir_paths.join)
81
+ end
82
+
83
+ private
84
+
85
+ def global_model_names
86
+ @global_model_names ||= calculate_global_models
87
+ end
88
+
89
+ def model_dir_paths
90
+ Dir[File.join(global_models_path, '**/*.rb')]
91
+ end
92
+
93
+ def calculate_global_models
94
+ all_model_paths = model_dir_paths.reject do |path|
95
+ path.include?('/concerns/')
96
+ end
97
+ all_models = all_model_paths.map do |path|
98
+ # Translates `app/models/foo/bar_baz.rb` to `Foo::BarBaz`.
99
+ file_name = path.gsub(global_models_path, '').gsub('.rb', '')
100
+ ActiveSupport::Inflector.classify(file_name)
101
+ end
102
+ all_models - allowed_global_models
103
+ end
104
+
105
+ def extract_class_name_node(assocation_hash_args)
106
+ assocation_hash_args.each_pair do |key, value|
107
+ return value if key.value == :class_name && value.str_type?
108
+ end
109
+ nil
110
+ end
111
+
112
+ def in_enforced_engine_file?
113
+ file_path = processed_source.path
114
+ return false unless file_path.include?(engines_path)
115
+ return false if in_disabled_engine?(file_path)
116
+
117
+ true
118
+ end
119
+
120
+ def in_disabled_engine?(file_path)
121
+ disabled_engines.any? do |e|
122
+ file_path.include?(File.join(engines_path, e))
123
+ end
124
+ end
125
+
126
+ def global_model_const?(const_node)
127
+ # Remove leading `::`, if any.
128
+ class_name = const_node.source.sub(/^:*/, '')
129
+ global_model?(class_name)
130
+ end
131
+
132
+ # class_name is e.g. "FooGlobalModelNamespace::BarModel"
133
+ def global_model?(class_name)
134
+ global_model_names.include?(class_name)
135
+ end
136
+
137
+ def child_of_const?(node)
138
+ node.parent.const_type?
139
+ end
140
+
141
+ def global_models_path
142
+ path = cop_config['GlobalModelsPath']
143
+ path += '/' unless path.end_with?('/')
144
+ path
145
+ end
146
+
147
+ def engines_path
148
+ cop_config['EnginesPath']
149
+ end
150
+
151
+ def disabled_engines
152
+ raw = cop_config['DisabledEngines'] || []
153
+ raw.map do |e|
154
+ ActiveSupport::Inflector.underscore(e)
155
+ end
156
+ end
157
+
158
+ def allowed_global_models
159
+ cop_config['AllowedGlobalModels'] || []
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -12,6 +12,12 @@ module RuboCop
12
12
  # Use RuboCop's standard `Exclude` file list parameter to exclude
13
13
  # existing global model files from counting as violations for this cop.
14
14
  #
15
+ # If you have a monorepo with multiple Rails services, you may wish to
16
+ # set GlobalModelsPath to something more specific than `app/models` so
17
+ # that it only matches the main monolith service and allows global
18
+ # models in other, smaller services. To do this, just set GlobalModelsPath
19
+ # to e.g. `flexport/app/models` in your `.rubocop.yml`.
20
+ #
15
21
  # @example AllowNamespacedGlobalModels: true (default)
16
22
  # # When `AllowNamespacedGlobalModels` is true, the cop only forbids
17
23
  # # additions at the top-level directory.
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'flexport/global_model_access_from_engine'
3
4
  require_relative 'flexport/new_global_model'
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Flexport
5
- VERSION = '0.1.0'
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-flexport
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Flexport Engineering
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-11-19 00:00:00.000000000 Z
11
+ date: 2019-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rubocop
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -37,6 +51,7 @@ files:
37
51
  - bin/setup
38
52
  - config/default.yml
39
53
  - lib/rubocop-flexport.rb
54
+ - lib/rubocop/cop/flexport/global_model_access_from_engine.rb
40
55
  - lib/rubocop/cop/flexport/new_global_model.rb
41
56
  - lib/rubocop/cop/flexport_cops.rb
42
57
  - lib/rubocop/flexport.rb