require "../spec_helper" describe "Semantic: warnings" do describe "deprecated types" do it "detects deprecated class methods" do assert_warning <<-CRYSTAL, @[Deprecated] class Foo end Foo.new CRYSTAL "warning in line 5\nWarning: Deprecated Foo." assert_warning <<-CRYSTAL, @[Deprecated] module Foo::Bar def self.baz end end Foo::Bar.baz CRYSTAL "warning in line 7\nWarning: Deprecated Foo::Bar." end it "detects deprecated superclass" do assert_warning <<-CRYSTAL, @[Deprecated] class Foo end class Bar < Foo end CRYSTAL "warning in line 5\nWarning: Deprecated Foo." end it "doesn't check superclass when the class is deprecated" do assert_warning <<-CRYSTAL, @[Deprecated] class Foo end @[Deprecated] class Bar < Foo end Bar.new CRYSTAL "warning in line 9\nWarning: Deprecated Bar." end it "detects deprecated type reference" do assert_warning <<-CRYSTAL, @[Deprecated] class Foo end def p(x) x end p Foo CRYSTAL "warning in line 9\nWarning: Deprecated Foo." end it "only affects the type not the namespace" do assert_no_warning <<-CRYSTAL @[Deprecated] class Foo class Bar end end Foo::Bar.new CRYSTAL end it "doesn't deprecate instance methods (constructors already warn)" do assert_warning <<-CRYSTAL, @[Deprecated] class Foo def do_something end end foo = Foo.new foo.do_something CRYSTAL "warning in line 7\nWarning: Deprecated Foo." end it "detects deprecated through alias" do assert_warning <<-CRYSTAL, @[Deprecated] class Foo end alias Bar = Foo alias Baz = Bar Baz.new CRYSTAL "warning in line 8\nWarning: Deprecated Foo." end it "detects deprecated constant in generic argument" do assert_warning <<-CRYSTAL, @[Deprecated("Do not use me")] class Foo end class Bar(T) end Bar(Foo) CRYSTAL "warning in line 8\nWarning: Deprecated Foo. Do not use me" end it "detects deprecated constant in include" do assert_warning <<-CRYSTAL, @[Deprecated("Do not use me")] module Foo end class Bar include Foo end Bar.new CRYSTAL "warning in line 6\nWarning: Deprecated Foo. Do not use me" end it "detects deprecated constant in extend" do assert_warning <<-CRYSTAL, @[Deprecated("Do not use me")] module Foo end class Bar extend Foo end CRYSTAL "warning in line 6\nWarning: Deprecated Foo. Do not use me" end end describe "deprecated alias" do it "detects deprecated class method calls" do assert_warning <<-CRYSTAL, class Foo end @[Deprecated("Use Foo.")] alias Bar = Foo Bar.new CRYSTAL "warning in line 7\nWarning: Deprecated Bar. Use Foo." assert_warning <<-CRYSTAL, module Foo::Bar def self.baz; end end @[Deprecated("Use Foo::Bar.")] alias Bar = Foo::Bar Bar.baz CRYSTAL "warning in line 8\nWarning: Deprecated Bar. Use Foo::Bar." end it "doesn't deprecate the aliased type" do assert_no_warning <<-CRYSTAL class Foo end @[Deprecated("Use Foo.")] alias Bar = Foo Foo.new CRYSTAL end end describe "deprecated annotations" do it "detects deprecated annotations" do assert_warning <<-CRYSTAL, @[Deprecated] annotation Foo; end @[Foo] def bar; end bar CRYSTAL "warning in line 2\nWarning: Deprecated annotation Foo." end it "detects deprecated namespaced annotations" do assert_warning <<-CRYSTAL, module MyNamespace @[Deprecated] annotation Foo; end end @[MyNamespace::Foo] def bar; end bar CRYSTAL "warning in line 3\nWarning: Deprecated annotation MyNamespace::Foo." end end describe "deprecated methods" do it "detects top-level deprecated methods" do assert_warning <<-CRYSTAL, @[Deprecated("Do not use me")] def foo end foo CRYSTAL "warning in line 5\nWarning: Deprecated ::foo. Do not use me" end it "deprecation reason is optional" do assert_warning <<-CRYSTAL, @[Deprecated] def foo end foo CRYSTAL "warning in line 5\nWarning: Deprecated ::foo." end it "detects deprecated instance methods" do assert_warning <<-CRYSTAL, class Foo @[Deprecated("Do not use me")] def m end end Foo.new.m CRYSTAL "warning in line 7\nWarning: Deprecated Foo#m. Do not use me" end it "detects deprecated class methods" do assert_warning <<-CRYSTAL, class Foo @[Deprecated("Do not use me")] def self.m end end Foo.m CRYSTAL "warning in line 7\nWarning: Deprecated Foo.m. Do not use me" end it "detects deprecated generic instance methods" do assert_warning <<-CRYSTAL, class Foo(T) @[Deprecated("Do not use me")] def m end end Foo(Int32).new.m CRYSTAL "warning in line 7\nWarning: Deprecated Foo(Int32)#m. Do not use me" end it "detects deprecated generic class methods" do assert_warning <<-CRYSTAL, class Foo(T) @[Deprecated("Do not use me")] def self.m end end Foo(Int32).m CRYSTAL "warning in line 7\nWarning: Deprecated Foo(Int32).m. Do not use me" end it "detects deprecated module methods" do assert_warning <<-CRYSTAL, module Foo @[Deprecated("Do not use me")] def self.m end end Foo.m CRYSTAL "warning in line 7\nWarning: Deprecated Foo.m. Do not use me" end it "detects deprecated methods with named arguments" do assert_warning <<-CRYSTAL, @[Deprecated] def foo(*, a) end foo(a: 2) CRYSTAL "warning in line 5\nWarning: Deprecated ::foo." end it "detects deprecated initialize" do assert_warning <<-CRYSTAL, class Foo @[Deprecated] def initialize end end Foo.new CRYSTAL "warning in line 7\nWarning: Deprecated Foo.new." end it "detects deprecated initialize with named arguments" do assert_warning <<-CRYSTAL, class Foo @[Deprecated] def initialize(*, a) end end Foo.new(a: 2) CRYSTAL "warning in line 7\nWarning: Deprecated Foo.new:a." end it "informs warnings once per call site location (a)" do warning_failures = warnings_result <<-CRYSTAL class Foo @[Deprecated("Do not use me")] def m end def b m end end Foo.new.b Foo.new.b CRYSTAL warning_failures.size.should eq(1) end it "informs warnings once per call site location (b)" do warning_failures = warnings_result <<-CRYSTAL class Foo @[Deprecated("Do not use me")] def m end end Foo.new.m Foo.new.m CRYSTAL warning_failures.size.should eq(2) end it "informs warnings once per yield" do warning_failures = warnings_result <<-CRYSTAL class Foo @[Deprecated("Do not use me")] def m end end def twice yield yield end twice { Foo.new.m } CRYSTAL warning_failures.size.should eq(1) end it "informs warnings once per target type" do warning_failures = warnings_result <<-CRYSTAL class Foo(T) @[Deprecated("Do not use me")] def m end def b m end end Foo(Int32).new.b Foo(Int64).new.b CRYSTAL warning_failures.size.should eq(2) end it "ignore deprecation excluded locations" do with_tempdir("check_warnings_excludes") do Dir.mkdir "lib" # NOTE tempfile might be created in symlinked folder # which affects how to match current dir /var/folders/... # with the real path /private/var/folders/... path = File.realpath(".") main_filename = File.join(path, "main.cr") output_filename = File.join(path, "main") File.write main_filename, <<-CRYSTAL require "./lib/foo" bar foo CRYSTAL File.write File.join(path, "lib", "foo.cr"), <<-CRYSTAL @[Deprecated("Do not use me")] def foo end def bar foo end CRYSTAL compiler = create_spec_compiler compiler.warnings.level = :all compiler.warnings.exclude_lib_path = true compiler.prelude = "empty" compiler.compile Compiler::Source.new(main_filename, File.read(main_filename)), output_filename compiler.warnings.infos.size.should eq(1) end end it "ignores nested calls to deprecated methods" do assert_warning <<-CRYSTAL, @[Deprecated] def foo; bar; end @[Deprecated] def bar; end foo CRYSTAL "warning in line 7\nWarning: Deprecated ::foo." end it "errors if invalid argument type" do assert_error <<-CRYSTAL, @[Deprecated(42)] def foo end CRYSTAL "first argument must be a String" end it "errors if too many arguments" do assert_error <<-CRYSTAL, @[Deprecated("Do not use me", "extra arg")] def foo end CRYSTAL "wrong number of deprecated annotation arguments (given 2, expected 1)" end it "errors if invalid named arguments" do assert_error <<-CRYSTAL, @[Deprecated(invalid: "Do not use me")] def foo end CRYSTAL "too many named arguments (given 1, expected maximum 0)" end end describe "deprecated method args" do describe "defs" do it "warns when a deprecated positional argument is passed" do assert_warning <<-CRYSTAL, def foo(a, @[Deprecated] b, c) end foo(1, 2, 3) CRYSTAL "warning in line 4\nWarning: Deprecated argument b." end it "warns when a deprecated keyword argument is passed" do assert_warning <<-CRYSTAL, def foo(x, *, a, @[Deprecated] b) end foo(0, a: 1, b: 2) CRYSTAL "warning in line 4\nWarning: Deprecated argument b." end it "warns when a deprecated splat argument is passed" do assert_warning <<-CRYSTAL, def foo(a, @[Deprecated] *args) end foo(1, 2) CRYSTAL "warning in line 4\nWarning: Deprecated argument args." end it "warns when a deprecated double splat argument is passed" do assert_warning <<-CRYSTAL, def foo(*, a, @[Deprecated] **opts) end foo(a: 1, bad: 2) CRYSTAL "warning in line 4\nWarning: Deprecated argument opts." end it "warns when a deprecated default positional argument is passed" do assert_warning <<-CRYSTAL, def foo(a, @[Deprecated] b = nil, c = nil) end foo(1, 2) CRYSTAL "warning in line 4\nWarning: Deprecated argument b." end it "warns when a deprecated default keyword argument is passed" do assert_warning <<-CRYSTAL, def foo(*, a, @[Deprecated] b = nil) end foo(a: 1, b: 2) CRYSTAL "warning in line 4\nWarning: Deprecated argument b." end it "doesn't warn when a deprecated default positional argument isn't explicitly passed" do assert_no_warning <<-CRYSTAL def foo(a, @[Deprecated] b = nil, c = nil) end foo(1) foo(1, c: 3) CRYSTAL end it "doesn't warn when a deprecated default keyword argument isn't explicitly passed" do assert_no_warning <<-CRYSTAL def foo(*, a, @[Deprecated] b = nil) end foo(a: 1) CRYSTAL end it "warns when a default value calls a method with a deprecated arg" do assert_warning <<-CRYSTAL, def bar(@[Deprecated] x) end def foo(a, @[Deprecated] b = bar(a)) end foo(1) CRYSTAL "warning in line 4\nWarning: Deprecated argument x." assert_warning <<-CRYSTAL, def bar(@[Deprecated] x) end def foo(a, @[Deprecated] b = bar(a)) end foo(1, 2) CRYSTAL "warning in line 7\nWarning: Deprecated argument b." end it "warns when a deprecated arg default value calls a method with a deprecated arg" do assert_warning <<-CRYSTAL, def bar(@[Deprecated] x) end def foo(@[Deprecated] b = bar(1)) end foo CRYSTAL "warning in line 4\nWarning: Deprecated argument x." end end describe "constructors" do it "warns when a deprecated positional arg is passed" do assert_warning <<-CRYSTAL, class Foo def initialize(a, @[Deprecated] b, c) end end Foo.new(1, 2, 3) CRYSTAL "warning in line 6\nWarning: Deprecated argument b." end it "warns when a deprecated keyword argument is passed" do assert_warning <<-CRYSTAL, class Foo def initialize(x, *, a, @[Deprecated] b) end end Foo.new(0, a: 1, b: 2) CRYSTAL "warning in line 6\nWarning: Deprecated argument b." end it "warns when a deprecated splat argument is passed" do assert_warning <<-CRYSTAL, class Foo def initialize(a, @[Deprecated] *args) end end Foo.new(1, 2) CRYSTAL "warning in line 6\nWarning: Deprecated argument args." end it "warns when a deprecated double splat argument is passed" do assert_warning <<-CRYSTAL, class Foo def initialize(*, a, @[Deprecated] **opts) end end Foo.new(a: 1, bad: 2) CRYSTAL "warning in line 6\nWarning: Deprecated argument opts." end end end describe "deprecated macros" do it "detects top-level deprecated macros" do assert_warning <<-CRYSTAL, @[Deprecated("Do not use me")] macro foo end foo CRYSTAL "warning in line 5\nWarning: Deprecated ::foo. Do not use me" end it "deprecation reason is optional" do assert_warning <<-CRYSTAL, @[Deprecated] macro foo end foo CRYSTAL "warning in line 5\nWarning: Deprecated ::foo." end it "detects deprecated class macros" do assert_warning <<-CRYSTAL, class Foo @[Deprecated("Do not use me")] macro m end end Foo.m CRYSTAL "warning in line 7\nWarning: Deprecated Foo.m. Do not use me" end it "detects deprecated generic class macros" do assert_warning <<-CRYSTAL, class Foo(T) @[Deprecated("Do not use me")] macro m end end Foo.m CRYSTAL "warning in line 7\nWarning: Deprecated Foo.m. Do not use me" end it "detects deprecated module macros" do assert_warning <<-CRYSTAL, module Foo @[Deprecated("Do not use me")] macro m end end Foo.m CRYSTAL "warning in line 7\nWarning: Deprecated Foo.m. Do not use me" end it "detects deprecated macros with named arguments" do assert_warning <<-CRYSTAL, @[Deprecated] macro foo(*, a) end foo(a: 2) CRYSTAL "warning in line 5\nWarning: Deprecated ::foo." end it "informs warnings once per call site location (a)" do warning_failures = warnings_result <<-CRYSTAL class Foo @[Deprecated("Do not use me")] macro m end macro b Foo.m end end Foo.b Foo.b CRYSTAL warning_failures.size.should eq(1) end it "informs warnings once per call site location (b)" do warning_failures = warnings_result <<-CRYSTAL class Foo @[Deprecated("Do not use me")] macro m end end Foo.m Foo.m CRYSTAL warning_failures.size.should eq(2) end it "ignore deprecation excluded locations" do with_tempdir("check_warnings_excludes") do Dir.mkdir_p "lib" # NOTE tempfile might be created in symlinked folder # which affects how to match current dir /var/folders/... # with the real path /private/var/folders/... path = File.realpath(".") main_filename = File.join(path, "main.cr") output_filename = File.join(path, "main") File.write main_filename, %( require "./lib/foo" bar foo ) File.write File.join(path, "lib", "foo.cr"), %( @[Deprecated("Do not use me")] macro foo end macro bar foo end ) compiler = create_spec_compiler compiler.warnings.level = :all compiler.warnings.exclude_lib_path = true compiler.prelude = "empty" compiler.compile Compiler::Source.new(main_filename, File.read(main_filename)), output_filename compiler.warnings.infos.size.should eq(1) end end it "errors if invalid argument type" do assert_error <<-CRYSTAL, "first argument must be a String" @[Deprecated(42)] macro foo end CRYSTAL end it "errors if too many arguments" do assert_error <<-CRYSTAL, "wrong number of deprecated annotation arguments (given 2, expected 1)" @[Deprecated("Do not use me", "extra arg")] macro foo end CRYSTAL end it "errors if invalid named argument" do assert_error <<-CRYSTAL, "too many named arguments (given 1, expected maximum 0)" @[Deprecated(invalid: "Do not use me")] macro foo end CRYSTAL end end describe "deprecated constants" do it "detects deprecated constants" do assert_warning <<-CRYSTAL, @[Deprecated("Do not use me")] FOO = 1 FOO CRYSTAL "warning in line 4\nWarning: Deprecated FOO. Do not use me" end it "detects deprecated constants inside macros" do assert_warning <<-CRYSTAL, @[Deprecated("Do not use me")] FOO = 1 {% FOO %} CRYSTAL "warning in line 4\nWarning: Deprecated FOO. Do not use me" end it "detects deprecated constants in type declarations (1)" do assert_warning <<-CRYSTAL, @[Deprecated("Do not use me")] FOO = 1 class Foo(N) end class Bar < Foo(FOO) end CRYSTAL "warning in line 7\nWarning: Deprecated FOO. Do not use me" end it "detects deprecated constants in type declarations (2)" do assert_warning <<-CRYSTAL, @[Deprecated("Do not use me")] FOO = 1 module Foo(N) end class Bar include Foo(FOO) end CRYSTAL "warning in line 8\nWarning: Deprecated FOO. Do not use me" end it "detects deprecated constants in type declarations (3)" do assert_warning <<-CRYSTAL, @[Deprecated("Do not use me")] FOO = 1 class Foo(N) end alias Bar = Foo(FOO) CRYSTAL "warning in line 7\nWarning: Deprecated FOO. Do not use me" end end describe "abstract def positional parameter name mismatch" do it "detects mismatch with single parameter" do assert_warning <<-CRYSTAL, "warning in line 6\nWarning: positional parameter 'y' corresponds to parameter 'x' of the overridden method" abstract class Foo abstract def foo(x) end class Bar < Foo def foo(y); end end CRYSTAL end it "detects mismatch within many parameters" do assert_warning <<-CRYSTAL, "warning in line 6\nWarning: positional parameter 'e' corresponds to parameter 'c' of the overridden method" abstract class Foo abstract def foo(a, b, c, d) end class Bar < Foo def foo(a, b, e, d); end end CRYSTAL end it "detects multiple mismatches" do warnings_result(<<-CRYSTAL).size.should eq(2) abstract class Foo abstract def foo(src, dst) end class Bar < Foo def foo(dst, src); end end CRYSTAL end it "respects external names of positional parameters (1)" do assert_warning <<-CRYSTAL, "warning in line 6\nWarning: positional parameter 'a' corresponds to parameter 'b' of the overridden method" abstract class Foo abstract def foo(b) end class Bar < Foo def foo(a b); end end CRYSTAL end it "respects external names of positional parameters (2)" do assert_warning <<-CRYSTAL, "warning in line 6\nWarning: positional parameter 'b' corresponds to parameter 'a' of the overridden method" abstract class Foo abstract def foo(a b) end class Bar < Foo def foo(b); end end CRYSTAL end it "doesn't warn if external parameter name matches (1)" do warnings_result(<<-CRYSTAL).should be_empty abstract class Foo abstract def foo(a) end class Bar < Foo def foo(a b); end end CRYSTAL end it "doesn't warn if external parameter name matches (2)" do warnings_result(<<-CRYSTAL).should be_empty abstract class Foo abstract def foo(a b) end class Bar < Foo def foo(a c); end end CRYSTAL end it "doesn't compare positional parameters to single splat" do warnings_result(<<-CRYSTAL).should be_empty abstract class Foo abstract def foo(x) end class Bar < Foo def foo(*y); end end CRYSTAL end it "doesn't compare single splats" do warnings_result(<<-CRYSTAL).should be_empty abstract class Foo abstract def foo(*x) end class Bar < Foo def foo(*y); end end CRYSTAL end it "informs warnings once per matching overload (1)" do assert_warning <<-CRYSTAL, "warning in line 6\nWarning: positional parameter 'y' corresponds to parameter 'x' of the overridden method" abstract class Foo abstract def foo(x : Int32) end class Bar < Foo def foo(y : Int32 | Char); end def foo(x : Int32 | String); end end CRYSTAL end it "informs warnings once per matching overload (2)" do warnings_result(<<-CRYSTAL).size.should eq(2) abstract class Foo abstract def foo(x : Int32) end class Bar < Foo def foo(y : Int32 | Char); end def foo(z : Int32 | String); end end CRYSTAL end describe "stops warning after implementation with matching parameters is found (#12150)" do it "exact match" do warnings_result(<<-CRYSTAL).should be_empty abstract class Foo abstract def foo(x : Int32) end class Bar < Foo def foo(x : Int32); end def foo(y : Int32 | String); end end CRYSTAL end it "contravariant restrictions" do warnings_result(<<-CRYSTAL).should be_empty abstract class Foo abstract def foo(x : Int32, y : Int32) end class Bar < Foo def foo(x : Int32 | Char, y : Int); end def foo(y : Int32 | String, z : Int32); end end CRYSTAL end it "different single splats" do warnings_result(<<-CRYSTAL).should be_empty abstract class Foo abstract def foo(x : Int32, *y) end class Bar < Foo def foo(x : Int32, *z); end def foo(y : Int32 | String, *z); end end CRYSTAL end it "reordered named parameters" do warnings_result(<<-CRYSTAL).should be_empty abstract class Foo abstract def foo(x : Int32, *, y : Int32, z : Int32) end class Bar < Foo def foo(x : Int32, *, z : Int32, y : Int32); end def foo(w : Int, *, y : Int32, z : Int32); end end CRYSTAL end end describe "continues warning if implementation with matching parameters is not found (#12150)" do it "not a full implementation" do assert_warning <<-CRYSTAL, "warning in line 8\nWarning: positional parameter 'y' corresponds to parameter 'x' of the overridden method" abstract class Foo abstract def foo(x : Int32 | String) end class Bar < Foo def foo(x : Int32); end def foo(x : String); end def foo(y : Int32 | String); end end CRYSTAL end it "single splat" do assert_warning <<-CRYSTAL, "warning in line 7\nWarning: positional parameter 'y' corresponds to parameter 'x' of the overridden method" abstract class Foo abstract def foo(x : Int32) end class Bar < Foo def foo(x : Int32, *y); end def foo(y : Int32 | String); end end CRYSTAL end it "double splat" do assert_warning <<-CRYSTAL, "warning in line 7\nWarning: positional parameter 'z' corresponds to parameter 'x' of the overridden method" abstract class Foo abstract def foo(x : Int32, *, y) end class Bar < Foo def foo(x : Int32, **opts); end def foo(z : Int32, *, y); end end CRYSTAL end end it "doesn't warn if current type is abstract (#12266)" do warnings_result(<<-CRYSTAL).should be_empty class Foo def foo(x); end end abstract class Bar < Foo abstract def foo(y) end abstract class Baz < Bar end CRYSTAL end it "doesn't warn if current type is a module (#12266)" do warnings_result(<<-CRYSTAL).should be_empty module Foo def foo(x); end # Warning: positional parameter 'x' corresponds to parameter 'y' of the overridden method Bar#foo(y), which has a different name and may affect named argument passing end module Bar include Foo abstract def foo(y) end module Baz include Bar end CRYSTAL end end it "exposes syntax warnings" do assert_warning UInt64::MAX.to_s, "Warning: #{UInt64::MAX} doesn't fit in an Int64, try using the suffix u64 or i128" end it "exposes syntax warnings after macro interpolation" do assert_warning "{% begin %}0x8000_0000_0000_000{{ 0 }}{% end %}", "Warning: 0x8000_0000_0000_0000 doesn't fit in an Int64, try using the suffix u64 or i128" end end