rubocop-crystal 0.0.2 → 0.0.4

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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +16 -0
  3. data/.github/workflows/rubocop.yml +16 -0
  4. data/.github/workflows/test.yml +1 -1
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +37 -0
  7. data/CHANGELOG.md +26 -0
  8. data/README.md +6 -2
  9. data/config/default.yml +36 -1
  10. data/lib/rubocop/cop/crystal/enumerable_reduce.rb +58 -0
  11. data/lib/rubocop/cop/crystal/enumerable_size.rb +31 -0
  12. data/lib/rubocop/cop/crystal/file_extension.rb +1 -0
  13. data/lib/rubocop/cop/crystal/file_read_lines.rb +115 -0
  14. data/lib/rubocop/cop/crystal/interpolation_in_single_quotes.rb +3 -2
  15. data/lib/rubocop/cop/crystal/method_name_starting_with_uppercase_letter.rb +30 -0
  16. data/lib/rubocop/cop/crystal/method_returning_char.rb +54 -0
  17. data/lib/rubocop/cop/crystal/require_at_top_level.rb +35 -0
  18. data/lib/rubocop/cop/crystal/require_relative.rb +6 -5
  19. data/lib/rubocop/cop/crystal_cops.rb +6 -0
  20. data/lib/rubocop/crystal/plugin.rb +28 -0
  21. data/lib/rubocop-crystal.rb +1 -3
  22. data/rubocop-crystal.gemspec +5 -2
  23. data/spec/rubocop/cop/crystal/enumerable_reduce_spec.rb +101 -0
  24. data/spec/rubocop/cop/crystal/enumerable_size_spec.rb +64 -0
  25. data/spec/rubocop/cop/crystal/file_read_lines_spec.rb +156 -0
  26. data/spec/rubocop/cop/crystal/interpolation_in_single_quotes_spec.rb +30 -0
  27. data/spec/rubocop/cop/crystal/method_name_starting_with_uppercase_letter_spec.rb +35 -0
  28. data/spec/rubocop/cop/crystal/method_returning_char_spec.rb +150 -0
  29. data/spec/rubocop/cop/crystal/require_at_top_level_spec.rb +42 -0
  30. data/spec/rubocop/cop/crystal/require_relative_spec.rb +51 -0
  31. data/spec/spec_helper.rb +12 -0
  32. data/test/string.rb +55 -0
  33. metadata +41 -11
  34. data/lib/rubocop/crystal/inject.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3beb9db336ee0d664fdd093775a9c14dc9512c8ec84be250ec177a6763b4924d
4
- data.tar.gz: 015acc39018447fef5d62304bf91dddab3b66faa795149f00b3e447de6db5990
3
+ metadata.gz: 3e7cede16a18df18a0021747a7a5305d1503af8229d0a2790242b3b4ac5e27c2
4
+ data.tar.gz: 2373953ed41151d8a00749f28db4ac84ff144db91e02413b50e71f4d97c5627b
5
5
  SHA512:
6
- metadata.gz: 2077733fe1bc6189b71854a249601f8294366a2d86cd013822bb6ec9fd1371e9229ad6622dc169426ae35c75b1f65e26f5308538a749e2b02fb7239ac99a087e
7
- data.tar.gz: ea546caea1eeb127c946df8c97ec77ba7e5572db33c63402bba1ad4ffeeaca05d19d402d6c6a96aa37d4641f2aeda11d95a8f26a2e6b1c18a473961b8468d221
6
+ metadata.gz: e33199a6a2a74eb3e7739f0b2355b58e65b89d6bcc9001c4c62e246eea89fa7cb098dfb274da12b7dac220f90de5d672d94f2abb27d078c8a2ec273f8b81f990
7
+ data.tar.gz: bccb8bb8fba84d6b2208cb578bcbcc7e7a3c6c2202cee707e8fe511018ff128cb4dd6113aefd11912df78a9971183ea8497fe344b684002aa3144279756732c4
@@ -0,0 +1,16 @@
1
+ name: RSpec
2
+ on: [push, pull_request]
3
+ jobs:
4
+ test:
5
+ runs-on: ubuntu-latest
6
+ steps:
7
+ - name: Download source
8
+ uses: actions/checkout@v4
9
+ - name: Install Ruby
10
+ uses: ruby/setup-ruby@v1
11
+ with:
12
+ ruby-version: '3.1.2'
13
+ - name: Install dependencies
14
+ run: gem install rspec rubocop
15
+ - name: Run spec
16
+ run: rspec
@@ -0,0 +1,16 @@
1
+ name: Rubocop
2
+ on: [push, pull_request]
3
+ jobs:
4
+ test:
5
+ runs-on: ubuntu-latest
6
+ steps:
7
+ - name: Download source
8
+ uses: actions/checkout@v4
9
+ - name: Install Ruby
10
+ uses: ruby/setup-ruby@v1
11
+ with:
12
+ ruby-version: '3.1.2'
13
+ - name: Install rubocop
14
+ run: gem install rubocop
15
+ - name: Run rubocop
16
+ run: rubocop
@@ -21,6 +21,6 @@ jobs:
21
21
  gem build rubocop-crystal
22
22
  gem install rubocop-crystal
23
23
  - name: Convert test files
24
- run: rubocop --require rubocop-crystal --fail-level fatal -A test
24
+ run: rubocop --plugin rubocop-crystal -c config/default.yml --fail-level fatal -A test
25
25
  - name: Run tests (Crystal)
26
26
  run: crystal run test/string.cr
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,37 @@
1
+ plugins:
2
+ - rubocop-internal_affairs
3
+
4
+ AllCops:
5
+ NewCops: enable
6
+ Exclude:
7
+ - 'test/*'
8
+
9
+ Gemspec/RequireMFA:
10
+ Enabled: false
11
+
12
+ # TODO: Find out what ruby version we actually require and then remove this.
13
+ Gemspec/RequiredRubyVersion:
14
+ Enabled: false
15
+
16
+ InternalAffairs/OnSendWithoutOnCSend:
17
+ Exclude:
18
+ - 'lib/rubocop/cop/crystal/require_at_top_level.rb'
19
+ - 'lib/rubocop/cop/crystal/require_relative.rb'
20
+
21
+ Layout/LineLength:
22
+ Enabled: false
23
+
24
+ Metrics:
25
+ Enabled: false
26
+
27
+ Naming/FileName:
28
+ Enabled: false
29
+
30
+ Style/Documentation:
31
+ Enabled: False
32
+
33
+ Style/FrozenStringLiteralComment:
34
+ Enabled: False
35
+
36
+ Style/MutableConstant:
37
+ Enabled: False
data/CHANGELOG.md CHANGED
@@ -1,3 +1,29 @@
1
+ ## 0.0.4 (2025-09-21)
2
+
3
+ ### New features
4
+
5
+ * Add Crystal/EnumerableReduce cop ([@zopolis4][])
6
+ * Add Crystal/EnumerableSize cop ([@zopolis4][])
7
+ * Add Crystal/FileReadLines cop ([@zopolis4][])
8
+ * Add Crystal/MethodReturningChar cop ([@zopolis4][])
9
+ * Add Crystal/RequireAtTopLevel cop ([@zopolis4][])
10
+
11
+ ### Changes
12
+
13
+ * Add files ending in .cr to the list of files to be checked in the default config ([@zopolis4][])
14
+
15
+ ## 0.0.3 (2025-04-28)
16
+
17
+ ### New features
18
+
19
+ * Enable Style/BlockComments cop ([@zopolis4][])
20
+ * Enable Style/WhileUntilDo cop ([@zopolis4][])
21
+ * Add Crystal/MethodNameStartingWithUppercaseLetter cop ([@zopolis4][])
22
+
23
+ ### Changes
24
+
25
+ * Convert to Rubocop plugin ([@zopolis4][])
26
+
1
27
  ## 0.0.2 (2024-08-01)
2
28
 
3
29
  ### New features
data/README.md CHANGED
@@ -10,7 +10,7 @@ Getting static type information about Ruby files isn't the difficult part, the p
10
10
 
11
11
  Inserting Crystal types into Ruby code is a no-go, because that causes `Lint/Syntax` errors in RuboCop.
12
12
 
13
- Possible paths foward:
13
+ Possible paths forward:
14
14
  - Modify the parser to accept Crystal type declarations, or at least not break on them.
15
15
  - Modify Crystal to accept type declarations from `.rbs` and/or `.rbi` files.
