summaryrefslogtreecommitdiff
path: root/test/prism/warnings_test.rb
blob: d01db01a0e03812ed31f9a899951b8055329aa15 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# frozen_string_literal: true

return if RUBY_VERSION < "3.1"

require_relative "test_helper"
require "stringio"

module Prism
  class WarningsTest < TestCase
    def test_ambiguous_uminus
      assert_warning("a -b", "ambiguous first argument")
    end

    def test_ambiguous_uplus
      assert_warning("a +b", "ambiguous first argument")
    end

    def test_ambiguous_ustar
      assert_warning("a *b", "argument prefix")
    end

    def test_ambiguous_regexp
      assert_warning("a /b/", "wrap regexp in parentheses")
    end

    def test_equal_in_conditional
      assert_warning("if a = 1; end; a = a", "should be ==")
    end

    def test_dot_dot_dot_eol
      assert_warning("_ = foo...", "... at EOL")
      assert_warning("def foo(...) = bar ...", "... at EOL")

      assert_warning("_ = foo... #", "... at EOL")
      assert_warning("_ = foo... \t\v\f\n", "... at EOL")

      refute_warning("p foo...bar")
      refute_warning("p foo...      bar")
    end

    def test_END_in_method
      assert_warning("def foo; END {}; end", "END in method")
    end

    def test_duplicated_hash_key
      assert_warning("{ a: 1, a: 2 }", "duplicated and overwritten")
      assert_warning("{ a: 1, **{ a: 2 } }", "duplicated and overwritten")
    end

    def test_duplicated_when_clause
      assert_warning("case 1; when 1, 1; end", "clause with line")
    end

    def test_float_out_of_range
      assert_warning("_ = 1.0e100000", "out of range")
    end

    def test_integer_in_flip_flop
      assert_warning("1 if 2..foo", "integer")
    end

    def test_keyword_eol
      assert_warning("if\ntrue; end", "end of line")
      assert_warning("if true\nelsif\nfalse; end", "end of line")
    end

    def test_string_in_predicate
      assert_warning("if 'foo'; end", "string")
      assert_warning("if \"\#{foo}\"; end", "string")
      assert_warning("if __FILE__; end", "string")
    end

    def test_symbol_in_predicate
      assert_warning("if :foo; end", "symbol")
      assert_warning("if :\"\#{foo}\"; end", "symbol")
    end

    def test_literal_in_predicate
      assert_warning("if __LINE__; end", "literal")
      assert_warning("if __ENCODING__; end", "literal")
      assert_warning("if 1; end", "literal")
      assert_warning("if 1.0; end", "literal")
      assert_warning("if 1r; end", "literal")
      assert_warning("if 1i; end", "literal")
    end

    def test_regexp_in_predicate
      assert_warning("if /foo/; end", "regex")
      assert_warning("if /foo\#{bar}/; end", "regex")
    end

    def test_unused_local_variables
      assert_warning("foo = 1", "unused")

      refute_warning("foo = 1", compare: false, command_line: "e")
      refute_warning("foo = 1", compare: false, scopes: [[]])

      assert_warning("def foo; bar = 1; end", "unused")
      assert_warning("def foo; bar, = 1; end", "unused")

      refute_warning("def foo; bar &&= 1; end")
      refute_warning("def foo; bar ||= 1; end")
      refute_warning("def foo; bar += 1; end")

      refute_warning("def foo; bar = bar; end")
      refute_warning("def foo; bar = bar = 1; end")
      refute_warning("def foo; bar = (bar = 1); end")
      refute_warning("def foo; bar = begin; bar = 1; end; end")
      refute_warning("def foo; bar = (qux; bar = 1); end")
      refute_warning("def foo; bar, = bar = 1; end")
      refute_warning("def foo; bar, = 1, bar = 1; end")

      refute_warning("def foo(bar); end")
      refute_warning("def foo(bar = 1); end")
      refute_warning("def foo((bar)); end")
      refute_warning("def foo(*bar); end")
      refute_warning("def foo(*, bar); end")
      refute_warning("def foo(*, (bar)); end")
      refute_warning("def foo(bar:); end")
      refute_warning("def foo(**bar); end")
      refute_warning("def foo(&bar); end")
      refute_warning("->(bar) {}")
      refute_warning("->(; bar) {}", compare: false)

      refute_warning("def foo; bar = 1; tap { bar }; end")
      refute_warning("def foo; bar = 1; tap { baz = bar; baz }; end")
    end

    def test_void_statements
      assert_warning("foo = 1; foo", "a variable in void")
      assert_warning("@foo", "a variable in void")
      assert_warning("@@foo", "a variable in void")
      assert_warning("$foo", "a variable in void")
      assert_warning("$+", "a variable in void")
      assert_warning("$1", "a variable in void")

      assert_warning("self", "self in void")
      assert_warning("nil", "nil in void")
      assert_warning("true", "true in void")
      assert_warning("false", "false in void")

      assert_warning("1", "literal in void")
      assert_warning("1.0", "literal in void")
      assert_warning("1r", "literal in void")
      assert_warning("1i", "literal in void")
      assert_warning(":foo", "literal in void")
      assert_warning("\"foo\"", "literal in void")
      assert_warning("\"foo\#{1}\"", "literal in void")
      assert_warning("/foo/", "literal in void")
      assert_warning("/foo\#{1}/", "literal in void")

      assert_warning("Foo", "constant in void")
      assert_warning("::Foo", ":: in void")
      assert_warning("Foo::Bar", ":: in void")

      assert_warning("1..2", ".. in void")
      assert_warning("1..", ".. in void")
      assert_warning("..2", ".. in void")
      assert_warning("1...2", "... in void")
      assert_warning("1...;", "... in void")
      assert_warning("...2", "... in void")

      assert_warning("defined?(foo)", "defined? in void")

      assert_warning("1 + 1", "+ in void")
      assert_warning("1 - 1", "- in void")
      assert_warning("1 * 1", "* in void")
      assert_warning("1 / 1", "/ in void")
      assert_warning("1 % 1", "% in void")
      assert_warning("1 | 1", "| in void")
      assert_warning("1 ^ 1", "^ in void")
      assert_warning("1 & 1", "& in void")
      assert_warning("1 > 1", "> in void")
      assert_warning("1 < 1", "< in void")

      assert_warning("1 ** 1", "** in void")
      assert_warning("1 <= 1", "<= in void")
      assert_warning("1 >= 1", ">= in void")
      assert_warning("1 != 1", "!= in void")
      assert_warning("1 == 1", "== in void")
      assert_warning("1 <=> 1", "<=> in void")

      assert_warning("+foo", "+@ in void")
      assert_warning("-foo", "-@ in void")

      assert_warning("def foo; @bar; @baz; end", "variable in void")
      refute_warning("def foo; @bar; end")
      refute_warning("@foo", compare: false, scopes: [[]])
    end

    def test_unreachable_statement
      assert_warning("begin; rescue; retry; foo; end", "statement not reached")

      assert_warning("return; foo", "statement not reached")

      assert_warning("tap { break; foo }", "statement not reached")
      assert_warning("tap { break 1; foo }", "statement not reached")

      assert_warning("tap { next; foo }", "statement not reached")
      assert_warning("tap { next 1; foo }", "statement not reached")

      assert_warning("tap { redo; foo }", "statement not reached")
    end

    private

    def assert_warning(source, message)
      warnings = Prism.parse(source).warnings

      assert_equal 1, warnings.length
      assert_include warnings.first.message, message

      if defined?(RubyVM::AbstractSyntaxTree)
        assert_include capture_warning { RubyVM::AbstractSyntaxTree.parse(source) }, message
      end
    end

    def refute_warning(source, compare: true, **options)
      assert_empty Prism.parse(source, **options).warnings

      if compare && defined?(RubyVM::AbstractSyntaxTree)
        assert_empty capture_warning { RubyVM::AbstractSyntaxTree.parse(source) }
      end
    end

    def capture_warning
      stderr, $stderr, verbose, $VERBOSE = $stderr, StringIO.new, $VERBOSE, true

      begin
        yield
        $stderr.string
      ensure
        $stderr, $VERBOSE = stderr, verbose
      end
    end
  end
end