rubocop-rubomatic 1.0.0.pre.rc.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.
@@ -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'