16
16
  - Modify Crystal to accept type declarations from sorbet/rbs-inline/other annotations.
@@ -38,5 +38,9 @@ gem install rubocop-crystal
38
38
  ## Usage
39
39
 
40
40
  ```
41
- rubocop --require rubocop-crystal
41
+ rubocop --plugin rubocop-crystal
42
42
  ```
43
+
44
+ Note that there are some differences between Ruby and Crystal that can be automatically resolved, while some (at least for now) require manual intervention.
45
+
46
+ If you wish to only process the autocorrectable offenses, add `--disable-uncorrectable`, while reporting only the offenses requiring manual intervention is waiting on rubocop/rubocop#13275.
data/config/default.yml CHANGED
@@ -1,14 +1,43 @@
1
1
  AllCops:
2
2
  DisabledByDefault: true
3
+ inherit_mode:
4
+ merge:
5
+ - Include
6
+ Include:
7
+ - '**/*.cr'
8
+
9
+ Crystal/EnumerableReduce:
10
+ Description: 'This cop replaces .inject with .reduce and converts reducer functions to their block counterparts.'
11
+ Enabled: true
12
+
13
+ Crystal/EnumerableSize:
14
+ Description: 'This cop replaces .length and .count with .size when they are used as aliases for .size'
15
+ Enabled: true
3
16
 
4
17
  Crystal/FileExtension:
5
18
  Description: 'This cop renames files ending in `.rb` to `.cr`.'
6
19
  Enabled: true
7
20
 
21
+ Crystal/FileReadLines:
22
+ Description: "This cop replaces Ruby's IO.readlines with Crystal's File.read_lines or IO.each_line."
23
+ Enabled: true
24
+
8
25
  Crystal/InterpolationInSingleQuotes:
9
26
  Description: "This cop uses %q in place of ' if the enclosed string would be affected by interpolation."
10
27
  Enabled: true
11
28
 
29
+ Crystal/MethodNameStartingWithUppercaseLetter:
30
+ Description: 'This cop detects method names that start with uppercase letters.'
31
+ Enabled: true
32
+
33
+ Crystal/MethodReturningChar:
34
+ Description: 'This cop detects methods that, in Crystal, return the Char type instead of a 1-character string, and modifies them accordingly.'
35
+ Enabled: true
36
+
37
+ Crystal/RequireAtTopLevel:
38
+ Description: 'This cop detects instances of require that are not at the top level.'
39
+ Enabled: true
40
+
12
41
  Crystal/RequireRelative:
13
42
  Description: 'This cop replaces require_relative with require while maintaining behavior.'
14
43
  Enabled: true
@@ -16,9 +45,15 @@ Crystal/RequireRelative:
16
45
  Lint/ImplicitStringConcatenation:
17
46
  Enabled: true
18
47
 
48
+ Style/BlockComments:
49
+ Enabled: true
50
+
51
+ Style/MethodDefParentheses:
52
+ Enabled: true
53
+
19
54
  Style/StringLiterals:
20
55
  Enabled: true
21
56
  EnforcedStyle: double_quotes
22
57
 
23
- Style/MethodDefParentheses:
58
+ Style/WhileUntilDo:
24
59
  Enabled: true
