blob: 5685ba6f71a5359faf6546906ad3d0f1a6996f8f [file] [log] [blame]
// Copyright (c) 2014, 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 'dart:collection';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/ast_factory.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/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/exception/exception.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/context/builder.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/ast_factory.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/member.dart' show ConstructorMember;
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/resolver/exit_detector.dart';
import 'package:analyzer/src/dart/resolver/extension_member_resolver.dart';
import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
import 'package:analyzer/src/dart/resolver/scope.dart';
import 'package:analyzer/src/diagnostic/diagnostic_factory.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/constant.dart';
import 'package:analyzer/src/generated/element_resolver.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/static_type_analyzer.dart';
import 'package:analyzer/src/generated/type_promotion_manager.dart';
import 'package:analyzer/src/generated/type_system.dart';
import 'package:analyzer/src/generated/variable_type_provider.dart';
import 'package:analyzer/src/lint/linter.dart';
import 'package:analyzer/src/workspace/workspace.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as path;
export 'package:analyzer/src/dart/constant/constant_verifier.dart';
export 'package:analyzer/src/dart/resolver/exit_detector.dart';
export 'package:analyzer/src/dart/resolver/inheritance_manager.dart';
export 'package:analyzer/src/dart/resolver/scope.dart';
export 'package:analyzer/src/generated/type_system.dart';
/// Instances of the class `BestPracticesVerifier` traverse an AST structure
/// looking for violations of Dart best practices.
class BestPracticesVerifier extends RecursiveAstVisitor<void> {
// static String _HASHCODE_GETTER_NAME = "hashCode";
static String _NULL_TYPE_NAME = "Null";
static String _TO_INT_METHOD_NAME = "toInt";
/// The class containing the AST nodes being visited, or `null` if we are not
/// in the scope of a class.
ClassElementImpl _enclosingClass;
/// A flag indicating whether a surrounding member (compilation unit or class)
/// is deprecated.
bool _inDeprecatedMember;
/// The error reporter by which errors will be reported.
final ErrorReporter _errorReporter;
/// The type [Null].
final InterfaceType _nullType;
/// The type system primitives
final TypeSystem _typeSystem;
/// The inheritance manager to access interface type hierarchy.
final InheritanceManager3 _inheritanceManager;
/// The current library
final LibraryElement _currentLibrary;
final _InvalidAccessVerifier _invalidAccessVerifier;
/// The [WorkspacePackage] in which [_currentLibrary] is declared.
WorkspacePackage _workspacePackage;
/// The [LinterContext] used for possible const calculations.
LinterContext _linterContext;
/// Is `true` if NNBD is enabled for the library being analyzed.
final bool _isNonNullable;
/// True if inference failures should be reported, otherwise false.
final bool _strictInference;
/// Create a new instance of the [BestPracticesVerifier].
///
/// @param errorReporter the error reporter
BestPracticesVerifier(
this._errorReporter,
TypeProvider typeProvider,
this._currentLibrary,
CompilationUnit unit,
String content, {
TypeSystem typeSystem,
@required InheritanceManager3 inheritanceManager,
ResourceProvider resourceProvider,
DeclaredVariables declaredVariables,
AnalysisOptions analysisOptions,
}) : _nullType = typeProvider.nullType,
_typeSystem = typeSystem ?? new Dart2TypeSystem(typeProvider),
_isNonNullable = unit.featureSet.isEnabled(Feature.non_nullable),
_strictInference =
(analysisOptions as AnalysisOptionsImpl).strictInference,
_inheritanceManager = inheritanceManager,
_invalidAccessVerifier =
new _InvalidAccessVerifier(_errorReporter, _currentLibrary) {
_inDeprecatedMember = _currentLibrary.hasDeprecated;
String libraryPath = _currentLibrary.source.fullName;
ContextBuilder builder = new ContextBuilder(
resourceProvider, null /* sdkManager */, null /* contentCache */);
Workspace workspace =
ContextBuilder.createWorkspace(resourceProvider, libraryPath, builder);
_workspacePackage = workspace.findPackageFor(libraryPath);
_linterContext = LinterContextImpl(
null /* allUnits */,
new LinterContextUnit(content, unit),
declaredVariables,
typeProvider,
_typeSystem,
_inheritanceManager,
analysisOptions);
}
@override
void visitAnnotation(Annotation node) {
ElementAnnotation element = node.elementAnnotation;
AstNode parent = node.parent;
if (element?.isFactory == true) {
if (parent is MethodDeclaration) {
_checkForInvalidFactory(parent);
} else {
_errorReporter
.reportErrorForNode(HintCode.INVALID_FACTORY_ANNOTATION, node, []);
}
} else if (element?.isImmutable == true) {
if (parent is! ClassOrMixinDeclaration && parent is! ClassTypeAlias) {
_errorReporter.reportErrorForNode(
HintCode.INVALID_IMMUTABLE_ANNOTATION, node, []);
}
} else if (element?.isLiteral == true) {
if (parent is! ConstructorDeclaration ||
(parent as ConstructorDeclaration).constKeyword == null) {
_errorReporter
.reportErrorForNode(HintCode.INVALID_LITERAL_ANNOTATION, node, []);
}
} else if (element?.isSealed == true) {
if (!(parent is ClassDeclaration || parent is ClassTypeAlias)) {
_errorReporter.reportErrorForNode(
HintCode.INVALID_SEALED_ANNOTATION, node, [node.element.name]);
}
} else if (element?.isVisibleForTemplate == true ||
element?.isVisibleForTesting == true) {
if (parent is Declaration) {
reportInvalidAnnotation(Element declaredElement) {
_errorReporter.reportErrorForNode(
HintCode.INVALID_VISIBILITY_ANNOTATION,
node,
[declaredElement.name, node.name.name]);
}
if (parent is TopLevelVariableDeclaration) {
for (VariableDeclaration variable in parent.variables.variables) {
if (Identifier.isPrivateName(variable.declaredElement.name)) {
reportInvalidAnnotation(variable.declaredElement);
}
}
} else if (parent is FieldDeclaration) {
for (VariableDeclaration variable in parent.fields.variables) {
if (Identifier.isPrivateName(variable.declaredElement.name)) {
reportInvalidAnnotation(variable.declaredElement);
}
}
} else if (parent.declaredElement != null &&
Identifier.isPrivateName(parent.declaredElement.name)) {
reportInvalidAnnotation(parent.declaredElement);
}
} else {
// Something other than a declaration was annotated. Whatever this is,
// it probably warrants a Hint, but this has not been specified on
// visibleForTemplate or visibleForTesting, so leave it alone for now.
}
}
super.visitAnnotation(node);
}
@override
void visitArgumentList(ArgumentList node) {
for (Expression argument in node.arguments) {
ParameterElement parameter = argument.staticParameterElement;
if (parameter?.isOptionalPositional == true) {
_checkForDeprecatedMemberUse(parameter, argument);
}
}
super.visitArgumentList(node);
}
@override
void visitAsExpression(AsExpression node) {
_checkForUnnecessaryCast(node);
super.visitAsExpression(node);
}
@override
void visitAssignmentExpression(AssignmentExpression node) {
TokenType operatorType = node.operator.type;
if (operatorType != TokenType.EQ) {
_checkForDeprecatedMemberUse(node.staticElement, node);
}
super.visitAssignmentExpression(node);
}
@override
void visitBinaryExpression(BinaryExpression node) {
_checkForDivisionOptimizationHint(node);
_checkForDeprecatedMemberUse(node.staticElement, node);
super.visitBinaryExpression(node);
}
@override
void visitClassDeclaration(ClassDeclaration node) {
var element = AbstractClassElementImpl.getImpl(node.declaredElement);
_enclosingClass = element;
_invalidAccessVerifier._enclosingClass = element;
bool wasInDeprecatedMember = _inDeprecatedMember;
if (element != null && element.hasDeprecated) {
_inDeprecatedMember = true;
}
try {
// Commented out until we decide that we want this hint in the analyzer
// checkForOverrideEqualsButNotHashCode(node);
_checkForImmutable(node);
_checkForInvalidSealedSuperclass(node);
super.visitClassDeclaration(node);
} finally {
_enclosingClass = null;
_invalidAccessVerifier._enclosingClass = null;
_inDeprecatedMember = wasInDeprecatedMember;
}
}
@override
void visitClassTypeAlias(ClassTypeAlias node) {
_checkForImmutable(node);
_checkForInvalidSealedSuperclass(node);
super.visitClassTypeAlias(node);
}
@override
void visitConstructorDeclaration(ConstructorDeclaration node) {
if (node.declaredElement.isFactory) {
if (node.body is BlockFunctionBody) {
// Check the block for a return statement, if not, create the hint.
if (!ExitDetector.exits(node.body)) {
_errorReporter.reportErrorForNode(
HintCode.MISSING_RETURN, node, [node.returnType.name]);
}
}
}
_checkStrictInferenceInParameters(node.parameters);
super.visitConstructorDeclaration(node);
}
@override
void visitExportDirective(ExportDirective node) {
_checkForDeprecatedMemberUse(node.uriElement, node);
super.visitExportDirective(node);
}
@override
void visitFieldDeclaration(FieldDeclaration node) {
bool wasInDeprecatedMember = _inDeprecatedMember;
if (_hasDeprecatedAnnotation(node.metadata)) {
_inDeprecatedMember = true;
}
try {
super.visitFieldDeclaration(node);
} finally {
_inDeprecatedMember = wasInDeprecatedMember;
}
}
@override
void visitFormalParameterList(FormalParameterList node) {
_checkRequiredParameter(node);
super.visitFormalParameterList(node);
}
@override
void visitFunctionDeclaration(FunctionDeclaration node) {
bool wasInDeprecatedMember = _inDeprecatedMember;
ExecutableElement element = node.declaredElement;
if (element != null && element.hasDeprecated) {
_inDeprecatedMember = true;
}
try {
_checkForMissingReturn(
node.returnType, node.functionExpression.body, element, node);
// Return types are inferred only on non-recursive local functions.
if (node.parent is CompilationUnit) {
_checkStrictInferenceReturnType(node.returnType, node, node.name.name);
}
_checkStrictInferenceInParameters(node.functionExpression.parameters);
super.visitFunctionDeclaration(node);
} finally {
_inDeprecatedMember = wasInDeprecatedMember;
}
}
@override
void visitFunctionDeclarationStatement(FunctionDeclarationStatement node) {
// TODO(srawlins): Check strict-inference return type on recursive
// local functions.
super.visitFunctionDeclarationStatement(node);
}
@override
void visitFunctionExpression(FunctionExpression node) {
if (node.parent is! FunctionDeclaration) {
_checkForMissingReturn(null, node.body, node.declaredElement, node);
}
DartType functionType = InferenceContext.getContext(node);
if (functionType is! FunctionType) {
_checkStrictInferenceInParameters(node.parameters);
}
super.visitFunctionExpression(node);
}
@override
void visitFunctionTypeAlias(FunctionTypeAlias node) {
_checkStrictInferenceReturnType(node.returnType, node, node.name.name);
super.visitFunctionTypeAlias(node);
}
@override
void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
_checkStrictInferenceReturnType(
node.returnType, node, node.identifier.name);
_checkStrictInferenceInParameters(node.parameters);
super.visitFunctionTypedFormalParameter(node);
}
@override
void visitGenericFunctionType(GenericFunctionType node) {
// GenericTypeAlias is handled in [visitGenericTypeAlias], where a proper
// name can be reported in any message.
if (node.parent is! GenericTypeAlias) {
_checkStrictInferenceReturnType(node.returnType, node, node.toString());
}
super.visitGenericFunctionType(node);
}
@override
void visitGenericTypeAlias(GenericTypeAlias node) {
if (node.functionType != null) {
_checkStrictInferenceReturnType(
node.functionType.returnType, node, node.name.name);
}
super.visitGenericTypeAlias(node);
}
@override
void visitImportDirective(ImportDirective node) {
_checkForDeprecatedMemberUse(node.uriElement, node);
ImportElement importElement = node.element;
if (importElement != null && importElement.isDeferred) {
_checkForLoadLibraryFunction(node, importElement);
}
super.visitImportDirective(node);
}
@override
void visitIndexExpression(IndexExpression node) {
_checkForDeprecatedMemberUse(node.staticElement, node);
super.visitIndexExpression(node);
}
@override
void visitInstanceCreationExpression(InstanceCreationExpression node) {
_checkForDeprecatedMemberUse(node.staticElement, node);
_checkForLiteralConstructorUse(node);
super.visitInstanceCreationExpression(node);
}
@override
void visitIsExpression(IsExpression node) {
_checkAllTypeChecks(node);
super.visitIsExpression(node);
}
@override
void visitMethodDeclaration(MethodDeclaration node) {
bool wasInDeprecatedMember = _inDeprecatedMember;
ExecutableElement element = node.declaredElement;
bool elementIsOverride() {
if (element is ClassMemberElement) {
Name name = new Name(_currentLibrary.source.uri, element.name);
Element enclosingElement = element.enclosingElement;
if (enclosingElement is ClassElement) {
InterfaceType classType = enclosingElement.thisType;
return _inheritanceManager.getOverridden(classType, name) != null;
}
}
return false;
}
if (element != null && element.hasDeprecated) {
_inDeprecatedMember = true;
}
try {
// This was determined to not be a good hint, see: dartbug.com/16029
//checkForOverridingPrivateMember(node);
_checkForMissingReturn(node.returnType, node.body, element, node);
_checkForUnnecessaryNoSuchMethod(node);
if (_strictInference && !node.isSetter && !elementIsOverride()) {
_checkStrictInferenceReturnType(node.returnType, node, node.name.name);
}
_checkStrictInferenceInParameters(node.parameters);
super.visitMethodDeclaration(node);
} finally {
_inDeprecatedMember = wasInDeprecatedMember;
}
}
@override
void visitMethodInvocation(MethodInvocation node) {
_checkForNullAwareHints(node, node.operator);
DartType staticInvokeType = node.staticInvokeType;
Element callElement = staticInvokeType?.element;
if (callElement is MethodElement &&
callElement.name == FunctionElement.CALL_METHOD_NAME) {
_checkForDeprecatedMemberUse(callElement, node);
}
super.visitMethodInvocation(node);
}
@override
void visitMixinDeclaration(MixinDeclaration node) {
_enclosingClass = node.declaredElement;
_invalidAccessVerifier._enclosingClass = _enclosingClass;
bool wasInDeprecatedMember = _inDeprecatedMember;
if (_hasDeprecatedAnnotation(node.metadata)) {
_inDeprecatedMember = true;
}
try {
_checkForImmutable(node);
_checkForInvalidSealedSuperclass(node);
super.visitMixinDeclaration(node);
} finally {
_enclosingClass = null;
_invalidAccessVerifier._enclosingClass = null;
_inDeprecatedMember = wasInDeprecatedMember;
}
}
@override
void visitPostfixExpression(PostfixExpression node) {
_checkForDeprecatedMemberUse(node.staticElement, node);
super.visitPostfixExpression(node);
}
@override
void visitPrefixExpression(PrefixExpression node) {
_checkForDeprecatedMemberUse(node.staticElement, node);
super.visitPrefixExpression(node);
}
@override
void visitPropertyAccess(PropertyAccess node) {
_checkForNullAwareHints(node, node.operator);
super.visitPropertyAccess(node);
}
@override
void visitRedirectingConstructorInvocation(
RedirectingConstructorInvocation node) {
_checkForDeprecatedMemberUse(node.staticElement, node);
super.visitRedirectingConstructorInvocation(node);
}
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
_checkForDeprecatedMemberUseAtIdentifier(node);
_invalidAccessVerifier.verify(node);
super.visitSimpleIdentifier(node);
}
@override
void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
_checkForDeprecatedMemberUse(node.staticElement, node);
super.visitSuperConstructorInvocation(node);
}
@override
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
bool wasInDeprecatedMember = _inDeprecatedMember;
if (_hasDeprecatedAnnotation(node.metadata)) {
_inDeprecatedMember = true;
}
try {
super.visitTopLevelVariableDeclaration(node);
} finally {
_inDeprecatedMember = wasInDeprecatedMember;
}
}
/// Check for the passed is expression for the unnecessary type check hint
/// codes as well as null checks expressed using an is expression.
///
/// @param node the is expression to check
/// @return `true` if and only if a hint code is generated on the passed node
/// See [HintCode.TYPE_CHECK_IS_NOT_NULL], [HintCode.TYPE_CHECK_IS_NULL],
/// [HintCode.UNNECESSARY_TYPE_CHECK_TRUE], and
/// [HintCode.UNNECESSARY_TYPE_CHECK_FALSE].
bool _checkAllTypeChecks(IsExpression node) {
Expression expression = node.expression;
TypeAnnotation typeName = node.type;
DartType lhsType = expression.staticType;
DartType rhsType = typeName.type;
if (lhsType == null || rhsType == null) {
return false;
}
String rhsNameStr = typeName is TypeName ? typeName.name.name : null;
// if x is dynamic
if (rhsType.isDynamic && rhsNameStr == Keyword.DYNAMIC.lexeme) {
if (node.notOperator == null) {
// the is case
_errorReporter.reportErrorForNode(
HintCode.UNNECESSARY_TYPE_CHECK_TRUE, node);
} else {
// the is not case
_errorReporter.reportErrorForNode(
HintCode.UNNECESSARY_TYPE_CHECK_FALSE, node);
}
return true;
}
Element rhsElement = rhsType.element;
LibraryElement libraryElement = rhsElement?.library;
if (libraryElement != null && libraryElement.isDartCore) {
// if x is Object or null is Null
if (rhsType.isObject ||
(expression is NullLiteral && rhsNameStr == _NULL_TYPE_NAME)) {
if (node.notOperator == null) {
// the is case
_errorReporter.reportErrorForNode(
HintCode.UNNECESSARY_TYPE_CHECK_TRUE, node);
} else {
// the is not case
_errorReporter.reportErrorForNode(
HintCode.UNNECESSARY_TYPE_CHECK_FALSE, node);
}
return true;
} else if (rhsNameStr == _NULL_TYPE_NAME) {
if (node.notOperator == null) {
// the is case
_errorReporter.reportErrorForNode(HintCode.TYPE_CHECK_IS_NULL, node);
} else {
// the is not case
_errorReporter.reportErrorForNode(
HintCode.TYPE_CHECK_IS_NOT_NULL, node);
}
return true;
}
}
return false;
}
/// Given some [element], look at the associated metadata and report the use
/// of the member if it is declared as deprecated. If a diagnostic is reported
/// it should be reported at the given [node].
void _checkForDeprecatedMemberUse(Element element, AstNode node) {
bool isDeprecated(Element element) {
if (element is PropertyAccessorElement && element.isSynthetic) {
// TODO(brianwilkerson) Why isn't this the implementation for PropertyAccessorElement?
Element variable = element.variable;
if (variable == null) {
return false;
}
return variable.hasDeprecated;
}
return element.hasDeprecated;
}
bool isLocalParameter(Element element, AstNode node) {
if (element is ParameterElement) {
ExecutableElement definingFunction = element.enclosingElement;
FunctionBody body = node.thisOrAncestorOfType<FunctionBody>();
while (body != null) {
ExecutableElement enclosingFunction;
AstNode parent = body.parent;
if (parent is ConstructorDeclaration) {
enclosingFunction = parent.declaredElement;
} else if (parent is FunctionExpression) {
enclosingFunction = parent.declaredElement;
} else if (parent is MethodDeclaration) {
enclosingFunction = parent.declaredElement;
}
if (enclosingFunction == definingFunction) {
return true;
}
body = parent?.thisOrAncestorOfType<FunctionBody>();
}
}
return false;
}
if (!_inDeprecatedMember &&
element != null &&
isDeprecated(element) &&
!isLocalParameter(element, node)) {
String displayName = element.displayName;
if (element is ConstructorElement) {
// TODO(jwren) We should modify ConstructorElement.getDisplayName(),
// or have the logic centralized elsewhere, instead of doing this logic
// here.
displayName = element.enclosingElement.displayName;
if (element.displayName.isNotEmpty) {
displayName = "$displayName.${element.displayName}";
}
} else if (element is LibraryElement) {
displayName = element.definingCompilationUnit.source.uri.toString();
} else if (displayName == FunctionElement.CALL_METHOD_NAME &&
node is MethodInvocation &&
node.staticInvokeType is InterfaceType) {
DartType staticInvokeType = node.staticInvokeType;
displayName = "${staticInvokeType.displayName}.${element.displayName}";
}
LibraryElement library =
element is LibraryElement ? element : element.library;
String message = _deprecatedMessage(element);
if (message == null || message.isEmpty) {
HintCode hintCode = _isLibraryInWorkspacePackage(library)
? HintCode.DEPRECATED_MEMBER_USE_FROM_SAME_PACKAGE
: HintCode.DEPRECATED_MEMBER_USE;
_errorReporter.reportErrorForNode(hintCode, node, [displayName]);
} else {
HintCode hintCode = _isLibraryInWorkspacePackage(library)
? HintCode.DEPRECATED_MEMBER_USE_FROM_SAME_PACKAGE_WITH_MESSAGE
: HintCode.DEPRECATED_MEMBER_USE_WITH_MESSAGE;
_errorReporter
.reportErrorForNode(hintCode, node, [displayName, message]);
}
}
}
/// For [SimpleIdentifier]s, only call [checkForDeprecatedMemberUse]
/// if the node is not in a declaration context.
///
/// Also, if the identifier is a constructor name in a constructor invocation,
/// then calls to the deprecated constructor will be caught by
/// [visitInstanceCreationExpression] and
/// [visitSuperConstructorInvocation], and can be ignored by
/// this visit method.
///
/// @param identifier some simple identifier to check for deprecated use of
/// @return `true` if and only if a hint code is generated on the passed node
/// See [HintCode.DEPRECATED_MEMBER_USE].
void _checkForDeprecatedMemberUseAtIdentifier(SimpleIdentifier identifier) {
if (identifier.inDeclarationContext()) {
return;
}
AstNode parent = identifier.parent;
if ((parent is ConstructorName && identical(identifier, parent.name)) ||
(parent is ConstructorDeclaration &&
identical(identifier, parent.returnType)) ||
(parent is SuperConstructorInvocation &&
identical(identifier, parent.constructorName)) ||
parent is HideCombinator) {
return;
}
_checkForDeprecatedMemberUse(identifier.staticElement, identifier);
}
/// Check for the passed binary expression for the
/// [HintCode.DIVISION_OPTIMIZATION].
///
/// @param node the binary expression to check
/// @return `true` if and only if a hint code is generated on the passed node
/// See [HintCode.DIVISION_OPTIMIZATION].
bool _checkForDivisionOptimizationHint(BinaryExpression node) {
// Return if the operator is not '/'
if (node.operator.type != TokenType.SLASH) {
return false;
}
// Return if the '/' operator is not defined in core, or if we don't know
// its static type
MethodElement methodElement = node.staticElement;
if (methodElement == null) {
return false;
}
LibraryElement libraryElement = methodElement.library;
if (libraryElement != null && !libraryElement.isDartCore) {
return false;
}
// Report error if the (x/y) has toInt() invoked on it
AstNode parent = node.parent;
if (parent is ParenthesizedExpression) {
ParenthesizedExpression parenthesizedExpression =
_wrapParenthesizedExpression(parent);
AstNode grandParent = parenthesizedExpression.parent;
if (grandParent is MethodInvocation) {
if (_TO_INT_METHOD_NAME == grandParent.methodName.name &&
grandParent.argumentList.arguments.isEmpty) {
_errorReporter.reportErrorForNode(
HintCode.DIVISION_OPTIMIZATION, grandParent);
return true;
}
}
}
return false;
}
/// Checks whether [node] violates the rules of [immutable].
///
/// If [node] is marked with [immutable] or inherits from a class or mixin
/// marked with [immutable], this function searches the fields of [node] and
/// its superclasses, reporting a hint if any non-final instance fields are
/// found.
void _checkForImmutable(NamedCompilationUnitMember node) {
/// Return `true` if the given class [element] is annotated with the
/// `@immutable` annotation.
bool isImmutable(ClassElement element) {
for (ElementAnnotation annotation in element.metadata) {
if (annotation.isImmutable) {
return true;
}
}
return false;
}
/// Return `true` if the given class [element] or any superclass of it is
/// annotated with the `@immutable` annotation.
bool isOrInheritsImmutable(
ClassElement element, HashSet<ClassElement> visited) {
if (visited.add(element)) {
if (isImmutable(element)) {
return true;
}
for (InterfaceType interface in element.mixins) {
if (isOrInheritsImmutable(interface.element, visited)) {
return true;
}
}
for (InterfaceType mixin in element.interfaces) {
if (isOrInheritsImmutable(mixin.element, visited)) {
return true;
}
}
if (element.supertype != null) {
return isOrInheritsImmutable(element.supertype.element, visited);
}
}
return false;
}
/// Return `true` if the given class [element] defines a non-final instance
/// field.
Iterable<String> nonFinalInstanceFields(ClassElement element) {
return element.fields
.where((FieldElement field) =>
!field.isSynthetic && !field.isFinal && !field.isStatic)
.map((FieldElement field) => '${element.name}.${field.name}');
}
/// Return `true` if the given class [element] defines or inherits a
/// non-final field.
Iterable<String> definedOrInheritedNonFinalInstanceFields(
ClassElement element, HashSet<ClassElement> visited) {
Iterable<String> nonFinalFields = [];
if (visited.add(element)) {
nonFinalFields = nonFinalInstanceFields(element);
nonFinalFields = nonFinalFields.followedBy(element.mixins.expand(
(InterfaceType mixin) => nonFinalInstanceFields(mixin.element)));
if (element.supertype != null) {
nonFinalFields = nonFinalFields.followedBy(
definedOrInheritedNonFinalInstanceFields(
element.supertype.element, visited));
}
}
return nonFinalFields;
}
ClassElement element = node.declaredElement;
if (isOrInheritsImmutable(element, new HashSet<ClassElement>())) {
Iterable<String> nonFinalFields =
definedOrInheritedNonFinalInstanceFields(
element, new HashSet<ClassElement>());
if (nonFinalFields.isNotEmpty) {
_errorReporter.reportErrorForNode(
HintCode.MUST_BE_IMMUTABLE, node.name, [nonFinalFields.join(', ')]);
}
}
}
void _checkForInvalidFactory(MethodDeclaration decl) {
// Check declaration.
// Note that null return types are expected to be flagged by other analyses.
DartType returnType = decl.returnType?.type;
if (returnType is VoidType) {
_errorReporter.reportErrorForNode(HintCode.INVALID_FACTORY_METHOD_DECL,
decl.name, [decl.name.toString()]);
return;
}
// Check implementation.
FunctionBody body = decl.body;
if (body is EmptyFunctionBody) {
// Abstract methods are OK.
return;
}
// `new Foo()` or `null`.
bool factoryExpression(Expression expression) =>
expression is InstanceCreationExpression || expression is NullLiteral;
if (body is ExpressionFunctionBody && factoryExpression(body.expression)) {
return;
} else if (body is BlockFunctionBody) {
NodeList<Statement> statements = body.block.statements;
if (statements.isNotEmpty) {
Statement last = statements.last;
if (last is ReturnStatement && factoryExpression(last.expression)) {
return;
}
}
}
_errorReporter.reportErrorForNode(HintCode.INVALID_FACTORY_METHOD_IMPL,
decl.name, [decl.name.toString()]);
}
void _checkForInvalidSealedSuperclass(NamedCompilationUnitMember node) {
bool currentPackageContains(Element element) {
return _isLibraryInWorkspacePackage(element.library);
}
// [NamedCompilationUnitMember.declaredElement] is not necessarily a
// ClassElement, but [_checkForInvalidSealedSuperclass] should only be
// called with a [ClassOrMixinDeclaration], or a [ClassTypeAlias]. The
// `declaredElement` of these specific classes is a [ClassElement].
ClassElement element = node.declaredElement;
// TODO(srawlins): Perhaps replace this with a getter on Element, like
// `Element.hasOrInheritsSealed`?
for (InterfaceType supertype in element.allSupertypes) {
ClassElement superclass = supertype.element;
if (superclass.hasSealed) {
if (!currentPackageContains(superclass)) {
if (element.superclassConstraints.contains(supertype)) {
// This is a special violation of the sealed class contract,
// requiring specific messaging.
_errorReporter.reportErrorForNode(HintCode.MIXIN_ON_SEALED_CLASS,
node, [superclass.name.toString()]);
} else {
// This is a regular violation of the sealed class contract.
_errorReporter.reportErrorForNode(HintCode.SUBTYPE_OF_SEALED_CLASS,
node, [superclass.name.toString()]);
}
}
}
}
}
/// Check that the instance creation node is const if the constructor is
/// marked with [literal].
_checkForLiteralConstructorUse(InstanceCreationExpression node) {
ConstructorName constructorName = node.constructorName;
ConstructorElement constructor = constructorName.staticElement;
if (constructor == null) {
return;
}
if (!node.isConst &&
constructor.hasLiteral &&
_linterContext.canBeConst(node)) {
// Echoing jwren's TODO from _checkForDeprecatedMemberUse:
// TODO(jwren) We should modify ConstructorElement.getDisplayName(), or
// have the logic centralized elsewhere, instead of doing this logic
// here.
String fullConstructorName = constructorName.type.name.name;
if (constructorName.name != null) {
fullConstructorName = '$fullConstructorName.${constructorName.name}';
}
HintCode hint = node.keyword?.keyword == Keyword.NEW
? HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW
: HintCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR;
_errorReporter.reportErrorForNode(hint, node, [fullConstructorName]);
}
}
/// Check that the imported library does not define a loadLibrary function.
/// The import has already been determined to be deferred when this is called.
///
/// @param node the import directive to evaluate
/// @param importElement the [ImportElement] retrieved from the node
/// @return `true` if and only if an error code is generated on the passed
/// node
/// See [CompileTimeErrorCode.IMPORT_DEFERRED_LIBRARY_WITH_LOAD_FUNCTION].
bool _checkForLoadLibraryFunction(
ImportDirective node, ImportElement importElement) {
LibraryElement importedLibrary = importElement.importedLibrary;
if (importedLibrary == null) {
return false;
}
if (importedLibrary.hasLoadLibraryFunction) {
_errorReporter.reportErrorForNode(
HintCode.IMPORT_DEFERRED_LIBRARY_WITH_LOAD_FUNCTION,
node,
[importedLibrary.name]);
return true;
}
return false;
}
/// Generate a hint for functions or methods that have a return type, but do
/// not have a return statement on all branches. At the end of blocks with no
/// return, Dart implicitly returns `null`, avoiding these implicit returns is
/// considered a best practice.
///
/// Note: for async functions/methods, this hint only applies when the
/// function has a return type that Future<Null> is not assignable to.
///
/// @param node the binary expression to check
/// @param body the function body
/// @return `true` if and only if a hint code is generated on the passed node
/// See [HintCode.MISSING_RETURN].
void _checkForMissingReturn(TypeAnnotation returnNode, FunctionBody body,
ExecutableElement element, AstNode functionNode) {
if (body is BlockFunctionBody) {
// Prefer the type from the element model, in case we've inferred one.
DartType returnType = element?.returnType ?? returnNode?.type;
AstNode errorNode = returnNode ?? functionNode;
// Skip the check if we're missing a return type (e.g. erroneous code).
// Generators are never required to have a return statement.
if (returnType == null || body.isGenerator) {
return;
}
var flattenedType =
body.isAsynchronous ? _typeSystem.flatten(returnType) : returnType;
// Function expressions without a return will have their return type set
// to `Null` regardless of their context type. So we need to figure out
// if a return type was expected from the original downwards context.
//
// This helps detect hint cases like `int Function() f = () {}`.
// See https://github.com/dart-lang/sdk/issues/28233 for context.
if (flattenedType.isDartCoreNull && functionNode is FunctionExpression) {
var contextType = InferenceContext.getContext(functionNode);
if (contextType is FunctionType) {
returnType = contextType.returnType;
flattenedType = body.isAsynchronous
? _typeSystem.flatten(returnType)
: returnType;
}
}
// dynamic, Null, void, and FutureOr<T> where T is (dynamic, Null, void)
// are allowed to omit a return.
if (flattenedType.isDartAsyncFutureOr) {
flattenedType = (flattenedType as InterfaceType).typeArguments[0];
}
if (flattenedType.isDynamic ||
flattenedType.isDartCoreNull ||
flattenedType.isVoid) {
return;
}
// Otherwise issue a warning if the block doesn't have a return.
if (!ExitDetector.exits(body)) {
_errorReporter.reportErrorForNode(
HintCode.MISSING_RETURN, errorNode, [returnType.displayName]);
}
}
}
/// Produce several null-aware related hints.
void _checkForNullAwareHints(Expression node, Token operator) {
if (_isNonNullable) {
return;
}
if (operator == null || operator.type != TokenType.QUESTION_PERIOD) {
return;
}
// childOfParent is used to know from which branch node comes.
var childOfParent = node;
var parent = node.parent;
while (parent is ParenthesizedExpression) {
childOfParent = parent;
parent = parent.parent;
}
// CAN_BE_NULL_AFTER_NULL_AWARE
if (parent is MethodInvocation &&
!parent.isNullAware &&
_nullType.lookUpMethod(parent.methodName.name, _currentLibrary) ==
null) {
_errorReporter.reportErrorForNode(
HintCode.CAN_BE_NULL_AFTER_NULL_AWARE, childOfParent);
return;
}
if (parent is PropertyAccess &&
!parent.isNullAware &&
_nullType.lookUpGetter(parent.propertyName.name, _currentLibrary) ==
null) {
_errorReporter.reportErrorForNode(
HintCode.CAN_BE_NULL_AFTER_NULL_AWARE, childOfParent);
return;
}
if (parent is CascadeExpression && parent.target == childOfParent) {
_errorReporter.reportErrorForNode(
HintCode.CAN_BE_NULL_AFTER_NULL_AWARE, childOfParent);
return;
}
// NULL_AWARE_IN_CONDITION
if (parent is IfStatement && parent.condition == childOfParent ||
parent is ForPartsWithDeclarations &&
parent.condition == childOfParent ||
parent is DoStatement && parent.condition == childOfParent ||
parent is WhileStatement && parent.condition == childOfParent ||
parent is ConditionalExpression && parent.condition == childOfParent ||
parent is AssertStatement && parent.condition == childOfParent) {
_errorReporter.reportErrorForNode(
HintCode.NULL_AWARE_IN_CONDITION, childOfParent);
return;
}
// NULL_AWARE_IN_LOGICAL_OPERATOR
if (parent is PrefixExpression && parent.operator.type == TokenType.BANG ||
parent is BinaryExpression &&
[TokenType.BAR_BAR, TokenType.AMPERSAND_AMPERSAND]
.contains(parent.operator.type)) {
_errorReporter.reportErrorForNode(
HintCode.NULL_AWARE_IN_LOGICAL_OPERATOR, childOfParent);
return;
}
// NULL_AWARE_BEFORE_OPERATOR
if (parent is BinaryExpression &&
![TokenType.EQ_EQ, TokenType.BANG_EQ, TokenType.QUESTION_QUESTION]
.contains(parent.operator.type) &&
parent.leftOperand == childOfParent) {
_errorReporter.reportErrorForNode(
HintCode.NULL_AWARE_BEFORE_OPERATOR, childOfParent);
return;
}
}
/// Check for the passed as expression for the [HintCode.UNNECESSARY_CAST]
/// hint code.
///
/// @param node the as expression to check
/// @return `true` if and only if a hint code is generated on the passed node
/// See [HintCode.UNNECESSARY_CAST].
bool _checkForUnnecessaryCast(AsExpression node) {
// TODO(jwren) After dartbug.com/13732, revisit this, we should be able to
// remove the (x is! TypeParameterType) checks.
AstNode parent = node.parent;
if (parent is ConditionalExpression &&
(node == parent.thenExpression || node == parent.elseExpression)) {
Expression thenExpression = parent.thenExpression;
DartType thenType;
if (thenExpression is AsExpression) {
thenType = thenExpression.expression.staticType;
} else {
thenType = thenExpression.staticType;
}
Expression elseExpression = parent.elseExpression;
DartType elseType;
if (elseExpression is AsExpression) {
elseType = elseExpression.expression.staticType;
} else {
elseType = elseExpression.staticType;
}
if (thenType != null &&
elseType != null &&
!thenType.isDynamic &&
!elseType.isDynamic &&
!_typeSystem.isSubtypeOf(thenType, elseType) &&
!_typeSystem.isSubtypeOf(elseType, thenType)) {
return false;
}
}
DartType lhsType = node.expression.staticType;
DartType rhsType = node.type.type;
if (lhsType != null &&
rhsType != null &&
!lhsType.isDynamic &&
!rhsType.isDynamic &&
_typeSystem.isSubtypeOf(lhsType, rhsType)) {
_errorReporter.reportErrorForNode(HintCode.UNNECESSARY_CAST, node);
return true;
}
return false;
}
/// Generate a hint for `noSuchMethod` methods that do nothing except of
/// calling another `noSuchMethod` that is not defined by `Object`.
///
/// @return `true` if and only if a hint code is generated on the passed node
/// See [HintCode.UNNECESSARY_NO_SUCH_METHOD].
bool _checkForUnnecessaryNoSuchMethod(MethodDeclaration node) {
if (node.name.name != FunctionElement.NO_SUCH_METHOD_METHOD_NAME) {
return false;
}
bool isNonObjectNoSuchMethodInvocation(Expression invocation) {
if (invocation is MethodInvocation &&
invocation.target is SuperExpression &&
invocation.argumentList.arguments.length == 1) {
SimpleIdentifier name = invocation.methodName;
if (name.name == FunctionElement.NO_SUCH_METHOD_METHOD_NAME) {
Element methodElement = name.staticElement;
Element classElement = methodElement?.enclosingElement;
return methodElement is MethodElement &&
classElement is ClassElement &&
!classElement.isDartCoreObject;
}
}
return false;
}
FunctionBody body = node.body;
if (body is ExpressionFunctionBody) {
if (isNonObjectNoSuchMethodInvocation(body.expression)) {
_errorReporter.reportErrorForNode(
HintCode.UNNECESSARY_NO_SUCH_METHOD, node);
return true;
}
} else if (body is BlockFunctionBody) {
List<Statement> statements = body.block.statements;
if (statements.length == 1) {
Statement returnStatement = statements.first;
if (returnStatement is ReturnStatement &&
isNonObjectNoSuchMethodInvocation(returnStatement.expression)) {
_errorReporter.reportErrorForNode(
HintCode.UNNECESSARY_NO_SUCH_METHOD, node);
return true;
}
}
}
return false;
}
void _checkRequiredParameter(FormalParameterList node) {
final requiredParameters =
node.parameters.where((p) => p.declaredElement?.hasRequired == true);
final nonNamedParamsWithRequired =
requiredParameters.where((p) => p.isPositional);
final namedParamsWithRequiredAndDefault = requiredParameters
.where((p) => p.isNamed)
.where((p) => p.declaredElement.defaultValueCode != null);
for (final param in nonNamedParamsWithRequired.where((p) => p.isOptional)) {
_errorReporter.reportErrorForNode(
HintCode.INVALID_REQUIRED_OPTIONAL_POSITIONAL_PARAM,
param,
[param.identifier.name]);
}
for (final param in nonNamedParamsWithRequired.where((p) => p.isRequired)) {
_errorReporter.reportErrorForNode(
HintCode.INVALID_REQUIRED_POSITIONAL_PARAM,
param,
[param.identifier.name]);
}
for (final param in namedParamsWithRequiredAndDefault) {
_errorReporter.reportErrorForNode(HintCode.INVALID_REQUIRED_NAMED_PARAM,
param, [param.identifier.name]);
}
}
/// In "strict-inference" mode, check that each of the [parameters]' type is
/// specified.
_checkStrictInferenceInParameters(FormalParameterList parameters) {
void checkParameterTypeIsKnown(SimpleFormalParameter parameter) {
if (parameter.type == null) {
ParameterElement element = parameter.declaredElement;
_errorReporter.reportTypeErrorForNode(
HintCode.INFERENCE_FAILURE_ON_UNTYPED_PARAMETER,
parameter,
[element.displayName],
);
}
}
if (_strictInference && parameters != null) {
for (FormalParameter parameter in parameters.parameters) {
if (parameter is SimpleFormalParameter) {
checkParameterTypeIsKnown(parameter);
} else if (parameter is DefaultFormalParameter) {
if (parameter.parameter is SimpleFormalParameter) {
checkParameterTypeIsKnown(parameter.parameter);
}
}
}
}
}
/// In "strict-inference" mode, check that [returnNode]'s return type is
/// specified.
void _checkStrictInferenceReturnType(
AstNode returnType, AstNode reportNode, String displayName) {
if (!_strictInference) {
return;
}
if (returnType == null) {
_errorReporter.reportErrorForNode(
HintCode.INFERENCE_FAILURE_ON_FUNCTION_RETURN_TYPE,
reportNode,
[displayName]);
}
}
bool _isLibraryInWorkspacePackage(LibraryElement library) {
if (_workspacePackage == null || library == null) {
// Better to not make a big claim that they _are_ in the same package,
// if we were unable to determine what package [_currentLibrary] is in.
return false;
}
return _workspacePackage.contains(library.source);
}
/// Return the message in the deprecated annotation on the given [element], or
/// `null` if the element doesn't have a deprecated annotation or if the
/// annotation does not have a message.
static String _deprecatedMessage(Element element) {
ElementAnnotationImpl annotation = element.metadata.firstWhere(
(e) => e.isDeprecated,
orElse: () => null,
);
if (annotation == null || annotation.element is PropertyAccessorElement) {
return null;
}
DartObject constantValue = annotation.computeConstantValue();
return constantValue?.getField('message')?.toStringValue() ??
constantValue?.getField('expires')?.toStringValue();
}
/// Check for the passed class declaration for the
/// [HintCode.OVERRIDE_EQUALS_BUT_NOT_HASH_CODE] hint code.
///
/// @param node the class declaration to check
/// @return `true` if and only if a hint code is generated on the passed node
/// See [HintCode.OVERRIDE_EQUALS_BUT_NOT_HASH_CODE].
// bool _checkForOverrideEqualsButNotHashCode(ClassDeclaration node) {
// ClassElement classElement = node.element;
// if (classElement == null) {
// return false;
// }
// MethodElement equalsOperatorMethodElement =
// classElement.getMethod(sc.TokenType.EQ_EQ.lexeme);
// if (equalsOperatorMethodElement != null) {
// PropertyAccessorElement hashCodeElement =
// classElement.getGetter(_HASHCODE_GETTER_NAME);
// if (hashCodeElement == null) {
// _errorReporter.reportErrorForNode(
// HintCode.OVERRIDE_EQUALS_BUT_NOT_HASH_CODE,
// node.name,
// [classElement.displayName]);
// return true;
// }
// }
// return false;
// }
//
// /// Return `true` if the given [type] represents `Future<void>`.
// bool _isFutureVoid(DartType type) {
// if (type.isDartAsyncFuture) {
// List<DartType> typeArgs = (type as InterfaceType).typeArguments;
// if (typeArgs.length == 1 && typeArgs[0].isVoid) {
// return true;
// }
// }
// return false;
// }
static bool _hasDeprecatedAnnotation(List<Annotation> annotations) {
for (var i = 0; i < annotations.length; i++) {
if (annotations[i].elementAnnotation.isDeprecated) {
return true;
}
}
return false;
}
/// Given a parenthesized expression, this returns the parent (or recursively
/// grand-parent) of the expression that is a parenthesized expression, but
/// whose parent is not a parenthesized expression.
///
/// For example given the code `(((e)))`: `(e) -> (((e)))`.
///
/// @param parenthesizedExpression some expression whose parent is a
/// parenthesized expression
/// @return the first parent or grand-parent that is a parenthesized
/// expression, that does not have a parenthesized expression parent
static ParenthesizedExpression _wrapParenthesizedExpression(
ParenthesizedExpression parenthesizedExpression) {
AstNode parent = parenthesizedExpression.parent;
if (parent is ParenthesizedExpression) {
return _wrapParenthesizedExpression(parent);
}
return parenthesizedExpression;
}
}
/// Utilities for [LibraryElementImpl] building.
class BuildLibraryElementUtils {
/// Look through all of the compilation units defined for the given [library],
/// looking for getters and setters that are defined in different compilation
/// units but that have the same names. If any are found, make sure that they
/// have the same variable element.
static void patchTopLevelAccessors(LibraryElementImpl library) {
// Without parts getters/setters already share the same variable element.
List<CompilationUnitElement> parts = library.parts;
if (parts.isEmpty) {
return;
}
// Collect getters and setters.
Map<String, PropertyAccessorElement> getters =
new HashMap<String, PropertyAccessorElement>();
List<PropertyAccessorElement> setters = <PropertyAccessorElement>[];
_collectAccessors(getters, setters, library.definingCompilationUnit);
int partLength = parts.length;
for (int i = 0; i < partLength; i++) {
CompilationUnitElement unit = parts[i];
_collectAccessors(getters, setters, unit);
}
// Move every setter to the corresponding getter's variable (if exists).
int setterLength = setters.length;
for (int j = 0; j < setterLength; j++) {
PropertyAccessorElement setter = setters[j];
PropertyAccessorElement getter = getters[setter.displayName];
if (getter != null) {
TopLevelVariableElementImpl variable = getter.variable;
TopLevelVariableElementImpl setterVariable = setter.variable;
CompilationUnitElementImpl setterUnit = setterVariable.enclosingElement;
setterUnit.replaceTopLevelVariable(setterVariable, variable);
variable.setter = setter;
(setter as PropertyAccessorElementImpl).variable = variable;
}
}
}
/// Add all of the non-synthetic [getters] and [setters] defined in the given
/// [unit] that have no corresponding accessor to one of the given
/// collections.
static void _collectAccessors(Map<String, PropertyAccessorElement> getters,
List<PropertyAccessorElement> setters, CompilationUnitElement unit) {
List<PropertyAccessorElement> accessors = unit.accessors;
int length = accessors.length;
for (int i = 0; i < length; i++) {
PropertyAccessorElement accessor = accessors[i];
if (accessor.isGetter) {
if (!accessor.isSynthetic && accessor.correspondingSetter == null) {
getters[accessor.displayName] = accessor;
}
} else {
if (!accessor.isSynthetic && accessor.correspondingGetter == null) {
setters.add(accessor);
}
}
}
}
}
/// Instances of the class `Dart2JSVerifier` traverse an AST structure looking
/// for hints for code that will be compiled to JS, such as
/// [HintCode.IS_DOUBLE].
class Dart2JSVerifier extends RecursiveAstVisitor<void> {
/// The name of the `double` type.
static String _DOUBLE_TYPE_NAME = "double";
/// The error reporter by which errors will be reported.
final ErrorReporter _errorReporter;
/// Create a new instance of the [Dart2JSVerifier].
///
/// @param errorReporter the error reporter
Dart2JSVerifier(this._errorReporter);
@override
void visitIsExpression(IsExpression node) {
_checkForIsDoubleHints(node);
super.visitIsExpression(node);
}
/// Check for instances of `x is double`, `x is int`, `x is! double` and
/// `x is! int`.
///
/// @param node the is expression to check
/// @return `true` if and only if a hint code is generated on the passed node
/// See [HintCode.IS_DOUBLE],
/// [HintCode.IS_INT],
/// [HintCode.IS_NOT_DOUBLE], and
/// [HintCode.IS_NOT_INT].
bool _checkForIsDoubleHints(IsExpression node) {
DartType type = node.type.type;
Element element = type?.element;
if (element != null) {
String typeNameStr = element.name;
LibraryElement libraryElement = element.library;
// if (typeNameStr.equals(INT_TYPE_NAME) && libraryElement != null
// && libraryElement.isDartCore()) {
// if (node.getNotOperator() == null) {
// errorReporter.reportError(HintCode.IS_INT, node);
// } else {
// errorReporter.reportError(HintCode.IS_NOT_INT, node);
// }
// return true;
// } else
if (typeNameStr == _DOUBLE_TYPE_NAME &&
libraryElement != null &&
libraryElement.isDartCore) {
if (node.notOperator == null) {
_errorReporter.reportErrorForNode(HintCode.IS_DOUBLE, node);
} else {
_errorReporter.reportErrorForNode(HintCode.IS_NOT_DOUBLE, node);
}
return true;
}
}
return false;
}
}
/// A visitor that finds dead code and unused labels.
class DeadCodeVerifier extends RecursiveAstVisitor<void> {
/// The error reporter by which errors will be reported.
final ErrorReporter _errorReporter;
/// The type system for this visitor
final TypeSystem _typeSystem;
/// The object used to track the usage of labels within a given label scope.
_LabelTracker labelTracker;
/// Is `true` if this unit has been parsed as non-nullable.
final bool _isNonNullableUnit;
/// Initialize a newly created dead code verifier that will report dead code
/// to the given [errorReporter] and will use the given [typeSystem] if one is
/// provided.
DeadCodeVerifier(this._errorReporter, FeatureSet featureSet,
{TypeSystem typeSystem})
: this._typeSystem = typeSystem ?? new Dart2TypeSystem(null),
_isNonNullableUnit = featureSet.isEnabled(Feature.non_nullable);
@override
void visitAssignmentExpression(AssignmentExpression node) {
TokenType operatorType = node.operator.type;
if (operatorType == TokenType.QUESTION_QUESTION_EQ) {
_checkForDeadNullCoalesce(
node.leftHandSide.staticType, node.rightHandSide);
}
super.visitAssignmentExpression(node);
}
@override
void visitBinaryExpression(BinaryExpression node) {
Token operator = node.operator;
bool isAmpAmp = operator.type == TokenType.AMPERSAND_AMPERSAND;
bool isBarBar = operator.type == TokenType.BAR_BAR;
bool isQuestionQuestion = operator.type == TokenType.QUESTION_QUESTION;
if (isAmpAmp || isBarBar) {
Expression lhsCondition = node.leftOperand;
if (!_isDebugConstant(lhsCondition)) {
EvaluationResultImpl lhsResult = _getConstantBooleanValue(lhsCondition);
if (lhsResult != null) {
bool value = lhsResult.value.toBoolValue();
if (value == true && isBarBar) {
// Report error on "else" block: true || !e!
_errorReporter.reportErrorForNode(
HintCode.DEAD_CODE, node.rightOperand);
// Only visit the LHS:
lhsCondition?.accept(this);
return;
} else if (value == false && isAmpAmp) {
// Report error on "if" block: false && !e!
_errorReporter.reportErrorForNode(
HintCode.DEAD_CODE, node.rightOperand);
// Only visit the LHS:
lhsCondition?.accept(this);
return;
}
}
}
// How do we want to handle the RHS? It isn't dead code, but "pointless"
// or "obscure"...
// Expression rhsCondition = node.getRightOperand();
// ValidResult rhsResult = getConstantBooleanValue(rhsCondition);
// if (rhsResult != null) {
// if (rhsResult == ValidResult.RESULT_TRUE && isBarBar) {
// // report error on else block: !e! || true
// errorReporter.reportError(HintCode.DEAD_CODE, node.getRightOperand());
// // only visit the RHS:
// rhsCondition?.accept(this);
// return null;
// } else if (rhsResult == ValidResult.RESULT_FALSE && isAmpAmp) {
// // report error on if block: !e! && false
// errorReporter.reportError(HintCode.DEAD_CODE, node.getRightOperand());
// // only visit the RHS:
// rhsCondition?.accept(this);
// return null;
// }
// }
} else if (isQuestionQuestion && _isNonNullableUnit) {
_checkForDeadNullCoalesce(node.leftOperand.staticType, node.rightOperand);
}
super.visitBinaryExpression(node);
}
/// For each block, this method reports and error on all statements between
/// the end of the block and the first return statement (assuming there it is
/// not at the end of the block.)
@override
void visitBlock(Block node) {
NodeList<Statement> statements = node.statements;
_checkForDeadStatementsInNodeList(statements);
}
@override
void visitBreakStatement(BreakStatement node) {
labelTracker?.recordUsage(node.label?.name);
}
@override
void visitConditionalExpression(ConditionalExpression node) {
Expression conditionExpression = node.condition;
conditionExpression?.accept(this);
if (!_isDebugConstant(conditionExpression)) {
EvaluationResultImpl result =
_getConstantBooleanValue(conditionExpression);
if (result != null) {
if (result.value.toBoolValue() == true) {
// Report error on "else" block: true ? 1 : !2!
_errorReporter.reportErrorForNode(
HintCode.DEAD_CODE, node.elseExpression);
node.thenExpression?.accept(this);
return;
} else {
// Report error on "if" block: false ? !1! : 2
_errorReporter.reportErrorForNode(
HintCode.DEAD_CODE, node.thenExpression);
node.elseExpression?.accept(this);
return;
}
}
}
super.visitConditionalExpression(node);
}
@override
void visitContinueStatement(ContinueStatement node) {
labelTracker?.recordUsage(node.label?.name);
}
@override
void visitExportDirective(ExportDirective node) {
ExportElement exportElement = node.element;
if (exportElement != null) {
// The element is null when the URI is invalid.
LibraryElement library = exportElement.exportedLibrary;
if (library != null && !library.isSynthetic) {
for (Combinator combinator in node.combinators) {
_checkCombinator(library, combinator);
}
}
}
super.visitExportDirective(node);
}
@override
void visitIfElement(IfElement node) {
Expression conditionExpression = node.condition;
conditionExpression?.accept(this);
if (!_isDebugConstant(conditionExpression)) {
EvaluationResultImpl result =
_getConstantBooleanValue(conditionExpression);
if (result != null) {
if (result.value.toBoolValue() == true) {
// Report error on else block: if(true) {} else {!}
CollectionElement elseElement = node.elseElement;
if (elseElement != null) {
_errorReporter.reportErrorForNode(HintCode.DEAD_CODE, elseElement);
node.thenElement?.accept(this);
return;
}
} else {
// Report error on if block: if (false) {!} else {}
_errorReporter.reportErrorForNode(
HintCode.DEAD_CODE, node.thenElement);
node.elseElement?.accept(this);
return;
}
}
}
super.visitIfElement(node);
}
@override
void visitIfStatement(IfStatement node) {
Expression conditionExpression = node.condition;
conditionExpression?.accept(this);
if (!_isDebugConstant(conditionExpression)) {
EvaluationResultImpl result =
_getConstantBooleanValue(conditionExpression);
if (result != null) {
if (result.value.toBoolValue() == true) {
// Report error on else block: if(true) {} else {!}
Statement elseStatement = node.elseStatement;
if (elseStatement != null) {
_errorReporter.reportErrorForNode(
HintCode.DEAD_CODE, elseStatement);
node.thenStatement?.accept(this);
return;
}
} else {
// Report error on if block: if (false) {!} else {}
_errorReporter.reportErrorForNode(
HintCode.DEAD_CODE, node.thenStatement);
node.elseStatement?.accept(this);
return;
}
}
}
super.visitIfStatement(node);
}
@override
void visitImportDirective(ImportDirective node) {
ImportElement importElement = node.element;
if (importElement != null) {
// The element is null when the URI is invalid, but not when the URI is
// valid but refers to a non-existent file.
LibraryElement library = importElement.importedLibrary;
if (library != null && !library.isSynthetic) {
for (Combinator combinator in node.combinators) {
_checkCombinator(library, combinator);
}
}
}
super.visitImportDirective(node);
}
@override
void visitLabeledStatement(LabeledStatement node) {
_pushLabels(node.labels);
try {
super.visitLabeledStatement(node);
} finally {
_popLabels();
}
}
@override
void visitSwitchCase(SwitchCase node) {
_checkForDeadStatementsInNodeList(node.statements, allowMandated: true);
super.visitSwitchCase(node);
}
@override
void visitSwitchDefault(SwitchDefault node) {
_checkForDeadStatementsInNodeList(node.statements, allowMandated: true);
super.visitSwitchDefault(node);
}
@override
void visitSwitchStatement(SwitchStatement node) {
List<Label> labels = <Label>[];
for (SwitchMember member in node.members) {
labels.addAll(member.labels);
}
_pushLabels(labels);
try {
super.visitSwitchStatement(node);
} finally {
_popLabels();
}
}
@override
void visitTryStatement(TryStatement node) {
node.body?.accept(this);
node.finallyBlock?.accept(this);
NodeList<CatchClause> catchClauses = node.catchClauses;
int numOfCatchClauses = catchClauses.length;
List<DartType> visitedTypes = new List<DartType>();
for (int i = 0; i < numOfCatchClauses; i++) {
CatchClause catchClause = catchClauses[i];
if (catchClause.onKeyword != null) {
// An on-catch clause was found; verify that the exception type is not a
// subtype of a previous on-catch exception type.
DartType currentType = catchClause.exceptionType?.type;
if (currentType != null) {
if (currentType.isObject) {
// Found catch clause clause that has Object as an exception type,
// this is equivalent to having a catch clause that doesn't have an
// exception type, visit the block, but generate an error on any
// following catch clauses (and don't visit them).
catchClause?.accept(this);
if (i + 1 != numOfCatchClauses) {
// This catch clause is not the last in the try statement.
CatchClause nextCatchClause = catchClauses[i + 1];
CatchClause lastCatchClause = catchClauses[numOfCatchClauses - 1];
int offset = nextCatchClause.offset;
int length = lastCatchClause.end - offset;
_errorReporter.reportErrorForOffset(
HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH, offset, length);
return;
}
}
int length = visitedTypes.length;
for (int j = 0; j < length; j++) {
DartType type = visitedTypes[j];
if (_typeSystem.isSubtypeOf(currentType, type)) {
CatchClause lastCatchClause = catchClauses[numOfCatchClauses - 1];
int offset = catchClause.offset;
int length = lastCatchClause.end - offset;
_errorReporter.reportErrorForOffset(
HintCode.DEAD_CODE_ON_CATCH_SUBTYPE,
offset,
length,
[currentType.displayName, type.displayName]);
return;
}
}
visitedTypes.add(currentType);
}
catchClause?.accept(this);
} else {
// Found catch clause clause that doesn't have an exception type,
// visit the block, but generate an error on any following catch clauses
// (and don't visit them).
catchClause?.accept(this);
if (i + 1 != numOfCatchClauses) {
// This catch clause is not the last in the try statement.
CatchClause nextCatchClause = catchClauses[i + 1];
CatchClause lastCatchClause = catchClauses[numOfCatchClauses - 1];
int offset = nextCatchClause.offset;
int length = lastCatchClause.end - offset;
_errorReporter.reportErrorForOffset(
HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH, offset, length);
return;
}
}
}
}
@override
void visitWhileStatement(WhileStatement node) {
Expression conditionExpression = node.condition;
conditionExpression?.accept(this);
if (!_isDebugConstant(conditionExpression)) {
EvaluationResultImpl result =
_getConstantBooleanValue(conditionExpression);
if (result != null) {
if (result.value.toBoolValue() == false) {
// Report error on while block: while (false) {!}
_errorReporter.reportErrorForNode(HintCode.DEAD_CODE, node.body);
return;
}
}
}
node.body?.accept(this);
}
/// Resolve the names in the given [combinator] in the scope of the given
/// [library].
void _checkCombinator(LibraryElement library, Combinator combinator) {
Namespace namespace =
new NamespaceBuilder().createExportNamespaceForLibrary(library);
NodeList<SimpleIdentifier> names;
ErrorCode hintCode;
if (combinator is HideCombinator) {
names = combinator.hiddenNames;
hintCode = HintCode.UNDEFINED_HIDDEN_NAME;
} else {
names = (combinator as ShowCombinator).shownNames;
hintCode = HintCode.UNDEFINED_SHOWN_NAME;
}
for (SimpleIdentifier name in names) {
String nameStr = name.name;
Element element = namespace.get(nameStr);
if (element == null) {
element = namespace.get("$nameStr=");
}
if (element == null) {
_errorReporter
.reportErrorForNode(hintCode, name, [library.identifier, nameStr]);
}
}
}
void _checkForDeadNullCoalesce(TypeImpl lhsType, Expression rhs) {
if (_isNonNullableUnit && _typeSystem.isNonNullable(lhsType)) {
_errorReporter.reportErrorForNode(HintCode.DEAD_CODE, rhs, []);
}
}
/// Given some list of [statements], loop through the list searching for dead
/// statements. If [allowMandated] is true, then allow dead statements that
/// are mandated by the language spec. This allows for a final break,
/// continue, return, or throw statement at the end of a switch case, that are
/// mandated by the language spec.
void _checkForDeadStatementsInNodeList(NodeList<Statement> statements,
{bool allowMandated: false}) {
bool statementExits(Statement statement) {
if (statement is BreakStatement) {
return statement.label == null;
} else if (statement is ContinueStatement) {
return statement.label == null;
}
return ExitDetector.exits(statement);
}
int size = statements.length;
for (int i = 0; i < size; i++) {
Statement currentStatement = statements[i];
currentStatement?.accept(this);
if (statementExits(currentStatement) && i != size - 1) {
Statement nextStatement = statements[i + 1];
Statement lastStatement = statements[size - 1];
// If mandated statements are allowed, and only the last statement is
// dead, and it's a BreakStatement, then assume it is a statement
// mandated by the language spec, there to avoid a
// CASE_BLOCK_NOT_TERMINATED error.
if (allowMandated && i == size - 2 && nextStatement is BreakStatement) {
return;
}
int offset = nextStatement.offset;
int length = lastStatement.end - offset;
_errorReporter.reportErrorForOffset(HintCode.DEAD_CODE, offset, length);
return;
}
}
}
/// Given some [expression], return [ValidResult.RESULT_TRUE] if it is `true`,
/// [ValidResult.RESULT_FALSE] if it is `false`, or `null` if the expression
/// is not a constant boolean value.
EvaluationResultImpl _getConstantBooleanValue(Expression expression) {
if (expression is BooleanLiteral) {
if (expression.value) {
return new EvaluationResultImpl(
new DartObjectImpl(null, BoolState.from(true)));
} else {
return new EvaluationResultImpl(
new DartObjectImpl(null, BoolState.from(false)));
}
}
// Don't consider situations where we could evaluate to a constant boolean
// expression with the ConstantVisitor
// else {
// EvaluationResultImpl result = expression.accept(new ConstantVisitor());
// if (result == ValidResult.RESULT_TRUE) {
// return ValidResult.RESULT_TRUE;
// } else if (result == ValidResult.RESULT_FALSE) {
// return ValidResult.RESULT_FALSE;
// }
// return null;
// }
return null;
}
/// Return `true` if the given [expression] is resolved to a constant
/// variable.
bool _isDebugConstant(Expression expression) {
Element element;
if (expression is Identifier) {
element = expression.staticElement;
} else if (expression is PropertyAccess) {
element = expression.propertyName.staticElement;
}
if (element is PropertyAccessorElement) {
PropertyInducingElement variable = element.variable;
return variable != null && variable.isConst;
}
return false;
}
/// Exit the most recently entered label scope after reporting any labels that
/// were not referenced within that scope.
void _popLabels() {
for (Label label in labelTracker.unusedLabels()) {
_errorReporter
.reportErrorForNode(HintCode.UNUSED_LABEL, label, [label.label.name]);
}
labelTracker = labelTracker.outerTracker;
}
/// Enter a new label scope in which the given [labels] are defined.
void _pushLabels(List<Label> labels) {
labelTracker = new _LabelTracker(labelTracker, labels);
}
}
/// A visitor that resolves directives in an AST structure to already built
/// elements.
///
/// The resulting AST must have everything resolved that would have been
/// resolved by a [DirectiveElementBuilder].
class DirectiveResolver extends SimpleAstVisitor {
final Map<Source, int> sourceModificationTimeMap;
final Map<Source, SourceKind> importSourceKindMap;
final Map<Source, SourceKind> exportSourceKindMap;
final List<AnalysisError> errors = <AnalysisError>[];
LibraryElement _enclosingLibrary;
DirectiveResolver(this.sourceModificationTimeMap, this.importSourceKindMap,
this.exportSourceKindMap);
@override
void visitCompilationUnit(CompilationUnit node) {
_enclosingLibrary = node.declaredElement.library;
for (Directive directive in node.directives) {
directive.accept(this);
}
}
@override
void visitExportDirective(ExportDirective node) {
int nodeOffset = node.offset;
node.element = null;
for (ExportElement element in _enclosingLibrary.exports) {
if (element.nameOffset == nodeOffset) {
node.element = element;
// Verify the exported source kind.
LibraryElement exportedLibrary = element.exportedLibrary;
if (exportedLibrary != null) {
Source exportedSource = exportedLibrary.source;
int exportedTime = sourceModificationTimeMap[exportedSource] ?? -1;
if (exportedTime >= 0 &&
exportSourceKindMap[exportedSource] != SourceKind.LIBRARY) {
StringLiteral uriLiteral = node.uri;
errors.add(new AnalysisError(
_enclosingLibrary.source,
uriLiteral.offset,
uriLiteral.length,
CompileTimeErrorCode.EXPORT_OF_NON_LIBRARY,
[uriLiteral.toSource()]));
}
}
break;
}
}
}
@override
void visitImportDirective(ImportDirective node) {
int nodeOffset = node.offset;
node.element = null;
for (ImportElement element in _enclosingLibrary.imports) {
if (element.nameOffset == nodeOffset) {
node.element = element;
// Verify the imported source kind.
LibraryElement importedLibrary = element.importedLibrary;
if (importedLibrary != null) {
Source importedSource = importedLibrary.source;
int importedTime = sourceModificationTimeMap[importedSource] ?? -1;
if (importedTime >= 0 &&
importSourceKindMap[importedSource] != SourceKind.LIBRARY) {
StringLiteral uriLiteral = node.uri;
errors.add(new AnalysisError(
_enclosingLibrary.source,
uriLiteral.offset,
uriLiteral.length,
CompileTimeErrorCode.IMPORT_OF_NON_LIBRARY,
[uriLiteral.toSource()]));
}
}
break;
}
}
}
@override
void visitLibraryDirective(LibraryDirective node) {
node.element = _enclosingLibrary;
}
}
/// Instances of the class `ElementHolder` hold on to elements created while
/// traversing an AST structure so that they can be accessed when creating their
/// enclosing element.
class ElementHolder {
List<PropertyAccessorElement> _accessors;
List<ConstructorElement> _constructors;
List<ClassElement> _enums;
List<FieldElement> _fields;
List<FunctionElement> _functions;
List<LabelElement> _labels;
List<LocalVariableElement> _localVariables;
List<MethodElement> _methods;
List<ClassElement> _mixins;
List<ParameterElement> _parameters;
List<TopLevelVariableElement> _topLevelVariables;
List<ClassElement> _types;
List<FunctionTypeAliasElement> _typeAliases;
List<TypeParameterElement> _typeParameters;
List<PropertyAccessorElement> get accessors {
if (_accessors == null) {
return const <PropertyAccessorElement>[];
}
List<PropertyAccessorElement> result = _accessors;
_accessors = null;
return result;
}
List<ConstructorElement> get constructors {
if (_constructors == null) {
return const <ConstructorElement>[];
}
List<ConstructorElement> result = _constructors;
_constructors = null;
return result;
}
List<ClassElement> get enums {
if (_enums == null) {
return const <ClassElement>[];
}
List<ClassElement> result = _enums;
_enums = null;
return result;
}
List<FieldElement> get fields {
if (_fields == null) {
return const <FieldElement>[];
}
List<FieldElement> result = _fields;
_fields = null;
return result;
}
List<FieldElement> get fieldsWithoutFlushing {
if (_fields == null) {
return const <FieldElement>[];
}
List<FieldElement> result = _fields;
return result;
}
List<FunctionElement> get functions {
if (_functions == null) {
return const <FunctionElement>[];
}
List<FunctionElement> result = _functions;
_functions = null;
return result;
}
List<LabelElement> get labels {
if (_labels == null) {
return const <LabelElement>[];
}
List<LabelElement> result = _labels;
_labels = null;
return result;
}
List<LocalVariableElement> get localVariables {
if (_localVariables == null) {
return const <LocalVariableElement>[];
}
List<LocalVariableElement> result = _localVariables;
_localVariables = null;
return result;
}
List<MethodElement> get methods {
if (_methods == null) {
return const <MethodElement>[];
}
List<MethodElement> result = _methods;
_methods = null;
return result;
}
List<ClassElement> get mixins {
if (_mixins == null) {
return const <ClassElement>[];
}
List<ClassElement> result = _mixins;
_mixins = null;
return result;
}
List<ParameterElement> get parameters {
if (_parameters == null) {
return const <ParameterElement>[];
}
List<ParameterElement> result = _parameters;
_parameters = null;
return result;
}
List<TopLevelVariableElement> get topLevelVariables {
if (_topLevelVariables == null) {
return const <TopLevelVariableElement>[];
}
List<TopLevelVariableElement> result = _topLevelVariables;
_topLevelVariables = null;
return result;
}
List<FunctionTypeAliasElement> get typeAliases {
if (_typeAliases == null) {
return const <FunctionTypeAliasElement>[];
}
List<FunctionTypeAliasElement> result = _typeAliases;
_typeAliases = null;
return result;
}
List<TypeParameterElement> get typeParameters {
if (_typeParameters == null) {
return const <TypeParameterElement>[];
}
List<TypeParameterElement> result = _typeParameters;
_typeParameters = null;
return result;
}
List<ClassElement> get types {
if (_types == null) {
return const <ClassElement>[];
}
List<ClassElement> result = _types;
_types = null;
return result;
}
void addAccessor(PropertyAccessorElement element) {
if (_accessors == null) {
_accessors = new List<PropertyAccessorElement>();
}
_accessors.add(element);
}
void addConstructor(ConstructorElement element) {
if (_constructors == null) {
_constructors = new List<ConstructorElement>();
}
_constructors.add(element);
}
void addEnum(ClassElement element) {
if (_enums == null) {
_enums = new List<ClassElement>();
}
_enums.add(element);
}
void addField(FieldElement element) {
if (_fields == null) {
_fields = new List<FieldElement>();
}
_fields.add(element);
}
void addFunction(FunctionElement element) {
if (_functions == null) {
_functions = new List<FunctionElement>();
}
_functions.add(element);
}
void addLabel(LabelElement element) {
if (_labels == null) {
_labels = new List<LabelElement>();
}
_labels.add(element);
}
void addLocalVariable(LocalVariableElement element) {
if (_localVariables == null) {
_localVariables = new List<LocalVariableElement>();
}
_localVariables.add(element);
}
void addMethod(MethodElement element) {
if (_methods == null) {
_methods = new List<MethodElement>();
}
_methods.add(element);
}
void addMixin(ClassElement element) {
if (_mixins == null) {
_mixins = new List<ClassElement>();
}
_mixins.add(element);
}
void addParameter(ParameterElement element) {
if (_parameters == null) {
_parameters = new List<ParameterElement>();
}
_parameters.add(element);
}
void addTopLevelVariable(TopLevelVariableElement element) {
if (_topLevelVariables == null) {
_topLevelVariables = new List<TopLevelVariableElement>();
}
_topLevelVariables.add(element);
}
void addType(ClassElement element) {
if (_types == null) {
_types = new List<ClassElement>();
}
_types.add(element);
}
void addTypeAlias(FunctionTypeAliasElement element) {
if (_typeAliases == null) {
_typeAliases = new List<FunctionTypeAliasElement>();
}
_typeAliases.add(element);
}
void addTypeParameter(TypeParameterElement element) {
if (_typeParameters == null) {
_typeParameters = new List<TypeParameterElement>();
}
_typeParameters.add(element);
}
FieldElement getField(String fieldName, {bool synthetic: false}) {
if (_fields == null) {
return null;
}
int length = _fields.length;
for (int i = 0; i < length; i++) {
FieldElement field = _fields[i];
if (field.name == fieldName && field.isSynthetic == synthetic) {
return field;
}
}
return null;
}
TopLevelVariableElement getTopLevelVariable(String variableName) {
if (_topLevelVariables == null) {
return null;
}
int length = _topLevelVariables.length;
for (int i = 0; i < length; i++) {
TopLevelVariableElement variable = _topLevelVariables[i];
if (variable.name == variableName) {
return variable;
}
}
return null;
}
void validate() {
StringBuffer buffer = new StringBuffer();
if (_accessors != null) {
buffer.write(_accessors.length);
buffer.write(" accessors");
}
if (_constructors != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_constructors.length);
buffer.write(" constructors");
}
if (_fields != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_fields.length);
buffer.write(" fields");
}
if (_functions != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_functions.length);
buffer.write(" functions");
}
if (_labels != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_labels.length);
buffer.write(" labels");
}
if (_localVariables != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_localVariables.length);
buffer.write(" local variables");
}
if (_methods != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_methods.length);
buffer.write(" methods");
}
if (_parameters != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_parameters.length);
buffer.write(" parameters");
}
if (_topLevelVariables != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_topLevelVariables.length);
buffer.write(" top-level variables");
}
if (_types != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_types.length);
buffer.write(" types");
}
if (_typeAliases != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_typeAliases.length);
buffer.write(" type aliases");
}
if (_typeParameters != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_typeParameters.length);
buffer.write(" type parameters");
}
if (buffer.length > 0) {
AnalysisEngine.instance.logger
.logError("Failed to capture elements: $buffer");
}
}
}
/// An [AstVisitor] that fills [UsedLocalElements].
class GatherUsedLocalElementsVisitor extends RecursiveAstVisitor {
final UsedLocalElements usedElements = new UsedLocalElements();
final LibraryElement _enclosingLibrary;
ClassElement _enclosingClass;
ExecutableElement _enclosingExec;
GatherUsedLocalElementsVisitor(this._enclosingLibrary);
@override
visitCatchClause(CatchClause node) {
SimpleIdentifier exceptionParameter = node.exceptionParameter;
SimpleIdentifier stackTraceParameter = node.stackTraceParameter;
if (exceptionParameter != null) {
Element element = exceptionParameter.staticElement;
usedElements.addCatchException(element);
if (stackTraceParameter != null || node.onKeyword == null) {
usedElements.addElement(element);
}
}
if (stackTraceParameter != null) {
Element element = stackTraceParameter.staticElement;
usedElements.addCatchStackTrace(element);
}
super.visitCatchClause(node);
}
@override
visitClassDeclaration(ClassDeclaration node) {
ClassElement enclosingClassOld = _enclosingClass;
try {
_enclosingClass = node.declaredElement;
super.visitClassDeclaration(node);
} finally {
_enclosingClass = enclosingClassOld;
}
}
@override
visitFunctionDeclaration(FunctionDeclaration node) {
ExecutableElement enclosingExecOld = _enclosingExec;
try {
_enclosingExec = node.declaredElement;
super.visitFunctionDeclaration(node);
} finally {
_enclosingExec = enclosingExecOld;
}
}
@override
visitFunctionExpression(FunctionExpression node) {
if (node.parent is! FunctionDeclaration) {
usedElements.addElement(node.declaredElement);
}
super.visitFunctionExpression(node);
}
@override
visitMethodDeclaration(MethodDeclaration node) {
ExecutableElement enclosingExecOld = _enclosingExec;
try {
_enclosingExec = node.declaredElement;
super.visitMethodDeclaration(node);
} finally {
_enclosingExec = enclosingExecOld;
}
}
@override
visitSimpleIdentifier(SimpleIdentifier node) {
if (node.inDeclarationContext()) {
return;
}
Element element = node.staticElement;
bool isIdentifierRead = _isReadIdentifier(node);
if (element is PropertyAccessorElement &&
element.isSynthetic &&
isIdentifierRead &&
element.variable is TopLevelVariableElement) {
usedElements.addElement(element.variable);
} else if (element is LocalVariableElement) {
if (isIdentifierRead) {
usedElements.addElement(element);
}
} else {
_useIdentifierElement(node);
if (element == null ||
element.enclosingElement is ClassElement &&
!identical(element, _enclosingExec)) {
usedElements.members.add(node.name);
if (isIdentifierRead) {
usedElements.readMembers.add(node.name);
}
}
}
}
/// Marks an [Element] of [node] as used in the library.
void _useIdentifierElement(Identifier node) {
Element element = node.staticElement;
if (element == null) {
return;
}
// check if a local element
if (!identical(element.library, _enclosingLibrary)) {
return;
}
// ignore references to an element from itself
if (identical(element, _enclosingClass)) {
return;
}
if (identical(element, _enclosingExec)) {
return;
}
// ignore places where the element is not actually used
if (node.parent is TypeName) {
if (element is ClassElement) {
AstNode parent2 = node.parent.parent;
if (parent2 is IsExpression) {
return;
}
if (parent2 is VariableDeclarationList) {
// If it's a field's type, it still counts as used.
if (parent2.parent is! FieldDeclaration) {
return;
}
}
}
}
// OK
usedElements.addElement(element);
}
static bool _isReadIdentifier(SimpleIdentifier node) {
// not reading at all
if (!node.inGetterContext()) {
return false;
}
// check if useless reading
AstNode parent = node.parent;
if (parent.parent is ExpressionStatement) {
if (parent is PrefixExpression || parent is PostfixExpression) {
// v++;
// ++v;
return false;
}
if (parent is AssignmentExpression && parent.leftHandSide == node) {
// v ??= doSomething();
// vs.
// v += 2;
TokenType operatorType = parent.operator?.type;
return operatorType == TokenType.QUESTION_QUESTION_EQ;
}
}
// OK
return true;
}
}
/// Maintains and manages contextual type information used for
/// inferring types.
class InferenceContext {
// TODO(leafp): Consider replacing these node properties with a
// hash table help in an instance of this class.
static const String _typeProperty =
'analyzer.src.generated.InferenceContext.contextType';
/// The error listener on which to record inference information.
final ErrorReporter _errorReporter;
/// If true, emit hints when types are inferred
final bool _inferenceHints;
/// Type provider, needed for type matching.
final TypeProvider _typeProvider;
/// The type system in use.
final TypeSystem _typeSystem;
/// When no context type is available, this will track the least upper bound
/// of all return statements in a lambda.
///
/// This will always be kept in sync with [_returnStack].
final List<DartType> _inferredReturn = <DartType>[];
/// A stack of return types for all of the enclosing
/// functions and methods.
final List<DartType> _returnStack = <DartType>[];
InferenceContext._(TypeProvider typeProvider, this._typeSystem,
this._inferenceHints, this._errorReporter)
: _typeProvider = typeProvider;
/// Get the return type of the current enclosing function, if any.
///
/// The type returned for a function is the type that is expected
/// to be used in a return or yield context. For ordinary functions
/// this is the same as the return type of the function. For async
/// functions returning Future<T> and for generator functions
/// returning Stream<T> or Iterable<T>, this is T.
DartType get returnContext =>
_returnStack.isNotEmpty ? _returnStack.last : null;
/// Records the type of the expression of a return statement.
///
/// This will be used for inferring a block bodied lambda, if no context
/// type was available.
void addReturnOrYieldType(DartType type) {
if (_returnStack.isEmpty) {
return;
}
DartType inferred = _inferredReturn.last;
inferred = _typeSystem.getLeastUpperBound(type, inferred);
_inferredReturn[_inferredReturn.length - 1] = inferred;
}
/// Pop a return type off of the return stack.
///
/// Also record any inferred return type using [setType], unless this node
/// already has a context type. This recorded type will be the least upper
/// bound of all types added with [addReturnOrYieldType].
void popReturnContext(FunctionBody node) {
if (_returnStack.isNotEmpty && _inferredReturn.isNotEmpty) {
DartType context = _returnStack.removeLast() ?? DynamicTypeImpl.instance;
DartType inferred = _inferredReturn.removeLast();
if (_typeSystem.isSubtypeOf(inferred, context)) {
setType(node, inferred);
}
} else {
assert(false);
}
}
/// Push a block function body's return type onto the return stack.
void pushReturnContext(FunctionBody node) {
_returnStack.add(getContext(node));
_inferredReturn.add(_typeProvider.nullType);
}
/// Place an info node into the error stream indicating that a
/// [type] has been inferred as the type of [node].
void recordInference(Expression node, DartType type) {
if (!_inferenceHints) {
return;
}
ErrorCode error;
if (node is Literal) {
error = StrongModeCode.INFERRED_TYPE_LITERAL;
} else if (node is InstanceCreationExpression) {
error = StrongModeCode.INFERRED_TYPE_ALLOCATION;
} else if (node is FunctionExpression) {
error = StrongModeCode.INFERRED_TYPE_CLOSURE;
} else {
error = StrongModeCode.INFERRED_TYPE;
}
_errorReporter.reportErrorForNode(error, node, [node, type]);
}
/// Clear the type information associated with [node].
static void clearType(AstNode node) {
node?.setProperty(_typeProperty, null);
}
/// Look for contextual type information attached to [node], and returns
/// the type if found.
///
/// The returned type may be partially or completely unknown, denoted with an
/// unknown type `?`, for example `List<?>` or `(?, int) -> void`.
/// You can use [Dart2TypeSystem.upperBoundForType] or
/// [Dart2TypeSystem.lowerBoundForType] if you would prefer a known type
/// that represents the bound of the context type.
static DartType getContext(AstNode node) => node?.getProperty(_typeProperty);
/// Attach contextual type information [type] to [node] for use during
/// inference.
static void setType(AstNode node, DartType type) {
if (type == null || type.isDynamic) {
clearType(node);
} else {
node?.setProperty(_typeProperty, type);
}
}
/// Attach contextual type information [type] to [node] for use during
/// inference.
static void setTypeFromNode(AstNode innerNode, AstNode outerNode) {
setType(innerNode, getContext(outerNode));
}
}
/// The four states of a field initialization state through a constructor
/// signature, not initialized, initialized in the field declaration,
/// initialized in the field formal, and finally, initialized in the
/// initializers list.
class INIT_STATE implements Comparable<INIT_STATE> {
static const INIT_STATE NOT_INIT = const INIT_STATE('NOT_INIT', 0);
static const INIT_STATE INIT_IN_DECLARATION =
const INIT_STATE('INIT_IN_DECLARATION', 1);
static const INIT_STATE INIT_IN_FIELD_FORMAL =
const INIT_STATE('INIT_IN_FIELD_FORMAL', 2);
static const INIT_STATE INIT_IN_INITIALIZERS =
const INIT_STATE('INIT_IN_INITIALIZERS', 3);
static const List<INIT_STATE> values = const [
NOT_INIT,
INIT_IN_DECLARATION,
INIT_IN_FIELD_FORMAL,
INIT_IN_INITIALIZERS
];
/// The name of this init state.
final String name;
/// The ordinal value of the init state.
final int ordinal;
const INIT_STATE(this.name, this.ordinal);
@override
int get hashCode => ordinal;
@override
int compareTo(INIT_STATE other) => ordinal - other.ordinal;
@override
String toString() => name;
}
/// An AST visitor that is used to re-resolve the initializers of instance
/// fields. Although this class is an AST visitor, clients are expected to use
/// the method [resolveCompilationUnit] to run it over a compilation unit.
class InstanceFieldResolverVisitor extends ResolverVisitor {
/// Initialize a newly created visitor to resolve the nodes in an AST node.
///
/// The [definingLibrary] is the element for the library containing the node
/// being visited. The [source] is the source representing the compilation
/// unit containing the node being visited. The [typeProvider] is the object
/// used to access the types from the core library. The [errorListener] is the
/// error listener that will be informed of any errors that are found during
/// resolution. The [nameScope] is the scope used to resolve identifiers in
/// the node that will first be visited. If `null` or unspecified, a new
/// [LibraryScope] will be created based on the [definingLibrary].
InstanceFieldResolverVisitor(
InheritanceManager3 inheritance,
LibraryElement definingLibrary,
Source source,
TypeProvider typeProvider,
AnalysisErrorListener errorListener,
FeatureSet featureSet,
{Scope nameScope})
: super(inheritance, definingLibrary, source, typeProvider, errorListener,
featureSet: featureSet, nameScope: nameScope);
/// Resolve the instance fields in the given compilation unit [node].
void resolveCompilationUnit(CompilationUnit node) {
NodeList<CompilationUnitMember> declarations = node.declarations;
int declarationCount = declarations.length;
for (int i = 0; i < declarationCount; i++) {
CompilationUnitMember declaration = declarations[i];
if (declaration is ClassDeclaration) {
_resolveClassDeclaration(declaration);
}
}
}
/// Resolve the instance fields in the given class declaration [node].
void _resolveClassDeclaration(ClassDeclaration node) {
_enclosingClassDeclaration = node;
ClassElement outerType = enclosingClass;
Scope outerScope = nameScope;
try {
enclosingClass = node.declaredElement;
typeAnalyzer.thisType = enclosingClass?.thisType;
if (enclosingClass == null) {
AnalysisEngine.instance.logger.logInformation(
"Missing element for class declaration ${node.name.name} in ${definingLibrary.source.fullName}",
new CaughtException(new AnalysisException(), null));
// Don't try to re-resolve the initializers if we cannot set up the
// right name scope for resolution.
} else {
nameScope = new ClassScope(nameScope, enclosingClass);
NodeList<ClassMember> members = node.members;
int length = members.length;
for (int i = 0; i < length; i++) {
ClassMember member = members[i];
if (member is FieldDeclaration) {
_resolveFieldDeclaration(member);
}
}
}
} finally {
nameScope = outerScope;
typeAnalyzer.thisType = outerType?.thisType;
enclosingClass = outerType;
_enclosingClassDeclaration = null;
}
}
/// Resolve the instance fields in the given field declaration [node].
void _resolveFieldDeclaration(FieldDeclaration node) {
if (!node.isStatic) {
for (VariableDeclaration field in node.fields.variables) {
if (field.initializer != null) {
field.initializer.accept(this);
FieldElement fieldElement = field.name.staticElement;
if (fieldElement.initializer != null) {
(fieldElement.initializer as ExecutableElementImpl).returnType =
field.initializer.staticType;
}
}
}
}
}
}
/// Instances of the class `OverrideVerifier` visit all of the declarations in a
/// compilation unit to verify that if they have an override annotation it is
/// being used correctly.
class OverrideVerifier extends RecursiveAstVisitor {
/// The inheritance manager used to find overridden methods.
final InheritanceManager3 _inheritance;
/// The URI of the library being verified.
final Uri _libraryUri;
/// The error reporter used to report errors.
final ErrorReporter _errorReporter;
/// The current class or mixin.
InterfaceType _currentType;
OverrideVerifier(
this._inheritance, LibraryElement library, this._errorReporter)
: _libraryUri = library.source.uri;
@override
visitClassDeclaration(ClassDeclaration node) {
_currentType = node.declaredElement.thisType;
super.visitClassDeclaration(node);
_currentType = null;
}
@override
visitFieldDeclaration(FieldDeclaration node) {
for (VariableDeclaration field in node.fields.variables) {
FieldElement fieldElement = field.declaredElement;
if (fieldElement.hasOverride) {
PropertyAccessorElement getter = fieldElement.getter;
if (getter != null && _isOverride(getter)) continue;
PropertyAccessorElement setter = fieldElement.setter;
if (setter != null && _isOverride(setter)) continue;
_errorReporter.reportErrorForNode(
HintCode.OVERRIDE_ON_NON_OVERRIDING_FIELD,
field.name,
);
}
}
}
@override
visitMethodDeclaration(MethodDeclaration node) {
ExecutableElement element = node.declaredElement;
if (element.hasOverride && !_isOverride(element)) {
if (element is MethodElement) {
_errorReporter.reportErrorForNode(
HintCode.OVERRIDE_ON_NON_OVERRIDING_METHOD,
node.name,
);
} else if (element is PropertyAccessorElement) {
if (element.isGetter) {
_errorReporter.reportErrorForNode(
HintCode.OVERRIDE_ON_NON_OVERRIDING_GETTER,
node.name,
);
} else {
_errorReporter.reportErrorForNode(
HintCode.OVERRIDE_ON_NON_OVERRIDING_SETTER,
node.name,
);
}
}
}
}
@override
visitMixinDeclaration(MixinDeclaration node) {
_currentType = node.declaredElement.thisType;
super.visitMixinDeclaration(node);
_currentType = null;
}
/// Return `true` if the [member] overrides a member from a superinterface.
bool _isOverride(ExecutableElement member) {
var name = new Name(_libraryUri, member.name);
return _inheritance.getOverridden(_currentType, name) != null;
}
}
/// An AST visitor that is used to resolve some of the nodes within a single
/// compilation unit. The nodes that are skipped are those that are within
/// function bodies.
class PartialResolverVisitor extends ResolverVisitor {
/// The static variables and fields that have an initializer. These are the
/// variables that need to be re-resolved after static variables have their
/// types inferred. A subset of these variables are those whose types should
/// be inferred.
final List<VariableElement> staticVariables = <VariableElement>[];
/// Initialize a newly created visitor to resolve the nodes in an AST node.
///
/// The [definingLibrary] is the element for the library containing the node
/// being visited. The [source] is the source representing the compilation
/// unit containing the node being visited. The [typeProvider] is the object
/// used to access the types from the core library. The [errorListener] is the
/// error listener that will be informed of any errors that are found during
/// resolution. The [nameScope] is the scope used to resolve identifiers in
/// the node that will first be visited. If `null` or unspecified, a new
/// [LibraryScope] will be created based on [definingLibrary] and
/// [typeProvider].
PartialResolverVisitor(
InheritanceManager3 inheritance,
LibraryElement definingLibrary,
Source source,
TypeProvider typeProvider,
AnalysisErrorListener errorListener,
FeatureSet featureSet,
{Scope nameScope})
: super(inheritance, definingLibrary, source, typeProvider, errorListener,
featureSet: featureSet, nameScope: nameScope);
@override
void visitBlockFunctionBody(BlockFunctionBody node) {
if (_shouldBeSkipped(node)) {
return null;
}
super.visitBlockFunctionBody(node);
}
@override
void visitExpressionFunctionBody(ExpressionFunctionBody node) {
if (_shouldBeSkipped(node)) {
return null;
}
super.visitExpressionFunctionBody(node);
}
@override
void visitFieldDeclaration(FieldDeclaration node) {
if (node.isStatic) {
_addStaticVariables(node.fields.variables);
}
super.visitFieldDeclaration(node);
}
@override
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
_addStaticVariables(node.variables.variables);
super.visitTopLevelVariableDeclaration(node);
}
/// Add all of the [variables] with initializers to the list of variables
/// whose type can be inferred. Technically, we only infer the types of
/// variables that do not have a static type, but all variables with
/// initializers potentially need to be re-resolved after inference because
/// they might refer to a field whose type was inferred.
void _addStaticVariables(List<VariableDeclaration> variables) {
int length = variables.length;
for (int i = 0; i < length; i++) {
VariableDeclaration variable = variables[i];
if (variable.name.name.isNotEmpty && variable.initializer != null) {
staticVariables.add(variable.declaredElement);
}
}
}
/// Return `true` if the given function body should be skipped because it is
/// the body of a top-level function, method or constructor.
bool _shouldBeSkipped(FunctionBody body) {
AstNode parent = body.parent;
if (parent is MethodDeclaration) {
return parent.body == body;
}
if (parent is ConstructorDeclaration) {
return parent.body == body;
}
if (parent is FunctionExpression) {
AstNode parent2 = parent.parent;
if (parent2 is FunctionDeclaration &&
parent2.parent is! FunctionDeclarationStatement) {
return parent.body == body;
}
}
return false;
}
}
/// The enumeration `ResolverErrorCode` defines the error codes used for errors
/// detected by the resolver. The convention for this class is for the name of
/// the error code to indicate the problem that caused the error to be generated
/// and for the error message to explain what is wrong and, when appropriate,
/// how the problem can be corrected.
class ResolverErrorCode extends ErrorCode {
static const ResolverErrorCode BREAK_LABEL_ON_SWITCH_MEMBER =
const ResolverErrorCode('BREAK_LABEL_ON_SWITCH_MEMBER',