rubocop-sorbet 0.3.7 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +2 -0
  3. data/.github/probots.yml +1 -1
  4. data/.github/stale.yml +20 -0
  5. data/.gitignore +0 -2
  6. data/.rspec +3 -0
  7. data/.rubocop.yml +2 -12
  8. data/.travis.yml +3 -1
  9. data/Gemfile +6 -7
  10. data/Gemfile.lock +27 -42
  11. data/README.md +64 -6
  12. data/Rakefile +34 -5
  13. data/config/default.yml +143 -2
  14. data/lib/rubocop-sorbet.rb +9 -1
  15. data/lib/rubocop/cop/sorbet/binding_constants_without_type_alias.rb +24 -1
  16. data/lib/rubocop/cop/sorbet/forbid_extend_t_sig_helpers_in_shims.rb +53 -0
  17. data/lib/rubocop/cop/sorbet/forbid_include_const_literal.rb +0 -40
  18. data/lib/rubocop/cop/sorbet/forbid_superclass_const_literal.rb +0 -17
  19. data/lib/rubocop/cop/sorbet/forbid_untyped_struct_props.rb +58 -0
  20. data/lib/rubocop/cop/sorbet/one_ancestor_per_line.rb +75 -0
  21. data/lib/rubocop/cop/sorbet/sigils/enforce_sigil_order.rb +11 -1
  22. data/lib/rubocop/cop/sorbet/signatures/enforce_signatures.rb +19 -10
  23. data/lib/rubocop/cop/sorbet/signatures/parameters_ordering_in_signature.rb +14 -6
  24. data/lib/rubocop/cop/sorbet/signatures/signature_build_order.rb +22 -3
  25. data/lib/rubocop/cop/sorbet/single_line_rbi_class_module_definitions.rb +46 -0
  26. data/lib/rubocop/cop/sorbet_cops.rb +25 -0
  27. data/lib/rubocop/sorbet.rb +15 -0
  28. data/lib/rubocop/sorbet/inject.rb +20 -0
  29. data/lib/rubocop/sorbet/version.rb +6 -0
  30. data/manual/cops.md +32 -0
  31. data/manual/cops_sorbet.md +424 -0
  32. data/rubocop-sorbet.gemspec +18 -18
  33. data/service.yml +1 -1
  34. data/tasks/cops_documentation.rake +317 -0
  35. metadata +26 -12
  36. data/lib/rubocop_sorbet.rb +0 -22
@@ -1,31 +1,31 @@
1
1
  # frozen_string_literal: true
2
-
3
- lib = File.expand_path('../lib', __FILE__)
4
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
2
+ require_relative "lib/rubocop/sorbet/version"
5
3
 
6
4
  Gem::Specification.new do |spec|
7
- spec.name = 'rubocop-sorbet'
8
- spec.version = '0.3.7'
9
- spec.authors = ['Ufuk Kayserilioglu', 'Alan Wu', 'Alexandre Terrasa', 'Peter Zhu']
10
- spec.email = ['ruby@shopify.com']
5
+ spec.name = "rubocop-sorbet"
6
+ spec.version = RuboCop::Sorbet::VERSION
7
+ spec.authors = ["Ufuk Kayserilioglu", "Alan Wu", "Alexandre Terrasa", "Peter Zhu"]
8
+ spec.email = ["ruby@shopify.com"]
11
9
 
12
- spec.summary = 'Automatic Sorbet code style checking tool.'
13
- spec.homepage = 'https://github.com/shopify/rubocop-sorbet'
14
- spec.license = 'MIT'
10
+ spec.summary = "Automatic Sorbet code style checking tool."
11
+ spec.homepage = "https://github.com/shopify/rubocop-sorbet"
12
+ spec.license = "MIT"
15
13
 
16
- spec.metadata['homepage_uri'] = spec.homepage
17
- spec.metadata['source_code_uri'] = 'https://github.com/shopify/rubocop-sorbet'
14
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = "https://github.com/shopify/rubocop-sorbet"
18
17
 
19
18
  # Specify which files should be added to the gem when it is released.
20
19
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
22
21
  %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
22
  end
24
- spec.bindir = 'exe'
23
+ spec.bindir = "exe"
25
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
- spec.require_paths = ['lib']
25
+ spec.require_paths = ["lib"]
26
+
27
+ spec.add_development_dependency("rspec", "~> 3.7")
28
+ spec.add_development_dependency("unparser")
27
29
 
28
- spec.add_development_dependency('rspec', '~> 3.7')
29
- spec.add_development_dependency('unparser', '~> 0.4.2')
30
- spec.add_development_dependency('rubocop', '~> 0.57')
30
+ spec.add_runtime_dependency("rubocop")
31
31
  end
data/service.yml CHANGED
@@ -3,4 +3,4 @@ owners:
3
3
  - Shopify/sorbet
4
4
  classification: library
5
5
  slack_channels:
6
- - sorbet
6
+ - help-ruby-typing
@@ -0,0 +1,317 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yard'
4
+ require 'rubocop'
5
+ require 'rubocop-sorbet'
6
+
7
+ YARD::Rake::YardocTask.new(:yard_for_generate_documentation) do |task|
8
+ task.files = ['lib/rubocop/cop/**/*.rb']
9
+ task.options = ['--no-output']
10
+ end
11
+
12
+ desc('Generate docs of all cops departments')
13
+ task generate_cops_documentation: :yard_for_generate_documentation do
14
+ def cops_of_department(cops, department)
15
+ cops.with_department(department).sort!
16
+ end
17
+
18
+ def cops_body(config, cop, description, examples_objects, pars)
19
+ content = h2(cop.cop_name)
20
+ content << required_ruby_version(cop)
21
+ content << properties(config, cop)
22
+ content << "#{description}\n"
23
+ content << examples(examples_objects) if examples_objects.count.positive?
24
+ content << configurations(pars)
25
+ content << references(config, cop)
26
+ content
27
+ end
28
+
29
+ def examples(examples_object)
30
+ examples_object.each_with_object(h3('Examples').dup) do |example, content|
31
+ content << h4(example.name) unless example.name == ''
32
+ content << code_example(example)
33
+ end
34
+ end
35
+
36
+ def required_ruby_version(cop)
37
+ return '' unless cop.respond_to?(:required_minimum_ruby_version)
38
+
39
+ <<~NOTE
40
+ !!! Note
41
+
42
+ Required Ruby version: #{cop.required_minimum_ruby_version}
43
+
44
+ NOTE
45
+ end
46
+
47
+ def properties(config, cop)
48
+ header = [
49
+ 'Enabled by default', 'Safe', 'Supports autocorrection', 'VersionAdded',
50
+ 'VersionChanged'
51
+ ]
52
+ config = config.for_cop(cop)
53
+ safe_auto_correct = config.fetch('SafeAutoCorrect', true)
54
+ autocorrect = if cop.new.support_autocorrect?
55
+ "Yes #{'(Unsafe)' unless safe_auto_correct}"
56
+ else
57
+ 'No'
58
+ end
59
+ content = [[
60
+ config.fetch('Enabled') ? 'Enabled' : 'Disabled',
61
+ config.fetch('Safe', true) ? 'Yes' : 'No',
62
+ autocorrect,
63
+ config.fetch('VersionAdded', '-'),
64
+ config.fetch('VersionChanged', '-'),
65
+ ]]
66
+ to_table(header, content) + "\n"
67
+ end
68
+
69
+ def h2(title)
70
+ content = +"\n"
71
+ content << "## #{title}\n"
72
+ content << "\n"
73
+ content
74
+ end
75
+
76
+ def h3(title)
77
+ content = +"\n"
78
+ content << "### #{title}\n"
79
+ content << "\n"
80
+ content
81
+ end
82
+
83
+ def h4(title)
84
+ content = +"#### #{title}\n"
85
+ content << "\n"
86
+ content
87
+ end
88
+
89
+ def code_example(ruby_code)
90
+ content = +"```ruby\n"
91
+ content << ruby_code.text
92
+ .gsub('@good', '# good').gsub('@bad', '# bad').strip
93
+ content << "\n```\n"
94
+ content
95
+ end
96
+
97
+ def configurations(pars)
98
+ return '' if pars.empty?
99
+
100
+ header = ['Name', 'Default value', 'Configurable values']
101
+ configs = pars.each_key.reject { |key| key.start_with?('Supported') }
102
+ content = configs.map do |name|
103
+ configurable = configurable_values(pars, name)
104
+ default = format_table_value(pars[name])
105
+ [name, default, configurable]
106
+ end
107
+
108
+ h3('Configurable attributes') + to_table(header, content)
109
+ end
110
+
111
+ def configurable_values(pars, name)
112
+ case name
113
+ when /^Enforced/
114
+ supported_style_name = RuboCop::Cop::Util.to_supported_styles(name)
115
+ format_table_value(pars[supported_style_name])
116
+ when 'IndentationWidth'
117
+ 'Integer'
118
+ when 'Database'
119
+ format_table_value(pars['SupportedDatabases'])
120
+ else
121
+ case pars[name]
122
+ when String
123
+ 'String'
124
+ when Integer
125
+ 'Integer'
126
+ when Float
127
+ 'Float'
128
+ when true, false
129
+ 'Boolean'
130
+ when Array
131
+ 'Array'
132
+ else
133
+ ''
134
+ end
135
+ end
136
+ end
137
+ # rubocop:enable
138
+
139
+ def to_table(header, content)
140
+ table = [
141
+ header.join(' | '),
142
+ Array.new(header.size, '---').join(' | '),
143
+ ]
144
+ table.concat(content.map { |c| c.join(' | ') })
145
+ table.join("\n") + "\n"
146
+ end
147
+
148
+ def format_table_value(val)
149
+ value =
150
+ case val
151
+ when Array
152
+ if val.empty?
153
+ '`[]`'
154
+ else
155
+ val.map { |config| format_table_value(config) }.join(', ')
156
+ end
157
+ else
158
+ "`#{val.nil? ? '<none>' : val}`"
159
+ end
160
+ value.gsub("#{Dir.pwd}/", '').rstrip
161
+ end
162
+
163
+ def references(config, cop)
164
+ cop_config = config.for_cop(cop)
165
+ urls = RuboCop::Cop::MessageAnnotator.new(
166
+ config, cop.name, cop_config, {}
167
+ ).urls
168
+ return '' if urls.empty?
169
+
170
+ content = h3('References')
171
+ content << urls.map { |url| "* [#{url}](#{url})" }.join("\n")
172
+ content << "\n"
173
+ content
174
+ end
175
+
176
+ def print_cops_of_department(cops, department, config)
177
+ selected_cops = cops_of_department(cops, department).select do |cop|
178
+ cop.to_s.start_with?('RuboCop::Cop::Sorbet')
179
+ end
180
+ return if selected_cops.empty?
181
+
182
+ content = +"# #{department}\n"
183
+ selected_cops.each do |cop|
184
+ content << print_cop_with_doc(cop, config)
185
+ end
186
+ file_name = "#{Dir.pwd}/manual/cops_#{department.downcase}.md"
187
+ File.open(file_name, 'w') do |file|
188
+ puts "* generated #{file_name}"
189
+ file.write(content.strip + "\n")
190
+ end
191
+ end
192
+
193
+ def print_cop_with_doc(cop, config)
194
+ t = config.for_cop(cop)
195
+ non_display_keys = %w[
196
+ Description Enabled StyleGuide Reference Safe SafeAutoCorrect VersionAdded
197
+ VersionChanged
198
+ ]
199
+ pars = t.reject { |k| non_display_keys.include?(k) }
200
+ description = 'No documentation'
201
+ examples_object = []
202
+ YARD::Registry.all(:class).detect do |code_object|
203
+ next unless RuboCop::Cop::Badge.for(code_object.to_s) == cop.badge
204
+
205
+ description = code_object.docstring unless code_object.docstring.blank?
206
+ examples_object = code_object.tags('example')
207
+ end
208
+ cops_body(config, cop, description, examples_object, pars)
209
+ end
210
+
211
+ def table_of_content_for_department(cops, department)
212
+ selected_cops = cops_of_department(cops, department.to_sym).select do |cop|
213
+ cop.to_s.start_with?('RuboCop::Cop::Sorbet')
214
+ end
215
+ return if selected_cops.empty?
216
+
217
+ type_title = department[0].upcase + department[1..-1]
218
+ filename = "cops_#{department.downcase}.md"
219
+ content = +"#### Department [#{type_title}](#{filename})\n\n"
220
+ selected_cops.each do |cop|
221
+ anchor = cop.cop_name.sub('/', '').downcase
222
+ content << "* [#{cop.cop_name}](#{filename}##{anchor})\n"
223
+ end
224
+
225
+ content
226
+ end
227
+
228
+ def print_table_of_contents(cops)
229
+ path = "#{Dir.pwd}/manual/cops.md"
230
+ original = File.read(path)
231
+ content = +"<!-- START_COP_LIST -->\n"
232
+
233
+ content << table_contents(cops)
234
+
235
+ content << "\n<!-- END_COP_LIST -->"
236
+
237
+ content = if original.empty?
238
+ content
239
+ else
240
+ original.sub(
241
+ /<!-- START_COP_LIST -->.+<!-- END_COP_LIST -->/m, content
242
+ )
243
+ end
244
+ File.write(path, content)
245
+ end
246
+
247
+ def table_contents(cops)
248
+ cops
249
+ .departments
250
+ .map(&:to_s)
251
+ .sort
252
+ .map { |department| table_of_content_for_department(cops, department) }
253
+ .reject(&:nil?)
254
+ .join("\n")
255
+ end
256
+
257
+ def assert_manual_synchronized
258
+ # Do not print diff and yield whether exit code was zero
259
+ sh('git diff --quiet manual') do |outcome, _|
260
+ return if outcome
261
+
262
+ # Output diff before raising error
263
+ sh('GIT_PAGER=cat git diff manual')
264
+
265
+ warn 'The manual directory is out of sync. ' \
266
+ 'Run `rake generate_cops_documentation` and commit the results.'
267
+ exit!
268
+ end
269
+ end
270
+
271
+ def main
272
+ cops = RuboCop::Cop::Cop.registry
273
+ config = RuboCop::ConfigLoader.load_file('config/default.yml')
274
+
275
+ YARD::Registry.load!
276
+ cops.departments.sort!.each do |department|
277
+ print_cops_of_department(cops, department, config)
278
+ end
279
+
280
+ print_table_of_contents(cops)
281
+
282
+ assert_manual_synchronized if ENV['CI'] == 'true'
283
+ ensure
284
+ RuboCop::ConfigLoader.default_configuration = nil
285
+ end
286
+
287
+ main
288
+ end
289
+
290
+ desc('Syntax check for the documentation comments')
291
+ task documentation_syntax_check: :yard_for_generate_documentation do
292
+ require 'parser/ruby25'
293
+
294
+ ok = true
295
+ YARD::Registry.load!
296
+ cops = RuboCop::Cop::Cop.registry
297
+ cops.each do |cop|
298
+ examples = YARD::Registry.all(:class).find do |code_object|
299
+ next unless RuboCop::Cop::Badge.for(code_object.to_s) == cop.badge
300
+
301
+ break code_object.tags('example')
302
+ end
303
+
304
+ examples.to_a.each do |example|
305
+ buffer = Parser::Source::Buffer.new('<code>', 1)
306
+ buffer.source = example.text
307
+ parser = Parser::Ruby25.new(RuboCop::AST::Builder.new)
308
+ parser.diagnostics.all_errors_are_fatal = true
309
+ parser.parse(buffer)
310
+ rescue Parser::SyntaxError => e
311
+ path = example.object.file
312
+ puts "#{path}: Syntax Error in an example. #{e}"
313
+ ok = false
314
+ end
315
+ end
316
+ abort unless ok
317
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-sorbet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.7
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ufuk Kayserilioglu
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: exe
13
13
  cert_chain: []
