blob: ec955019e16708909720f0ad30a09a69769a050a [file] [log] [blame]
// Copyright (c) 2016, 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';
/// Helpers for Analyzer's Element model and corelib model.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart'
show
ClassElement,
CompilationUnitElement,
Element,
ExecutableElement,
FunctionElement,
LibraryElement,
TypeParameterizedElement;
import 'package:analyzer/dart/element/type.dart'
show DartType, InterfaceType, ParameterizedType;
import 'package:analyzer/src/dart/element/type.dart' show DynamicTypeImpl;
import 'package:analyzer/src/generated/constant.dart'
show DartObject, DartObjectImpl;
class Tuple2<T0, T1> {
final T0 e0;
final T1 e1;
Tuple2(this.e0, this.e1);
}
// TODO(jmesserly): replace this with instantiateToBounds
T fillDynamicTypeArgs<T extends DartType>(T t) {
if (t is ParameterizedType && t.typeArguments.isNotEmpty) {
var rawT = (t.element as TypeParameterizedElement).type;
var dyn =
new List.filled(rawT.typeArguments.length, DynamicTypeImpl.instance);
return rawT.substitute2(dyn, rawT.typeArguments) as T;
}
return t;
}
/// Given an annotated [node] and a [test] function, returns the first matching
/// constant valued annotation.
///
/// For example if we had the ClassDeclaration node for `FontElement`:
///
/// @js.JS('HTMLFontElement')
/// @deprecated
/// class FontElement { ... }
///
/// We could match `@deprecated` with a test function like:
///
/// (v) => v.type.name == 'Deprecated' && v.type.element.library.isDartCore
///
DartObject findAnnotation(Element element, bool test(DartObjectImpl value)) {
for (var metadata in element.metadata) {
var value = metadata.computeConstantValue();
if (value is DartObjectImpl && test(value)) return value;
}
return null;
}
/// Searches all supertype, in order of most derived members, to see if any
/// [match] a condition. If so, returns the first match, otherwise returns null.
InterfaceType findSupertype(InterfaceType type, bool match(InterfaceType t)) {
for (var m in type.mixins.reversed) {
if (match(m)) return m;
}
var s = type.superclass;
if (s == null) return null;
if (match(s)) return type;
return findSupertype(s, match);
}
//TODO(leafp): Is this really necessary? In theory I think
// the static type should always be filled in for resolved
// ASTs. This may be a vestigial workaround.
DartType getStaticType(Expression e) =>
e.staticType ?? DynamicTypeImpl.instance;
// TODO(leafp) Factor this out or use an existing library
/// Similar to [SimpleIdentifier] inGetterContext, inSetterContext, and
/// inDeclarationContext, this method returns true if [node] is used in an
/// invocation context such as a MethodInvocation.
bool inInvocationContext(SimpleIdentifier node) {
var parent = node.parent;
return parent is MethodInvocation && parent.methodName == node;
}
bool isInlineJS(Element e) {
if (e != null && e.name == 'JS' && e is FunctionElement) {
var uri = e.librarySource.uri;
return uri.scheme == 'dart' && uri.path == '_foreign_helper';
}
return false;
}
ExecutableElement getFunctionBodyElement(FunctionBody body) {
var f = body.parent;
if (f is FunctionExpression) {
return f.element;
} else if (f is MethodDeclaration) {
return f.element;
} else {
return (f as ConstructorDeclaration).element;
}
}
/// Returns the value of the `name` field from the [match]ing annotation on
/// [element], or `null` if we didn't find a valid match or it was not a string.
///
/// For example, consider this code:
///
/// class MyAnnotation {
/// final String name;
/// // ...
/// const MyAnnotation(this.name/*, ... other params ... */);
/// }
///
/// @MyAnnotation('FooBar')
/// main() { ... }
///
/// If we match the annotation for the `@MyAnnotation('FooBar')` this will
/// return the string 'FooBar'.
String getAnnotationName(Element element, bool match(DartObjectImpl value)) =>
findAnnotation(element, match)?.getField('name')?.toStringValue();
List<ClassElement> getSuperclasses(ClassElement cls) {
var result = <ClassElement>[];
var visited = new HashSet<ClassElement>();
while (cls != null && visited.add(cls)) {
for (var mixinType in cls.mixins.reversed) {
var mixin = mixinType.element;
if (mixin != null) result.add(mixin);
}
var supertype = cls.supertype;
if (supertype == null) break;
cls = supertype.element;
result.add(cls);
}
return result;
}
List<ClassElement> getImmediateSuperclasses(ClassElement c) {
var result = <ClassElement>[];
for (var m in c.mixins.reversed) {
result.add(m.element);
}
var s = c.supertype;
if (s != null) result.add(s.element);
return result;
}
/// Returns true if the library [l] is dart:_runtime.
// TODO(jmesserly): unlike other methods in this file, this one wouldn't be
// suitable for upstream to Analyzer, as it's DDC specific.
bool isSdkInternalRuntime(LibraryElement l) {
var uri = l.source.uri;
return uri.scheme == 'dart' && uri.path == '_runtime';
}
/// Return `true` if the given [classElement] has a noSuchMethod() method
/// distinct from the one declared in class Object, as per the Dart Language
/// Specification (section 10.4).
// TODO(jmesserly): this was taken from error_verifier.dart
bool hasNoSuchMethod(ClassElement classElement) {
// TODO(jmesserly): this is slow in Analyzer. It's a linear scan through all
// methods, up through the class hierarchy.
var method = classElement.lookUpMethod(
FunctionElement.NO_SUCH_METHOD_METHOD_NAME, classElement.library);
var definingClass = method?.enclosingElement;
return definingClass != null && !definingClass.type.isObject;
}
/// Returns true if this class is of the form:
/// `class C = Object with M [implements I1, I2 ...];`
///
/// A mixin alias class is a mixin application, that can also be itself used as
/// a mixin.
bool isMixinAliasClass(ClassElement c) {
return c.isMixinApplication && c.supertype.isObject && c.mixins.length == 1;
}
Uri uriForCompilationUnit(CompilationUnitElement unit) {
if (unit.source.isInSystemLibrary) {
return unit.source.uri;
}
// TODO(jmesserly): this needs serious cleanup.
// There does appear to be something strange going on with Analyzer
// URIs if we try and use them directly on Windows.
// See also compiler.dart placeSourceMap, which could use cleanup too.
var sourcePath = unit.source.fullName;
return sourcePath.startsWith('package:')
? Uri.parse(sourcePath)
// TODO(jmesserly): shouldn't this be path.toUri?
: new Uri.file(sourcePath);
}
/// Returns true iff this factory constructor just throws [UnsupportedError]/
///
/// `dart:html` has many of these.
bool isUnsupportedFactoryConstructor(ConstructorDeclaration node) {
var ctorBody = node.body;
var element = node.element;
if (element.isPrivate &&
element.librarySource.isInSystemLibrary &&
ctorBody is BlockFunctionBody) {
var statements = ctorBody.block.statements;
if (statements.length == 1) {
var statement = statements[0];
if (statement is ExpressionStatement) {
var expr = statement.expression;
if (expr is ThrowExpression &&
expr.expression is InstanceCreationExpression) {
if (expr.expression.staticType.name == 'UnsupportedError') {
// HTML adds a lot of private constructors that are unreachable.
// Skip these.
return true;
}
}
}
}
}
return false;
}
bool isBuiltinAnnotation(
DartObjectImpl value, String libraryName, String annotationName) {
var e = value?.type?.element;
if (e?.name != annotationName) return false;
var uri = e.source.uri;
var path = uri.pathSegments[0];
return uri.scheme == 'dart' && path == libraryName;
}