blob: b49abc0fad784a8fe78306449bb4f14ebf6b530d [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 js;
import 'package:js_ast/js_ast.dart';
import '../common.dart';
import '../options.dart';
import '../dump_info.dart' show DumpInfoTask;
import '../io/code_output.dart' show CodeBuffer, CodeOutputListener;
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 = new JavaScriptPrintingOptions(
shouldCompressOutput: enableMinification,
minifyLocalVariables: allowVariableMinification,
preferSemicolonToNewlineInMinifiedOutput:
preferSemicolonToNewlineInMinifiedOutput);
SimpleJavaScriptPrintingContext context =
new SimpleJavaScriptPrintingContext();
Printer printer = new Printer(options, context);
printer.visit(node);
return context.getText();
}
CodeBuffer createCodeBuffer(Node node, CompilerOptions compilerOptions,
JavaScriptSourceInformationStrategy sourceInformationStrategy,
{DumpInfoTask monitor,
bool allowVariableMinification: true,
List<CodeOutputListener> listeners: const []}) {
JavaScriptPrintingOptions options = new JavaScriptPrintingOptions(
shouldCompressOutput: compilerOptions.enableMinification,
minifyLocalVariables: allowVariableMinification);
CodeBuffer outBuffer = new CodeBuffer(listeners);
SourceInformationProcessor sourceInformationProcessor =
sourceInformationStrategy.createProcessor(
new SourceMapperProviderImpl(outBuffer),
const SourceInformationReader());
Dart2JSJavaScriptPrintingContext context =
new Dart2JSJavaScriptPrintingContext(
monitor, outBuffer, sourceInformationProcessor);
Printer printer = new Printer(options, context);
printer.visit(node);
sourceInformationProcessor.process(node, outBuffer);
return outBuffer;
}
class Dart2JSJavaScriptPrintingContext implements JavaScriptPrintingContext {
final DumpInfoTask monitor;
final CodeBuffer outBuffer;
final CodePositionListener codePositionListener;
Dart2JSJavaScriptPrintingContext(
this.monitor, this.outBuffer, this.codePositionListener);
@override
void error(String message) {
failedAt(NO_LOCATION_SPANNABLE, 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);
}
}
/// 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 BaseVisitor {
@override
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 {
super.visitNode(node);
}
}
void countTokens(Node node) => node.accept(this);
}
abstract class ReferenceCountedAstNode implements Node {
void markSeen(TokenCounter visitor);
}
/// 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;
LiteralString _cachedLiteral;
@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 get _literal {
if (_cachedLiteral == null) {
String text = prettyPrint(tree, enableMinification: _enableMinification);
if (_protectForEval) {
if (tree is Fun) text = '($text)';
if (tree is LiteralExpression) {
LiteralExpression literalExpression = tree;
String template = literalExpression.template;
if (template.startsWith("function ") || template.startsWith("{")) {
text = '($text)';
}
}
}
_cachedLiteral = js.escapedString(text);
}
return _cachedLiteral;
}
@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) {
PropertyAccess access = node;
if (access.receiver is InterpolatedExpression) {
InterpolatedExpression hole = access.receiver;
return hole.isPositional && hole.nameOrPosition == 0;
}
}
return false;
}