dark_finger 0.3.1 → 0.6.1

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
- SHA1:
3
- metadata.gz: 2c5b4a2b9fd64fc712fe87e8217705c9fc10fb37
4
- data.tar.gz: 01cf3213671f4e9a18ad2e355b549dc5ee38ab06
2
+ SHA256:
3
+ metadata.gz: c387170753bd389d4df0457821fa9e56bf06f1d609e73ea2b4864753e9e09816
4
+ data.tar.gz: 12946aef0ce96a285a69714a8bb5f830335b47c4509b69151734a8523df76fea
5
5
  SHA512:
6
- metadata.gz: 256aea1e41241022db13ec4c7f7dabdcd7eeeb878a96282833ab63b679c0411c4d8db2dd193116847c8d42d6f96ebf78c14c4ff152f46d93d01137f8bf917f10
7
- data.tar.gz: 5a74ed3a93cbdb4951fa3b98acb9b7a4d37581452c04fc651009298ff0b3011abae28bf87915c80b6fdb3acc2bc6eaab981dbbdd93067f621fd3f4a3dff0b46a
6
+ metadata.gz: 0db6984e3639dd26fd5db716e94055dcbe481fb89fc947778f8b34a250b2b0fd042ab710ba5b6bba1351b1c5fde2e4dcb3d73234c4eae77ab5dc87d407b9b2c0
7
+ data.tar.gz: 334320e100727d04675b0cdc3cbf92d9ce597783090fe78d4e2ab08d379567412e8667d93981408c66c87edf86df416d42e17a4f66d5c2461688226c69044fbb
Binary file
data/.gitignore CHANGED
@@ -8,3 +8,4 @@
8
8
  /spec/reports/
9
9
  /tmp/
10
10
  tags
11
+ vendor/
data/README.md CHANGED
@@ -1,23 +1,13 @@
1
- # DarkFinger
1
+ ![Dark Finger](.github/dark_finger.jpg "Dark Finger")
2
2
 
3
- A Rubocop extension to check the layout of ActiveModel files. The cop will
4
- check that elements appear together, in the right order, and commented as
5
- desired.
3
+ A Rubocop extension containing two cops:
6
4
 
7
- Supported elements:
8
-
9
- * associations
10
- * attributes
11
- * callbacks
12
- * constants
13
- * enums
14
- * includes
15
- * modules
16
- * scopes
17
- * validations
18
- * class_methods
19
- * instance_methods
5
+ 1. [Model Structure](docs/model_structure.md). Keep those "macro" methods at the top
6
+ of your model files clean and consistent across the project.
20
7
 
8
+ 2. [Migration Constants](docs/migration_constants.md). Prevent a common source of
9
+ migration breakage - dependencies on things that are likely to change by the
10
+ time the migration is actually run.
21
11
 
22
12
  ## Installation
23
13
 
@@ -37,45 +27,7 @@ Or install it yourself as:
37
27
 
38
28
  ## Usage
39
29
 
40
- Install the gem. Then, in your `.rubycop.yml` file, require `dark_finger` and
41
- add your desired config.
42
-
43
- For example, here is the default config:
44
-
45
- ```ruby
46
- # in .rubocop.yml
47
-
48
-
49
- # this is required
50
- require: dark_finger
51
-
52
- DarkFinger/ModelStructure:
53
- Include:
54
- - 'app/models/*'
55
- required_order:
56
- - module
57
- - include
58
- - enum
59
- - constant
60
- - association
61
- - validation
62
- - scope
63
- - attributes
64
- - callback
65
- - class_method
66
- - instance_method
67
- required_comments:
68
- association: '# Relationships'
69
- attribute: '# Attributes'
70
- callback: '# Callbacks'
71
- constant: '# Constants'
72
- enum: '# Enums'
73
- include: '# Includes'
74
- module: '# Modules'
75
- scope: '# Scopes'
76
- validation: '# Validations'
77
-
78
- ```
30
+ See the guides for each of the Cops for details.
79
31
 
80
32
  ## License
81
33
 
