blob: e0c34fda5ad71939e7c1e62aadaa31333c9b73a9 [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/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/dart/element/type_provider.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/error/codes.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 ErrorReporter _errorReporter;
/// The element representing the library containing the unit to be verified.
final LibraryElement _containingLibrary;
/// The typ provider used to access SDK types.
final TypeProvider _typeProvider;
/// The version constraint for the SDK.
final VersionConstraint _versionConstraint;
/// A cached flag indicating whether references to the constant-update-2018
/// features need to be checked. Use [checkConstantUpdate2018] to access this
/// field.
bool? _checkConstantUpdate2018;
/// A cached flag indicating whether uses of extension method features need to
/// be checked. Use [checkExtensionMethods] to access this field.
bool? _checkExtensionMethods;
/// A cached flag indicating whether references to Future and Stream need to
/// be checked. Use [checkFutureAndStream] to access this field.
bool? _checkFutureAndStream;
/// A cached flag indicating whether references to set literals need to
/// be checked. Use [checkSetLiterals] to access this field.
bool? _checkSetLiterals;
/// A flag indicating whether we are visiting code inside a set literal. Used
/// to prevent over-reporting uses of set literals.
bool _inSetLiteral = false;
/// A cached flag indicating whether references to the ui-as-code features
/// need to be checked. Use [checkUiAsCode] to access this field.
bool? _checkUiAsCode;
/// A flag indicating whether we are visiting code inside one of the
/// ui-as-code features. Used to prevent over-reporting uses of these
/// features.
bool _inUiAsCode = false;
/// Initialize a newly created verifier to use the given [_errorReporter] to
/// report errors.
SdkConstraintVerifier(this._errorReporter, this._containingLibrary,
this._typeProvider, this._versionConstraint);
/// 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'), includeMax: false);
/// 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'), includeMax: false);
/// 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'), includeMax: false);
/// 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'), includeMax: false);
/// 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'), includeMax: false);
/// Return `true` if references to the constant-update-2018 features need to
/// be checked.
bool get checkConstantUpdate2018 => _checkConstantUpdate2018 ??=
!before_2_5_0.intersect(_versionConstraint).isEmpty;
/// Return `true` if references to the extension method features need to
/// be checked.
bool get checkExtensionMethods => _checkExtensionMethods ??=
!before_2_6_0.intersect(_versionConstraint).isEmpty;
/// Return `true` if references to Future and Stream need to be checked.
bool get checkFutureAndStream => _checkFutureAndStream ??=
!before_2_1_0.intersect(_versionConstraint).isEmpty;
/// Return `true` if references to the non-nullable features need to be
/// checked.
bool get checkNnbd => !_containingLibrary.isNonNullableByDefault;
/// Return `true` if references to set literals need to be checked.
bool get checkSetLiterals =>
_checkSetLiterals ??= !before_2_2_0.intersect(_versionConstraint).isEmpty;
/// Return `true` if references to the ui-as-code features (control flow and
/// spread collections) need to be checked.
bool get checkUiAsCode =>
_checkUiAsCode ??= !before_2_2_2.intersect(_versionConstraint).isEmpty;
@override
void visitAsExpression(AsExpression node) {
if (checkConstantUpdate2018 && node.inConstantContext) {
_errorReporter.reportErrorForNode(
HintCode.SDK_VERSION_AS_EXPRESSION_IN_CONST_CONTEXT, node);
}
super.visitAsExpression(node);
}
@override
void visitBinaryExpression(BinaryExpression node) {
if (checkConstantUpdate2018) {
TokenType operatorType = node.operator.type;
if (operatorType == TokenType.GT_GT_GT) {
_errorReporter.reportErrorForToken(
HintCode.SDK_VERSION_GT_GT_GT_OPERATOR, node.operator);
} else if ((operatorType == TokenType.AMPERSAND ||
operatorType == TokenType.BAR ||
operatorType == TokenType.CARET) &&
node.inConstantContext) {
if (node.leftOperand.typeOrThrow.isDartCoreBool) {
_errorReporter.reportErrorForToken(
HintCode.SDK_VERSION_BOOL_OPERATOR_IN_CONST_CONTEXT,
node.operator,
[node.operator.lexeme]);
}
} else if (operatorType == TokenType.EQ_EQ && node.inConstantContext) {
bool primitive(Expression node) {
DartType type = node.typeOrThrow;
return type.isDartCoreBool ||
type.isDartCoreDouble ||
type.isDartCoreInt ||
type.isDartCoreNull ||
type.isDartCoreString;
}
if (!primitive(node.leftOperand) || !primitive(node.rightOperand)) {
_errorReporter.reportErrorForToken(
HintCode.SDK_VERSION_EQ_EQ_OPERATOR_IN_CONST_CONTEXT,
node.operator);
}
}
}
super.visitBinaryExpression(node);
}
@override
void visitExtensionDeclaration(ExtensionDeclaration node) {
if (checkExtensionMethods) {
_errorReporter.reportErrorForToken(
HintCode.SDK_VERSION_EXTENSION_METHODS, node.extensionKeyword);
}
super.visitExtensionDeclaration(node);
}
@override
void visitExtensionOverride(ExtensionOverride node) {
if (checkExtensionMethods) {
_errorReporter.reportErrorForNode(
HintCode.SDK_VERSION_EXTENSION_METHODS, node.extensionName);
}
super.visitExtensionOverride(node);
}
@override
void visitForElement(ForElement node) {
_validateUiAsCode(node);
_validateUiAsCodeInConstContext(node);
bool wasInUiAsCode = _inUiAsCode;
_inUiAsCode = true;
super.visitForElement(node);
_inUiAsCode = wasInUiAsCode;
}
@override
void visitHideCombinator(HideCombinator node) {
// Don't flag references to either `Future` or `Stream` within a combinator.
}
@override
void visitIfElement(IfElement node) {
_validateUiAsCode(node);
_validateUiAsCodeInConstContext(node);
bool wasInUiAsCode = _inUiAsCode;
_inUiAsCode = true;
super.visitIfElement(node);
_inUiAsCode = wasInUiAsCode;
}
@override
void visitIsExpression(IsExpression node) {
if (checkConstantUpdate2018 && node.inConstantContext) {
_errorReporter.reportErrorForNode(
HintCode.SDK_VERSION_IS_EXPRESSION_IN_CONST_CONTEXT, node);
}
super.visitIsExpression(node);
}
@override
void visitMethodDeclaration(MethodDeclaration node) {
if (checkConstantUpdate2018 && node.isOperator && node.name.name == '>>>') {
_errorReporter.reportErrorForNode(
HintCode.SDK_VERSION_GT_GT_GT_OPERATOR, node.name);
}
super.visitMethodDeclaration(node);
}
@override
void visitSetOrMapLiteral(SetOrMapLiteral node) {
if (node.isSet && checkSetLiterals && !_inSetLiteral) {
_errorReporter.reportErrorForNode(HintCode.SDK_VERSION_SET_LITERAL, node);
}
bool wasInSetLiteral = _inSetLiteral;
_inSetLiteral = true;
super.visitSetOrMapLiteral(node);
_inSetLiteral = wasInSetLiteral;
}
@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;
}
var element = node.staticElement;
if (checkFutureAndStream &&
element is ClassElement &&
(element == _typeProvider.futureElement ||
element == _typeProvider.streamElement)) {
for (LibraryElement importedLibrary
in _containingLibrary.importedLibraries) {
if (!importedLibrary.isDartCore) {
var namespace = importedLibrary.exportNamespace;
if (namespace.get(element.name) != null) {
return;
}
}
}
_errorReporter.reportErrorForNode(
HintCode.SDK_VERSION_ASYNC_EXPORTED_FROM_CORE, node, [element.name]);
} else if (checkNnbd && element == _typeProvider.neverType.element) {
_errorReporter.reportErrorForNode(HintCode.SDK_VERSION_NEVER, node);
}
}
@override
void visitSpreadElement(SpreadElement node) {
_validateUiAsCode(node);
_validateUiAsCodeInConstContext(node);
bool wasInUiAsCode = _inUiAsCode;
_inUiAsCode = true;
super.visitSpreadElement(node);
_inUiAsCode = wasInUiAsCode;
}
/// 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.
void _validateUiAsCode(AstNode node) {
if (checkUiAsCode && !_inUiAsCode) {
_errorReporter.reportErrorForNode(HintCode.SDK_VERSION_UI_AS_CODE, node);
}
}
/// Given that the [node] is only valid when the ui-as-code feature is
/// enabled in a const context, check that the code will not be executed with
/// a version of the SDK that does not support the feature.
void _validateUiAsCodeInConstContext(AstNode node) {
if (checkConstantUpdate2018 &&
!_inUiAsCode &&
node.thisOrAncestorOfType<TypedLiteral>()!.isConst) {
_errorReporter.reportErrorForNode(
HintCode.SDK_VERSION_UI_AS_CODE_IN_CONST_CONTEXT, node);
}
}
}