| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 import 'dart:convert' show JSON, JsonEncoder; | |
| 6 import 'dart:io' show Directory, File, Platform, Process; | |
| 7 | |
| 8 import 'package:analyzer/dart/ast/ast.dart'; | |
| 9 import 'package:path/path.dart' as path; | |
| 10 import 'package:source_maps/source_maps.dart' as srcmaps show Printer; | |
| 11 import 'package:source_maps/source_maps.dart' show SourceMapSpan; | |
| 12 import 'package:source_span/source_span.dart' show SourceLocation; | |
| 13 | |
| 14 import '../js/js_ast.dart' as JS; | |
| 15 import '../utils.dart' show FileSystem, computeHash, locationForOffset; | |
| 16 | |
| 17 import 'js_names.dart' show TemporaryNamer; | |
| 18 | |
| 19 void writeJsLibrary(JS.Program jsTree, String outputPath, String inputDir, | |
| 20 {bool emitSourceMaps: false, | |
| 21 bool emitTypes: false, | |
| 22 FileSystem fileSystem}) { | |
| 23 var outFilename = path.basename(outputPath); | |
| 24 var outDir = path.dirname(outputPath); | |
| 25 | |
| 26 JS.JavaScriptPrintingContext context; | |
| 27 if (emitSourceMaps) { | |
| 28 var printer = new srcmaps.Printer(outFilename); | |
| 29 context = new SourceMapPrintingContext(printer, outDir, inputDir, null); | |
| 30 } else { | |
| 31 context = new JS.SimpleJavaScriptPrintingContext(); | |
| 32 } | |
| 33 | |
| 34 var opts = new JS.JavaScriptPrintingOptions( | |
| 35 emitTypes: emitTypes, | |
| 36 allowKeywordsInProperties: true, | |
| 37 allowSingleLineIfStatements: true); | |
| 38 var jsNamer = new TemporaryNamer(jsTree); | |
| 39 jsTree.accept(new JS.Printer(opts, context, localNamer: jsNamer)); | |
| 40 | |
| 41 String text; | |
| 42 if (context is SourceMapPrintingContext) { | |
| 43 var printer = context.printer; | |
| 44 printer.add('//# sourceMappingURL=$outFilename.map\n'); | |
| 45 // Write output file and source map | |
| 46 text = printer.text; | |
| 47 var sourceMap = JSON.decode(printer.map); | |
| 48 // TODO(jmesserly): I'm not sure where this logic came from, but we should | |
| 49 // upstream this, rather than workaround source_map's formatting ourselves. | |
| 50 var sourceMapText = new JsonEncoder.withIndent(' ').convert(sourceMap); | |
| 51 // Convert: | |
| 52 // "names": [ | |
| 53 // "state", | |
| 54 // "print" | |
| 55 // ] | |
| 56 // to: | |
| 57 // "names": ["state","print"] | |
| 58 sourceMapText = | |
| 59 sourceMapText.replaceAll('\n ', '').replaceAll('\n ]', ']'); | |
| 60 fileSystem.writeAsStringSync('$outputPath.map', '$sourceMapText\n'); | |
| 61 } else { | |
| 62 text = (context as JS.SimpleJavaScriptPrintingContext).getText(); | |
| 63 } | |
| 64 fileSystem.writeAsStringSync(outputPath, text); | |
| 65 if (jsTree.scriptTag != null) { | |
| 66 // Mark executable. | |
| 67 // TODO(jmesserly): should only do this if the input file was executable? | |
| 68 if (!Platform.isWindows) Process.runSync('chmod', ['+x', outputPath]); | |
| 69 } | |
| 70 } | |
| 71 | |
| 72 class SourceMapPrintingContext extends JS.JavaScriptPrintingContext { | |
| 73 final srcmaps.Printer printer; | |
| 74 final String outputDir; | |
| 75 final String inputDir; | |
| 76 | |
| 77 final Uri baseUri; | |
| 78 | |
| 79 CompilationUnit unit; | |
| 80 Uri uri; | |
| 81 | |
| 82 SourceMapPrintingContext( | |
| 83 this.printer, this.outputDir, this.inputDir, this.baseUri); | |
| 84 | |
| 85 void emit(String string) { | |
| 86 printer.add(string); | |
| 87 } | |
| 88 | |
| 89 AstNode _currentTopLevelDeclaration; | |
| 90 | |
| 91 void enterNode(JS.Node jsNode) { | |
| 92 AstNode node = jsNode.sourceInformation; | |
| 93 if (node == null || node.offset == -1) return; | |
| 94 if (unit == null) { | |
| 95 // This is a top-level declaration. Note: consecutive top-level | |
| 96 // declarations may come from different compilation units due to | |
| 97 // parts. | |
| 98 _currentTopLevelDeclaration = node; | |
| 99 unit = node.getAncestor((n) => n is CompilationUnit); | |
| 100 uri = _makeRelativeUri(unit.element.source.uri); | |
| 101 } | |
| 102 if (unit == null) return; | |
| 103 | |
| 104 assert(unit != null); | |
| 105 var loc = _location(node.offset); | |
| 106 var name = _getIdentifier(node); | |
| 107 if (name != null) { | |
| 108 // TODO(jmesserly): mark only uses the beginning of the span, but | |
| 109 // we're required to pass this as a valid span. | |
| 110 var end = _location(node.end); | |
| 111 printer.mark(new SourceMapSpan(loc, end, name, isIdentifier: true)); | |
| 112 } else { | |
| 113 printer.mark(loc); | |
| 114 } | |
| 115 } | |
| 116 | |
| 117 SourceLocation _location(int offset) => | |
| 118 locationForOffset(unit.lineInfo, uri, offset); | |
| 119 | |
| 120 Uri _makeRelativeUri(Uri src) { | |
| 121 if (baseUri == null) { | |
| 122 return new Uri(path: path.relative(src.path, from: outputDir)); | |
| 123 } else { | |
| 124 if (src.path.startsWith('/')) { | |
| 125 return baseUri.resolve(path.relative(src.path, from: inputDir)); | |
| 126 } else { | |
| 127 return baseUri.resolve(path.join('packages', src.path)); | |
| 128 } | |
| 129 } | |
| 130 } | |
| 131 | |
| 132 void exitNode(JS.Node jsNode) { | |
| 133 AstNode node = jsNode.sourceInformation; | |
| 134 if (unit == null || node == null || node.offset == -1) return; | |
| 135 | |
| 136 // TODO(jmesserly): in many cases marking the end will be unnecessary. | |
| 137 printer.mark(_location(node.end)); | |
| 138 | |
| 139 if (_currentTopLevelDeclaration == node) { | |
| 140 unit = null; | |
| 141 uri = null; | |
| 142 _currentTopLevelDeclaration == null; | |
| 143 return; | |
| 144 } | |
| 145 } | |
| 146 | |
| 147 String _getIdentifier(AstNode node) { | |
| 148 if (node is SimpleIdentifier) return node.name; | |
| 149 return null; | |
| 150 } | |
| 151 } | |
| OLD | NEW |