@@ -0,0 +1,62 @@
1
+ # The Migration Constants Cop
2
+
3
+ In our rails migration files we don't want dependencies on, for example,
4
+ ActiveRecord model files.
5
+
6
+ This is because migration files should be "timeless" and able to run at any
7
+ point in the future. Our model files change very frequently - and therefore
8
+ cannot be depended on directly. We must redeclare the model inside the
9
+ migration file.
10
+
11
+ For example:
12
+
13
+ ```ruby
14
+ # BAD :'(
15
+ #
16
+ # This migration depends on `SomeModel` and `.some_scope`. When this
17
+ # migration is actually run, either of those things could have changed name,
18
+ # or perhaps `some_scope` might behave differently by then or have been
19
+ # deleted.
20
+ class FooMigration < ActiveRecord::Migration[5.1]
21
+ def up
22
+ SomeModel.some_scope.each do
23
+ # stuff
24
+ end
25
+ end
26
+ end
27
+
28
+ # GOOD :-D
29
+ #
30
+ # This migration has no external dependencies on our app. It is unlikely to
31
+ # break in the future as our app changes.
32
+ class FooMigration < ActiveRecord::Migration[5.1]
33
+ class SomeModel < ActiveRecord::Base
34
+ scope :some_scope, -> { ... }
35
+ end
36
+
37
+ def up
38
+ SomeModel.some_scope.each do
39
+ # stuff
40
+ end
41
+ end
42
+ end
43
+ ```
44
+
45
+ This cop will issue warnings if a migration file depends on certain constants
46
+ (like model files) that it doesn't declare.
47
+
48
+ ## Usage
49
+
50
+ Install the gem and then add this to your `.rubocop.yml` file:
51
+
52
+ ```yaml
53
+ # this is required
54
+ require: dark_finger
55
+
56
+ DarkFinger/MigrationConstants:
57
+ Include:
58
+ - 'db/migrate/*.rb'
59
+ whitelisted_constants:
60
+ - 'MyConstant'
61
+ - 'MyOtherConstant'
62
+ ```
@@ -0,0 +1,145 @@
1
+ # The Model Structure Cop
2
+
3
+ At work we've found that, as our model files grow in size, there are many
4
+ "macro" methods (scopes, validations, etc) at the top that become messy and
5
+ inconsistent across files.
6
+
7
+ This cop will issue warnings if the various model elements:
8
+
9
+ 1. Aren't grouped together
10
+ 2. The groups don't appear in the right order
11
+ 3. The groups aren't commented properly
12
+
13
+ This is the kind of model file that we like. Notice how all the model elements
14
+ are grouped, commented, and ordered (although ordering is not visible from just
15
+ one file):
16
+
17
+ ```ruby
18
+ class Horse < ApplicationRecord
19
+ ## Includes ##
20
+ include GallopingMagicPowers
21
+ include LazerEyes
22
+
23
+ ## Enums ##
24
+ enum breed: %i[thoroughbred
25
+ arabian
26
+ american quarter horse
27
+ clydesdale
28
+ mustang]
29
+
30
+ ## Associations ##
31
+ has_many :legs
32
+ belongs_to :brain
33
+ belongs_to :saddle
34
+
35
+ ## Validations ##
36
+ validates_presence_of :breed
37
+ validates_presence_of :age
38
+
39
+ ## Scopes ##
40
+ scope :dead, -> { where(state: 'dead') }
41
+ scope :alive, -> { where(state: 'alive') }
42
+
43
+ ## Attributes ##
44
+ attr_accessor :promote_to_demon_horse
45
+
46
+ ## Callbacks ##
47
+ after_save :callbacks_are_evil_you_should_be_ashamed
48
+
49
+ ## Misc ##
50
+ serialize :nose_hairs, Array
51
+ acts_as_taggable
52
+
53
+ def self.foobario
54
+ # foo
55
+ end
56
+
57
+ def gallop_hard
58
+ # ...
59
+ end
60
+ end
61
+
62
+ ```
63
+
64
+ ## Usage and Configuration
65
+
66
+ Install the gem. Then, in your `.rubycop.yml` file, require `dark_finger` and
67
+ add your desired config.
68
+
69
+ For example:
70
+
71
+ ```yaml
72
+ # in .rubocop.yml
73
+
74
+ # this is required
75
+ require: dark_finger
76
+
77
+ DarkFinger/ModelStructure:
78
+
79
+ # this is also required
80
+ Include:
81
+ - 'app/models/*'
82
+
83
+ # specify the order that the model elements must appear in
84
+ required_order:
85
+ - module
86
+ - include
87
+ - enum
88
+ - constant
89
+ - association
90
+ - validation
91
+ - scope
92
+ - attributes
93
+ - callback
94
+ - misc
95
+ - constructor
96
+ - class_method
97
+ - instance_method
98
+
99
+ # specify the comments that must appear above each group of model elements
100
+ required_comments:
101
+ association: '## Relationships ##'
102
+ attribute: '## Attributes ##'
103
+ callback: '## Callbacks ##'
104
+ constant: '## Constants ##'
105
+ enum: '## Enums ##'
106
+ include: '## Includes ##'
107
+ misc: '## Misc ##'
108
+ module: '## Modules ##'
109
+ scope: '## Scopes ##'
110
+ validation: '## Validations ##'
111
+
112
+ # specify the methods that are categorized as "misc"
113
+ misc_method_names:
114
+ - acts_as_list
115
+ - acts_as_taggable
116
+ - friendly_id
117
+ - serialize
118
+ - workflow
119
+ ```
120
+
121
+ Supported model elements:
122
+
123
+
124
+ | Config key | Description (when not obvious) |
125
+ |-----------------|--------------------------------------------|
126
+ | association | |
127
+ | attribute | `attr_reader` and friends |
128
+ | callback | `after_save` et al. |
129
+ | class_method | |
130
+ | constant | |
131
+ | constructor | |
132
+ | enum | |
133
+ | include | |
134
+ | instance_method | |
135
+ | misc | This is a configurable set of method calls |
136
+ | module | Any `module Foo; ...; end` declarations |
137
+ | scope | Any `scope` or `default_scope` calls |
138
+ | validation | |
139
+
140
+
141
+ Any model elements that are not included in "required order" will be ignored.
142
+
143
+ Dark Finger ignores everything that appears after `private`. This may change in
144
+ future, but for now we have just agreed that anything goes in the private
145
+ section of our models.
@@ -2,3 +2,5 @@ require 'rubocop'
2
2
  require "dark_finger/version"
