rubocop-on-rbs 0.2.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 +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/CODE_OF_CONDUCT.md +84 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +186 -0
  6. data/config/default.yml +165 -0
  7. data/lib/rubocop/cop/rbs/layout/comment_indentation.rb +69 -0
  8. data/lib/rubocop/cop/rbs/layout/empty_lines_around_overloads.rb +49 -0
  9. data/lib/rubocop/cop/rbs/layout/end_alignment.rb +55 -0
  10. data/lib/rubocop/cop/rbs/layout/extra_spacing.rb +55 -0
  11. data/lib/rubocop/cop/rbs/layout/indentation_width.rb +64 -0
  12. data/lib/rubocop/cop/rbs/layout/overload_indentation.rb +69 -0
  13. data/lib/rubocop/cop/rbs/layout/space_around_arrow.rb +56 -0
  14. data/lib/rubocop/cop/rbs/layout/space_around_braces.rb +77 -0
  15. data/lib/rubocop/cop/rbs/layout/space_before_colon.rb +43 -0
  16. data/lib/rubocop/cop/rbs/layout/space_before_overload.rb +46 -0
  17. data/lib/rubocop/cop/rbs/layout/trailing_whitespace.rb +42 -0
  18. data/lib/rubocop/cop/rbs/lint/syntax.rb +18 -0
  19. data/lib/rubocop/cop/rbs/lint/type_params_arity.rb +170 -0
  20. data/lib/rubocop/cop/rbs/lint/useless_overload_type_params.rb +57 -0
  21. data/lib/rubocop/cop/rbs/lint/will_syntax_error.rb +205 -0
  22. data/lib/rubocop/cop/rbs/style/block_return_boolish.rb +35 -0
  23. data/lib/rubocop/cop/rbs/style/classic_type.rb +73 -0
  24. data/lib/rubocop/cop/rbs/style/duplicated_type.rb +61 -0
  25. data/lib/rubocop/cop/rbs/style/initialize_return_type.rb +40 -0
  26. data/lib/rubocop/cop/rbs/style/merge_untyped.rb +91 -0
  27. data/lib/rubocop/cop/rbs/style/optional_nil.rb +50 -0
  28. data/lib/rubocop/cop/rbs/style/true_false.rb +84 -0
  29. data/lib/rubocop/cop/rbs_cops.rb +28 -0
  30. data/lib/rubocop/rbs/cop_base.rb +91 -0
  31. data/lib/rubocop/rbs/inject.rb +20 -0
  32. data/lib/rubocop/rbs/processed_rbs_source.rb +29 -0
  33. data/lib/rubocop/rbs/version.rb +7 -0
  34. data/lib/rubocop/rbs.rb +15 -0
  35. data/lib/rubocop-on-rbs.rb +13 -0
  36. metadata +106 -0
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RBS
6
+ module Layout
7
+ # @example default
8
+ # # bad
9
+ # def foo: () -> void
10
+ #
11
+ # # good
12
+ # def foo: () -> void
13
+ class ExtraSpacing < RuboCop::RBS::CopBase
14
+ extend AutoCorrector
15
+
16
+ MSG = 'Unnecessary spacing detected.'
17
+
18
+ def on_rbs_new_investigation
19
+ aligned_comments = aligned_locations()
20
+ tokens = processed_rbs_source.tokens.reject { |t| t.type == :tTRIVIA }
21
+ tokens.each_cons(2) do |a, b|
22
+ next unless a&.location
23
+ next unless b&.location
24
+
25
+ if (b.type == :tCOMMENT || b.type == :tLINECOMMENT)
26
+ next if aligned_comments.include?(b.location.start_line)
27
+ end
28
+ next if a.location.end_line != b.location.start_line
29
+ next if a.location.end_pos + 1 >= b.location.start_pos
30
+
31
+ space = range_between(a.location.end_pos, b.location.start_pos - 1)
32
+ add_offense(space) do |corrector|
33
+ corrector.remove(space)
34
+ end
35
+ end
36
+ end
37
+
38
+ def aligned_locations
39
+ comments = processed_rbs_source.tokens.select(&:comment?)
40
+ Set.new.tap do |aligned|
41
+ comments.map(&:location).each_cons(2) do |loc1, loc2|
42
+ next unless loc1
43
+ next unless loc2
44
+
45
+ if loc1.start_column == loc2.start_column
46
+ aligned << loc1.start_line << loc2.start_line
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RBS
6
+ module Layout
7
+ # @example default
8
+ # # bad
9
+ # class Foo
10
+ # def foo: () -> void
11
+ # end
12
+ #
13
+ # # good
14
+ # class Foo
15
+ # def foo: () -> void
16
+ # end
17
+ class IndentationWidth < RuboCop::RBS::CopBase
18
+ extend AutoCorrector
19
+
20
+ def on_rbs_new_investigation
21
+ @first_char_columns = processed_source.raw_source.each_line.map do |line|
22
+ line.index(/[^[:space:]]/) || 0
23
+ end
24
+ processed_rbs_source.decls.each do |decl|
25
+ check_indentation(decl, expect: 0)
26
+ end
27
+ end
28
+
29
+ def check_indentation(decl, expect:)
30
+ if decl.respond_to?(:members)
31
+ check(decl, expect: expect)
32
+ decl.members.each do |member|
33
+ check_indentation(member, expect: expect + 2)
34
+ end
35
+ else
36
+ check(decl, expect: expect)
37
+ end
38
+ end
39
+
40
+ def check(decl, expect:)
41
+ line_start_pos = line_start_pos(decl)
42
+ actual = @first_char_columns[decl.location.start_line - 1]
43
+ if actual != expect
44
+ range = range_between(line_start_pos, line_start_pos + actual)
45
+ message = "Use #{expect} (not #{actual}) spaces for indentation."
46
+ add_offense(range, message: message) do |corrector|
47
+ corrector.replace(range, ' ' * expect)
48
+ end
49
+ end
50
+ end
51
+
52
+ def line_start_pos(decl)
53
+ rindex = processed_source.raw_source.rindex(/\R/, decl.location.start_pos)
54
+ if rindex
55
+ rindex + 1
56
+ else
57
+ 0
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RBS
6
+ module Layout
7
+ # @example default
8
+ # # bad
9
+ # def foo: () -> String | () -> (Integer)
10
+ #
11
+ # # bad
12
+ # def foo: () -> String
13
+ # | () -> (Integer)
14
+ #
15
+ # # bad
16
+ # def foo: () -> String |
17
+ # () -> (Integer)
18
+ #
19
+ # # good
20
+ # def foo: () -> String
21
+ # | () -> Integer
22
+ class OverloadIndentation < RuboCop::RBS::CopBase
23
+ extend AutoCorrector
24
+
25
+ def on_rbs_def(decl)
26
+ base_pos = decl.location.start_pos
27
+ base_col = decl.location.start_column
28
+ overload_starts = decl.overloads.map { |overload| overload.method_type.location.start_pos }
29
+ tokens = ::RBS::Parser.lex(decl.location.source).value.reject { |t| t.type == :tTRIVIA }
30
+ first_colon_column = base_col + tokens.find { |t| t.type == :pCOLON }&.location&.start_column
31
+ ([nil] + tokens).each_cons(3) do |before, bar, after|
32
+ next unless before
33
+ next unless bar
34
+ next unless after
35
+ next unless bar&.type == :pBAR
36
+
37
+ loc = bar&.location
38
+ next unless loc
39
+ next unless overload_starts.include?(base_pos + after.location.start_pos)
40
+
41
+ if before.location.end_line == bar.location.start_line
42
+ range = range_between(base_pos + bar.location.start_pos, base_pos + bar.location.end_pos)
43
+ add_offense(range, message: "Insert newline before `|`") do |corrector|
44
+ space = range_between(base_pos + before.location.end_pos, base_pos + bar.location.start_pos)
45
+ corrector.replace(space, "\n#{' ' * first_colon_column}")
46
+ end
47
+ end
48
+
49
+ if bar.location.end_line != after.location.start_line
50
+ range = range_between(base_pos + bar.location.start_pos, base_pos + bar.location.end_pos)
51
+ add_offense(range, message: "Remove newline after `|`") do |corrector|
52
+ space = range_between(base_pos + bar.location.end_pos, base_pos + after.location.start_pos)
53
+ corrector.replace(space, ' ')
54
+ end
55
+ elsif bar.location.start_column != first_colon_column
56
+ range = range_between(base_pos + bar.location.start_pos, base_pos + bar.location.end_pos)
57
+ add_offense(range, message: 'Indent the `|` to the first `:`') do |corrector|
58
+ space = range_between(base_pos + bar.location.start_pos - bar.location.start_column,
59
+ base_pos + bar.location.start_pos)
60
+ corrector.replace(space, ' ' * first_colon_column)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RBS
6
+ module Layout
7
+ # @example default
8
+ # # bad
9
+ # def foo: ()->void
10
+ #
11
+ # # bad
12
+ # def bar: () { ()->void } -> void
13
+ #
14
+ # # good
15
+ # def foo: () -> void
16
+ #
17
+ # # good
18
+ # def bar: () { () -> void } -> void
19
+ class SpaceAroundArrow < RuboCop::RBS::CopBase
20
+ extend AutoCorrector
21
+
22
+ MSG_BEFORE = 'Use one space before `->`.'
23
+ MSG_AFTER = 'Use one space after `->`.'
24
+
25
+ # @sig decl: ::RBS::AST::Members::MethodDefinition
26
+ def on_rbs_def(decl)
27
+ base = decl.location.start_pos
28
+ tokens = ::RBS::Parser.lex(decl.location.source).value.reject { |t| t.type == :tTRIVIA }
29
+ ([nil] + tokens).each_cons(3) do |before, token, after|
30
+ next unless token&.type == :pARROW
31
+
32
+ loc = token&.location
33
+ next unless loc
34
+
35
+ if before && (before.location.end_pos + 1 != loc.start_pos)
36
+ arrow = location_to_range(loc).adjust(begin_pos: base, end_pos: base)
37
+ add_offense(arrow, message: MSG_BEFORE) do |corrector|
38
+ range = range_between(before.location.end_pos, loc.start_pos)
39
+ corrector.replace(range.adjust(begin_pos: base, end_pos: base), ' ')
40
+ end
41
+ end
42
+
43
+ if loc.end_pos + 1 != after.location.start_pos
44
+ arrow = location_to_range(loc).adjust(begin_pos: base, end_pos: base)
45
+ add_offense(arrow, message: MSG_AFTER) do |corrector|
46
+ range = range_between(loc.end_pos, after.location.start_pos)
47
+ corrector.replace(range.adjust(begin_pos: base, end_pos: base), ' ')
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RBS
6
+ module Layout
7
+ # @example default
8
+ # # bad
9
+ # def bar: (){() -> void}-> void
10
+ #
11
+ # # good
12
+ # def bar: () { () -> void } -> void
13
+ class SpaceAroundBraces < RuboCop::RBS::CopBase
14
+ extend AutoCorrector
15
+
16
+ # @sig decl: ::RBS::AST::Members::MethodDefinition
17
+ def on_rbs_def(decl)
18
+ paren_level = 0
19
+ tokens = ::RBS::Parser.lex(decl.location.source).value.reject { |t| t.type == :tTRIVIA }
20
+ ([nil] + tokens).each_cons(3) do |before, token, after|
21
+ case token.type
22
+ when :pLPAREN # '('
23
+ paren_level += 1
24
+ when :pRPAREN # ')'
25
+ paren_level -= 1
26
+ when :pLBRACE # '{'
27
+ next unless before&.type != :pQUESTION # '?'
28
+ next unless (after.type == :pLPAREN || after.type == :pARROW)
29
+ next unless paren_level == 0
30
+
31
+ check_around_space(decl.location, before, token, after)
32
+ when :pRBRACE # '}'
33
+ next unless after.type == :pARROW
34
+ next unless paren_level == 0
35
+
36
+ check_around_space(decl.location, before, token, after)
37
+ end
38
+ end
39
+ end
40
+
41
+ def check_around_space(base_loc, before, token, after)
42
+ return unless before.nil? || before.location.end_line == token.location.start_line
43
+ return unless token.location.end_line == after.location.start_line
44
+
45
+ if before && (before.location.end_pos + 1 != token.location.start_pos)
46
+ brace = range_between(
47
+ base_loc.start_pos + token.location.start_pos,
48
+ base_loc.start_pos + token.location.end_pos,
49
+ )
50
+ add_offense(brace, message: "Use one space before `#{token.location.source}`.") do |corrector|
51
+ range = range_between(
52
+ base_loc.start_pos + before.location.end_pos,
53
+ base_loc.start_pos + token.location.start_pos
54
+ )
55
+ corrector.replace(range, ' ')
56
+ end
57
+ end
58
+
59
+ if token.location.end_pos + 1 != after.location.start_pos
60
+ brace = range_between(
61
+ base_loc.start_pos + token.location.start_pos,
62
+ base_loc.start_pos + token.location.end_pos,
63
+ )
64
+ add_offense(brace, message: "Use one space after `#{token.location.source}`.") do |corrector|
65
+ range = range_between(
66
+ base_loc.start_pos + token.location.end_pos,
67
+ base_loc.start_pos + after.location.start_pos
68
+ )
69
+ corrector.replace(range, ' ')
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RBS
6
+ module Layout
7
+ # @example default
8
+ # # bad
9
+ # def foo : () -> void
10
+ #
11
+ # # good
12
+ # def foo: () -> void
13
+ class SpaceBeforeColon < RuboCop::RBS::CopBase
14
+ extend AutoCorrector
15
+
16
+ MSG = 'Avoid using spaces before `:`.'
17
+
18
+ # @sig decl: ::RBS::AST::Members::MethodDefinition
19
+ def on_rbs_def(decl)
20
+ source = processed_source.raw_source
21
+ loc = decl.location
22
+ colon_start_pos = source.index(':', loc.start_pos)
23
+ return unless colon_start_pos
24
+
25
+ word_before_colon_pos = source.rindex(/[^\s]/, colon_start_pos - 1)
26
+ return unless word_before_colon_pos
27
+
28
+ if word_before_colon_pos + 1 != colon_start_pos
29
+ colon = range_between(colon_start_pos, colon_start_pos + 1)
30
+ add_offense(colon) do |corrector|
31
+ range = range_between(
32
+ word_before_colon_pos + 1,
33
+ colon_start_pos,
34
+ )
35
+ corrector.remove(range)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RBS
6
+ module Layout
7
+ # @example default
8
+ # # bad
9
+ # def foo:() -> void
10
+ # | () -> void
11
+ #
12
+ # # good
13
+ # def foo: () -> void
14
+ # | () -> void
15
+ class SpaceBeforeOverload < RuboCop::RBS::CopBase
16
+ extend AutoCorrector
17
+
18
+ MSG = 'Use one space before overload.'
19
+
20
+ # @sig decl: ::RBS::AST::Members::MethodDefinition
21
+ def on_rbs_def(decl)
22
+ source = processed_source.raw_source
23
+ decl.overloads.each_with_index do |overload, i|
24
+ loc = overload.method_type.location
25
+ overload_char = i == 0 ? ':' : '|'
26
+
27
+ char_start_pos = source.rindex(overload_char, loc.start_pos)
28
+ next unless char_start_pos
29
+
30
+ word_after_char_pos = source.index(/[^\s]/, char_start_pos + 1)
31
+ next unless word_after_char_pos
32
+
33
+ if char_start_pos + 2 != word_after_char_pos
34
+ char = range_between(char_start_pos, char_start_pos + 1)
35
+ add_offense(char) do |corrector|
36
+ range = range_between(char_start_pos + 1, word_after_char_pos)
37
+ corrector.replace(range, ' ')
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RBS
6
+ module Layout
7
+ # @example default
8
+ # # bad
9
+ # class Foo[:space:]
10
+ # def foo: () -> void[:space:]
11
+ # end[:space:]
12
+ #
13
+ # # good
14
+ # class Foo
15
+ # def foo: () -> void
16
+ # end
17
+ class TrailingWhitespace < RuboCop::RBS::CopBase
18
+ extend AutoCorrector
19
+
20
+ MSG = "Trailing whitespace detected."
21
+
22
+ def on_rbs_new_investigation
23
+ total = 0
24
+ processed_source.raw_source.each_line do |line|
25
+ total += line.bytesize
26
+ chomped = line.chomp
27
+ next unless chomped.end_with?(' ', "\t")
28
+
29
+ range = range_between(
30
+ total - line.bytesize + chomped.rstrip.bytesize,
31
+ total - line.bytesize + chomped.bytesize,
32
+ )
33
+ add_offense(range) do |corrector|
34
+ corrector.remove(range)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RBS
6
+ module Lint
7
+ # Just only for syntax error
8
+ class Syntax < RuboCop::RBS::CopBase
9
+ def on_rbs_parsing_error
10
+ e = processed_rbs_source.error or raise
11
+ message = "#{e.error_message}, token=`#{e.location.source}` (#{e.token_type})"
12
+ add_offense(location_to_range(e.location), message:, severity: :fatal)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RBS
6
+ module Lint
7
+ # This cop checks the arity of type parameters arity.
8
+ # You can add expect settings in your .rubocop.yml.
9
+ #
10
+ # @example Expects settings
11
+ # RBS/Lint/TypeParamsArity:
12
+ # Expects:
13
+ # Your::Class: 1
14
+ #
15
+ # @example default
16
+ # # bad
17
+ # type a = Array[Integer, String, Symbol]
18
+ #
19
+ # # bad
20
+ # class Foo
21
+ # include Enumerable
22
+ # end
23
+ class TypeParamsArity < RuboCop::RBS::CopBase
24
+ Types = ::RBS::Types
25
+
26
+ def on_rbs_module(decl)
27
+ check_type_params(decl)
28
+ decl.self_types.each do |self_type|
29
+ check(
30
+ name: self_type.name,
31
+ args: self_type.args,
32
+ location: self_type.location
33
+ )
34
+ end
35
+ check_each_mixin(decl)
36
+ end
37
+
38
+ def on_rbs_class(decl)
39
+ check_type_params(decl)
40
+ if decl.super_class
41
+ check(
42
+ name: decl.super_class.name,
43
+ args: decl.super_class.args,
44
+ location: decl.super_class.location
45
+ )
46
+ end
47
+ check_each_mixin(decl)
48
+ end
49
+
50
+ def on_rbs_interface(decl)
51
+ check_type_params(decl)
52
+ check_each_mixin(decl)
53
+ end
54
+
55
+ def on_rbs_constant(const)
56
+ check_type(const.type)
57
+ end
58
+
59
+ def on_rbs_global(global)
60
+ check_type(global.type)
61
+ end
62
+
63
+ def on_rbs_type_alias(decl)
64
+ check_type_params(decl)
65
+ check_type(decl.type)
66
+ decl.type.each_type do |type|
67
+ check_type(type)
68
+ end
69
+ end
70
+
71
+ def on_rbs_def(member)
72
+ member.overloads.each do |overload|
73
+ overload.method_type.each_type do |type|
74
+ check_type(type)
75
+ end
76
+ end
77
+ end
78
+
79
+ def on_rbs_attribute(attr)
80
+ check_type(attr.type)
81
+ end
82
+
83
+ def check_each_mixin(decl)
84
+ decl.each_mixin do |mixin|
85
+ check(
86
+ name: mixin.name,
87
+ args: mixin.args,
88
+ location: mixin.location
89
+ )
90
+ end
91
+ end
92
+
93
+ def check_type_params(decl)
94
+ decl.type_params.each do |type_param|
95
+ if type_param.upper_bound
96
+ check(
97
+ name: type_param.upper_bound.name,
98
+ args: type_param.upper_bound.args,
99
+ location: type_param.upper_bound.location
100
+ )
101
+ end
102
+ end
103
+ end
104
+
105
+ def check_type(type)
106
+ case type
107
+ when Types::Record,
108
+ Types::Tuple,
109
+ Types::Union,
110
+ Types::Intersection,
111
+ Types::Optional,
112
+ Types::Proc
113
+ type.each_type.each do |t|
114
+ check_type(t)
115
+ end
116
+ when Types::Interface,
117
+ Types::Alias,
118
+ Types::ClassInstance
119
+ check(
120
+ name: type.name,
121
+ args: type.args,
122
+ location: type.location,
123
+ )
124
+ end
125
+ end
126
+
127
+ def check(name:, args:, location:)
128
+ return unless name.absolute?
129
+ return unless location
130
+
131
+ expect_size = expects[name.to_s]
132
+ return unless expect_size
133
+ return unless expect_size != args.size
134
+
135
+ message = if args.size == 0
136
+ "Type `#{name}` is generic but used as a non generic type."
137
+ else
138
+ "Type `#{name}` expects #{expect_size} arguments, but #{args.size} arguments are given."
139
+ end
140
+ add_offense(
141
+ location_to_range(location),
142
+ message: message,
143
+ severity: :error
144
+ )
145
+ end
146
+
147
+ def expects
148
+ @expects ||= begin
149
+ expects = cop_config['Expects']
150
+ raise "Expects must be a hash" unless expects.is_a?(Hash)
151
+
152
+ unless expects.all? { |k, _| k.is_a?(String) }
153
+ raise "[RBS/Lint/TypeParamsArity] Keys of Expects must be strings"
154
+ end
155
+ unless expects.all? { |_, v| v.is_a?(Integer) }
156
+ raise "[RBS/Lint/TypeParamsArity] Values of Expects must be integers"
157
+ end
158
+
159
+ expects.transform_keys! do |k|
160
+ k.start_with?('::') ? k : "::#{k}"
161
+ end
162
+
163
+ expects
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end