blob: d2a80c1ae032972823a4edaf3ffb01add7f85b4a [file] [log] [blame]
// Copyright (c) 2018, 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/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/syntactic_entity.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/utilities/extensions/version.dart';
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<void> {
/// The error reporter to be used to report errors.
final DiagnosticReporter _errorReporter;
/// The version constraint for the SDK.
final VersionConstraint _versionConstraint;
/// A cached flag indicating whether references to the triple-shift features
/// need to be checked. Use [checkTripleShift] to access this field.
bool? _checkTripleShift;
/// Initialize a newly created verifier to use the given [_errorReporter] to
/// report errors.
SdkConstraintVerifier(this._errorReporter, this._versionConstraint);
/// Return a range covering every version up to, but not including, 2.14.0.
VersionRange get before_2_14_0 => VersionRange(max: Version.parse('2.14.0'));
/// Return a range covering every version up to, but not including, 2.1.0.
VersionRange get before_2_1_0 => VersionRange(max: Version.parse('2.1.0'));
/// Return a range covering every version up to, but not including, 2.2.0.
VersionRange get before_2_2_0 => VersionRange(max: Version.parse('2.2.0'));
/// Return a range covering every version up to, but not including, 2.2.2.
VersionRange get before_2_2_2 => VersionRange(max: Version.parse('2.2.2'));
/// Return a range covering every version up to, but not including, 2.5.0.
VersionRange get before_2_5_0 => VersionRange(max: Version.parse('2.5.0'));
/// Return a range covering every version up to, but not including, 2.6.0.
VersionRange get before_2_6_0 => VersionRange(max: Version.parse('2.6.0'));
/// Return `true` if references to the constant-update-2018 features need to
/// be checked.
bool get checkTripleShift =>
_checkTripleShift ??=
!before_2_14_0.intersect(_versionConstraint).isEmpty;
@override
void visitArgumentList(ArgumentList node) {
// Check (optional) positional arguments.
// Named arguments are checked in [NamedExpression].
for (var argument in node.arguments) {
if (argument is! NamedExpression) {
var parameter = argument.correspondingParameter;
_checkSinceSdkVersion(parameter, node, errorEntity: argument);
}
}
super.visitArgumentList(node);
}
@override
void visitAssignmentExpression(AssignmentExpression node) {
_checkSinceSdkVersion(node.readElement2, node);
_checkSinceSdkVersion(node.writeElement2, node);
super.visitAssignmentExpression(node);
}
@override
void visitBinaryExpression(BinaryExpression node) {
if (checkTripleShift) {
TokenType operatorType = node.operator.type;
if (operatorType == TokenType.GT_GT_GT) {
_errorReporter.atToken(
node.operator,
WarningCode.SDK_VERSION_GT_GT_GT_OPERATOR,
);
}
}
super.visitBinaryExpression(node);
}
@override
void visitConstructorName(ConstructorName node) {
_checkSinceSdkVersion(node.element, node);
super.visitConstructorName(node);
}
@override
void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
_checkSinceSdkVersion(node.element, node);
super.visitFunctionExpressionInvocation(node);
}
@override
void visitHideCombinator(HideCombinator node) {
// Don't flag references to either `Future` or `Stream` within a combinator.
}
@override
void visitIndexExpression(IndexExpression node) {
_checkSinceSdkVersion(node.element, node);
super.visitIndexExpression(node);
}
@override
void visitMethodDeclaration(MethodDeclaration node) {
if (checkTripleShift && node.isOperator && node.name.lexeme == '>>>') {
_errorReporter.atToken(
node.name,
WarningCode.SDK_VERSION_GT_GT_GT_OPERATOR,
);
}
super.visitMethodDeclaration(node);
}
@override
void visitMethodInvocation(MethodInvocation node) {
_checkSinceSdkVersion(node.methodName.element, node);
super.visitMethodInvocation(node);
}
@override
void visitNamedType(NamedType node) {
_checkSinceSdkVersion(node.element2, node);
super.visitNamedType(node);
}
@override
void visitPrefixedIdentifier(PrefixedIdentifier node) {
_checkSinceSdkVersion(node.element, node);
super.visitPrefixedIdentifier(node);
}
@override
void visitPropertyAccess(PropertyAccess node) {
_checkSinceSdkVersion(node.propertyName.element, node);
super.visitPropertyAccess(node);
}
@override
void visitShowCombinator(ShowCombinator node) {
// Don't flag references to either `Future` or `Stream` within a combinator.
}
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
if (node.inDeclarationContext()) {
return;
}
_checkSinceSdkVersion(node.element, node);
}
void _checkSinceSdkVersion(
Element? element,
AstNode target, {
SyntacticEntity? errorEntity,
}) {
element = element?.nonSynthetic;
if (element case HasSinceSdkVersion hasSince) {
var sinceSdkVersion = hasSince.sinceSdkVersion;
if (sinceSdkVersion != null) {
if (!_versionConstraint.requiresAtLeast(sinceSdkVersion)) {
if (errorEntity == null) {
if (!_shouldReportEnumIndex(target, element!)) {
return;
}
if (target is AssignmentExpression) {
target = target.leftHandSide;
}
if (target is ConstructorName) {
errorEntity = target.name?.token ?? target.type.name;
} else if (target is ExtensionOverride) {
errorEntity = target.name;
} else if (target is FunctionExpressionInvocation) {
errorEntity = target.argumentList;
} else if (target is IndexExpression) {
errorEntity = target.leftBracket;
} else if (target is MethodInvocation) {
errorEntity = target.methodName;
} else if (target is NamedType) {
errorEntity = target.name;
} else if (target is PrefixedIdentifier) {
errorEntity = target.identifier;
} else if (target is PropertyAccess) {
errorEntity = target.propertyName;
} else if (target is SimpleIdentifier) {
errorEntity = target;
} else {
throw UnimplementedError('(${target.runtimeType}) $target');
}
}
_errorReporter.atEntity(
errorEntity,
WarningCode.SDK_VERSION_SINCE,
arguments: [
sinceSdkVersion.toString(),
_versionConstraint.toString(),
],
);
}
}
}
}
/// Returns `false` if [element] is the `index` property, and the target
/// of [node] is exactly the `Enum` class from `dart:core`. We have already
/// checked that the property is not available to the enclosing package.
///
/// Returns `true` if [element] is something else, or if the target is a
/// concrete enum. The `index` was always available for concrete enums,
/// but there was no common `Enum` supertype for all enums.
static bool _shouldReportEnumIndex(AstNode node, Element element) {
if (element is PropertyAccessorElement && element.name3 == 'index') {
DartType? targetType;
if (node is PrefixedIdentifier) {
targetType = node.prefix.staticType;
} else if (node is PropertyAccess) {
targetType = node.realTarget.staticType;
}
if (targetType != null) {
var targetElement = targetType.element;
return targetElement is ClassElement && targetElement.isDartCoreEnum;
}
return false;
} else {
return true;
}
}
}