| // 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 = 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, |
| {DumpInfoTask monitor, |
| bool allowVariableMinification = true, |
| List<CodeOutputListener> listeners = const []}) { |
| JavaScriptPrintingOptions options = JavaScriptPrintingOptions( |
| shouldCompressOutput: compilerOptions.enableMinification, |
| minifyLocalVariables: allowVariableMinification); |
| CodeBuffer outBuffer = CodeBuffer(listeners); |
| SourceInformationProcessor sourceInformationProcessor = |
| sourceInformationStrategy.createProcessor( |
| SourceMapperProviderImpl(outBuffer), const SourceInformationReader()); |
| Dart2JSJavaScriptPrintingContext context = Dart2JSJavaScriptPrintingContext( |
| monitor, outBuffer, sourceInformationProcessor); |
| Printer printer = 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); |
| } |
| |
| @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 { |
| 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.string(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; |
| } |