rubocop-rubomatic 1.0.0.pre.rc.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ = Layout
2
+
3
+ Layout cops check for stylistic whitespace in your code
4
+
5
+ == Cops
6
+
7
+ * xref:./multiline_array_line_breaks/README.adoc[``Layout/MultilineArrayLineBreaks``]
@@ -0,0 +1,44 @@
1
+ = ``Layout/MultilineArrayLineBreaks``
2
+
3
+ == Description
4
+
5
+ Extends the built-in link:https://docs.rubocop.org/rubocop/1.49/cops_layout.html#layoutmultilinearraylinebreaks[``Layout/MultilineArrayLineBreaks``]
6
+ and adds the ``AllowPercentArray`` option
7
+
8
+ == Examples
9
+
10
+ [source,ruby]
11
+ ----
12
+ # bad
13
+ %w[
14
+ 1
15
+ 2
16
+ 3
17
+ 4
18
+ ]
19
+
20
+ # good
21
+ %w[
22
+ 1 2
23
+ 3 4
24
+ ]
25
+ ----
26
+
27
+ == Configurable Attributes
28
+
29
+ |===
30
+ |Name |Default value |Configurable values
31
+
32
+ |AllowMultilineFinalElement
33
+ |false
34
+ |Boolean
35
+
36
+ |AllowPercentArray
37
+ |false
38
+ |Boolean
39
+
40
+ |===
41
+
42
+ == References
43
+
44
+ * https://github.com/BrandsInsurance/expert-chainsaw/issues/1032
@@ -0,0 +1,7 @@
1
+ = Style
2
+
3
+ Style cops check for stylistic consistency of your code
4
+
5
+ == Cops
6
+
7
+ * xref:./disallowed_methods/README.adoc[``Style/DisallowedMethods``]
@@ -0,0 +1,28 @@
1
+ = ``Style/DisallowedMethods``
2
+
3
+ == Description
4
+
5
+ Certain methods should not be used
6
+
7
+ == Examples
8
+
9
+ [source,ruby]
10
+ ----
11
+ # Bad (Don't use `tap`)
12
+ tap do
13
+ end
14
+
15
+ # Bad (Don't use `abort`)
16
+ abort
17
+
18
+ # Good (Use `raise` instead of `abort`)
19
+ raise
20
+ ----
21
+
22
+ == Configurable Attributes
23
+
24
+ There are no configurable attributes
25
+
26
+ == References
27
+
28
+ * https://github.com/BrandsInsurance/expert-chainsaw/issues/434
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rubomatic
6
+ class Generator
7
+ class CopReadmeInjector < DeptReadmeInjector
8
+ TEMPLATE =
9
+ '* xref:./%{cop_folder}/README.adoc[``%{department}/%{cop}``]'
10
+
11
+ # :nodoc:
12
+ def initialize(badge:, **kwargs)
13
+ super(badge: badge, **kwargs)
14
+
15
+ @cop = badge.cop_name
16
+ end
17
+
18
+ private
19
+
20
+ # @return [String]
21
+ attr_reader :cop
22
+
23
+ # @see super
24
+ def new_readme_entry
25
+ format(TEMPLATE, {
26
+ department_folder: snake_case(department),
27
+ cop_folder: snake_case(cop),
28
+ department: department,
29
+ cop: cop
30
+ })
31
+ end
32
+
33
+ # @see super
34
+ def line_is_good?(line)
35
+ return true if super
36
+
37
+ matches = line.match(target_regex)
38
+ return false if matches.nil?
39
+
40
+ department == matches[:department] && cop < matches[:cop]
41
+ end
42
+
43
+ # @see super
44
+ def target_regex
45
+ %r{\* xref:\./docs/cops/[a-z_]+/[a-z_]+/README\.adoc\[``(?<department>[a-zA-Z_]+)/(?<cop>[a-zA-Z_]+)``\]}
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rubomatic
6
+ class Generator
7
+ class DeptReadmeInjector
8
+ TEMPLATE = '* xref:./docs/cops/%{department_folder}/README.adoc[``%{department}``]'
9
+
10
+ # :nodoc:
11
+ def initialize(readme_file_path:, badge:, department:)
12
+ @readme_file_path = readme_file_path
13
+ @badge = badge
14
+ @department = department
15
+ @output = output
16
+ end
17
+
18
+ # Performs the actual string injection into the file
19
+ # modified version of `inject` from RuboCop::Cop::Generator::ConfigurationInjector
20
+ # Named `inject_string` becuase rubocop thought when called it was `Array#inject`
21
+ #
22
+ # @return [void]
23
+ #
24
+ def inject_string
25
+ target_line = find_target_line
26
+
27
+ if target_line
28
+ readme_entries.insert(target_line, "#{new_readme_entry}\n")
29
+ else
30
+ readme_entries.push(new_readme_entry)
31
+ end
32
+
33
+ File.write(readme_file_path, readme_entries.join(''))
34
+
35
+ yield if block_given?
36
+ end
37
+
38
+ private
39
+
40
+ # @return [String]
41
+ attr_reader :readme_file_path
42
+ # @return [RuboCop::Cop::Badge]
43
+ attr_reader :badge
44
+ # @return [String]
45
+ attr_reader :department
46
+ # @return [*] Default $stdout
47
+ attr_reader :output
48
+
49
+ # Lines in <department>/README.adoc
50
+ #
51
+ # @return [Array<String>]
52
+ #
53
+ def readme_entries
54
+ @readme_entries ||= File.readlines(readme_file_path)
55
+ end
56
+
57
+ # Modified version from Rubocop::Cop::Generator::ConfigurationInjector
58
+ #
59
+ # @return [String]
60
+ #
61
+ def new_readme_entry
62
+ format(TEMPLATE, {
63
+ department_folder: snake_case(department),
64
+ department: department
65
+ })
66
+ end
67
+
68
+ # Modified version from Rubocop::Cop::Generator::ConfigurationInjector
69
+ #
70
+ # @return [Integer, Nil]
71
+ #
72
+ def find_target_line
73
+ readme_entries.find.with_index do |line, index|
74
+ return index if line_is_good?(line)
75
+ end
76
+
77
+ return nil
78
+ end
79
+
80
+ # Determines if the given line is the same type we're trying to add and if it's alphabetically before
81
+ #
82
+ # @return [Boolean]
83
+ #
84
+ def line_is_good?(line)
85
+ matches = line.match(target_regex)
86
+ return false if matches.nil?
87
+
88
+ department < line.match(target_regex)[:department]
89
+ end
90
+
91
+ # Regex to look for in the readme
92
+ #
93
+ # @return [Regexp]
94
+ #
95
+ def target_regex
96
+ %r{\* xref:\./docs/cops/[a-z_]+/README\.adoc\[``(?<department>[a-zA-Z_]+)``\]}
97
+ end
98
+
99
+ # Copied from Rubocop::Cop::Generator
100
+ #
101
+ # @return [String]
102
+ #
103
+ def snake_case(camel_case_string)
104
+ camel_case_string
105
+ .gsub('RSpec', 'Rspec')
106
+ .gsub(%r{([^A-Z/])([A-Z]+)}, '\1_\2')
107
+ .gsub(%r{([A-Z])([A-Z][^A-Z\d/]+)}, '\1_\2')
108
+ .downcase
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,283 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './generator/dept_readme_injector'
4
+
5
+ require_relative './generator/cop_readme_injector'
6
+
7
+ module RuboCop
8
+ module Cop
9
+ module Rubomatic
10
+ class Generator
11
+ COP_DOC = <<~RUBY
12
+ # TODO: Write cop description and example of bad / good code. For every
13
+ # `SupportedStyle` and unique configuration, there needs to be examples.
14
+ # Examples must have valid Ruby syntax. Do not use upticks.
15
+ #
16
+ # @safety
17
+ # Delete this section if the cop is not unsafe (`Safe: false` or
18
+ # `SafeAutoCorrect: false`), or use it to explain how the cop is
19
+ # unsafe.
20
+ #
21
+ # @example EnforcedStyle: bar (default)
22
+ # # Description of the `bar` style.
23
+ #
24
+ # # bad
25
+ # bad_bar_method
26
+ #
27
+ # # bad
28
+ # bad_bar_method(args)
29
+ #
30
+ # # good
31
+ # good_bar_method
32
+ #
33
+ # # good
34
+ # good_bar_method(args)
35
+ #
36
+ # @example EnforcedStyle: foo
37
+ # # Description of the `foo` style.
38
+ #
39
+ # # bad
40
+ # bad_foo_method
41
+ #
42
+ # # bad
43
+ # bad_foo_method(args)
44
+ #
45
+ # # good
46
+ # good_foo_method
47
+ #
48
+ # # good
49
+ # good_foo_method(args)
50
+ #
51
+ RUBY
52
+ SOURCE_TEMPLATE = <<~RUBY
53
+ # frozen_string_literal: true
54
+
55
+ module RuboCop
56
+ module Cop
57
+ module %{department}
58
+ class %{cop_name} < Base
59
+ # TODO: Implement the cop in here.
60
+ #
61
+ # In many cases, you can use a node matcher for matching node pattern.
62
+ # See https://github.com/rubocop/rubocop-ast/blob/master/lib/rubocop/ast/node_pattern.rb
63
+ #
64
+ # For example
65
+ MSG = 'Use `#good_method` instead of `#bad_method`.'
66
+
67
+ # TODO: Don't call `on_send` unless the method name is in this list
68
+ # If you don't need `on_send` in the cop you created, remove it.
69
+ RESTRICT_ON_SEND = %%i[bad_method].freeze
70
+
71
+ # @!method bad_method?(node)
72
+ def_node_matcher :bad_method?, <<~PATTERN
73
+ (send nil? :bad_method ...)
74
+ PATTERN
75
+
76
+ def on_send(node)
77
+ return unless bad_method?(node)
78
+
79
+ add_offense(node)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ RUBY
86
+ README_ADDED_MESSAGE = '[modify] A link for the %{dept_vs_cop} has been added into %{readme_file_path}.'
87
+
88
+ DEPT_README_TEMPLATE = <<~ADOC
89
+ = %{department}
90
+
91
+ Describe the department here
92
+
93
+ == Cops
94
+
95
+ ADOC
96
+ COP_README_TEMPLATE = <<~ADOC
97
+ = ``%{department}/%{cop_name}``
98
+
99
+ == Description
100
+
101
+ Add a description here
102
+
103
+ == Examples
104
+
105
+ [source,ruby]
106
+ ----
107
+ # Bad
108
+ # Add a bad example here
109
+
110
+ # Good
111
+ # Add a good example here
112
+ ----
113
+
114
+ == Configurable Attributes
115
+
116
+ |===
117
+ |Name |Default value |Configurable values
118
+
119
+ |Max
120
+ |120
121
+ |Integer
122
+
123
+ |===
124
+
125
+ == References
126
+
127
+ https://github.com/BrandsInsurance/expert-chainsaw/issues
128
+ ADOC
129
+
130
+ # :nodoc:
131
+ def initialize(name, output: $stdout)
132
+ name = ['Rubomatic', name].join('/') unless name.start_with?('Rubomatic/')
133
+
134
+ unless name.count('/') == 2
135
+ raise(
136
+ [
137
+ 'You must provide a single department under Rubomatic i.e. Rubomatic/Department/CopName',
138
+ 'or Department/CopName'
139
+ ].join(' ')
140
+ )
141
+ end
142
+
143
+ @base_gen = RuboCop::Cop::Generator.new(name, output: output)
144
+ end
145
+
146
+ # Calls methods in the base class
147
+ #
148
+ # @return [*]
149
+ #
150
+ def method_missing(...)
151
+ @base_gen.__send__(...)
152
+ end
153
+
154
+ # `self` responds to `method_name` if `@base_gen` does
155
+ #
156
+ def respond_to_missing?(method_name, include_private = false)
157
+ @base_gen.respond_to?(method_name, include_private)
158
+ end
159
+
160
+ # Creates the department readme if it doesn't exist
161
+ # Modified version of `wirte_source` from RuboCop::Cop::Generator
162
+ #
163
+ # @return [void]
164
+ #
165
+ def write_dept_readme
166
+ return if File.exist?(dept_docs_path)
167
+
168
+ write_unless_file_exists(dept_docs_path, generated_dept_docs)
169
+ end
170
+
171
+ # Creates the cop readme if it doesn't exist
172
+ # Modified version of `wirte_source` from RuboCop::Cop::Generator
173
+ #
174
+ # @return [void]
175
+ #
176
+ def write_cop_readme
177
+ write_unless_file_exists(docs_path, generated_cop_docs)
178
+ end
179
+
180
+ # Injects the, possibly new, department readme link into the base readme
181
+ # Modified version of `inject_config` from RuboCop::Cop::Generator
182
+ #
183
+ # @return [void]
184
+ #
185
+ def inject_dept_readme(readme_file_path: 'README.adoc')
186
+ # Add this dept to base readme if not already there
187
+ injector = DeptReadmeInjector.new(
188
+ readme_file_path: readme_file_path,
189
+ badge: badge,
190
+ department: department
191
+ )
192
+
193
+ injector.inject_string do
194
+ output.puts(format(README_ADDED_MESSAGE, readme_file_path: readme_file_path, dept_vs_cop: 'department'))
195
+ end
196
+ end
197
+
198
+ # Injects the new cop readme link into the department readme
199
+ # Modified version of `inject_config` from RuboCop::Cop::Generator
200
+ #
201
+ # @return [void]
202
+ #
203
+ def inject_cop_readme(readme_file_path: dept_docs_path)
204
+ # Add this cop to the dept readme
205
+ injector = CopReadmeInjector.new(
206
+ readme_file_path: readme_file_path,
207
+ badge: badge,
208
+ department: department
209
+ )
210
+
211
+ injector.inject_string do
212
+ output.puts(format(README_ADDED_MESSAGE, readme_file_path: readme_file_path, dept_vs_cop: 'cop'))
213
+ end
214
+ end
215
+
216
+ private
217
+
218
+ # The rubocop department without the Rubomatic prefix
219
+ #
220
+ # @return [String]
221
+ #
222
+ def department
223
+ badge.department_name.gsub('Rubomatic/', '')
224
+ end
225
+
226
+ # Modified version of `generated_source` from Rubocop::Cop::Generator
227
+ #
228
+ # @return [String]
229
+ #
230
+ def generated_dept_docs
231
+ generate_readme(DEPT_README_TEMPLATE)
232
+ end
233
+
234
+ # Modified version of `generated_source` from Rubocop::Cop::Generator
235
+ #
236
+ # @return [String]
237
+ #
238
+ def generated_cop_docs
239
+ generate_readme(COP_README_TEMPLATE)
240
+ end
241
+
242
+ # Modified version from Rubocop::Cop::Generator
243
+ #
244
+ # @return [String]
245
+ #
246
+ def generate_readme(template)
247
+ format(template, {
248
+ department: department,
249
+ cop_name: badge.cop_name,
250
+ cop_folder: snake_case(badge.cop_name.to_s)
251
+ })
252
+ end
253
+
254
+ # Path to <department>/README.adoc
255
+ #
256
+ # @return [String]
257
+ #
258
+ def dept_docs_path
259
+ File.join(
260
+ 'docs',
261
+ 'cops',
262
+ snake_case(department),
263
+ 'README.adoc'
264
+ )
265
+ end
266
+
267
+ # Path to <department>/<cop>/README.adoc
268
+ #
269
+ # @return [String]
270
+ #
271
+ def docs_path
272
+ File.join(
273
+ 'docs',
274
+ 'cops',
275
+ snake_case(department),
276
+ snake_case(badge.cop_name.to_s),
277
+ 'README.adoc'
278
+ )
279
+ end
280
+ end
281
+ end
282
+ end
283
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rubomatic
6
+ module Layout
7
+ class MultilineArrayLineBreaks < RuboCop::Cop::Layout::MultilineArrayLineBreaks
8
+ # @see super
9
+ def on_array(node)
10
+ return if allowed_percent_array?(node)
11
+
12
+ super
13
+ end
14
+
15
+ private
16
+
17
+ # Check conig option
18
+ #
19
+ # @return [Boolean]
20
+ #
21
+ def allowed_percent_array?(node)
22
+ cop_config.fetch('AllowPercentArray', true) && node.percent_literal?
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rubomatic
6
+ module Style
7
+ class DisallowedMethods < RuboCop::Cop::Base
8
+ # In many cases, you can use a node matcher for matching node pattern.
9
+ # See https://github.com/rubocop/rubocop-ast/blob/master/lib/rubocop/ast/node_pattern.rb
10
+ #
11
+ # For example
12
+ MESSAGES = {
13
+ abort: 'Use `#raise` instead of `#abort`.',
14
+ tap: 'Do not use `#tap`'
15
+ }.freeze
16
+
17
+ # `on_send` will only be called if the method name is in this list
18
+ RESTRICT_ON_SEND = %i[abort tap].freeze
19
+
20
+ # @!method using_abort?(node)
21
+ def_node_matcher :using_abort?, <<~PATTERN
22
+ (send nil? :abort ...)
23
+ PATTERN
24
+
25
+ # @!method using_tap?(node)
26
+ def_node_matcher :using_tap?, <<~PATTERN
27
+ (send nil? :tap ...)
28
+ PATTERN
29
+
30
+ # The main logic method of the cop
31
+ #
32
+ # @return [void]
33
+ #
34
+ def on_send(node)
35
+ message =
36
+ if using_abort?(node)
37
+ :abort
38
+ elsif using_tap?(node)
39
+ :tap
40
+ end
41
+
42
+ return if message.nil?
43
+
44
+ add_offense(node, message: MESSAGES.fetch(message))
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rubomatic/layout/multiline_array_line_breaks'
4
+ require_relative 'rubomatic/style/disallowed_methods'
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Rubomatic
5
+ module Inject
6
+ # This was a generated method from https://github.com/rubocop/rubocop-extension-generator
7
+ #
8
+ def self.defaults!
9
+ path = CONFIG_DEFAULT.to_s
10
+ hash = ConfigLoader.__send__(:load_yaml_configuration, path)
11
+ config = Config.new(hash, path).tap(&:make_excludes_absolute)
12
+
13
+ puts("configuration from #{path}") if ConfigLoader.debug?
14
+
15
+ config = ConfigLoader.merge_with_default(config, path)
16
+
17
+ ConfigLoader.instance_variable_set(:@default_configuration, config)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Rubomatic
5
+ VERSION = '1.0.0-rc.1'
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rubomatic/version'
4
+ require 'yaml'
5
+
6
+ module RuboCop
7
+ module Rubomatic
8
+ class Error < StandardError
9
+ end
10
+
11
+ PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze
12
+ CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze
13
+ CONFIG = ::YAML.safe_load(CONFIG_DEFAULT.read).freeze
14
+
15
+ private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop'
4
+
5
+ require_relative 'rubocop/cop/rubomatic/generator'
6
+ require_relative 'rubocop/rubomatic'
7
+ require_relative 'rubocop/rubomatic/inject'
8
+ require_relative 'rubocop/rubomatic/version'
9
+
10
+ RuboCop::Rubomatic::Inject.defaults!
11
+
12
+ require_relative 'rubocop/cop/rubomatic_cops'