rubocop-sorbet 0.3.6 → 0.5.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.
@@ -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.6'
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
@@ -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.6
4
+ version: 0.5.1
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-01-24 00:00:00.000000000 Z
14
+ date: 2020-10-08 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"
@@ -84,6 +87,7 @@ files:
84
87
  - lib/rubocop/cop/sorbet/constants_from_strings.rb
85
88
  - lib/rubocop/cop/sorbet/forbid_include_const_literal.rb
86
89
  - lib/rubocop/cop/sorbet/forbid_superclass_const_literal.rb
90
+ - lib/rubocop/cop/sorbet/forbid_untyped_struct_props.rb
87
91
  - lib/rubocop/cop/sorbet/sigils/enforce_sigil_order.rb
88
92
  - lib/rubocop/cop/sorbet/sigils/false_sigil.rb
89
93
  - lib/rubocop/cop/sorbet/sigils/has_sigil.rb
@@ -99,14 +103,21 @@ files:
99
103
  - lib/rubocop/cop/sorbet/signatures/parameters_ordering_in_signature.rb
100
104
  - lib/rubocop/cop/sorbet/signatures/signature_build_order.rb
101
105
  - lib/rubocop/cop/sorbet/signatures/signature_cop.rb
102
- - lib/rubocop_sorbet.rb
106
+ - lib/rubocop/cop/sorbet_cops.rb
107
+ - lib/rubocop/sorbet.rb
108
+ - lib/rubocop/sorbet/inject.rb
109
+ - lib/rubocop/sorbet/version.rb
110
+ - manual/cops.md
111
+ - manual/cops_sorbet.md
103
112
  - rubocop-sorbet.gemspec
104
113
  - service.yml
105
114
  - shipit.yml
115
+ - tasks/cops_documentation.rake
106
116
  homepage: https://github.com/shopify/rubocop-sorbet
107
117
  licenses:
108
118
  - MIT
109
119
  metadata:
120
+ allowed_push_host: https://rubygems.org
110
121
  homepage_uri: https://github.com/shopify/rubocop-sorbet
111
122
  source_code_uri: https://github.com/shopify/rubocop-sorbet
112
123
  post_install_message: