blob: 1ac621b84d66a5bf6e9f7129270b69adb60cb5d2 [file] [log] [blame] [edit]
// 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;
import 'package:compiler/src/common/codegen.dart';
import 'package:js_ast/js_ast.dart';
import '../common.dart';
import '../js_backend/deferred_holder_expression.dart';
import '../js_backend/string_reference.dart';
import '../js_backend/type_reference.dart';
import '../options.dart';
import '../dump_info.dart' show DumpInfoJsAstRegistry;
import '../io/code_output.dart'
show AbstractCodeOutput, CodeBuffer, CodeOutputListener;
import '../serialization/deferrable.dart';
import '../serialization/serialization.dart';
import 'js_source_mapping.dart';
export 'package:js_ast/js_ast.dart';
export 'js_debug.dart';
String prettyPrint(
Node node, {
bool enableMinification = false,
bool allowVariableMinification = true,
bool preferSemicolonToNewlineInMinifiedOutput = false,
}) {
// TODO(johnniwinther): Do we need all the options here?
JavaScriptPrintingOptions options = JavaScriptPrintingOptions(
shouldCompressOutput: enableMinification,
minifyLocalVariables: allowVariableMinification,
preferSemicolonToNewlineInMinifiedOutput:
preferSemicolonToNewlineInMinifiedOutput,
);
SimpleJavaScriptPrintingContext context = SimpleJavaScriptPrintingContext();
Printer printer = Printer(options, context);
printer.visit(node);
return context.getText();
}
CodeBuffer createCodeBuffer(
Node node,
CompilerOptions compilerOptions,
JavaScriptSourceInformationStrategy sourceInformationStrategy, {
DumpInfoJsAstRegistry? monitor,
JavaScriptAnnotationMonitor annotationMonitor =
const JavaScriptAnnotationMonitor(),
bool allowVariableMinification = true,
List<CodeOutputListener> listeners = const [],
}) {
JavaScriptPrintingOptions options = JavaScriptPrintingOptions(
utf8: compilerOptions.features.writeUtf8.isEnabled,
shouldCompressOutput: compilerOptions.enableMinification,
minifyLocalVariables: allowVariableMinification,
);
CodeBuffer outBuffer = CodeBuffer(listeners);
SourceInformationProcessor sourceInformationProcessor =
sourceInformationStrategy.createProcessor(
SourceMapperProviderImpl(outBuffer),
const SourceInformationReader(),
);
Dart2JSJavaScriptPrintingContext context = Dart2JSJavaScriptPrintingContext(
monitor,
outBuffer,
sourceInformationProcessor,
annotationMonitor,
);
/// We defer deserialization of function bodies but deserialize bodies twice
/// while printing. Once to collect variable declarations for hoisting and
/// then again to print the function body. Cache the body so we don't
/// immediately deserialize the body twice.
final deferredBlockCollector = _CollectDeferredBlocksAndSetCaches();
deferredBlockCollector.setCache(node);
Printer printer = Printer(options, context);
printer.visit(node);
deferredBlockCollector.clearCache();
sourceInformationProcessor.process(node, outBuffer);
return outBuffer;
}
class _CollectDeferredBlocksAndSetCaches extends BaseVisitorVoid {
final List<DeferredBlock> _blocks = [];
_CollectDeferredBlocksAndSetCaches();
void setCache(Node node) {
node.accept(this);
}
void clearCache() {
for (var block in _blocks) {
block._clearCache();
}
_blocks.clear();
}
@override
void visitBlock(Block node) {
if (node is DeferredBlock) {
if (!node._isCached) {
_blocks.add(node);
node._setCache();
super.visitBlock(node);
}
} else {
super.visitBlock(node);
}
}
}
class JavaScriptAnnotationMonitor {
const JavaScriptAnnotationMonitor();
/// Called for each non-empty list of annotations in the JavaScript tree.
void onAnnotations(List<Object> annotations) {
// Should the position of the annotated node be recorded?
}
}
class Dart2JSJavaScriptPrintingContext implements JavaScriptPrintingContext {
final DumpInfoJsAstRegistry? monitor;
final AbstractCodeOutput outBuffer;
final CodePositionListener codePositionListener;
final JavaScriptAnnotationMonitor annotationMonitor;
Dart2JSJavaScriptPrintingContext(
this.monitor,
this.outBuffer,
this.codePositionListener,
this.annotationMonitor,
);
@override
void error(String message) {
failedAt(noLocationSpannable, message);
}
@override
void emit(String string) {
monitor?.emit(string);
outBuffer.add(string);
}
@override
void enterNode(Node node, int startPosition) {
monitor?.enterNode(node, startPosition);
codePositionListener.onStartPosition(node, startPosition);
}
@override
void exitNode(
Node node,
int startPosition,
int endPosition,
int? closingPosition,
) {
monitor?.exitNode(node, startPosition, endPosition, closingPosition);
codePositionListener.onPositions(
node,
startPosition,
endPosition,
closingPosition,
);
final annotations = node.annotations;
if (annotations.isNotEmpty) {
annotationMonitor.onAnnotations(annotations);
}
}
@override
bool get isDebugContext => false;
}
/// Interface for ast nodes that encapsulate an ast that needs to be
/// traversed when counting tokens.
abstract class AstContainer implements Node {
Iterable<Node> get containedNodes;
}
/// Interface for tasks in the compiler that need to finalize tokens after
/// counting them.
abstract class TokenFinalizer {
void finalizeTokens();
}
/// Implements reference counting for instances of [ReferenceCountedAstNode]
class TokenCounter extends BaseVisitorVoid {
@override
void visitNode(Node node) {
if (node is AstContainer) {
for (Node element in node.containedNodes) {
element.accept(this);
}
} else if (node is ReferenceCountedAstNode) {
node.markSeen(this);
} else {
// The bodies of these are all created without [ReferenceCountedAstNode]
// so any instances of them can only be injected in via deferred
// expressions.
final deferredExpressionData = getNodeDeferredExpressionData(node);
if (deferredExpressionData != null) {
deferredExpressionData.modularNames.forEach(visitNode);
deferredExpressionData.modularExpressions.forEach(visitNode);
deferredExpressionData.stringReferences.forEach(visitNode);
deferredExpressionData.typeReferences.forEach(visitNode);
deferredExpressionData.deferredHolderExpressions.forEach(visitNode);
} else {
super.visitNode(node);
}
}
}
void countTokens(Node node) => node.accept(this);
}
abstract class ReferenceCountedAstNode implements Node {
void markSeen(TokenCounter visitor);
}
DeferredExpressionData? getNodeDeferredExpressionData(Node node) {
final annotations = node.annotations;
if (annotations.isEmpty) return null;
for (final annotation in annotations) {
if (annotation is DeferredExpressionData) return annotation;
}
return null;
}
class DeferredExpressionRegistry {
final List<ModularName> modularNames = [];
final List<ModularExpression> modularExpressions = [];
final List<TypeReference> typeReferences = [];
final List<StringReference> stringReferences = [];
final List<DeferredHolderExpression> deferredHolderExpressions = [];
static DeferredExpressionData readDataFromDataSource(
DataSourceReader source,
) {
final modularNames =
source.readListOrNull(() => source.readJsNode() as ModularName) ??
const [];
final modularExpressions =
source.readListOrNull(() => source.readJsNode() as ModularExpression) ??
const [];
final typeReferences =
source.readListOrNull(() => source.readJsNode() as TypeReference) ??
const [];
final stringReferences =
source.readListOrNull(() => source.readJsNode() as StringReference) ??
const [];
final deferredHolderExpressions =
source.readListOrNull(
() => source.readJsNode() as DeferredHolderExpression,
) ??
const [];
return DeferredExpressionData._(
modularNames,
modularExpressions,
typeReferences,
stringReferences,
deferredHolderExpressions,
);
}
void writeToDataSink(DataSinkWriter sink) {
// Set [_serializing] so that we don't re-register nodes.
sink.writeList(modularNames, (ModularName node) => sink.writeJsNode(node));
sink.writeList(
modularExpressions,
(ModularExpression node) => sink.writeJsNode(node),
);
sink.writeList(
typeReferences,
(TypeReference node) => sink.writeJsNode(node),
);
sink.writeList(
stringReferences,
(StringReference node) => sink.writeJsNode(node),
);
sink.writeList(
deferredHolderExpressions,
(DeferredHolderExpression node) => sink.writeJsNode(node),
);
}
void registerModularName(ModularName node) {
modularNames.add(node);
}
void registerModularExpression(ModularExpression node) {
modularExpressions.add(node);
}
void registerTypeReference(TypeReference node) {
typeReferences.add(node);
}
void registerStringReference(StringReference node) {
stringReferences.add(node);
}
void registerDeferredHolderExpression(DeferredHolderExpression node) {
deferredHolderExpressions.add(node);
}
}
/// Contains pointers to deferred expressions within a portion of the AST.
///
/// These objects are attached nodes that have been deserialized but whose
/// bodies are kept in a serialized state. This allows us to skip deserializing
/// the entire function body when we are trying to link these deferred
/// expressions. A [DeferredExpressionData] will be added to
/// [Node.annotations] for these functions. Visitors that just need these
/// deferred expressions should then check the annotations for a [Node] and
/// process them accordingly rather than deserializing all the [Node] children.
class DeferredExpressionData {
final List<ModularName> modularNames;
final List<ModularExpression> modularExpressions;
final List<TypeReference> typeReferences;
final List<StringReference> stringReferences;
final List<DeferredHolderExpression> deferredHolderExpressions;
DeferredExpressionData(this.modularNames, this.modularExpressions)
: typeReferences = const [],
stringReferences = const [],
deferredHolderExpressions = const [];
DeferredExpressionData._(
this.modularNames,
this.modularExpressions,
this.typeReferences,
this.stringReferences,
this.deferredHolderExpressions,
);
}
/// A code [Block] that has not been fully deserialized but instead holds a
/// [Deferrable] to get the enclosed statements.
///
/// Each time [statements] is invoked, the enclosed [Statement] list will be
/// deserialized so care should be taken to limit this.
class DeferredBlock extends Statement implements Block {
List<Statement> getLoaded() => _statements.loaded();
List<Statement>? _cached;
void _setCache() => _cached = getLoaded();
void _clearCache() => _cached = null;
bool get _isCached => _cached != null;
@override
List<Statement> get statements => _cached ?? getLoaded();
final Deferrable<List<Statement>> _statements;
DeferredBlock(this._statements);
@override
T accept<T>(NodeVisitor<T> visitor) {
return visitor.visitBlock(this);
}
@override
R accept1<R, A>(NodeVisitor1<R, A> visitor, A arg) {
return visitor.visitBlock(this, arg);
}
@override
void visitChildren<T>(NodeVisitor<T> visitor) {
for (Statement statement in statements) {
statement.accept(visitor);
}
}
@override
void visitChildren1<R, A>(NodeVisitor1<R, A> visitor, A arg) {
for (Statement statement in statements) {
statement.accept1(visitor, arg);
}
}
}
/// Represents the LiteralString resulting from unparsing [expression]. The
/// actual unparsing is done on demand when requesting the [value] of this
/// node.
///
/// This is used when generated code needs to be represented as a string,
/// for example by the lazy emitter or when generating code generators.
class UnparsedNode extends DeferredString implements AstContainer {
final Node tree;
final bool _enableMinification;
final bool _protectForEval;
late final LiteralString _literal = _create(tree);
@override
Iterable<Node> get containedNodes => [tree];
/// A [js.Literal] that represents the string result of unparsing [ast].
///
/// When its string [value] is requested, the node pretty-prints the given
/// [ast] and, if [protectForEval] is true, wraps the resulting string in
/// parenthesis. The result is also escaped.
UnparsedNode(this.tree, this._enableMinification, this._protectForEval);
LiteralString _create(Node node) {
String text = prettyPrint(node, enableMinification: _enableMinification);
if (_protectForEval) {
if (node is Fun) text = '($text)';
if (node is LiteralExpression) {
String template = node.template;
if (template.startsWith("function ") || template.startsWith("{")) {
text = '($text)';
}
}
}
return js.string(text);
}
@override
String get value => _literal.value;
}
/// True if the given template consists of just a placeholder. Such templates
/// are sometimes used to manually promote the type of an expression.
bool isIdentityTemplate(Template template) {
return template.ast is InterpolatedExpression;
}
/// Returns `true` if [template] will immediately give a TypeError if the first
/// placeholder is `null` or `undefined`.
bool isNullGuardOnFirstArgument(Template template) {
// We look for a template of the form
//
// #.something
// #.something()
//
Node node = template.ast;
if (node is Call) {
Call call = node;
node = call.target;
}
if (node is PropertyAccess) {
final receiver = node.receiver;
if (receiver is InterpolatedExpression) {
return receiver.isPositional && receiver.nameOrPosition == 0;
}
}
return false;
}