@@ -0,0 +1,58 @@
1
+ module RuboCop
2
+ module Cop
3
+ module Crystal
4
+ # Ruby has Enumerable.inject, which can take either a block or a reducer function,
5
+ # and an optional initial value. Enumerable.reduce is an alias of this.
6
+ # Crystal only has Enumerable.reduce, which does not support reducer functions.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # x.inject { |r,v| r + v }
11
+ # x.inject(:+)
12
+ # x.reduce(:+)
13
+ #
14
+ # # good
15
+ # x.reduce { |r,v| r + v }
16
+ #
17
+ # # bad
18
+ # x.inject(y) { |r,v| r + v }
19
+ # x.inject(y, :+)
20
+ # x.reduce(y, :+)
21
+ #
22
+ # # good
23
+ # x.reduce(y) { |r,v| r + v }
24
+ #
25
+ class EnumerableReduce < Base
26
+ extend AutoCorrector
27
+
28
+ MSG = 'Crystal has .reduce instead of .inject'
29
+ RESTRICT_ON_SEND = %i[inject reduce]
30
+
31
+ def on_send(node)
32
+ new_node = node.receiver.source
33
+ new_node << if node.csend_type?
34
+ '&.'
35
+ else
36
+ '.'
37
+ end
38
+ new_node << 'reduce'
39
+ if node.arguments?
40
+ if node.parent&.block_type?
41
+ new_node << "(#{node.first_argument.source})"
42
+ else
43
+ new_node << "(#{node.first_argument.source})" if node.arguments.size == 2
44
+ new_node << " { |r,v| r.#{node.last_argument.source.delete_prefix(':')}(v) }"
45
+ end
46
+ end
47
+
48
+ return if node.source == new_node
49
+
50
+ add_offense(node.selector) do |corrector|
51
+ corrector.replace(node, new_node)
52
+ end
53
+ end
54
+ alias on_csend on_send
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,31 @@
1
+ module RuboCop
2
+ module Cop
3
+ module Crystal
4
+ # Crystal does not have the .length or .count methods as aliases to .size
5
+ #
6
+ # @example
7
+ # # bad
8
+ # x.length
9
+ # x.count
10
+ #
11
+ # # good
12
+ # x.size
13
+ #
14
+ class EnumerableSize < Base
15
+ extend AutoCorrector
16
+
17
+ MSG = 'Crystal does not have the .length or .count methods as aliases to .size'
18
+ RESTRICT_ON_SEND = %i[count length]
19
+
20
+ def on_send(node)
21
+ return if node.arguments? || node.parent&.block_type?
22
+
23
+ add_offense(node.selector) do |corrector|
24
+ corrector.replace(node.selector, 'size')
25
+ end
26
+ end
27
+ alias on_csend on_send
28
+ end
29
+ end
30
+ end
31
+ end
@@ -13,6 +13,7 @@ module RuboCop
13
13
  #
14
14
  class FileExtension < Base
15
15
  extend AutoCorrector
16
+
16
17
  MSG = 'Crystal files have `.cr` extensions, while Ruby files have `.rb` extensions.'
17
18
 
18
19
  def on_new_investigation
@@ -0,0 +1,115 @@
1
+ module RuboCop
2
+ module Cop
3
+ module Crystal
4
+ # Ruby has IO.readlines, which supports chomp (false by default), limit and separator arguments, and has special behaviors for empty and nil separators.
5
+ # Crystal has File.read_lines, which only supports the chomp (true by default) argument, and IO.each_line, which supports limit and separator arguments,
6
+ # but does not have special behaviors for empty and nil separators, so we recreate that ourselves.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # File.readlines('foo')
11
+ # IO.readlines('foo')
12
+ #
13
+ # # good
14
+ # File.read_lines('foo', chomp: false)
15
+ #
16
+ # # bad
17
+ # File.readlines('foo', 'b', 3)
18
+ # IO.readlines('foo', 'b', 3)
19
+ #
20
+ # # good
21
+ # File.open('foo').each_line('b', 3).to_a
22
+ #
23
+ # # bad
24
+ # File.readlines('foo', nil)
25
+ # IO.readlines('foo', nil)
26
+ #
27
+ # # bad
28
+ # File.readlines("foo", '')
29
+ # IO.readlines("foo", '')
30
+ #
31
+ # # good
32
+ # File.open("foo").each_line("\n").to_a.join.split(/\n{2,}/).reject { |e| e.empty? }.reverse.map_with_index {|e, i| i == 0 ? e : "#{e}\n\n" }.reverse
33
+ #
34
+ class FileReadLines < Base
35
+ extend AutoCorrector
36
+
37
+ MSG = 'Ruby has IO.readlines, while Crystal has File.read_lines and IO.each_line'
38
+ RESTRICT_ON_SEND = %i[readlines]
39
+
40
+ # @!method no_arguments?(node)
41
+ def_node_matcher :no_arguments?, <<~PATTERN
42
+ (send (send (const {nil? cbase} :File) ... ) :readlines)
43
+ PATTERN
44
+
45
+ # @!method path_argument?(node)
46
+ def_node_matcher :path_argument?, <<~PATTERN
47
+ (send (const {nil? cbase} {:File :IO}) :readlines (str $_))
48
+ PATTERN
49
+
50
+ # @!method path_and_chomp_argument?(node)
51
+ def_node_matcher :path_and_chomp_argument?, <<~PATTERN
52
+ (send (const {nil? cbase} {:File :IO}) :readlines (str _) (hash (pair (sym :chomp) _)))
53
+ PATTERN
54
+
55
+ # TODO: Do this properly once https://github.com/rubocop/rubocop-ast/pull/386 is merged.
56
+ # @!method path_and_empty_separator_argument?(node)
57
+ def_node_matcher :path_and_empty_separator_argument?, <<~PATTERN
58
+ (send (const {nil? cbase} {:File :IO}) :readlines (str $_) (str empty?))
59
+ PATTERN
60
+
61
+ # @!method path_and_nil_separator_argument?(node)
62
+ def_node_matcher :path_and_nil_separator_argument?, <<~PATTERN
63
+ (send (const {nil? cbase} {:File :IO}) :readlines (str $_) nil)
64
+ PATTERN
65
+
66
+ # @!method path_and_separator_argument?(node)
67
+ def_node_matcher :path_and_separator_argument?, <<~PATTERN
68
+ (send (const {nil? cbase} {:File :IO}) :readlines (str $_) (str $_))
69
+ PATTERN
70
+
71
+ # @!method path_and_limiter_argument?(node)
72
+ def_node_matcher :path_and_limiter_argument?, <<~PATTERN
73
+ (send (const {nil? cbase} {:File :IO}) :readlines (str $_) (int $_))
74
+ PATTERN
75
+
76
+ # @!method path_separator_and_limiter_argument?(node)
77
+ def_node_matcher :path_separator_and_limiter_argument?, <<~PATTERN
78
+ (send (const {nil? cbase} {:File :IO}) :readlines (str $_) (str $_) (int $_))
79
+ PATTERN
80
+
81
+ def on_send(node)
82
+ if no_arguments?(node)
83
+ autocorrect(node, node.source.sub('.readlines', '.each_line.to_a'))
84
+ elsif (path = path_argument?(node))
85
+ autocorrect(node, "File.read_lines(\"#{path}\", chomp: false)")
86
+ elsif path_and_chomp_argument?(node)
87
+ autocorrect(node, node.source.sub(/(File|IO)\.readlines/, 'File.read_lines'))
88
+ elsif (path = path_and_empty_separator_argument?(node))
89
+ # TODO: There's probably a slightly cleaner way to replicate Ruby's "paragraph" separator behavior.
90
+ autocorrect(node, "File.open(\"#{path}\").each_line(\"\\n\").to_a.join.split(/\\n{2,}/).reject { |e| e.empty? }.reverse.map_with_index {|e, i| i == 0 ? e : \"\#{e}\\n\\n\" }.reverse")
91
+ elsif (path = path_and_nil_separator_argument?(node))
92
+ # TODO: Crystal might technically support this more cleanly, although this doesn't appear to be documented.
93
+ # https://github.com/crystal-lang/crystal/blob/1.17.1/src/io.cr#L818-L822
94
+ autocorrect(node, "[File.open(\"#{path}\").each_line(chomp: false).to_a.join]")
95
+ elsif (path, separator = path_and_separator_argument?(node))
96
+ autocorrect(node, "File.open(\"#{path}\").each_line(\"#{separator}\").to_a")
97
+ elsif (path, limiter = path_and_limiter_argument?(node))
98
+ autocorrect(node, "File.open(\"#{path}\").each_line(#{limiter}).to_a")
99
+ elsif (path, separator, limiter = path_separator_and_limiter_argument?(node))
100
+ autocorrect(node, "File.open(\"#{path}\").each_line(\"#{separator}\", #{limiter}).to_a")
101
+ end
102
+ end
103
+ alias on_csend on_send
104
+
105
+ private
106
+
107
+ def autocorrect(node, replacement)
108
+ add_offense(node.selector) do |corrector|
109
+ corrector.replace(node, replacement)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -16,6 +16,7 @@ module RuboCop
16
16
  #
17
17
  class InterpolationInSingleQuotes < Base
18
18
  extend AutoCorrector
19
+
19
20
  MSG = 'Crystal does not support the use of single-quote deliminated strings to avoid interpolation.'
20
21
 
21
22
  def on_str(node)
@@ -23,10 +24,10 @@ module RuboCop
23
24
  return unless node.source.start_with?("'")
