diff --git a/spec/compiler/crystal/tools/doc/generator_spec.cr b/spec/compiler/crystal/tools/doc/generator_spec.cr index 0b64c6005b74..2528a04dc657 100644 --- a/spec/compiler/crystal/tools/doc/generator_spec.cr +++ b/spec/compiler/crystal/tools/doc/generator_spec.cr @@ -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 %(

#{ann.upcase} lorem ipsum

) + end + end + describe "with #{ann} annotation in parameter" do it "should generate the #{ann} tag" do program = Program.new diff --git a/spec/compiler/semantic/warnings_spec.cr b/spec/compiler/semantic/warnings_spec.cr index a21dcea49e9f..0f2d9755963c 100644 --- a/spec/compiler/semantic/warnings_spec.cr +++ b/spec/compiler/semantic/warnings_spec.cr @@ -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." + + 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, diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index ed50ff1f1063..27f5eae8c635 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -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 diff --git a/src/compiler/crystal/semantic/cleanup_transformer.cr b/src/compiler/crystal/semantic/cleanup_transformer.cr index 744ea068d997..eddd879c3f51 100644 --- a/src/compiler/crystal/semantic/cleanup_transformer.cr +++ b/src/compiler/crystal/semantic/cleanup_transformer.cr @@ -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) + 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 @@ -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) diff --git a/src/compiler/crystal/semantic/top_level_visitor.cr b/src/compiler/crystal/semantic/top_level_visitor.cr index 864579d0ba97..40f143a292b0 100644 --- a/src/compiler/crystal/semantic/top_level_visitor.cr +++ b/src/compiler/crystal/semantic/top_level_visitor.cr @@ -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 @@ -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 diff --git a/src/compiler/crystal/semantic/warnings.cr b/src/compiler/crystal/semantic/warnings.cr index 4de6de680fc7..4469d1248e11 100644 --- a/src/compiler/crystal/semantic/warnings.cr +++ b/src/compiler/crystal/semantic/warnings.cr @@ -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 @@ -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? @@ -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| @@ -57,6 +72,9 @@ module Crystal end @warnings.infos << use_site.warning(full_message) + true + else + false end end