14
- date: 2020-02-12 00:00:00.000000000 Z
14
+ date: 2021-03-12 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: rspec
@@ -31,30 +31,30 @@ dependencies:
31
31
  name: unparser
32
32
  requirement: !ruby/object:Gem::Requirement
33
33
  requirements:
34
- - - "~>"
34
+ - - ">="
35
35
  - !ruby/object:Gem::Version
36
- version: 0.4.2
36
+ version: '0'
37
37
  type: :development
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
40
40
  requirements:
41
- - - "~>"
41
+ - - ">="
42
42
  - !ruby/object:Gem::Version
43
- version: 0.4.2
43
+ version: '0'
44
44
  - !ruby/object:Gem::Dependency
45
45
  name: rubocop
46
46
  requirement: !ruby/object:Gem::Requirement
47
47
  requirements:
48
- - - "~>"
48
+ - - ">="
49
49
  - !ruby/object:Gem::Version
50
- version: '0.57'
51
- type: :development
50
+ version: '0'
51
+ type: :runtime
52
52
  prerelease: false
53
53
  version_requirements: !ruby/object:Gem::Requirement
54
54
  requirements:
55
- - - "~>"
55
+ - - ">="
56
56
  - !ruby/object:Gem::Version
57
- version: '0.57'
57
+ version: '0'
58
58
  description:
59
59
  email:
60
60
  - ruby@shopify.com
@@ -62,8 +62,11 @@ executables: []
62
62
  extensions: []
63
63
  extra_rdoc_files: []
64
64
  files:
65
+ - ".github/CODEOWNERS"
65
66
  - ".github/probots.yml"
67
+ - ".github/stale.yml"
66
68
  - ".gitignore"
69
+ - ".rspec"
67
70
  - ".rubocop.yml"
68
71
  - ".shopify-build/VERSION"
69
72
  - ".shopify-build/rubocop-sorbet.yml"
@@ -82,8 +85,11 @@ files:
82
85
  - lib/rubocop-sorbet.rb
83
86
  - lib/rubocop/cop/sorbet/binding_constants_without_type_alias.rb
84
87
  - lib/rubocop/cop/sorbet/constants_from_strings.rb
88
+ - lib/rubocop/cop/sorbet/forbid_extend_t_sig_helpers_in_shims.rb
85
89
  - lib/rubocop/cop/sorbet/forbid_include_const_literal.rb
86
90
  - lib/rubocop/cop/sorbet/forbid_superclass_const_literal.rb
91
+ - lib/rubocop/cop/sorbet/forbid_untyped_struct_props.rb
92
+ - lib/rubocop/cop/sorbet/one_ancestor_per_line.rb
87
93
  - lib/rubocop/cop/sorbet/sigils/enforce_sigil_order.rb