24
25
  # Replace the single quotes deliminating the string with double quotes, and check if the resulting ast is still the same.
25
26
  # If it is, the string doesn't have any interpolation to avoid, and we're done here.
26
- return if node == parse('"' + node.source[1..-2] + '"').ast
27
+ return if node == parse("\"#{node.source[1..-2]}\"").ast
27
28
 
28
29
  add_offense(node) do |corrector|
29
- corrector.replace(node, '%q(' + node.source[1..-2] + ')')
30
+ corrector.replace(node, "%q(#{node.source[1..-2]})")
30
31
  end
31
32
  end
32
33
  end
@@ -0,0 +1,30 @@
1
+ module RuboCop
2
+ module Cop
3
+ module Crystal
4
+ # Method names cannot start with uppercase letters in Crystal:
5
+ # https://crystal-lang.org/reference/latest/syntax_and_semantics/methods_and_instance_variables.html
6
+ # ^ "Method names begin with a lowercase letter and, as a convention, only use lowercase letters, underscores and numbers."
7
+ #
8
+ # @example
9
+ # # bad
10
+ # def Foo(bar)
11
+ # qux
12
+ # end
13
+ # Foo(bar)
14
+ #
15
+ # # good
16
+ # def foo(bar)
17
+ # qux
18
+ # end
19
+ # foo(bar)
20
+ #
21
+ class MethodNameStartingWithUppercaseLetter < Base
22
+ MSG = 'Method names must start with a lowercase letter in Crystal.'
23
+
24
+ def on_def(node)
25
+ add_offense(node.loc.name) if node.method_name.to_s.chr.capitalize!.nil?
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,54 @@
1
+ module RuboCop
2
+ module Cop
3
+ module Crystal
4
+ # In Crystal, certain methods return a single character of type Char, while in Ruby they return a 1-character string.
5
+ # Chars and 1-character strings are treated differently in Crystal, and will not count as equal even if they contain the same content.
6
+ #
7
+ # @example
8
+ # # bad
9
+ # x.chars
10
+ #
11
+ # # good
12
+ # x.chars.map { |c| c.to_s }
13
+ #
14
+ # # bad
15
+ # x.each_char { |c| y << c }
16
+ #
17
+ # # good
18
+ # x.each_char { |c| y << c.to_s }
19
+ #
20
+ class MethodReturningChar < Base
21
+ extend AutoCorrector
22
+
23
+ MSG = 'In Crystal, this method returns the Char type instead of a 1-character string.'
24
+ RESTRICT_ON_SEND = %i[chars each_char]
25
+
26
+ # @!method map_to_s?(node)
27
+ def_node_matcher :map_to_s?, <<~PATTERN
28
+ (block (send (call (...) :chars) :map) (args (arg :c)) (send (lvar :c) :to_s))
29
+ PATTERN
30
+
31
+ def on_send(node)
32
+ if node.method?(:chars) && !map_to_s?(node.parent&.parent)
33
+ add_offense(node.selector) do |corrector|
34
+ corrector.insert_after(node.selector, '.map { |c| c.to_s }')
35
+ end
36
+ elsif node.method?(:each_char)
37
+ nodes_to_correct = []
38
+ node.parent.body.each_descendant do |n|
39
+ # If a node is an lvar with the same name as the character argument and it does not have a .to_s, it needs to be corrected.
40
+ nodes_to_correct << n if n.lvar_type? && n.source == node.parent.first_argument.source.gsub('|', '') && !n.parent.method?(:to_s)
41
+ end
42
+
43
+ return if nodes_to_correct.empty?
44
+
45
+ add_offense(node.selector) do |corrector|
46
+ nodes_to_correct.each { |n| corrector.insert_after(n, '.to_s') }
47
+ end
48
+ end
49
+ end
50
+ alias on_csend on_send
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,35 @@
1
+ module RuboCop
2
+ module Cop
3
+ module Crystal
4
+ # As of Crystal 0.7.7, require is only allowed at the top level:
5
+ # https://github.com/crystal-lang/crystal/releases/tag/0.7.7
6
+ # ^ "(breaking change) require is now only allowed at the top-level, never inside other types or methods."
7
+ #
8
+ # @example
9
+ # # bad
10
+ # def foo
11
+ # require 'bar'
12
+ # end
13
+ #
14
+ # class Foo
15
+ # require 'bar'
16
+ # end
17
+ #
18
+ # module Foo
19
+ # require 'bar'
20
+ # end
21
+ #
22
+ # # good
23
+ # require 'bar'
24
+ #
25
+ class RequireAtTopLevel < Base
26
+ MSG = 'Crystal does not allow require anywhere other than the top level.'
27
+ RESTRICT_ON_SEND = [:require]
28
+
29
+ def on_send(node)
30
+ add_offense(node) if %i[def class module].include?(node.parent&.type)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -19,16 +19,17 @@ module RuboCop
19
19
  #
20
20
  class RequireRelative < Base
21
21
  extend AutoCorrector
22
+
22
23
  MSG = 'Crystal does not support require_relative.'
23
24
  RESTRICT_ON_SEND = [:require_relative]
24
25
 
25
26
  def on_send(node)
26
27
  add_offense(node) do |corrector|
27
- if node.first_argument.value.start_with?('.', '/')
28
- require_value = node.first_argument.value
29
- else
30
- require_value = "./#{node.first_argument.value}"
31
- end
28
+ require_value = if node.first_argument.value.start_with?('.', '/')
29
+ node.first_argument.value
30
+ else
31
+ "./#{node.first_argument.value}"
32
+ end
32
33
  corrector.replace(node, "require '#{require_value}'")
33
34
  end
34
35
  end
@@ -1,3 +1,9 @@
1
+ require_relative 'crystal/enumerable_reduce'
2
+ require_relative 'crystal/enumerable_size'
1
3
  require_relative 'crystal/file_extension'
4
+ require_relative 'crystal/file_read_lines'
2
5
  require_relative 'crystal/interpolation_in_single_quotes'
6
+ require_relative 'crystal/method_name_starting_with_uppercase_letter'
7
+ require_relative 'crystal/method_returning_char'
8
+ require_relative 'crystal/require_at_top_level'
3
9
  require_relative 'crystal/require_relative'
@@ -0,0 +1,28 @@
1
+ require 'lint_roller'
2
+
3
+ module RuboCop
4
+ module Crystal
5
+ class Plugin < LintRoller::Plugin
6
+ def about
7
+ LintRoller::About.new(
8
+ name: 'rubocop-crystal',
9
+ version: '0.0.4',
10
+ homepage: 'https://github.com/Zopolis4/rubocop-crystal',
11
+ description: 'A RuboCop extension for converting Ruby to Crystal.'
12
+ )
13
+ end
14
+
15
+ def supported?(context)
16
+ context.engine == :rubocop
17
+ end
18
+
19
+ def rules(_context)
20
+ LintRoller::Rules.new(
21
+ type: :path,
22
+ config_format: :rubocop,
23
+ value: Pathname.new(__dir__).join('../../../config/default.yml')
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,8 +1,6 @@
1
1
  require 'rubocop'
2
2
 
3
3
  require_relative 'rubocop/crystal'
4
- require_relative 'rubocop/crystal/inject'
5
-
6
- RuboCop::Crystal::Inject.defaults!
4
+ require_relative 'rubocop/crystal/plugin'
7
5
 
8
6
  require_relative 'rubocop/cop/crystal_cops'
@@ -1,14 +1,17 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = 'rubocop-crystal'
3
3
  spec.summary = 'A RuboCop extension for converting Ruby to Crystal.'
4
- spec.version = '0.0.2'
4
+ spec.version = '0.0.4'
5
5
  spec.license = 'GPL-3.0-or-later'
6
6
  spec.author = 'Zopolis4'
7
7
  spec.email = 'creatorsmithmdt@gmail.com'
8
8
  spec.homepage = 'https://github.com/Zopolis4/rubocop-crystal'
9
9
 
10
+ spec.metadata['default_lint_roller_plugin'] = 'RuboCop::Crystal::Plugin'
11
+
10
12
  spec.files = `git ls-files`.split("\n")
11
13
  spec.require_paths = ['lib']
12
14
 
13
- spec.add_dependency 'rubocop', '>= 1.65.1'
15
+ spec.add_dependency 'lint_roller'
16
+ spec.add_dependency 'rubocop', '>= 1.80.2'
14
17
  end