Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions spec/compiler/crystal/tools/doc/generator_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,19 @@ describe Doc::Generator do
end
end

describe "with alias" do
it "should generate the #{ann} tag" do
program = Program.new
generator = Doc::Generator.new program, ["."]
doc_type = Doc::Type.new generator, program

alias_type = AliasType.new(program, program, "Foo", Crystal::Path.new("Bar"))
alias_type.add_annotation(program.types[ann].as(Crystal::AnnotationType), Annotation.new(Crystal::Path.new(ann), ["lorem ipsum".string] of ASTNode))
doc_type = Doc::Type.new generator, alias_type
doc_type.formatted_doc.should eq %(<p><span class="flag #{color}">#{ann.upcase}</span> lorem ipsum</p>)
end
end

describe "with #{ann} annotation in parameter" do
it "should generate the #{ann} tag" do
program = Program.new
Expand Down
187 changes: 187 additions & 0 deletions spec/compiler/semantic/warnings_spec.cr
Comment thread
ysbaddaden marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,6 +1,193 @@
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."
Comment thread
ysbaddaden marked this conversation as resolved.

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,
Expand Down
5 changes: 5 additions & 0 deletions spec/spec_helper.cr
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ def assert_warning(code, message, *, file = __FILE__, line = __LINE__)
warning_failures[0].should contain(message), file: file, line: line
end

def assert_no_warning(code, *, file = __FILE__, line = __LINE__)
warning_failures = warnings_result(code)
warning_failures.should be_empty, file: file, line: line
end

def assert_macro(macro_body, expected, args = nil, *, expected_pragmas = nil, flags = nil, file = __FILE__, line = __LINE__)
assert_macro(macro_body, expected, expected_pragmas: expected_pragmas, flags: flags, file: file, line: line) { args }
end
Expand Down
15 changes: 15 additions & 0 deletions src/compiler/crystal/semantic/cleanup_transformer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ module Crystal
def transform(node : ClassDef)
super

# check superclass for deprecation if the node isn't deprecated
if (type = node.type.lookup_type?(node.name)) && !type.annotation(@program.deprecated_annotation)
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW: node.type is NilType ... I must lookup the type by its name (actually a Path) to get the actual type for the ClassDef node 🙃

There's probably a reason, but it escapes me.

if ((superclass = node.superclass).is_a?(Path) && (stype = superclass.type? || node.type.lookup_type?(superclass)))
@program.check_deprecated_type(stype, superclass)
end
end

node.hook_expansions.try &.map! &.transform self
node
end
Expand Down Expand Up @@ -441,11 +448,19 @@ module Crystal
const.value = const.value.transform self
const.cleaned_up = true
end
elsif type = node.target_type
@program.check_deprecated_type(type, node)
end

node
end

def transform(node : Generic)
transform_many node.type_vars

node
end

private def void_lib_call?(node)
return false unless node.is_a?(Call)

Expand Down
7 changes: 7 additions & 0 deletions src/compiler/crystal/semantic/top_level_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,9 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
end

alias_type = AliasType.new(@program, scope, name, node.value)
process_annotations(annotations) do |annotation_type, ann|
alias_type.add_annotation(annotation_type, ann)
end
attach_doc alias_type, node, annotations
scope.types[name] = alias_type

Expand Down Expand Up @@ -1127,6 +1130,10 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
node_name.raise "#{type} is not a module, it's a #{type.type_desc}"
end

if node_name.is_a?(Path)
@program.check_deprecated_type(type, node_name)
end

begin
current_type.as(ModuleType).include type
run_hooks hook_type(type), current_type, kind, node
Expand Down
24 changes: 21 additions & 3 deletions src/compiler/crystal/semantic/warnings.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Crystal
property warnings = WarningCollection.new

@deprecated_constants_detected = Set(String).new
@deprecated_types_detected = Set(String).new
@deprecated_methods_detected = Set(String).new
@deprecated_macros_detected = Set(String).new
@deprecated_annotations_detected = Set(String).new
Expand All @@ -16,6 +17,16 @@ module Crystal
check_deprecation(const, node, @deprecated_constants_detected)
end

def check_deprecated_type(type : Type, node : Path)
return unless @warnings.level.all?

unless check_deprecation(type, node, @deprecated_types_detected)
if type.is_a?(AliasType)
check_deprecation(type.aliased_type, node, @deprecated_types_detected)
end
end
end

def check_call_to_deprecated_macro(a_macro : Macro, call : Call)
return unless @warnings.level.all?

Expand All @@ -41,12 +52,16 @@ module Crystal
if (ann = object.annotation(self.deprecated_annotation)) &&
(deprecated_annotation = DeprecatedAnnotation.from(ann))
use_location = use_site.location.try(&.macro_location) || use_site.location
return if !use_location || @warnings.ignore_warning_due_to_location?(use_location)
return false if !use_location || @warnings.ignore_warning_due_to_location?(use_location)

# skip warning if the use site was already informed
name = object.short_reference
name = if object.responds_to?(:short_reference)
object.short_reference
else
object.to_s
end
warning_key = "#{name} #{use_location}"
return if detects.includes?(warning_key)
return true if detects.includes?(warning_key)
detects.add(warning_key)

full_message = String.build do |io|
Expand All @@ -57,6 +72,9 @@ module Crystal
end

@warnings.infos << use_site.warning(full_message)
true
else
false
Comment thread
ysbaddaden marked this conversation as resolved.
end
end

Expand Down