88
94
  - lib/rubocop/cop/sorbet/sigils/false_sigil.rb
89
95
  - lib/rubocop/cop/sorbet/sigils/has_sigil.rb
@@ -99,14 +105,22 @@ files:
99
105
  - lib/rubocop/cop/sorbet/signatures/parameters_ordering_in_signature.rb
100
106
  - lib/rubocop/cop/sorbet/signatures/signature_build_order.rb
101
107
  - lib/rubocop/cop/sorbet/signatures/signature_cop.rb
102
- - lib/rubocop_sorbet.rb
108
+ - lib/rubocop/cop/sorbet/single_line_rbi_class_module_definitions.rb
109
+ - lib/rubocop/cop/sorbet_cops.rb
110
+ - lib/rubocop/sorbet.rb
111
+ - lib/rubocop/sorbet/inject.rb
112
+ - lib/rubocop/sorbet/version.rb
113
+ - manual/cops.md
114
+ - manual/cops_sorbet.md
103
115
  - rubocop-sorbet.gemspec
104
116
  - service.yml
105
117
  - shipit.yml
118
+ - tasks/cops_documentation.rake
106
119
  homepage: https://github.com/shopify/rubocop-sorbet
107
120
  licenses:
108
121
  - MIT
109
122
  metadata:
123
+ allowed_push_host: https://rubygems.org
110
124
  homepage_uri: https://github.com/shopify/rubocop-sorbet
111
125
  source_code_uri: https://github.com/shopify/rubocop-sorbet
112
126
  post_install_message: