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 |