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.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +2 -0
- data/.github/probots.yml +1 -1
- data/.github/stale.yml +20 -0
- data/.gitignore +0 -2
- data/.rspec +3 -0
- data/.rubocop.yml +2 -12
- data/.travis.yml +3 -1
- data/Gemfile +6 -7
- data/Gemfile.lock +27 -42
- data/README.md +64 -6
- data/Rakefile +34 -5
- data/config/default.yml +143 -2
- data/lib/rubocop-sorbet.rb +9 -1
- data/lib/rubocop/cop/sorbet/binding_constants_without_type_alias.rb +24 -1
- data/lib/rubocop/cop/sorbet/forbid_extend_t_sig_helpers_in_shims.rb +53 -0
- data/lib/rubocop/cop/sorbet/forbid_include_const_literal.rb +0 -40
- data/lib/rubocop/cop/sorbet/forbid_superclass_const_literal.rb +0 -17
- data/lib/rubocop/cop/sorbet/forbid_untyped_struct_props.rb +58 -0
- data/lib/rubocop/cop/sorbet/one_ancestor_per_line.rb +75 -0
- data/lib/rubocop/cop/sorbet/sigils/enforce_sigil_order.rb +11 -1
- data/lib/rubocop/cop/sorbet/signatures/enforce_signatures.rb +19 -10
- data/lib/rubocop/cop/sorbet/signatures/parameters_ordering_in_signature.rb +14 -6
- data/lib/rubocop/cop/sorbet/signatures/signature_build_order.rb +22 -3
- data/lib/rubocop/cop/sorbet/single_line_rbi_class_module_definitions.rb +46 -0
- data/lib/rubocop/cop/sorbet_cops.rb +25 -0
- data/lib/rubocop/sorbet.rb +15 -0
- data/lib/rubocop/sorbet/inject.rb +20 -0
- data/lib/rubocop/sorbet/version.rb +6 -0
- data/manual/cops.md +32 -0
- data/manual/cops_sorbet.md +424 -0
- data/rubocop-sorbet.gemspec +18 -18
- data/service.yml +1 -1
- data/tasks/cops_documentation.rake +317 -0
- metadata +26 -12
- data/lib/rubocop_sorbet.rb +0 -22
data/rubocop-sorbet.gemspec
CHANGED
@@ -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 =
|
8
|
-
spec.version =
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
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 =
|
13
|
-
spec.homepage =
|
14
|
-
spec.license =
|
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[
|
17
|
-
spec.metadata[
|
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(
|
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 =
|
23
|
+
spec.bindir = "exe"
|
25
24
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
|
-
spec.require_paths = [
|
25
|
+
spec.require_paths = ["lib"]
|
26
|
+
|
27
|
+
spec.add_development_dependency("rspec", "~> 3.7")
|
28
|
+
spec.add_development_dependency("unparser")
|
27
29
|
|
28
|
-
spec.
|
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
@@ -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.
|
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:
|
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
|
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
|
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
|
51
|
-
type: :
|
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
|
+
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/
|
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:
|