diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml index 5b879f8f094..18c59bb28df 100644 --- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml +++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml @@ -2805,6 +2805,8 @@ WarningCode.SDK_VERSION_NEVER: notes: Deprecated WarningCode.SDK_VERSION_SET_LITERAL: status: hasFix +WarningCode.SDK_VERSION_SINCE: + status: needsEvaluation WarningCode.SDK_VERSION_UI_AS_CODE: status: hasFix WarningCode.SDK_VERSION_UI_AS_CODE_IN_CONST_CONTEXT: diff --git a/pkg/analyzer/lib/src/dart/ast/extensions.dart b/pkg/analyzer/lib/src/dart/ast/extensions.dart index 997ccdb8172..5a85d14c3c2 100644 --- a/pkg/analyzer/lib/src/dart/ast/extensions.dart +++ b/pkg/analyzer/lib/src/dart/ast/extensions.dart @@ -165,6 +165,15 @@ extension IdentifierExtension on Identifier { return _readElement(this); } + SimpleIdentifier get simpleName { + final self = this; + if (self is SimpleIdentifier) { + return self; + } else { + return (self as PrefixedIdentifier).identifier; + } + } + Element? get writeElement { return _writeElement(this); } diff --git a/pkg/analyzer/lib/src/error/codes.g.dart b/pkg/analyzer/lib/src/error/codes.g.dart index 995adf9f1aa..d5fe3321709 100644 --- a/pkg/analyzer/lib/src/error/codes.g.dart +++ b/pkg/analyzer/lib/src/error/codes.g.dart @@ -6736,6 +6736,16 @@ class WarningCode extends AnalyzerErrorCode { hasPublishedDocs: true, ); + /// Parameters: + /// 0: the version specified in the `@Since()` annotation + /// 1: the SDK version constraints + static const WarningCode SDK_VERSION_SINCE = WarningCode( + 'SDK_VERSION_SINCE', + "This API is available since SDK {0}, but constraints '{1}' don't " + "guarantee it.", + correctionMessage: "Try updating the SDK constraints.", + ); + /// No parameters. static const WarningCode SDK_VERSION_UI_AS_CODE = WarningCode( 'SDK_VERSION_UI_AS_CODE', diff --git a/pkg/analyzer/lib/src/error/error_code_values.g.dart b/pkg/analyzer/lib/src/error/error_code_values.g.dart index 67325cb8c5b..8ce7f60c8e7 100644 --- a/pkg/analyzer/lib/src/error/error_code_values.g.dart +++ b/pkg/analyzer/lib/src/error/error_code_values.g.dart @@ -993,6 +993,7 @@ const List errorCodeValues = [ WarningCode.SDK_VERSION_IS_EXPRESSION_IN_CONST_CONTEXT, WarningCode.SDK_VERSION_NEVER, WarningCode.SDK_VERSION_SET_LITERAL, + WarningCode.SDK_VERSION_SINCE, WarningCode.SDK_VERSION_UI_AS_CODE, WarningCode.SDK_VERSION_UI_AS_CODE_IN_CONST_CONTEXT, WarningCode.SUBTYPE_OF_SEALED_CLASS, diff --git a/pkg/analyzer/lib/src/hint/sdk_constraint_verifier.dart b/pkg/analyzer/lib/src/hint/sdk_constraint_verifier.dart index 07f17ad3613..87c52139788 100644 --- a/pkg/analyzer/lib/src/hint/sdk_constraint_verifier.dart +++ b/pkg/analyzer/lib/src/hint/sdk_constraint_verifier.dart @@ -16,6 +16,9 @@ import 'package:pub_semver/pub_semver.dart'; /// A visitor that finds code that assumes a later version of the SDK than the /// minimum version required by the SDK constraints in `pubspec.yaml`. class SdkConstraintVerifier extends RecursiveAstVisitor { + /// TODO(scheglov) Fix pre-existing violations and remove. + static bool shouldCheckSinceSdkVersion = false; + /// The error reporter to be used to report errors. final ErrorReporter _errorReporter; @@ -123,6 +126,20 @@ class SdkConstraintVerifier extends RecursiveAstVisitor { bool get checkUiAsCode => _checkUiAsCode ??= !before_2_2_2.intersect(_versionConstraint).isEmpty; + @override + void visitArgumentList(ArgumentList node) { + // Check (optional) positional arguments. + // Named arguments are checked in [NamedExpression]. + for (final argument in node.arguments) { + if (argument is! NamedExpression) { + final parameter = argument.staticParameterElement; + _checkSinceSdkVersion(parameter, node, errorNode: argument); + } + } + + super.visitArgumentList(node); + } + @override void visitAsExpression(AsExpression node) { if (checkConstantUpdate2018 && node.inConstantContext) { @@ -132,6 +149,13 @@ class SdkConstraintVerifier extends RecursiveAstVisitor { super.visitAsExpression(node); } + @override + void visitAssignmentExpression(AssignmentExpression node) { + _checkSinceSdkVersion(node.readElement, node); + _checkSinceSdkVersion(node.writeElement, node); + super.visitAssignmentExpression(node); + } + @override void visitBinaryExpression(BinaryExpression node) { if (checkTripleShift) { @@ -171,6 +195,12 @@ class SdkConstraintVerifier extends RecursiveAstVisitor { super.visitBinaryExpression(node); } + @override + void visitConstructorName(ConstructorName node) { + _checkSinceSdkVersion(node.staticElement, node); + super.visitConstructorName(node); + } + @override void visitExtensionDeclaration(ExtensionDeclaration node) { if (checkExtensionMethods) { @@ -199,6 +229,12 @@ class SdkConstraintVerifier extends RecursiveAstVisitor { _inUiAsCode = wasInUiAsCode; } + @override + void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { + _checkSinceSdkVersion(node.staticElement, node); + super.visitFunctionExpressionInvocation(node); + } + @override void visitHideCombinator(HideCombinator node) { // Don't flag references to either `Future` or `Stream` within a combinator. @@ -232,6 +268,30 @@ class SdkConstraintVerifier extends RecursiveAstVisitor { super.visitMethodDeclaration(node); } + @override + void visitMethodInvocation(MethodInvocation node) { + _checkSinceSdkVersion(node.methodName.staticElement, node); + super.visitMethodInvocation(node); + } + + @override + void visitNamedType(NamedType node) { + _checkSinceSdkVersion(node.name.staticElement, node); + super.visitNamedType(node); + } + + @override + void visitPrefixedIdentifier(PrefixedIdentifier node) { + _checkSinceSdkVersion(node.staticElement, node); + super.visitPrefixedIdentifier(node); + } + + @override + void visitPropertyAccess(PropertyAccess node) { + _checkSinceSdkVersion(node.propertyName.staticElement, node); + super.visitPropertyAccess(node); + } + @override void visitSetOrMapLiteral(SetOrMapLiteral node) { if (node.isSet && checkSetLiterals && !_inSetLiteral) { @@ -254,6 +314,7 @@ class SdkConstraintVerifier extends RecursiveAstVisitor { if (node.inDeclarationContext()) { return; } + _checkSinceSdkVersion(node.staticElement, node); var element = node.staticElement; if (checkFutureAndStream && element is InterfaceElement && @@ -287,6 +348,54 @@ class SdkConstraintVerifier extends RecursiveAstVisitor { _inUiAsCode = wasInUiAsCode; } + void _checkSinceSdkVersion( + Element? element, + AstNode target, { + AstNode? errorNode, + }) { + if (!shouldCheckSinceSdkVersion) { + return; + } + + if (element != null) { + final sinceSdkVersion = element.sinceSdkVersion; + if (sinceSdkVersion != null) { + if (!_versionConstraint.requiresAtLeast(sinceSdkVersion)) { + if (errorNode == null) { + if (target is AssignmentExpression) { + target = target.leftHandSide; + } + if (target is ConstructorName) { + errorNode = target.name ?? target.type.name.simpleName; + } else if (target is FunctionExpressionInvocation) { + errorNode = target.argumentList; + } else if (target is MethodInvocation) { + errorNode = target.methodName; + } else if (target is NamedType) { + errorNode = target.name.simpleName; + } else if (target is PrefixedIdentifier) { + errorNode = target.identifier; + } else if (target is PropertyAccess) { + errorNode = target.propertyName; + } else if (target is SimpleIdentifier) { + errorNode = target; + } else { + throw UnimplementedError('(${target.runtimeType}) $target'); + } + } + _errorReporter.reportErrorForNode( + WarningCode.SDK_VERSION_SINCE, + errorNode, + [ + sinceSdkVersion.toString(), + _versionConstraint.toString(), + ], + ); + } + } + } + } + /// Given that the [node] is only valid when the ui-as-code feature is /// enabled, check that the code will not be executed with a version of the /// SDK that does not support the feature. @@ -309,3 +418,22 @@ class SdkConstraintVerifier extends RecursiveAstVisitor { } } } + +extension on VersionConstraint { + bool requiresAtLeast(Version version) { + final self = this; + if (self is Version) { + return self == version; + } + if (self is VersionRange) { + final min = self.min; + if (min == null) { + return false; + } else { + return min >= version; + } + } + // We don't know, but will not complain. + return true; + } +} diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml index 9ada969e96b..2c2da0ec071 100644 --- a/pkg/analyzer/messages.yaml +++ b/pkg/analyzer/messages.yaml @@ -22889,6 +22889,13 @@ WarningCode: ```dart var s = new Set(); ``` + SDK_VERSION_SINCE: + problemMessage: "This API is available since SDK {0}, but constraints '{1}' don't guarantee it." + correctionMessage: Try updating the SDK constraints. + comment: |- + Parameters: + 0: the version specified in the `@Since()` annotation + 1: the SDK version constraints SDK_VERSION_UI_AS_CODE: problemMessage: "The for, if, and spread elements weren't supported until version 2.3.0, but this code is required to be able to run on earlier versions." correctionMessage: Try updating the SDK constraints. diff --git a/pkg/analyzer/test/src/diagnostics/sdk_constraint_verifier_support.dart b/pkg/analyzer/test/src/diagnostics/sdk_constraint_verifier_support.dart index 34dbac4f4c1..cfef5cf5ad1 100644 --- a/pkg/analyzer/test/src/diagnostics/sdk_constraint_verifier_support.dart +++ b/pkg/analyzer/test/src/diagnostics/sdk_constraint_verifier_support.dart @@ -20,4 +20,17 @@ class SdkConstraintVerifierTest extends PubPackageResolutionTest { await assertErrorsInCode(source, expectedErrors ?? []); } + + /// Verify that the [expectedErrors] are produced if the [source] is analyzed + /// in a context that uses given SDK [constraints]. + Future verifyVersion2(String constraints, String source, + {List? expectedErrors}) async { + writeTestPackagePubspecYamlFile( + PubspecYamlFileConfig( + sdkVersion: constraints, + ), + ); + + await assertErrorsInCode(source, expectedErrors ?? []); + } } diff --git a/pkg/analyzer/test/src/diagnostics/sdk_version_since_test.dart b/pkg/analyzer/test/src/diagnostics/sdk_version_since_test.dart new file mode 100644 index 00000000000..9d100f8a021 --- /dev/null +++ b/pkg/analyzer/test/src/diagnostics/sdk_version_since_test.dart @@ -0,0 +1,947 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:analyzer/src/error/codes.dart'; +import 'package:analyzer/src/hint/sdk_constraint_verifier.dart'; +import 'package:analyzer/src/test_utilities/mock_sdk.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import 'sdk_constraint_verifier_support.dart'; + +main() { + defineReflectiveSuite(() { + defineReflectiveTests(SdkVersionSinceTest); + }); +} + +@reflectiveTest +class SdkVersionSinceTest extends SdkConstraintVerifierTest { + @override + List additionalMockSdkLibraries = []; + + @override + void setUp() { + SdkConstraintVerifier.shouldCheckSinceSdkVersion = true; + super.setUp(); + } + + @override + Future tearDown() async { + SdkConstraintVerifier.shouldCheckSinceSdkVersion = false; + await super.tearDown(); + } + + test_class_constructor_formalParameter_optionalNamed() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +class A { + void A({@Since('2.15') int? foo}); +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f() { + A(foo: 0); +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 35, 3), + ]); + } + + test_class_constructor_formalParameter_optionalPositional() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +class A { + void A([@Since('2.15') int? foo]); +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f() { + A(42); +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 35, 2), + ]); + } + + test_class_constructor_named_instanceCreation() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +class A { + @Since('2.15') + A.named(); +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f() { + A.named(); +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 40, 5), + ]); + } + + test_class_constructor_named_tearOff() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +class A { + @Since('2.15') + A.named(); +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f() { + A.named; +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 40, 5), + ]); + } + + test_class_constructor_unnamed_instanceCreation() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +class A { + @Since('2.15') + A(); +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f() { + A(); +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 33, 1), + ]); + } + + test_class_field_read() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +class A { + @Since('2.15') + int foo = 0; +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f(A a) { + (a).foo; + a.foo; +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 40, 3), + error(WarningCode.SDK_VERSION_SINCE, 49, 3), + ]); + } + + test_class_field_readWrite() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +class A { + @Since('2.15') + int foo = 0; +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f(A a) { + (a).foo += 0; + a.foo += 0; +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 40, 3), + error(WarningCode.SDK_VERSION_SINCE, 54, 3), + ]); + } + + test_class_field_write() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +class A { + @Since('2.15') + int foo = 0; +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f(A a) { + (a).foo = 0; + a.foo = 0; +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 40, 3), + error(WarningCode.SDK_VERSION_SINCE, 53, 3), + ]); + } + + test_class_getter() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +class A { + @Since('2.15') + int get foo => 0; +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f(A a) { + (a).foo; + a.foo; +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 40, 3), + error(WarningCode.SDK_VERSION_SINCE, 49, 3), + ]); + } + + test_class_getterSetter_readWrite_both() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +class A { + @Since('2.15') + int get foo => 0; + @Since('2.15') + set foo(int _) {} +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f(A a) { + a.foo += 0; +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 38, 3), + ]); + } + + test_class_getterSetter_readWrite_getter() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +class A { + @Since('2.15') + int get foo => 0; + set foo(int _) {} +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f(A a) { + a.foo += 0; +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 38, 3), + ]); + } + + test_class_getterSetter_readWrite_setter() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +class A { + int get foo => 0; + @Since('2.15') + set foo(int _) {} +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f(A a) { + a.foo += 0; +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 38, 3), + ]); + } + + test_class_instanceCreation_prefixed() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +class A {} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo' as foo; + +void f() { + foo.A(); +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 44, 1), + ]); + } + + test_class_instanceCreation_unprefixed() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +class A {} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f() { + A(); +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 33, 1), + ]); + } + + test_class_method_call_functionExpressionInvocation() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +class A { + @Since('2.15') + void call() {} +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f(A a) { + a(); +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 37, 2), + ]); + } + + test_class_method_formalParameter_optionalNamed() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +void foo( + int a, { + @Since('2.15') + int? bar, +}) {} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f() { + foo(0, bar: 1); +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 40, 3), + ]); + } + + test_class_method_formalParameter_optionalPositional() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +void foo( + int a, [ + @Since('2.15') + int? bar, +]) {} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f() { + foo(0, 42); +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 40, 2), + ]); + } + + test_class_method_methodInvocation() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +class A { + @Since('2.15') + void foo() {} +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f(A a) { + a.foo(); +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 38, 3), + ]); + } + + test_class_method_methodTearOff_prefixedIdentifier() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +class A { + @Since('2.15') + void foo() {} +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f(A a) { + a.foo; +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 38, 3), + ]); + + final node = findNode.prefixed('.foo'); + assertResolvedNodeText(node, r''' +PrefixedIdentifier + prefix: SimpleIdentifier + token: a + staticElement: self::@function::f::@parameter::a + staticType: A + period: . + identifier: SimpleIdentifier + token: foo + staticElement: dart:foo::@class::A::@method::foo + staticType: void Function() + staticElement: dart:foo::@class::A::@method::foo + staticType: void Function() +'''); + } + + test_class_method_methodTearOff_propertyAccess() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +class A { + @Since('2.15') + void foo() {} +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f(A a) { + (a).foo; +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 40, 3), + ]); + + final node = findNode.propertyAccess('.foo'); + assertResolvedNodeText(node, r''' +PropertyAccess + target: ParenthesizedExpression + leftParenthesis: ( + expression: SimpleIdentifier + token: a + staticElement: self::@function::f::@parameter::a + staticType: A + rightParenthesis: ) + staticType: A + operator: . + propertyName: SimpleIdentifier + token: foo + staticElement: dart:foo::@class::A::@method::foo + staticType: void Function() + staticType: void Function() +'''); + } + + test_class_setter() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +class A { + @Since('2.15') + set foo(int _) {} +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f(A a) { + (a).foo = 0; + a.foo = 0; +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 40, 3), + error(WarningCode.SDK_VERSION_SINCE, 53, 3), + ]); + } + + test_class_typeAnnotation_prefixed() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +class A {} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo' as foo; + +void f(foo.A a) {} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 38, 1), + ]); + } + + test_class_typeAnnotation_unprefixed() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +class A {} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f(A a) {} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 27, 1), + ]); + } + + test_constraints_exact_equal() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +class A {} +'''); + + await verifyVersion2('2.15.0', ''' +import 'dart:foo'; + +void f(A a) {} +''', expectedErrors: []); + } + + test_constraints_exact_greater() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +class A {} +'''); + + await verifyVersion2('2.16.0', ''' +import 'dart:foo'; + +void f(A a) {} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 27, 1), + ]); + } + + test_constraints_exact_less() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +class A {} +'''); + + await verifyVersion2('2.14.0', ''' +import 'dart:foo'; + +void f(A a) {} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 27, 1), + ]); + } + + test_constraints_greater_equal() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +class A {} +'''); + + await verifyVersion2('>2.15.0', ''' +import 'dart:foo'; + +void f(A a) {} +''', expectedErrors: []); + } + + test_constraints_greaterOrEq_equal() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +class A {} +'''); + + await verifyVersion2('>=2.15.0', ''' +import 'dart:foo'; + +void f(A a) {} +''', expectedErrors: []); + } + + test_constraints_greaterOrEq_greater() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +class A {} +'''); + + await verifyVersion2('>=2.16.0', ''' +import 'dart:foo'; + +void f(A a) {} +''', expectedErrors: []); + } + + test_constraints_greaterOrEq_less() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +class A {} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f(A a) {} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 27, 1), + ]); + } + + test_enum_constant() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +enum E { + v1, + @Since('2.15') + v2 +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f() { + E.v2; +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 35, 2), + ]); + } + + test_enum_typeAnnotation() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +enum E { + v +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f(E a) {} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 27, 1), + ]); + } + + test_extension_getter() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +extension E on int { + @Since('2.15') + int get foo => 0; +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f() { + 0.foo; +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 35, 3), + ]); + } + + test_extension_itself_extensionOverride_methodInvocation() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +extension E on int { + void foo() {} +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f() { + E(0).foo(); +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 33, 1), + error(WarningCode.SDK_VERSION_SINCE, 38, 3), + ]); + } + + test_extension_itself_methodInvocation() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +extension E on int { + void foo() {} +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f() { + 0.foo(); +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 35, 3), + ]); + } + + test_extension_method_methodInvocation() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +extension E on int { + @Since('2.15') + void foo() {} +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f() { + 0.foo(); +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 35, 3), + ]); + } + + test_extension_setter() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +extension E on int { + @Since('2.15') + set foo(int _) {} +} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f() { + 0.foo = 1; +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 35, 3), + ]); + } + + test_functionTypeAlias() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +typedef void X(int _); +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f(X a) {} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 27, 1), + ]); + } + + test_genericTypeAlias() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +typedef X = List; +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f(X a) {} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 27, 1), + ]); + } + + test_mixin_typeAnnotation() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +mixin M {} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f(M a) {} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 27, 1), + ]); + } + + test_topLevelFunction_prefixed() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +void bar() {} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo' as foo; + +void f() { + foo.bar(); +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 44, 3), + ]); + } + + test_topLevelFunction_unprefixed() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +void foo() {} +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f() { + foo(); +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 33, 3), + ]); + } + + test_topLevelVariable_prefixed() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +const v = 0; +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo' as foo; + +void f() { + foo.v; +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 44, 1), + ]); + } + + test_topLevelVariable_unprefixed() async { + _addDartFooLibrary(r''' +import 'dart:_internal'; + +@Since('2.15') +const v = 0; +'''); + + await verifyVersion2('>=2.14.0', ''' +import 'dart:foo'; + +void f() { + v; +} +''', expectedErrors: [ + error(WarningCode.SDK_VERSION_SINCE, 33, 1), + ]); + } + + void _addDartFooLibrary(String content) { + additionalMockSdkLibraries.add( + MockSdkLibrary('foo', [ + MockSdkLibraryUnit('foo/foo.dart', content), + ]), + ); + } +} diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart index 4b048b4af0a..a4da1c35a75 100644 --- a/pkg/analyzer/test/src/diagnostics/test_all.dart +++ b/pkg/analyzer/test/src/diagnostics/test_all.dart @@ -723,6 +723,7 @@ import 'sdk_version_is_expression_in_const_context_test.dart' as sdk_version_is_expression_in_const_context; import 'sdk_version_never_test.dart' as sdk_version_never; import 'sdk_version_set_literal_test.dart' as sdk_version_set_literal; +import 'sdk_version_since_test.dart' as sdk_version_since; import 'sdk_version_ui_as_code_in_const_context_test.dart' as sdk_version_ui_as_code_in_const_context; import 'sdk_version_ui_as_code_test.dart' as sdk_version_ui_as_code; @@ -1345,6 +1346,7 @@ main() { sdk_version_is_expression_in_const_context.main(); sdk_version_never.main(); sdk_version_set_literal.main(); + sdk_version_since.main(); sdk_version_ui_as_code.main(); sdk_version_ui_as_code_in_const_context.main(); sealed_class_subtype_outside_of_library.main();