3
3
  require File.dirname(__FILE__) + "/rubocop/cop/dark_finger/model_structure"
4
4
  require File.dirname(__FILE__) + "/rubocop/cop/dark_finger/active_model_node_decorator"
5
+ require File.dirname(__FILE__) + "/rubocop/cop/dark_finger/migration_constants"
6
+ require File.dirname(__FILE__) + "/rubocop/cop/dark_finger/module_ancestor_chain_extractor"
@@ -1,3 +1,3 @@
1
1
  module DarkFinger
2
- VERSION = "0.3.1"
2
+ VERSION = "0.6.1"
3
3
  end
@@ -1,3 +1,5 @@
1
+ require 'delegate'
2
+
1
3
  module RuboCop
2
4
  module Cop
3
5
  module DarkFinger
@@ -60,6 +62,11 @@ module RuboCop
60
62
  end
61
63
  end
62
64
 
65
+ def private_declaration?
66
+ # need to check respond_to since this may not be called only "on_send"
67
+ respond_to?(:method_name) && method_name == :private && receiver.nil?
68
+ end
69
+
63
70
  private
64
71
 
65
72
  def nested_in_with_options?
@@ -0,0 +1,69 @@
1
+ module RuboCop
2
+ module Cop
3
+ module DarkFinger
4
+ class MigrationConstants < ::RuboCop::Cop::Cop
5
+ DEFAULT_ALLOWED_CONSTANTS = [
6
+ 'Migration',
7
+ 'ActiveRecord',
8
+ 'ActiveRecord::Migration',
9
+ 'ActiveRecord::Base',
10
+ 'ActiveRecord::IrreversibleMigration'
11
+ ]
12
+
13
+ attr_reader :allowed_constants
14
+
15
+ def initialize(*args, options)
16
+ super(*args)
17
+ @whitelisted_constants = options[:whitelisted_constants] || cop_config['whitelisted_constants'] || []
18
+ @allowed_constants =
19
+ DEFAULT_ALLOWED_CONSTANTS +
20
+ allowed_top_level_constants +
21
+ @whitelisted_constants
22
+ end
23
+
24
+ def on_const(node)
25
+ return if allowed_constants.include?(node.const_name)
26
+ add_offense(node, message: %Q(Undeclared constant: "#{node.const_name}"))
27
+ end
28
+
29
+ def on_casgn(node)
30
+ add_allowed_constant(node.children[1])
31
+ end
32
+
33
+ def on_class(node)
34
+ add_allowed_constant(node.children.first.const_name)
35
+ add_module_parent_chain_for(node)
36
+ end
37
+
38
+ def on_module(node)
39
+ add_allowed_constant(node.children.first.const_name)
40
+ add_module_parent_chain_for(node)
41
+ end
42
+
43
+ private
44
+
45
+ def allowed_top_level_constants
46
+ Module.constants.map(&:to_s) - top_level_model_classes_and_containing_modules
47
+ end
48
+
49
+ def top_level_model_classes_and_containing_modules
50
+ return [] unless Object.const_defined?('ActiveRecord::Base')
51
+
52
+ ::ActiveRecord::Base.descendants.map do |klass|
53
+ klass.name.sub(/::.*/, '').to_s
54
+ end.uniq
55
+ end
56
+
57
+ def add_allowed_constant(constant)
58
+ @allowed_constants << constant.to_s
59
+ @allowed_constants.uniq!
60
+ end
61
+
62
+ def add_module_parent_chain_for(node)
63
+ chain = ModuleAncestorChainExtractor.new(node).perform
64
+ add_allowed_constant(chain)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -68,12 +68,12 @@ module RuboCop
68
68
 
