rubocop-crystal 0.0.3 → 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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/CHANGELOG.md +14 -0
- data/README.md +1 -1
- data/config/default.yml +25 -0
- data/lib/rubocop/cop/crystal/enumerable_reduce.rb +58 -0
- data/lib/rubocop/cop/crystal/enumerable_size.rb +31 -0
- data/lib/rubocop/cop/crystal/file_extension.rb +1 -0
- data/lib/rubocop/cop/crystal/file_read_lines.rb +115 -0
- data/lib/rubocop/cop/crystal/interpolation_in_single_quotes.rb +1 -0
- data/lib/rubocop/cop/crystal/method_name_starting_with_uppercase_letter.rb +1 -1
- data/lib/rubocop/cop/crystal/method_returning_char.rb +54 -0
- data/lib/rubocop/cop/crystal/require_at_top_level.rb +35 -0
- data/lib/rubocop/cop/crystal/require_relative.rb +1 -0
- data/lib/rubocop/cop/crystal_cops.rb +5 -0
- data/lib/rubocop/crystal/plugin.rb +1 -1
- data/rubocop-crystal.gemspec +2 -2
- data/spec/rubocop/cop/crystal/enumerable_reduce_spec.rb +101 -0
- data/spec/rubocop/cop/crystal/enumerable_size_spec.rb +64 -0
- data/spec/rubocop/cop/crystal/file_read_lines_spec.rb +156 -0
- data/spec/rubocop/cop/crystal/method_name_starting_with_uppercase_letter_spec.rb +2 -2
- data/spec/rubocop/cop/crystal/method_returning_char_spec.rb +150 -0
- data/spec/rubocop/cop/crystal/require_at_top_level_spec.rb +42 -0
- data/test/string.rb +38 -1
- metadata +15 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e7cede16a18df18a0021747a7a5305d1503af8229d0a2790242b3b4ac5e27c2
|
4
|
+
data.tar.gz: 2373953ed41151d8a00749f28db4ac84ff144db91e02413b50e71f4d97c5627b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e33199a6a2a74eb3e7739f0b2355b58e65b89d6bcc9001c4c62e246eea89fa7cb098dfb274da12b7dac220f90de5d672d94f2abb27d078c8a2ec273f8b81f990
|
7
|
+
data.tar.gz: bccb8bb8fba84d6b2208cb578bcbcc7e7a3c6c2202cee707e8fe511018ff128cb4dd6113aefd11912df78a9971183ea8497fe344b684002aa3144279756732c4
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
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
|
+
|
1
15
|
## 0.0.3 (2025-04-28)
|
2
16
|
|
3
17
|
### 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
|
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.
|
data/config/default.yml
CHANGED
@@ -1,10 +1,27 @@
|
|
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
|
@@ -13,6 +30,14 @@ Crystal/MethodNameStartingWithUppercaseLetter:
|
|
13
30
|
Description: 'This cop detects method names that start with uppercase letters.'
|
14
31
|
Enabled: true
|
15
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
|
+
|
16
41
|
Crystal/RequireRelative:
|
17
42
|
Description: 'This cop replaces require_relative with require while maintaining behavior.'
|
18
43
|
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
|
@@ -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
|
@@ -19,7 +19,7 @@ module RuboCop
|
|
19
19
|
# foo(bar)
|
20
20
|
#
|
21
21
|
class MethodNameStartingWithUppercaseLetter < Base
|
22
|
-
MSG = 'Method names must start with a
|
22
|
+
MSG = 'Method names must start with a lowercase letter in Crystal.'
|
23
23
|
|
24
24
|
def on_def(node)
|
25
25
|
add_offense(node.loc.name) if node.method_name.to_s.chr.capitalize!.nil?
|
@@ -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
|
@@ -1,4 +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'
|
3
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'
|
4
9
|
require_relative 'crystal/require_relative'
|
data/rubocop-crystal.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
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.
|
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'
|
@@ -13,5 +13,5 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.require_paths = ['lib']
|
14
14
|
|
15
15
|
spec.add_dependency 'lint_roller'
|
16
|
-
spec.add_dependency 'rubocop', '>= 1.
|
16
|
+
spec.add_dependency 'rubocop', '>= 1.80.2'
|
17
17
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
RSpec.describe RuboCop::Cop::Crystal::EnumerableReduce, :config do
|
2
|
+
it 'registers an offense when .inject is used with a block and no initial value' do
|
3
|
+
expect_offense(<<~RUBY)
|
4
|
+
x.inject { |r,v| r + v }
|
5
|
+
^^^^^^ Crystal has .reduce instead of .inject
|
6
|
+
RUBY
|
7
|
+
|
8
|
+
expect_correction(<<~RUBY)
|
9
|
+
x.reduce { |r,v| r + v }
|
10
|
+
RUBY
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'registers an offense when &.inject is used with a block and no initial value' do
|
14
|
+
expect_offense(<<~RUBY)
|
15
|
+
x&.inject { |r,v| r + v }
|
16
|
+
^^^^^^ Crystal has .reduce instead of .inject
|
17
|
+
RUBY
|
18
|
+
|
19
|
+
expect_correction(<<~RUBY)
|
20
|
+
x&.reduce { |r,v| r + v }
|
21
|
+
RUBY
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'registers an offense when .inject is used with a block and an initial value' do
|
25
|
+
expect_offense(<<~RUBY)
|
26
|
+
x.inject(y) { |r,v| r + v }
|
27
|
+
^^^^^^ Crystal has .reduce instead of .inject
|
28
|
+
RUBY
|
29
|
+
|
30
|
+
expect_correction(<<~RUBY)
|
31
|
+
x.reduce(y) { |r,v| r + v }
|
32
|
+
RUBY
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'registers an offense when .inject is used with a reducer function and no initial value' do
|
36
|
+
expect_offense(<<~RUBY)
|
37
|
+
x.inject(:+)
|
38
|
+
^^^^^^ Crystal has .reduce instead of .inject
|
39
|
+
RUBY
|
40
|
+
|
41
|
+
expect_correction(<<~RUBY)
|
42
|
+
x.reduce { |r,v| r.+(v) }
|
43
|
+
RUBY
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'registers an offense when .inject is used with a reducer function and an initial value' do
|
47
|
+
expect_offense(<<~RUBY)
|
48
|
+
x.inject(4, :+)
|
49
|
+
^^^^^^ Crystal has .reduce instead of .inject
|
50
|
+
RUBY
|
51
|
+
|
52
|
+
expect_correction(<<~RUBY)
|
53
|
+
x.reduce(4) { |r,v| r.+(v) }
|
54
|
+
RUBY
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'registers an offense when .reduce is used with a reducer function and no initial value' do
|
58
|
+
expect_offense(<<~RUBY)
|
59
|
+
x.reduce(:prepend)
|
60
|
+
^^^^^^ Crystal has .reduce instead of .inject
|
61
|
+
RUBY
|
62
|
+
|
63
|
+
expect_correction(<<~RUBY)
|
64
|
+
x.reduce { |r,v| r.prepend(v) }
|
65
|
+
RUBY
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'registers an offense when &.reduce is used with a reducer function and no initial value' do
|
69
|
+
expect_offense(<<~RUBY)
|
70
|
+
x&.reduce(:prepend)
|
71
|
+
^^^^^^ Crystal has .reduce instead of .inject
|
72
|
+
RUBY
|
73
|
+
|
74
|
+
expect_correction(<<~RUBY)
|
75
|
+
x&.reduce { |r,v| r.prepend(v) }
|
76
|
+
RUBY
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'registers an offense when .reduce is used with a reducer function and an initial value' do
|
80
|
+
expect_offense(<<~RUBY)
|
81
|
+
x.reduce("foo", :prepend)
|
82
|
+
^^^^^^ Crystal has .reduce instead of .inject
|
83
|
+
RUBY
|
84
|
+
|
85
|
+
expect_correction(<<~RUBY)
|
86
|
+
x.reduce("foo") { |r,v| r.prepend(v) }
|
87
|
+
RUBY
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'does not register an offense when .reduce is used with a block and no initial value' do
|
91
|
+
expect_no_offenses(<<~RUBY)
|
92
|
+
x.reduce { |r,v| r + v }
|
93
|
+
RUBY
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'does not register an offense when &.reduce is used with a block and no initial value' do
|
97
|
+
expect_no_offenses(<<~RUBY)
|
98
|
+
x&.reduce { |r,v| r + v }
|
99
|
+
RUBY
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
RSpec.describe RuboCop::Cop::Crystal::EnumerableSize, :config do
|
2
|
+
it 'registers an offense when .length is used' do
|
3
|
+
expect_offense(<<~RUBY)
|
4
|
+
x.length
|
5
|
+
^^^^^^ Crystal does not have the .length or .count methods as aliases to .size
|
6
|
+
RUBY
|
7
|
+
|
8
|
+
expect_correction(<<~RUBY)
|
9
|
+
x.size
|
10
|
+
RUBY
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'registers an offense when &.length is used' do
|
14
|
+
expect_offense(<<~RUBY)
|
15
|
+
x&.length
|
16
|
+
^^^^^^ Crystal does not have the .length or .count methods as aliases to .size
|
17
|
+
RUBY
|
18
|
+
|
19
|
+
expect_correction(<<~RUBY)
|
20
|
+
x&.size
|
21
|
+
RUBY
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'registers an offense when .count is used with no argument and no block' do
|
25
|
+
expect_offense(<<~RUBY)
|
26
|
+
x.count
|
27
|
+
^^^^^ Crystal does not have the .length or .count methods as aliases to .size
|
28
|
+
RUBY
|
29
|
+
|
30
|
+
expect_correction(<<~RUBY)
|
31
|
+
x.size
|
32
|
+
RUBY
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'does not register an offense when .count is used with an argument and no block' do
|
36
|
+
expect_no_offenses(<<~RUBY)
|
37
|
+
x.count(y)
|
38
|
+
RUBY
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'does not register an offense when &.count is used with an argument and no block' do
|
42
|
+
expect_no_offenses(<<~RUBY)
|
43
|
+
x&.count(y)
|
44
|
+
RUBY
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'does not register an offense when .count is used with a block and no argument' do
|
48
|
+
expect_no_offenses(<<~RUBY)
|
49
|
+
x.count {|e| e > 2 }
|
50
|
+
RUBY
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'does not register an offense when .count is used with an argument and a block' do
|
54
|
+
expect_no_offenses(<<~RUBY)
|
55
|
+
x.count(y) {|e| e > 2 }
|
56
|
+
RUBY
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'does not register an offense when .size is used' do
|
60
|
+
expect_no_offenses(<<~RUBY)
|
61
|
+
x.size
|
62
|
+
RUBY
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
RSpec.describe RuboCop::Cop::Crystal::FileReadLines, :config do
|
2
|
+
it 'registers an offense when File.readlines is used with a path argument' do
|
3
|
+
expect_offense(<<~RUBY)
|
4
|
+
File.readlines("foo")
|
5
|
+
^^^^^^^^^ Ruby has IO.readlines, while Crystal has File.read_lines and IO.each_line
|
6
|
+
RUBY
|
7
|
+
|
8
|
+
expect_correction(<<~RUBY)
|
9
|
+
File.read_lines("foo", chomp: false)
|
10
|
+
RUBY
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'registers an offense when IO.readlines is used with a path argument' do
|
14
|
+
expect_offense(<<~RUBY)
|
15
|
+
IO.readlines("foo")
|
16
|
+
^^^^^^^^^ Ruby has IO.readlines, while Crystal has File.read_lines and IO.each_line
|
17
|
+
RUBY
|
18
|
+
|
19
|
+
expect_correction(<<~RUBY)
|
20
|
+
File.read_lines("foo", chomp: false)
|
21
|
+
RUBY
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'registers an offense when File.readlines is used with a path argument and chomp: false' do
|
25
|
+
expect_offense(<<~RUBY)
|
26
|
+
File.readlines("foo", chomp: false)
|
27
|
+
^^^^^^^^^ Ruby has IO.readlines, while Crystal has File.read_lines and IO.each_line
|
28
|
+
RUBY
|
29
|
+
|
30
|
+
expect_correction(<<~RUBY)
|
31
|
+
File.read_lines("foo", chomp: false)
|
32
|
+
RUBY
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'registers an offense when File.readlines is used with a path argument and chomp: true' do
|
36
|
+
expect_offense(<<~RUBY)
|
37
|
+
File.readlines("foo", chomp: true)
|
38
|
+
^^^^^^^^^ Ruby has IO.readlines, while Crystal has File.read_lines and IO.each_line
|
39
|
+
RUBY
|
40
|
+
|
41
|
+
expect_correction(<<~RUBY)
|
42
|
+
File.read_lines("foo", chomp: true)
|
43
|
+
RUBY
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'registers an offense when File.readlines is used with a path and a single-character separator argument' do
|
47
|
+
expect_offense(<<~RUBY)
|
48
|
+
File.readlines("foo", "a")
|
49
|
+
^^^^^^^^^ Ruby has IO.readlines, while Crystal has File.read_lines and IO.each_line
|
50
|
+
RUBY
|
51
|
+
|
52
|
+
expect_correction(<<~RUBY)
|
53
|
+
File.open("foo").each_line("a").to_a
|
54
|
+
RUBY
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'registers an offense when File.readlines is used with a path and a multi-character separator argument' do
|
58
|
+
expect_offense(<<~RUBY)
|
59
|
+
File.readlines("foo", "quz")
|
60
|
+
^^^^^^^^^ Ruby has IO.readlines, while Crystal has File.read_lines and IO.each_line
|
61
|
+
RUBY
|
62
|
+
|
63
|
+
expect_correction(<<~RUBY)
|
64
|
+
File.open("foo").each_line("quz").to_a
|
65
|
+
RUBY
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'registers an offense when File.readlines is used with a path and a nil separator argument' do
|
69
|
+
expect_offense(<<~RUBY)
|
70
|
+
File.readlines("foo", nil)
|
71
|
+
^^^^^^^^^ Ruby has IO.readlines, while Crystal has File.read_lines and IO.each_line
|
72
|
+
RUBY
|
73
|
+
|
74
|
+
expect_correction(<<~RUBY)
|
75
|
+
[File.open("foo").each_line(chomp: false).to_a.join]
|
76
|
+
RUBY
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'registers an offense when File.readlines is used with a path and an empty string separator argument' do
|
80
|
+
expect_offense(<<~RUBY)
|
81
|
+
File.readlines("foo", "")
|
82
|
+
^^^^^^^^^ Ruby has IO.readlines, while Crystal has File.read_lines and IO.each_line
|
83
|
+
RUBY
|
84
|
+
|
85
|
+
expect_correction(<<~'RUBY')
|
86
|
+
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
|
87
|
+
RUBY
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'registers an offense when File.readlines is used with a path and a limiter argument' do
|
91
|
+
expect_offense(<<~RUBY)
|
92
|
+
File.readlines("foo", 2)
|
93
|
+
^^^^^^^^^ Ruby has IO.readlines, while Crystal has File.read_lines and IO.each_line
|
94
|
+
RUBY
|
95
|
+
|
96
|
+
expect_correction(<<~RUBY)
|
97
|
+
File.open("foo").each_line(2).to_a
|
98
|
+
RUBY
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'registers an offense when File.readlines is used with a path, single-character separator, and limiter argument' do
|
102
|
+
expect_offense(<<~RUBY)
|
103
|
+
File.readlines("foo", "b", 3)
|
104
|
+
^^^^^^^^^ Ruby has IO.readlines, while Crystal has File.read_lines and IO.each_line
|
105
|
+
RUBY
|
106
|
+
|
107
|
+
expect_correction(<<~RUBY)
|
108
|
+
File.open("foo").each_line("b", 3).to_a
|
109
|
+
RUBY
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'registers an offense when File.readlines is used with a path, multi-character separator, and limiter argument' do
|
113
|
+
expect_offense(<<~RUBY)
|
114
|
+
File.readlines("foo", "quz", 3)
|
115
|
+
^^^^^^^^^ Ruby has IO.readlines, while Crystal has File.read_lines and IO.each_line
|
116
|
+
RUBY
|
117
|
+
|
118
|
+
expect_correction(<<~RUBY)
|
119
|
+
File.open("foo").each_line("quz", 3).to_a
|
120
|
+
RUBY
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'registers an offense when .readlines is used on a File object' do
|
124
|
+
expect_offense(<<~RUBY)
|
125
|
+
File.open("foo").readlines
|
126
|
+
^^^^^^^^^ Ruby has IO.readlines, while Crystal has File.read_lines and IO.each_line
|
127
|
+
RUBY
|
128
|
+
|
129
|
+
expect_correction(<<~RUBY)
|
130
|
+
File.open("foo").each_line.to_a
|
131
|
+
RUBY
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'registers an offense when .readlines is used on a chained File object' do
|
135
|
+
expect_offense(<<~RUBY)
|
136
|
+
File.new(File.expand_path("foo", "bar")).readlines
|
137
|
+
^^^^^^^^^ Ruby has IO.readlines, while Crystal has File.read_lines and IO.each_line
|
138
|
+
RUBY
|
139
|
+
|
140
|
+
expect_correction(<<~RUBY)
|
141
|
+
File.new(File.expand_path("foo", "bar")).each_line.to_a
|
142
|
+
RUBY
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'does not register an offense when .readlines is used from a non File/IO class' do
|
146
|
+
expect_no_offenses(<<~RUBY)
|
147
|
+
Bar.readlines("foo")
|
148
|
+
RUBY
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'does not register an offense when .readlines is used on a non File/IO object' do
|
152
|
+
expect_no_offenses(<<~RUBY)
|
153
|
+
Bar.open("foo").readlines
|
154
|
+
RUBY
|
155
|
+
end
|
156
|
+
end
|
@@ -2,7 +2,7 @@ RSpec.describe RuboCop::Cop::Crystal::MethodNameStartingWithUppercaseLetter, :co
|
|
2
2
|
it 'registers an offense when the first letter of a method name is capitalized' do
|
3
3
|
expect_offense(<<~RUBY)
|
4
4
|
def Foo
|
5
|
-
^^^ Method names must start with a
|
5
|
+
^^^ Method names must start with a lowercase letter in Crystal.
|
6
6
|
bar
|
7
7
|
end
|
8
8
|
RUBY
|
@@ -11,7 +11,7 @@ RSpec.describe RuboCop::Cop::Crystal::MethodNameStartingWithUppercaseLetter, :co
|
|
11
11
|
it 'registers an offense when all the letters of a method name are capitalized' do
|
12
12
|
expect_offense(<<~RUBY)
|
13
13
|
def FOO
|
14
|
-
^^^ Method names must start with a
|
14
|
+
^^^ Method names must start with a lowercase letter in Crystal.
|
15
15
|
bar
|
16
16
|
end
|
17
17
|
RUBY
|
@@ -0,0 +1,150 @@
|
|
1
|
+
RSpec.describe RuboCop::Cop::Crystal::MethodReturningChar, :config do
|
2
|
+
it 'registers an offense when .chars is used without converting the elements of the array to strings' do
|
3
|
+
expect_offense(<<~RUBY)
|
4
|
+
x.chars
|
5
|
+
^^^^^ In Crystal, this method returns the Char type instead of a 1-character string.
|
6
|
+
RUBY
|
7
|
+
|
8
|
+
expect_correction(<<~RUBY)
|
9
|
+
x.chars.map { |c| c.to_s }
|
10
|
+
RUBY
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'registers an offense when &.chars is used without converting the elements of the array to strings' do
|
14
|
+
expect_offense(<<~RUBY)
|
15
|
+
x&.chars
|
16
|
+
^^^^^ In Crystal, this method returns the Char type instead of a 1-character string.
|
17
|
+
RUBY
|
18
|
+
|
19
|
+
expect_correction(<<~RUBY)
|
20
|
+
x&.chars.map { |c| c.to_s }
|
21
|
+
RUBY
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'registers an offense when .chars is used in a chain ending in a block without converting the elements of the array to strings' do
|
25
|
+
expect_offense(<<~RUBY)
|
26
|
+
x.chars.each { |c| puts c}
|
27
|
+
^^^^^ In Crystal, this method returns the Char type instead of a 1-character string.
|
28
|
+
RUBY
|
29
|
+
|
30
|
+
expect_correction(<<~RUBY)
|
31
|
+
x.chars.map { |c| c.to_s }.each { |c| puts c}
|
32
|
+
RUBY
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'registers an offense when .chars is used on a string literal in a chain ending in a block without converting the elements of the array to strings' do
|
36
|
+
expect_offense(<<~RUBY)
|
37
|
+
"foo".chars.each { |c| puts c}
|
38
|
+
^^^^^ In Crystal, this method returns the Char type instead of a 1-character string.
|
39
|
+
RUBY
|
40
|
+
|
41
|
+
expect_correction(<<~RUBY)
|
42
|
+
"foo".chars.map { |c| c.to_s }.each { |c| puts c}
|
43
|
+
RUBY
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'registers an offense when .chars is used on a variable in a chain ending in a block without converting the elements of the array to strings' do
|
47
|
+
expect_offense(<<~RUBY)
|
48
|
+
x = "foo"
|
49
|
+
x.chars.each { |c| puts c}
|
50
|
+
^^^^^ In Crystal, this method returns the Char type instead of a 1-character string.
|
51
|
+
RUBY
|
52
|
+
|
53
|
+
expect_correction(<<~RUBY)
|
54
|
+
x = "foo"
|
55
|
+
x.chars.map { |c| c.to_s }.each { |c| puts c}
|
56
|
+
RUBY
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'registers an offense when .each_char is used without converting the character to a string' do
|
60
|
+
expect_offense(<<~RUBY)
|
61
|
+
x.each_char { |c| puts c }
|
62
|
+
^^^^^^^^^ In Crystal, this method returns the Char type instead of a 1-character string.
|
63
|
+
RUBY
|
64
|
+
|
65
|
+
expect_correction(<<~RUBY)
|
66
|
+
x.each_char { |c| puts c.to_s }
|
67
|
+
RUBY
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'registers an offense when .each_char with a multiline block is used without converting each instance of the character to a string' do
|
71
|
+
expect_offense(<<~RUBY)
|
72
|
+
x.each_char do |c|
|
73
|
+
^^^^^^^^^ In Crystal, this method returns the Char type instead of a 1-character string.
|
74
|
+
puts foo if c == y
|
75
|
+
bar
|
76
|
+
z << c
|
77
|
+
end
|
78
|
+
RUBY
|
79
|
+
|
80
|
+
expect_correction(<<~RUBY)
|
81
|
+
x.each_char do |c|
|
82
|
+
puts foo if c.to_s == y
|
83
|
+
bar
|
84
|
+
z << c.to_s
|
85
|
+
end
|
86
|
+
RUBY
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'does not register an an offense when .chars is used with converting the elements of the array to strings' do
|
90
|
+
expect_no_offenses(<<~RUBY)
|
91
|
+
x.chars.map { |c| c.to_s }
|
92
|
+
RUBY
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'does not register an an offense when &.chars is used with converting the elements of the array to strings' do
|
96
|
+
expect_no_offenses(<<~RUBY)
|
97
|
+
x&.chars.map { |c| c.to_s }
|
98
|
+
RUBY
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'does not register an offense when .chars is used in a chain ending in a block with converting the elements of the array to strings' do
|
102
|
+
expect_no_offenses(<<~RUBY)
|
103
|
+
x.chars.map { |c| c.to_s }.each { |c| puts c}
|
104
|
+
RUBY
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'does not register an offense when .chars is used on a string literal in a chain ending in a block with converting the elements of the array to strings' do
|
108
|
+
expect_no_offenses(<<~RUBY)
|
109
|
+
"foo".chars.map { |c| c.to_s }.each { |c| puts c}
|
110
|
+
RUBY
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'does not register an offense when .chars is used on a variable in a chain ending in a block with converting the elements of the array to strings' do
|
114
|
+
expect_no_offenses(<<~RUBY)
|
115
|
+
x = "foo"
|
116
|
+
x.chars.map { |c| c.to_s }.each { |c| puts c}
|
117
|
+
RUBY
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'does not register an offense when .each_char is used with converting the character to a string' do
|
121
|
+
expect_no_offenses(<<~RUBY)
|
122
|
+
x.each_char { |c| puts c.to_s }
|
123
|
+
RUBY
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'does not register an offense when .each_char with a multiline block is used with converting each instance of the character to a string' do
|
127
|
+
expect_no_offenses(<<~RUBY)
|
128
|
+
x.each_char do |c|
|
129
|
+
puts foo if c.to_s == y
|
130
|
+
bar
|
131
|
+
z << c.to_s
|
132
|
+
end
|
133
|
+
RUBY
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'does not register an offense when .each_char is used with no instances of the character' do
|
137
|
+
expect_no_offenses(<<~RUBY)
|
138
|
+
x.each_char { |c| puts x }
|
139
|
+
RUBY
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'does not register an offense when .each_char with a multiline block is used with no instances of the character' do
|
143
|
+
expect_no_offenses(<<~RUBY)
|
144
|
+
x.each_char do |c|
|
145
|
+
puts x
|
146
|
+
foo if y == z
|
147
|
+
end
|
148
|
+
RUBY
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
RSpec.describe RuboCop::Cop::Crystal::RequireAtTopLevel, :config do
|
2
|
+
it 'registers an offense when require is inside a function' do
|
3
|
+
expect_offense(<<~RUBY)
|
4
|
+
def foo
|
5
|
+
require 'bar'
|
6
|
+
^^^^^^^^^^^^^ Crystal does not allow require anywhere other than the top level.
|
7
|
+
end
|
8
|
+
RUBY
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'registers an offense when require is inside a class' do
|
12
|
+
expect_offense(<<~RUBY)
|
13
|
+
class Foo
|
14
|
+
require 'bar'
|
15
|
+
^^^^^^^^^^^^^ Crystal does not allow require anywhere other than the top level.
|
16
|
+
end
|
17
|
+
RUBY
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'registers an offense when require is inside a module' do
|
21
|
+
expect_offense(<<~RUBY)
|
22
|
+
module Foo
|
23
|
+
require 'bar'
|
24
|
+
^^^^^^^^^^^^^ Crystal does not allow require anywhere other than the top level.
|
25
|
+
end
|
26
|
+
RUBY
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'does not register an offense when require is at the top level' do
|
30
|
+
expect_no_offenses(<<~RUBY)
|
31
|
+
require 'bar'
|
32
|
+
RUBY
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'does not register an offense when a funciton does not contain require' do
|
36
|
+
expect_no_offenses(<<~RUBY)
|
37
|
+
def foo
|
38
|
+
bar
|
39
|
+
end
|
40
|
+
RUBY
|
41
|
+
end
|
42
|
+
end
|
data/test/string.rb
CHANGED
@@ -3,7 +3,7 @@ require_relative 'harness'
|
|
3
3
|
=begin
|
4
4
|
The Alternative Instruction Set is a relatively unknown 32-bit RISC ISA.
|
5
5
|
It is found inside certain VIA C3 CPUs, and is responsible for emulating x86 instructions.
|
6
|
-
This isn't relevant in the slightest, but I had to put something in this
|
6
|
+
This isn't relevant in the slightest, but I had to put something in this comment, and I think it's cool.
|
7
7
|
=end
|
8
8
|
|
9
9
|
Test.assert_equal 'foo', "foo"
|
@@ -25,6 +25,9 @@ Test.assert_unequal '#{1 + 1}', '2'
|
|
25
25
|
|
26
26
|
Test.assert_equal '#{foo}'"bar", '#{foo}bar'
|
27
27
|
|
28
|
+
Test.assert_equal 'a'.chars[0], "a"
|
29
|
+
"bbb".each_char {|c| Test.assert_equal c, 'b' }
|
30
|
+
|
28
31
|
i = 3
|
29
32
|
|
30
33
|
while i > 0 do
|
@@ -36,3 +39,37 @@ until i > 3 do
|
|
36
39
|
i += 1
|
37
40
|
end
|
38
41
|
Test.assert_equal 4, i
|
42
|
+
|
43
|
+
x = [0, 1, 1, 2, 3, 4]
|
44
|
+
|
45
|
+
Test.assert_equal x.length, 6
|
46
|
+
Test.assert_equal x.count, 6
|
47
|
+
Test.assert_equal x.size, 6
|
48
|
+
|
49
|
+
Test.assert_equal x.count(1), 2
|
50
|
+
Test.assert_equal x.count {|e| e > 1}, 3
|
51
|
+
|
52
|
+
Test.assert_equal x.inject { |r,v| r + v }, 11
|
53
|
+
Test.assert_equal x.inject(4) { |r,v| r - v }, -7
|
54
|
+
|
55
|
+
Test.assert_equal x.inject(:+), 11
|
56
|
+
Test.assert_equal x.reduce(:+), 11
|
57
|
+
Test.assert_equal x.inject(4, :-), -7
|
58
|
+
Test.assert_equal x.reduce(4, :-), -7
|
59
|
+
|
60
|
+
# TODO: This could be done using a true Tempfile, but Crystal removed theirs in https://github.com/crystal-lang/crystal/pull/6485.
|
61
|
+
File.write('temp', "first line\nsecond line\n\nfourth line\nfifth line")
|
62
|
+
|
63
|
+
Test.assert_equal File.readlines('temp'), ["first line\n", "second line\n", "\n", "fourth line\n", 'fifth line']
|
64
|
+
Test.assert_equal File.readlines('temp', chomp: true), ['first line', 'second line', '', 'fourth line', 'fifth line']
|
65
|
+
Test.assert_equal File.readlines('temp', ''), ["first line\nsecond line\n\n", "fourth line\nfifth line"]
|
66
|
+
Test.assert_equal File.readlines('temp', nil), ["first line\nsecond line\n\nfourth line\nfifth line"]
|
67
|
+
Test.assert_equal File.readlines('temp', 't'), ['first', " line\nsecond line\n\nfourt", "h line\nfift", 'h line']
|
68
|
+
Test.assert_equal File.readlines('temp', 'fi'), ['fi', "rst line\nsecond line\n\nfourth line\nfi", 'fth line']
|
69
|
+
Test.assert_equal File.readlines('temp', 7), ['first l', "ine\n", 'second ', "line\n", "\n", 'fourth ', "line\n", 'fifth l', 'ine']
|
70
|
+
# TODO: These are waiting on https://github.com/crystal-lang/crystal/issues/16134.
|
71
|
+
# Technically, we could fix the single-character separator by converting it to a Char, but that obviously won't work for the multi-character one, so we need to wait regardless.
|
72
|
+
# Test.assert_equal File.readlines('temp', 'e', 10), ['first line', "\nse", 'cond line', "\n\nfourth l", 'ine', "\nfifth lin", 'e']
|
73
|
+
# Test.assert_equal File.readlines('temp', 'li', 4), ["firs", "t li", "ne\ns", "econ", "d li", "ne\n\n", "four", "th l", "ine\n", "fift", 'h li', 'ne']
|
74
|
+
|
75
|
+
File.delete('temp')
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubocop-crystal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zopolis4
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: lint_roller
|
@@ -29,14 +29,14 @@ dependencies:
|
|
29
29
|
requirements:
|
30
30
|
- - ">="
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: 1.
|
32
|
+
version: 1.80.2
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
37
|
- - ">="
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: 1.
|
39
|
+
version: 1.80.2
|
40
40
|
email: creatorsmithmdt@gmail.com
|
41
41
|
executables: []
|
42
42
|
extensions: []
|
@@ -53,16 +53,26 @@ files:
|
|
53
53
|
- README.md
|
54
54
|
- config/default.yml
|
55
55
|
- lib/rubocop-crystal.rb
|
56
|
+
- lib/rubocop/cop/crystal/enumerable_reduce.rb
|
57
|
+
- lib/rubocop/cop/crystal/enumerable_size.rb
|
56
58
|
- lib/rubocop/cop/crystal/file_extension.rb
|
59
|
+
- lib/rubocop/cop/crystal/file_read_lines.rb
|
57
60
|
- lib/rubocop/cop/crystal/interpolation_in_single_quotes.rb
|
58
61
|
- lib/rubocop/cop/crystal/method_name_starting_with_uppercase_letter.rb
|
62
|
+
- lib/rubocop/cop/crystal/method_returning_char.rb
|
63
|
+
- lib/rubocop/cop/crystal/require_at_top_level.rb
|
59
64
|
- lib/rubocop/cop/crystal/require_relative.rb
|
60
65
|
- lib/rubocop/cop/crystal_cops.rb
|
61
66
|
- lib/rubocop/crystal.rb
|
62
67
|
- lib/rubocop/crystal/plugin.rb
|
63
68
|
- rubocop-crystal.gemspec
|
69
|
+
- spec/rubocop/cop/crystal/enumerable_reduce_spec.rb
|
70
|
+
- spec/rubocop/cop/crystal/enumerable_size_spec.rb
|
71
|
+
- spec/rubocop/cop/crystal/file_read_lines_spec.rb
|
64
72
|
- spec/rubocop/cop/crystal/interpolation_in_single_quotes_spec.rb
|
65
73
|
- spec/rubocop/cop/crystal/method_name_starting_with_uppercase_letter_spec.rb
|
74
|
+
- spec/rubocop/cop/crystal/method_returning_char_spec.rb
|
75
|
+
- spec/rubocop/cop/crystal/require_at_top_level_spec.rb
|
66
76
|
- spec/rubocop/cop/crystal/require_relative_spec.rb
|
67
77
|
- spec/spec_helper.rb
|
68
78
|
- test/harness.rb
|
@@ -86,7 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
86
96
|
- !ruby/object:Gem::Version
|
87
97
|
version: '0'
|
88
98
|
requirements: []
|
89
|
-
rubygems_version: 3.
|
99
|
+
rubygems_version: 3.7.1
|
90
100
|
specification_version: 4
|
91
101
|
summary: A RuboCop extension for converting Ruby to Crystal.
|
92
102
|
test_files: []
|