| // 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 '../dump_info.dart' show DumpInfoJsAstRegistry; | |
| import '../io/code_output.dart' | |
| show AbstractCodeOutput, CodeBuffer, CodeOutputListener; | |
| import '../js_backend/deferred_holder_expression.dart'; | |
| import '../js_backend/string_reference.dart'; | |
| import '../js_backend/type_reference.dart'; | |
| import '../options.dart'; | |
| 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 preferSemicolonToNewlineInMinifiedOutput = false, | |
| }) { | |
| // TODO(johnniwinther): Do we need all the options here? | |
| JavaScriptPrintingOptions options = JavaScriptPrintingOptions( | |
| minify: enableMinification, | |
| 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(), | |
| List<CodeOutputListener> listeners = const [], | |
| }) { | |
| bool enableMinification = compilerOptions.enableMinification; | |
| JavaScriptPrintingOptions options = JavaScriptPrintingOptions( | |
| utf8: compilerOptions.features.writeUtf8.isEnabled, | |
| minify: enableMinification, | |
| ); | |
| 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; | |
| } |