69
69
  attr_reader :required_order, :required_comments, :misc_method_names
70
70
 
71
- def initialize(*args, required_order: nil, required_comments: nil, misc_method_names: nil, **_)
71
+ def initialize(*args, options)
72
72
  super
73
73
  @class_elements_seen = []
74
- @required_order = required_order || cop_config['required_order'] || DEFAULT_REQUIRED_ORDER
75
- @required_comments = required_comments || cop_config['required_comments'] || DEFAULT_REQUIRED_COMMENTS
76
- @misc_method_names = misc_method_names || cop_config['misc_method_names'] || DEFAULT_MISC_METHOD_NAMES
74
+ @required_order = options[:required_order] || cop_config['required_order'] || DEFAULT_REQUIRED_ORDER
75
+ @required_comments = options[:required_comments] || cop_config['required_comments'] || DEFAULT_REQUIRED_COMMENTS
76
+ @misc_method_names = options[:misc_method_names] || cop_config['misc_method_names'] || DEFAULT_MISC_METHOD_NAMES
77
77
 
78
78
  # symbolize configs
79
79
  @required_order.map!(&:to_sym)
@@ -114,10 +114,16 @@ module RuboCop
114
114
 
115
115
  def process_node(node, seen_element: nil)
116
116
  return if @order_violation_reported
117
+ return if @seen_private_declaration
117
118
 
118
119
  node = ActiveModelNodeDecorator.new(node, misc_method_names: misc_method_names)
119
- seen_element ||= node.node_type
120
120
 
121
+ if node.private_declaration?
122
+ @seen_private_declaration = true
123
+ return
124
+ end
125
+
126
+ seen_element ||= node.node_type
121
127
  return unless seen_element
122
128
 
123
129
  return if node.ignore_due_to_nesting?
@@ -0,0 +1,25 @@
1
+ module RuboCop
2
+ module Cop
3
+ module DarkFinger
4
+ class ModuleAncestorChainExtractor
5
+ attr_reader :node
6
+
7
+ def initialize(node)
8
+ @node = node
9
+ end
10
+
11
+ def perform
12
+ module_chain = [node.children.first.const_name]
13
+
14
+ current_node = node
15
+ while current_node.parent && current_node.parent.module_type?
16
+ module_chain << current_node.parent.children.first.const_name
17
+ current_node = current_node.parent
18
+ end
19
+
20
+ module_chain.reverse.join("::")
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dark_finger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Professor Wang Matrix PhD
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2018-10-06 00:00:00.000000000 Z
12
+ date: 2020-07-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -89,6 +89,7 @@ executables: []
89
89
  extensions: []
90
90
  extra_rdoc_files: []
91
91
  files:
92
+ - ".github/dark_finger.jpg"
92
93
  - ".gitignore"
93
94
  - ".rspec"
94
95
  - ".travis.yml"
@@ -99,10 +100,14 @@ files:
99
100
  - bin/console
100
101
  - bin/setup
101
102
  - dark_finger.gemspec
103
+ - docs/migration_constants.md
104
+ - docs/model_structure.md
102
105
  - lib/dark_finger.rb
103
106
  - lib/dark_finger/version.rb
104
107
  - lib/rubocop/cop/dark_finger/active_model_node_decorator.rb
108
+ - lib/rubocop/cop/dark_finger/migration_constants.rb
105
109
  - lib/rubocop/cop/dark_finger/model_structure.rb
110
+ - lib/rubocop/cop/dark_finger/module_ancestor_chain_extractor.rb
106
111
  homepage: https://github.com/the-suBLAM-executive-council/dark_finger
107
112
  licenses:
108
113
  - MIT
@@ -122,8 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
127
  - !ruby/object:Gem::Version
123
128
  version: '0'
124
129
  requirements: []
125
- rubyforge_project:
126
- rubygems_version: 2.5.2.1
130
+ rubygems_version: 3.1.2
127
131
  signing_key:
128
132
  specification_version: 4
129
133
  summary: ActiveModel layout cop for Rubocop