blob: 34a80d77152fed58d7d5a0c8c7f4f61d90a6dd4b [file] [log] [blame]
// Copyright (c) 2015, 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.
library dart2js.source_information;
import 'package:kernel/ast.dart' show Location;
import '../common.dart';
import '../elements/elements.dart'
show
AstElement,
CompilationUnitElement,
LocalElement,
ResolvedAst,
ResolvedAstKind;
import '../elements/entities.dart';
import '../js/js.dart' show JavaScriptNodeSourceInformation;
import '../script.dart';
import 'source_file.dart';
/// Interface for passing source information, for instance for use in source
/// maps, through the backend.
abstract class SourceInformation extends JavaScriptNodeSourceInformation {
const SourceInformation();
SourceSpan get sourceSpan;
/// The source location associated with the start of the JS node.
SourceLocation get startPosition => null;
/// The source location associated with an inner of the JS node.
///
/// The inner position is for instance `foo()` in `o.foo()`.
SourceLocation get innerPosition => null;
/// The source location associated with the end of the JS node.
SourceLocation get endPosition => null;
/// All source locations associated with this source information.
List<SourceLocation> get sourceLocations;
/// Return a short textual representation of the source location.
String get shortText;
}
/// Strategy for creating, processing and applying [SourceInformation].
class SourceInformationStrategy<T> {
const SourceInformationStrategy();
/// Create a [SourceInformationBuilder] for [member].
SourceInformationBuilder<T> createBuilderForContext(
covariant MemberEntity member) {
return const SourceInformationBuilder();
}
/// Generate [SourceInformation] marker for non-preamble code.
SourceInformation buildSourceMappedMarker() => null;
/// Called when compilation has completed.
void onComplete() {}
}
/// Interface for generating [SourceInformation].
class SourceInformationBuilder<T> {
const SourceInformationBuilder();
/// Create a [SourceInformationBuilder] for [member].
SourceInformationBuilder forContext(covariant MemberEntity member) => this;
/// Generate [SourceInformation] the declaration of the [member].
SourceInformation buildDeclaration(covariant MemberEntity member) => null;
/// Generate [SourceInformation] for the generic [node].
@deprecated
SourceInformation buildGeneric(T node) => null;
/// Generate [SourceInformation] for an instantiation of a class using [node]
/// for the source position.
SourceInformation buildCreate(T node) => null;
/// Generate [SourceInformation] for the return [node].
SourceInformation buildReturn(T node) => null;
/// Generate [SourceInformation] for an implicit return in [element].
SourceInformation buildImplicitReturn(covariant MemberEntity element) => null;
/// Generate [SourceInformation] for the loop [node].
SourceInformation buildLoop(T node) => null;
/// Generate [SourceInformation] for a read access like `a.b` where in
/// [receiver] points to the left-most part of the access, `a` in the example,
/// and [property] points to the 'name' of accessed property, `b` in the
/// example.
SourceInformation buildGet(T node) => null;
/// Generate [SourceInformation] for the read access in [node].
SourceInformation buildCall(T receiver, T call) => null;
/// Generate [SourceInformation] for the if statement in [node].
SourceInformation buildIf(T node) => null;
/// Generate [SourceInformation] for the constructor invocation in [node].
SourceInformation buildNew(T node) => null;
/// Generate [SourceInformation] for the throw in [node].
SourceInformation buildThrow(T node) => null;
/// Generate [SourceInformation] for the assignment in [node].
SourceInformation buildAssignment(T node) => null;
/// Generate [SourceInformation] for the variable declaration inserted as
/// first statement of a function.
SourceInformation buildVariableDeclaration() => null;
/// Generate [SourceInformation] for the await [node].
SourceInformation buildAwait(T node) => null;
/// Generate [SourceInformation] for the yield or yield* [node].
SourceInformation buildYield(T node) => null;
/// Generate [SourceInformation] for async/await boiler plate code.
SourceInformation buildAsyncBody() => null;
/// Generate [SourceInformation] for exiting async/await code.
SourceInformation buildAsyncExit() => null;
/// Generate [SourceInformation] for an invocation of a foreign method.
SourceInformation buildForeignCode(T node) => null;
/// Generate [SourceInformation] for a string interpolation of [node].
SourceInformation buildStringInterpolation(T node) => null;
/// Generate [SourceInformation] for the for-in `iterator` access in [node].
SourceInformation buildForInIterator(T node) => null;
/// Generate [SourceInformation] for the for-in `moveNext` call in [node].
SourceInformation buildForInMoveNext(T node) => null;
/// Generate [SourceInformation] for the for-in `current` access in [node].
SourceInformation buildForInCurrent(T node) => null;
/// Generate [SourceInformation] for the for-in variable assignment in [node].
SourceInformation buildForInSet(T node) => null;
/// Generate [SourceInformation] for the operator `[]` access in [node].
SourceInformation buildIndex(T node) => null;
/// Generate [SourceInformation] for the operator `[]=` assignment in [node].
SourceInformation buildIndexSet(T node) => null;
/// Generate [SourceInformation] for the binary operation in [node].
SourceInformation buildBinary(T node) => null;
/// Generate [SourceInformation] for the unary operation in [node].
SourceInformation buildUnary(T node) => null;
/// Generate [SourceInformation] for the try statement in [node].
SourceInformation buildTry(T node) => null;
/// Generate [SourceInformation] for the unary operator in [node].
SourceInformation buildCatch(T node) => null;
/// Generate [SourceInformation] for the is-test in [node].
SourceInformation buildIs(T node) => null;
/// Generate [SourceInformation] for the as-cast in [node].
SourceInformation buildAs(T node) => null;
/// Generate [SourceInformation] for the switch statement [node].
SourceInformation buildSwitch(T node) => null;
/// Generate [SourceInformation] for the switch case in [node].
SourceInformation buildSwitchCase(T node) => null;
/// Generate [SourceInformation] for the list literal in [node].
SourceInformation buildListLiteral(T node) => null;
/// Generate [SourceInformation] for the break/continue in [node].
SourceInformation buildGoto(T node) => null;
}
/// A location in a source file.
abstract class SourceLocation {
const SourceLocation();
/// The absolute URI of the source file of this source location.
Uri get sourceUri;
/// The character offset of the this source location into the source file.
int get offset;
/// The 1-based line number of the [offset].
int get line;
/// The 1-based column number of the [offset] with its line.
int get column;
/// The name associated with this source location, if any.
String get sourceName;
/// `true` if the offset within the length of the source file.
bool get isValid;
int get hashCode {
return sourceUri.hashCode * 17 +
offset.hashCode * 19 +
sourceName.hashCode * 23;
}
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! SourceLocation) return false;
return sourceUri == other.sourceUri &&
offset == other.offset &&
sourceName == other.sourceName;
}
String get shortText => '${sourceUri?.pathSegments?.last}:[$line,$column]';
String toString() => '${sourceUri}:[${line},${column}]';
}
/// A location in a source file.
abstract class AbstractSourceLocation extends SourceLocation {
final SourceFile _sourceFile;
Location _location;
AbstractSourceLocation(this._sourceFile) {
assert(
isValid,
failedAt(
new SourceSpan(sourceUri, 0, 0),
"Invalid source location in ${sourceUri}: "
"offset=$offset, length=${_sourceFile.length}."));
}
AbstractSourceLocation.fromLocation(this._location) : _sourceFile = null;
/// The absolute URI of the source file of this source location.
Uri get sourceUri => _sourceFile.uri;
/// The character offset of the this source location into the source file.
int get offset;
/// The 1-based line number of the [offset].
int get line => (_location ??= _sourceFile.getLocation(offset)).line;
/// The 1-based column number of the [offset] with its line.
int get column => (_location ??= _sourceFile.getLocation(offset)).column;
/// The name associated with this source location, if any.
String get sourceName;
/// `true` if the offset within the length of the source file.
bool get isValid => offset < _sourceFile.length;
String get shortText => '${sourceUri.pathSegments.last}:[$line,$column]';
String toString() => '${sourceUri}:[$line,$column]';
}
class OffsetSourceLocation extends AbstractSourceLocation {
final int offset;
final String sourceName;
OffsetSourceLocation(SourceFile sourceFile, this.offset, this.sourceName)
: super(sourceFile);
String get shortText => '${super.shortText}:$sourceName';
String toString() => '${super.toString()}:$sourceName';
}
/// Compute the source map name for [element].
String computeElementNameForSourceMaps(Entity element) {
if (element is AstElement) {
return _computeAstElementNameForSourceMaps(element);
} else if (element is ClassEntity) {
return element.name;
} else if (element is MemberEntity) {
if (element is ConstructorEntity || element is ConstructorBodyEntity) {
String className = element.enclosingClass.name;
if (element.name == '') {
return className;
}
return '$className.${element.name}';
} else if (element.enclosingClass != null) {
if (element.enclosingClass.isClosure) {
return computeElementNameForSourceMaps(element.enclosingClass);
}
return '${element.enclosingClass.name}.${element.name}';
} else {
return element.name;
}
}
// TODO(redemption): Create element names from kernel locals and closures.
return element.name;
}
String _computeAstElementNameForSourceMaps(AstElement element) {
if (element.isClosure) {
return computeElementNameForSourceMaps(element.enclosingElement);
} else if (element.isClass) {
return element.name;
} else if (element.isConstructor || element.isGenerativeConstructorBody) {
String className = element.enclosingClass.name;
if (element.name == '') {
return className;
}
return '$className.${element.name}';
} else if (element.isLocal) {
LocalElement local = element;
String name = local.name;
if (name == '') {
name = '<anonymous function>';
}
return '${computeElementNameForSourceMaps(local.executableContext)}.$name';
} else if (element.enclosingClass != null) {
if (element.enclosingClass.isClosure) {
return computeElementNameForSourceMaps(element.enclosingClass);
}
return '${element.enclosingClass.name}.${element.name}';
} else {
return element.name;
}
}
/// Computes the [SourceFile] for the source code of [resolvedAst].
SourceFile computeSourceFile(ResolvedAst resolvedAst) {
SourceFile sourceFile;
if (resolvedAst.kind != ResolvedAstKind.PARSED) {
// Synthesized node. Use the enclosing element for the location.
sourceFile = resolvedAst.element.compilationUnit.script.file;
} else {
Uri uri = resolvedAst.sourceUri;
AstElement implementation = resolvedAst.element.implementation;
Script script = implementation.compilationUnit.script;
if (uri == script.resourceUri) {
sourceFile = script.file;
} else {
// Slow path, happens only for deserialized elements.
// TODO(johnniwinther): Support a way to get a [SourceFile] from a
// [Uri].
for (CompilationUnitElement compilationUnit
in implementation.library.compilationUnits) {
Script script = compilationUnit.script;
if (uri == script.resourceUri) {
sourceFile = script.file;
break;
}
}
}
}
return sourceFile;
}
class NoSourceLocationMarker extends SourceLocation {
const NoSourceLocationMarker();
@override
Uri get sourceUri => null;
@override
bool get isValid => true;
@override
String get sourceName => null;
@override
int get column => null;
@override
int get line => null;
@override
int get offset => null;
String get shortName => '<no-location>';
String toString() => '<no-location>';
}