dark_finger 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2175e36eaa195b08572b40995a9291bf961bba87
4
+ data.tar.gz: 5419ca9cfd34ccf28fc360da5b57cc98697c04fb
5
+ SHA512:
6
+ metadata.gz: 29e2b1f85d9d3ceb0027cc5596faf57fcd31de49460f1b6bca740ffb6be9f6a1d85f8bbef1f285c881c13bc8c8e768f1dfc24a5c5667f8ad2e0b145086a3d735
7
+ data.tar.gz: 528bfdced0087243bede4441486f3c63aa53cc0d284043dc42a85a97a0df08bd0fc582620b2e593e2f9a60188b091b54d61fb30f7be4780327a5fdcb909f4896
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ tags
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.12.5
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Professor Wang Matrix PhD
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # DarkFinger
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.
6
+
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
20
+
21
+
22
+ ## Installation
23
+
24
+ Add this line to your application's Gemfile:
25
+
26
+ ```ruby
27
+ gem 'dark_finger'
28
+ ```
29
+
30
+ And then execute:
31
+
32
+ $ bundle
33
+
34
+ Or install it yourself as:
35
+
36
+ $ gem install dark_finger
37
+
38
+ ## Usage
39
+
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
+ enabled: true
54
+ required_order:
55
+ - module
56
+ - include
57
+ - enum
58
+ - constant
59
+ - association
60
+ - validation
61
+ - scope
62
+ - attributes
63
+ - callback
64
+ - class_method
65
+ - instance_method
66
+ required_comments:
67
+ association: Relationships
68
+ attribute: Attributes
69
+ callback: Callbacks
70
+ constant: Constants
71
+ enum: Enums
72
+ include: Includes
73
+ module: Modules
74
+ scope: Scopes
75
+ validation: Validation
76
+
77
+ ```
78
+
79
+ ## License
80
+
81
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
82
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "dark_finger"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dark_finger/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "dark_finger"
8
+ spec.version = DarkFinger::VERSION
9
+ spec.authors = ["Professor Wang Matrix PhD", "Urban Cougar"]
10
+ spec.email = ["professor.wang.matrix.phd@gmail.com", "urbancougarltd@gmail.com"]
11
+
12
+ spec.summary = "ActiveModel layout cop for Rubocop"
13
+ spec.homepage = "https://github.com/the-suBLAM-executive-council/dark_finger"
14
+ spec.license = "MIT"
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.12"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency "rspec", "~> 3.0"
23
+ spec.add_development_dependency "pry"
24
+ spec.add_runtime_dependency 'rubocop', '>= 0.58.0'
25
+ end
@@ -0,0 +1,3 @@
1
+ module DarkFinger
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ require 'rubocop'
2
+ require "dark_finger/version"
3
+ require File.dirname(__FILE__) + "/rubocop/cop/dark_finger/model_structure"
4
+ require File.dirname(__FILE__) + "/rubocop/cop/dark_finger/active_model_node_decorator"
@@ -0,0 +1,113 @@
1
+ module RuboCop
2
+ module Cop
3
+ module DarkFinger
4
+ class ActiveModelNodeDecorator < SimpleDelegator
5
+ def initialize(node)
6
+ super
7
+ end
8
+
9
+ def node_type
10
+ if validation?
11
+ ModelStructure::VALIDATION
12
+ elsif association?
13
+ ModelStructure::ASSOCIATION
14
+ elsif callback?
15
+ ModelStructure::CALLBACK
16
+ elsif scope?
17
+ ModelStructure::SCOPE
18
+ elsif is_include?
19
+ ModelStructure::INCLUDE
20
+ elsif enum?
21
+ ModelStructure::ENUM
22
+ elsif attributes?
23
+ ModelStructure::ATTRIBUTES
24
+ else
25
+ nil
26
+ end
27
+ end
28
+
29
+ def ignore_due_to_nesting?
30
+ return false if nested_directly_in_class?
31
+ return false if nested_in_with_options?
32
+ true
33
+ end
34
+
35
+ def preceeding_comment(processed_source)
36
+ comment = processed_source.ast_with_comments[self].last&.text
37
+ return comment if comment
38
+
39
+ # This is needed since, in this example, ast_with_comments maps the
40
+ # comment to `:foo` instead of the outter `validate`. Not sure how
41
+ # else to do this.
42
+ #
43
+ # ## Validations ##
44
+ # validate { :foo }
45
+ comment = processed_source.comments.find do |comment|
46
+ comment.location.line == location.first_line - 1
47
+ end
48
+
49
+ return comment.text if comment
50
+
51
+ if nested_in_with_options?
52
+ ActiveModelNodeDecorator.new(parent).preceeding_comment(processed_source)
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def nested_in_with_options?
59
+ parent&.method_name == :with_options
60
+ end
61
+
62
+ def nested_directly_in_class?
63
+ return false unless parent
64
+
65
+ return true if parent.class_type?
66
+
67
+ return true if parent.begin_type? && parent.parent&.class_type?
68
+
69
+ if parent.block_type? || parent.lambda_or_proc?
70
+ grand_parent = parent.parent
71
+ great_grand_parent = parent.parent&.parent
72
+ return true if grand_parent.begin_type? && great_grand_parent&.class_type?
73
+ end
74
+
75
+ false
76
+ end
77
+
78
+ def validation?
79
+ method_name =~ /^validate/
80
+ end
81
+
82
+ def association?
83
+ method_name =~ /^(has_one|has_many|has_and_belongs_to_many|belongs_to)$/
84
+ end
85
+
86
+ def callback?
87
+ method_name =~ %r{
88
+ ^(after|before|around)
89
+ _
90
+ (initialize|find|touch|save|validation|create|update|destroy|commit|rollback)$
91
+ }x
92
+ end
93
+
94
+ def scope?
95
+ method_name =~ /(default_)?scope/
96
+ end
97
+
98
+ def is_include?
99
+ method_name.to_s == 'include'
100
+ end
101
+
102
+ def enum?
103
+ method_name.to_s == 'enum'
104
+ end
105
+
106
+ def attributes?
107
+ method_name =~ /^attr_/
108
+ end
109
+ end
110
+
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,148 @@
1
+ # bundle exec rubocop -d -r ./path/to/this/cop.rb --only DarkFinger/ModelStructure ./app/models/foo.rb
2
+ #
3
+ # TODO:
4
+ #
5
+ # * Constructors
6
+ # * Handle "Misc" stuff
7
+
8
+ require File.dirname(__FILE__) + '/active_model_node_decorator'
9
+
10
+ module RuboCop
11
+ module Cop
12
+ module DarkFinger
13
+ class ModelStructure < ::RuboCop::Cop::Cop
14
+ ASSOCIATION = :association
15
+ ATTRIBUTES = :attribute
16
+ CALLBACK = :callback
17
+ CLASS_METHOD = :class_method
18
+ CONSTANT = :constant
19
+ ENUM = :enum
20
+ INCLUDE = :include
21
+ INSTANCE_METHOD = :instance_method
22
+ MODULE = :module
23
+ SCOPE = :scope
24
+ VALIDATION = :validation
25
+
26
+ DEFAULT_REQUIRED_ORDER = [
27
+ MODULE,
28
+ INCLUDE,
29
+ ENUM,
30
+ CONSTANT,
31
+ ASSOCIATION,
32
+ VALIDATION,
33
+ SCOPE,
34
+ ATTRIBUTES,
35
+ CALLBACK,
36
+ CLASS_METHOD,
37
+ INSTANCE_METHOD,
38
+ ]
39
+
40
+ DEFAULT_REQUIRED_COMMENTS = {
41
+ ASSOCIATION => 'Relationships',
42
+ ATTRIBUTES => 'Attributes',
43
+ CALLBACK => 'Callbacks',
44
+ CONSTANT => 'Constants',
45
+ ENUM => 'Enums',
46
+ INCLUDE => 'Includes',
47
+ MODULE => 'Modules',
48
+ SCOPE => 'Scopes',
49
+ VALIDATION => 'Validations'
50
+ }
51
+
52
+ attr_reader :required_order, :required_comments
53
+
54
+ def initialize(*args, required_order: nil, required_comments: nil, **_)
55
+ super
56
+ @class_elements_seen = []
57
+ @required_order = required_order || cop_config['required_order'] || DEFAULT_REQUIRED_ORDER
58
+ @required_comments = required_comments || cop_config['required_comments'] || DEFAULT_REQUIRED_COMMENTS
59
+
60
+ # symbolize order/comments
61
+ @required_order.map!(&:to_sym)
62
+ @required_comments = Hash[ @required_comments.map {|k,v| [k.to_sym, v]} ]
63
+ end
64
+
65
+ def on_send(node)
66
+ process_node(node)
67
+ end
68
+
69
+ def on_casgn(node)
70
+ process_node(node, seen_element: CONSTANT)
71
+ end
72
+
73
+ def on_module(node)
74
+ process_node(node, seen_element: MODULE)
75
+ end
76
+
77
+ def on_def(node)
78
+ process_node(node, seen_element: INSTANCE_METHOD)
79
+ end
80
+
81
+ def on_defs(node)
82
+ process_node(node, seen_element: CLASS_METHOD)
83
+ end
84
+
85
+ private
86
+
87
+ attr_reader :class_elements_seen
88
+
89
+ def process_node(node, seen_element: nil)
90
+ return if @order_violation_reported
91
+
92
+ node = ActiveModelNodeDecorator.new(node)
93
+ seen_element ||= node.node_type
94
+
95
+ return unless seen_element
96
+
97
+ return if node.ignore_due_to_nesting?
98
+
99
+ if first_time_seeing?(seen_element)
100
+ detect_comment_violation(node, seen_element)
101
+ end
102
+
103
+ seen(seen_element)
104
+ detect_order_violation(node)
105
+ end
106
+
107
+ def seen(class_element)
108
+ return unless required_order.include?(class_element)
109
+ class_elements_seen << class_element
110
+ class_elements_seen.compact!
111
+ end
112
+
113
+ def first_time_seeing?(class_element)
114
+ !class_elements_seen.include?(class_element)
115
+ end
116
+
117
+ def detect_comment_violation(node, class_element)
118
+ return false unless required_comments[class_element]
119
+
120
+ comment = node.preceeding_comment(processed_source)
121
+ unless comment && comment =~ Regexp.new(%Q(^\s*##\s*#{required_comments[class_element]} ##$))
122
+ add_offense(node, message: "Expected preceeding comment: \"## #{required_comments[class_element]} ##\"")
123
+ end
124
+ end
125
+
126
+ def detect_order_violation(node)
127
+ if order_violation_detected?
128
+ @order_violation_reported = true
129
+ add_offense(
130
+ node,
131
+ message: "Model elements must appear in order: \n\n* #{required_order.join("\n* ")}\n".freeze
132
+ )
133
+ end
134
+ end
135
+
136
+ def order_violation_detected?
137
+ required_order_for_elements_seen != class_elements_seen
138
+ end
139
+
140
+ def required_order_for_elements_seen
141
+ class_elements_seen.sort do |class_elem_1, class_elem_2|
142
+ required_order.index(class_elem_1) <=> required_order.index(class_elem_2)
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dark_finger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Professor Wang Matrix PhD
8
+ - Urban Cougar
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2018-09-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.12'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1.12'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '10.0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '10.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rspec
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '3.0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '3.0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: pry
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rubocop
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: 0.58.0
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: 0.58.0
84
+ description:
85
+ email:
86
+ - professor.wang.matrix.phd@gmail.com
87
+ - urbancougarltd@gmail.com
88
+ executables: []
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - ".gitignore"
93
+ - ".rspec"
94
+ - ".travis.yml"
95
+ - Gemfile
96
+ - LICENSE.txt
97
+ - README.md
98
+ - Rakefile
99
+ - bin/console
100
+ - bin/setup
101
+ - dark_finger.gemspec
102
+ - lib/dark_finger.rb
103
+ - lib/dark_finger/version.rb
104
+ - lib/rubocop/cop/dark_finger/active_model_node_decorator.rb
105
+ - lib/rubocop/cop/dark_finger/model_structure.rb
106
+ homepage: https://github.com/the-suBLAM-executive-council/dark_finger
107
+ licenses:
108
+ - MIT
109
+ metadata: {}
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 2.5.2.1
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: ActiveModel layout cop for Rubocop
130
+ test_files: []