Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add --warnings --exclude-warnings compiler options
* Add warnings checks for deprecated methods * Support warning for build, run and spec commands * Extract path handling operations of formatter to reuse them * Defaults set to --warnings=emit --exclude-warnings=libs fixup! Add --warnings --exclude-warnings compiler options
- Loading branch information
Showing
13 changed files
with
359 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
require "../spec_helper" | ||
|
||
describe "Code gen: warnings" do | ||
it "detects top-level deprecated methods" do | ||
assert_warning %( | ||
@[Deprecated("Do not use me")] | ||
def foo | ||
end | ||
foo | ||
), "Warning in line 6: Deprecated top-level foo. Do not use me", | ||
inject_primitives: false | ||
end | ||
|
||
it "deprecation reason is optional" do | ||
assert_warning %( | ||
@[Deprecated] | ||
def foo | ||
end | ||
foo | ||
), "Warning in line 6: Deprecated top-level foo.", | ||
inject_primitives: false | ||
end | ||
|
||
it "detects deprecated instance methods" do | ||
assert_warning %( | ||
class Foo | ||
@[Deprecated("Do not use me")] | ||
def m | ||
end | ||
end | ||
Foo.new.m | ||
), "Warning in line 8: Deprecated Foo#m. Do not use me", | ||
inject_primitives: false | ||
end | ||
|
||
it "detects deprecated class methods" do | ||
assert_warning %( | ||
class Foo | ||
@[Deprecated("Do not use me")] | ||
def self.m | ||
end | ||
end | ||
Foo.m | ||
), "Warning in line 8: Deprecated Foo.m. Do not use me", | ||
inject_primitives: false | ||
end | ||
|
||
it "detects deprecated generic instance methods" do | ||
assert_warning %( | ||
class Foo(T) | ||
@[Deprecated("Do not use me")] | ||
def m | ||
end | ||
end | ||
Foo(Int32).new.m | ||
), "Warning in line 8: Deprecated Foo(Int32)#m. Do not use me", | ||
inject_primitives: false | ||
end | ||
|
||
it "detects deprecated generic class methods" do | ||
assert_warning %( | ||
class Foo(T) | ||
@[Deprecated("Do not use me")] | ||
def self.m | ||
end | ||
end | ||
Foo(Int32).m | ||
), "Warning in line 8: Deprecated Foo(Int32).m. Do not use me", | ||
inject_primitives: false | ||
end | ||
|
||
it "detects deprecated module methods" do | ||
assert_warning %( | ||
module Foo | ||
@[Deprecated("Do not use me")] | ||
def self.m | ||
end | ||
end | ||
Foo.m | ||
), "Warning in line 8: Deprecated Foo.m. Do not use me", | ||
inject_primitives: false | ||
end | ||
|
||
it "ignore deprecation excluded locations" do | ||
with_tempfile("check_warnings_excludes") do |path| | ||
FileUtils.mkdir_p File.join(path, "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.real_path(path) | ||
|
||
main_filename = File.join(path, "main.cr") | ||
output_filename = File.join(path, "main") | ||
|
||
Dir.cd(path) do | ||
File.write main_filename, %( | ||
require "./lib/foo" | ||
bar | ||
foo | ||
) | ||
File.write File.join(path, "lib", "foo.cr"), %( | ||
@[Deprecated("Do not use me")] | ||
def foo | ||
end | ||
def bar | ||
foo | ||
end | ||
) | ||
|
||
compiler = Compiler.new | ||
compiler.warnings = Warnings::Emit | ||
compiler.warnings_exclude << Crystal.normalize_path "lib" | ||
compiler.prelude = "empty" | ||
result = compiler.compile Compiler::Source.new(main_filename, File.read(main_filename)), output_filename | ||
|
||
result.program.warning_failures.size.should eq(1) | ||
end | ||
end | ||
end | ||
|
||
it "errors if invalid argument type" do | ||
assert_error %( | ||
@[Deprecated(42)] | ||
def foo | ||
end | ||
), | ||
"Error in line 3: first argument must be a String" | ||
end | ||
|
||
it "errors if too many arguments" do | ||
assert_error %( | ||
@[Deprecated("Do not use me", "extra arg")] | ||
def foo | ||
end | ||
), | ||
"Error in line 3: wrong number of deprecated annotation arguments (given 2, expected 1)" | ||
end | ||
|
||
it "errors if missing link arguments" do | ||
assert_error %( | ||
@[Deprecated(invalid: "Do not use me")] | ||
def foo | ||
end | ||
), | ||
"Error in line 3: too many named arguments (given 1, expected maximum 0)" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
module Crystal | ||
struct DeprecatedAnnotation | ||
getter message : String? | ||
|
||
def initialize(@message = nil) | ||
end | ||
|
||
def self.from(ann : Annotation) | ||
args = ann.args | ||
named_args = ann.named_args | ||
|
||
if named_args | ||
ann.raise "too many named arguments (given #{named_args.size}, expected maximum 0)" | ||
end | ||
|
||
message = nil | ||
count = 0 | ||
|
||
args.each do |arg| | ||
case count | ||
when 0 | ||
arg.raise "first argument must be a String" unless arg.is_a?(StringLiteral) | ||
message = arg.value | ||
else | ||
ann.wrong_number_of "deprecated annotation arguments", args.size, "1" | ||
end | ||
|
||
count += 1 | ||
end | ||
|
||
new(message) | ||
end | ||
end | ||
|
||
class Def | ||
def short_reference | ||
case owner | ||
when Program | ||
"top-level #{name}" | ||
when .metaclass? | ||
"#{owner.instance_type}.#{name}" | ||
else | ||
"#{owner}##{name}" | ||
end | ||
end | ||
end | ||
|
||
class CodeGenVisitor | ||
def check_call_to_deprecated_method(node : Call) | ||
return unless @program.warnings.emit? || @program.warnings.error? | ||
|
||
if (ann = node.target_def.annotation(@program.deprecated_annotation)) && | ||
(deprecated_annotation = DeprecatedAnnotation.from(ann)) | ||
return if ignore_warning_due_to_location(node.location) | ||
|
||
message = deprecated_annotation.message | ||
message = message ? " #{message}" : "" | ||
|
||
full_message = node.warning "Deprecated #{node.target_def.short_reference}.#{message}" | ||
|
||
@program.warning_failures << full_message | ||
end | ||
end | ||
|
||
private def ignore_warning_due_to_location(location : Location?) | ||
return false unless location | ||
|
||
filename = location.original_filename | ||
return false unless filename | ||
|
||
return @program.warnings_exclude.any? do |path| | ||
filename.starts_with?(path) | ||
end | ||
end | ||
end | ||
|
||
class Command | ||
def report_warnings(result : Compiler::Result) | ||
if (result.program.warnings.emit? || result.program.warnings.error?) && | ||
result.program.warning_failures.size > 0 | ||
result.program.warning_failures.each do |message| | ||
STDERR.puts message | ||
end | ||
|
||
STDERR.puts "A total of #{result.program.warning_failures.size} warnings were found." | ||
end | ||
end | ||
|
||
def warnings_fail_on_exit?(result : Compiler::Result) | ||
result.program.warnings.error? && result.program.warning_failures.size > 0 | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.