dark_finger 0.3.1 → 0.6.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.
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