Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(8)

Side by Side Diff: tests/compiler/dart2js/sourcemaps/sourcemap_helper.dart

Issue 1617083002: Base JavaScript code position computation on JavaScript tracer. (Closed) Base URL: https://github.com/dart-lang/sdk.git@master
Patch Set: Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library sourcemap.helper; 5 library sourcemap.helper;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'package:compiler/compiler_new.dart'; 8 import 'package:compiler/compiler_new.dart';
9 import 'package:compiler/src/apiimpl.dart' as api; 9 import 'package:compiler/src/apiimpl.dart' as api;
10 import 'package:compiler/src/null_compiler_output.dart' show NullSink; 10 import 'package:compiler/src/null_compiler_output.dart' show NullSink;
11 import 'package:compiler/src/elements/elements.dart'; 11 import 'package:compiler/src/elements/elements.dart';
12 import 'package:compiler/src/helpers/helpers.dart'; 12 import 'package:compiler/src/helpers/helpers.dart';
13 import 'package:compiler/src/filenames.dart'; 13 import 'package:compiler/src/filenames.dart';
14 import 'package:compiler/src/io/code_output.dart';
14 import 'package:compiler/src/io/source_file.dart'; 15 import 'package:compiler/src/io/source_file.dart';
15 import 'package:compiler/src/io/source_information.dart'; 16 import 'package:compiler/src/io/source_information.dart';
17 import 'package:compiler/src/io/position_information.dart';
16 import 'package:compiler/src/js/js.dart' as js; 18 import 'package:compiler/src/js/js.dart' as js;
17 import 'package:compiler/src/js/js_debug.dart'; 19 import 'package:compiler/src/js/js_debug.dart';
18 import 'package:compiler/src/js/js_source_mapping.dart'; 20 import 'package:compiler/src/js/js_source_mapping.dart';
19 import 'package:compiler/src/js_backend/js_backend.dart'; 21 import 'package:compiler/src/js_backend/js_backend.dart';
20 import 'package:compiler/src/source_file_provider.dart'; 22 import 'package:compiler/src/source_file_provider.dart';
21 import '../memory_compiler.dart'; 23 import '../memory_compiler.dart';
22 import '../output_collector.dart'; 24 import '../output_collector.dart';
23 25
26 class SourceFileSink implements EventSink<String> {
27 final String filename;
28 StringBuffer sb = new StringBuffer();
29 SourceFile sourceFile;
30
31 SourceFileSink(this.filename);
32
33 @override
34 void add(String event) {
35 sb.write(event);
36 }
37
38 @override
39 void addError(errorEvent, [StackTrace stackTrace]) {
40 // Ignore.
41 }
42
43 @override
44 void close() {
45 sourceFile = new StringSourceFile.fromName(filename, sb.toString());
46 }
47 }
48
24 class OutputProvider implements CompilerOutput { 49 class OutputProvider implements CompilerOutput {
25 BufferedEventSink jsMapOutput; 50 Map<Uri, SourceFileSink> outputMap = <Uri, SourceFileSink>{};
51
52 SourceFile getSourceFile(Uri uri) {
53 SourceFileSink sink = outputMap[uri];
54 if (sink != null) {
55 return sink.sourceFile;
56 }
57 return null;
58 }
59
60 SourceFileSink createSourceFileSink(String name, String extension) {
61 String filename = '$name.$extension';
62 SourceFileSink sink = new SourceFileSink(filename);
63 Uri uri = Uri.parse(filename);
64 outputMap[uri] = sink;
65 return sink;
66 }
26 67
27 @override 68 @override
28 EventSink<String> createEventSink(String name, String extension) { 69 EventSink<String> createEventSink(String name, String extension) {
29 if (extension == 'js.map') { 70 return createSourceFileSink(name, extension);
30 return jsMapOutput = new BufferedEventSink();
31 }
32 return new NullSink('$name.$extension');
33 } 71 }
34 } 72 }
35 73
36 class CloningOutputProvider extends OutputProvider { 74 class CloningOutputProvider extends OutputProvider {
37 RandomAccessFileOutputProvider outputProvider; 75 RandomAccessFileOutputProvider outputProvider;
38 76
39 CloningOutputProvider(Uri jsUri, Uri jsMapUri) 77 CloningOutputProvider(Uri jsUri, Uri jsMapUri)
40 : outputProvider = new RandomAccessFileOutputProvider(jsUri, jsMapUri); 78 : outputProvider = new RandomAccessFileOutputProvider(jsUri, jsMapUri);
41 79
42 @override 80 @override
43 EventSink<String> createEventSink(String name, String extension) { 81 EventSink<String> createEventSink(String name, String extension) {
44 EventSink<String> output = outputProvider(name, extension); 82 EventSink<String> output = outputProvider(name, extension);
45 if (extension == 'js.map') { 83 return new CloningEventSink(
46 output = new CloningEventSink( 84 [output, createSourceFileSink(name, extension)]);
47 [output, jsMapOutput = new BufferedEventSink()]);
48 }
49 return output;
50 } 85 }
51 } 86 }
52 87
53 abstract class SourceFileManager { 88 abstract class SourceFileManager {
54 SourceFile getSourceFile(var uri); 89 SourceFile getSourceFile(var uri);
55 } 90 }
56 91
57 class ProviderSourceFileManager implements SourceFileManager { 92 class ProviderSourceFileManager implements SourceFileManager {
58 final SourceFileProvider sourceFileProvider; 93 final SourceFileProvider sourceFileProvider;
94 final OutputProvider outputProvider;
59 95
60 ProviderSourceFileManager(this.sourceFileProvider); 96 ProviderSourceFileManager(this.sourceFileProvider, this.outputProvider);
61 97
62 @override 98 @override
63 SourceFile getSourceFile(uri) { 99 SourceFile getSourceFile(uri) {
64 return sourceFileProvider.getSourceFile(uri); 100 SourceFile sourceFile = sourceFileProvider.getSourceFile(uri);
101 if (sourceFile == null) {
102 sourceFile = outputProvider.getSourceFile(uri);
103 }
104 return sourceFile;
65 } 105 }
66 } 106 }
67 107
68 class RecordingPrintingContext extends LenientPrintingContext { 108 class RecordingPrintingContext extends LenientPrintingContext {
69 CodePositionListener listener; 109 CodePositionListener listener;
110 Map<js.Node, CodePosition> codePositions = <js.Node, CodePosition>{};
70 111
71 RecordingPrintingContext(this.listener); 112 RecordingPrintingContext(this.listener);
72 113
73 @override 114 @override
74 void exitNode(js.Node node, 115 void exitNode(js.Node node,
75 int startPosition, 116 int startPosition,
76 int endPosition, 117 int endPosition,
77 int closingPosition) { 118 int closingPosition) {
119 codePositions[node] =
120 new CodePosition(startPosition, endPosition, closingPosition);
78 listener.onPositions( 121 listener.onPositions(
79 node, startPosition, endPosition, closingPosition); 122 node, startPosition, endPosition, closingPosition);
80 } 123 }
81 } 124 }
82 125
126 class SourceMapperWrapper implements SourceMapper {
Siggi Cherem (dart-lang) 2016/01/21 18:18:34 + dartdoc, maybe rename to something more descript
Johnni Winther 2016/01/22 15:12:39 Done.
127 final SourceMapper sourceMapper;
128 final NodeToSourceLocationRecorder nodeToSourceLocationsMap;
129
130 SourceMapperWrapper(this.sourceMapper, this.nodeToSourceLocationsMap);
131
132 @override
133 void register(js.Node node, int codeOffset, SourceLocation sourceLocation) {
134 nodeToSourceLocationsMap.register(node, codeOffset, sourceLocation);
135 sourceMapper.register(node, codeOffset, sourceLocation);
136 }
137 }
138
139 class SourceInformationProcessorWrapper implements SourceInformationProcessor {
140 final SourceInformationStrategyWrapper wrapper;
141 final SourceInformationProcessor processor;
142 final CodePositionRecorder codePositions;
143 final NodeToSourceLocationsMap nodeToSourceLocationsMap;
144
145 SourceInformationProcessorWrapper(
146 this.wrapper,
147 this.processor,
148 this.codePositions,
149 this.nodeToSourceLocationsMap);
150
151 @override
152 void onPositions(js.Node node,
153 int startPosition,
154 int endPosition,
155 int closingPosition) {
156 codePositions.registerPositions(
157 node, startPosition, endPosition, closingPosition);
158 processor.onPositions(node, startPosition, endPosition, closingPosition);
159 }
160
161 @override
162 void process(js.Node node, BufferedCodeOutput code) {
163 processor.process(node, code);
164 wrapper.registerProcess(
165 node, code, codePositions, nodeToSourceLocationsMap);
166 }
167 }
168
169 class SourceInformationSubProcess {
Siggi Cherem (dart-lang) 2016/01/21 18:18:35 rename? SubProcess really makes me think that you
Johnni Winther 2016/01/22 15:12:39 Renamed to 'RecordedSourceInformationProcess'.
170 final js.Node root;
171 final String code;
172 final CodePositionRecorder codePositions;
173 final NodeToSourceLocationsMap nodeToSourceLocationsMap;
174
175 SourceInformationSubProcess(
176 this.root,
177 this.code,
178 this.codePositions,
179 this.nodeToSourceLocationsMap);
180 }
181
182
183 class SourceInformationStrategyWrapper
Siggi Cherem (dart-lang) 2016/01/21 18:18:35 it took me some time to figure out what these wrap
Johnni Winther 2016/01/22 15:12:38 It's (only) per element (also) output units. Updat
184 extends JavaScriptSourceInformationStrategy {
185 final JavaScriptSourceInformationStrategy strategy;
186 final Map<SourceInformationSubProcess, js.Node> processMap =
187 <SourceInformationSubProcess, js.Node>{};
188 final Map<js.Node, SourceInformationSubProcess> nodeMap =
189 <js.Node, SourceInformationSubProcess>{};
190
191 SourceInformationStrategyWrapper(this.strategy);
192
193 @override
194 SourceInformationBuilder createBuilderForContext(AstElement element) {
195 return strategy.createBuilderForContext(element);
196 }
197
198 @override
199 SourceInformationProcessor createProcessor(SourceMapper sourceMapper) {
200 NodeToSourceLocationsMap nodeToSourceLocationsMap =
201 new NodeToSourceLocationRecorder();
202 CodePositionRecorder codePositions = new CodePositionRecorder();
203 return new SourceInformationProcessorWrapper(
204 this,
205 strategy.createProcessor(new SourceMapperWrapper(
206 sourceMapper, nodeToSourceLocationsMap)),
207 codePositions, nodeToSourceLocationsMap);
208 }
209
210 void registerProcess(js.Node root,
211 BufferedCodeOutput code,
212 CodePositionRecorder codePositions,
213 NodeToSourceLocationsMap nodeToSourceLocationsMap) {
214 SourceInformationSubProcess subProcess = new SourceInformationSubProcess(
215 root, code.getText(), codePositions, nodeToSourceLocationsMap);
216 processMap[subProcess] = root;
217 }
218
219 SourceInformationSubProcess subProcessForNode(js.Node node) {
220 return nodeMap.putIfAbsent(node, () {
221 for (SourceInformationSubProcess subProcess in processMap.keys) {
222 js.Node root = processMap[subProcess];
223 FindVisitor visitor = new FindVisitor(node);
224 root.accept(visitor);
225 if (visitor.found) {
226 return new SourceInformationSubProcess(
227 node,
228 subProcess.code,
229 subProcess.codePositions,
230 new NodeToSourceLocationsMapWrapper(
231 visitor.nodes, subProcess.nodeToSourceLocationsMap));
232 }
233 return null;
234 }
235 });
236 }
237 }
238
239 class FindVisitor extends js.BaseVisitor {
Siggi Cherem (dart-lang) 2016/01/21 18:18:34 +dartdoc: /// Visitor that collects all nodes tha
240 final js.Node soughtNode;
241 bool found = false;
242 bool add = false;
243 final Set<js.Node> nodes = new Set<js.Node>();
244
245 FindVisitor(this.soughtNode);
246
247 visitNode(js.Node node) {
248 if (node == soughtNode) {
249 found = true;
250 add = true;
251 }
252 if (add) {
253 nodes.add(node);
254 }
255 node.visitChildren(this);
256 if (node == soughtNode) {
257 add = false;
258 }
259 }
260 }
261
262 const String USE_NEW_SOURCE_INFO = '--use-new-source-info';
263 const String DISABLE_INLINING = '--disable-inlining';
264
83 /// Processor that computes [SourceMapInfo] for the JavaScript compiled for a 265 /// Processor that computes [SourceMapInfo] for the JavaScript compiled for a
84 /// given Dart file. 266 /// given Dart file.
85 class SourceMapProcessor { 267 class SourceMapProcessor {
86 /// If `true` the output from the compilation is written to files. 268 /// If `true` the output from the compilation is written to files.
87 final bool outputToFile; 269 final bool outputToFile;
88 270
89 /// The [Uri] of the Dart entrypoint. 271 /// The [Uri] of the Dart entrypoint.
90 Uri inputUri; 272 Uri inputUri;
91 273
92 /// The name of the JavaScript output file. 274 /// The name of the JavaScript output file.
(...skipping 12 matching lines...) Expand all
105 SourceMapProcessor(String filename, {this.outputToFile: false}) { 287 SourceMapProcessor(String filename, {this.outputToFile: false}) {
106 inputUri = Uri.base.resolve(nativeToUriPath(filename)); 288 inputUri = Uri.base.resolve(nativeToUriPath(filename));
107 jsPath = 'out.js'; 289 jsPath = 'out.js';
108 targetUri = Uri.base.resolve(jsPath); 290 targetUri = Uri.base.resolve(jsPath);
109 sourceMapFileUri = Uri.base.resolve('${jsPath}.map'); 291 sourceMapFileUri = Uri.base.resolve('${jsPath}.map');
110 } 292 }
111 293
112 /// Computes the [SourceMapInfo] for the compiled elements. 294 /// Computes the [SourceMapInfo] for the compiled elements.
113 Future<List<SourceMapInfo>> process( 295 Future<List<SourceMapInfo>> process(
114 List<String> options, 296 List<String> options,
115 {bool verbose: true}) async { 297 {bool verbose: true,
298 bool perElement: true}) async {
116 OutputProvider outputProvider = outputToFile 299 OutputProvider outputProvider = outputToFile
117 ? new OutputProvider() 300 ? new CloningOutputProvider(targetUri, sourceMapFileUri)
118 : new CloningOutputProvider(targetUri, sourceMapFileUri); 301 : new OutputProvider();
119 if (options.contains('--use-new-source-info')) { 302 if (options.contains(USE_NEW_SOURCE_INFO)) {
120 if (verbose) print('Using the new source information system.'); 303 if (verbose) print('Using the new source information system.');
121 useNewSourceInfo = true; 304 useNewSourceInfo = true;
122 } 305 }
123 api.CompilerImpl compiler = await compilerFor( 306 api.CompilerImpl compiler = await compilerFor(
124 outputProvider: outputProvider, 307 outputProvider: outputProvider,
125 // TODO(johnniwinther): Use [verbose] to avoid showing diagnostics. 308 // TODO(johnniwinther): Use [verbose] to avoid showing diagnostics.
126 options: ['--out=$targetUri', '--source-map=$sourceMapFileUri'] 309 options: ['--out=$targetUri', '--source-map=$sourceMapFileUri']
127 ..addAll(options)); 310 ..addAll(options));
128 if (options.contains('--disable-inlining')) { 311 if (options.contains(DISABLE_INLINING)) {
129 if (verbose) print('Inlining disabled'); 312 if (verbose) print('Inlining disabled');
130 compiler.disableInlining = true; 313 compiler.disableInlining = true;
131 } 314 }
132 315
133 JavaScriptBackend backend = compiler.backend; 316 JavaScriptBackend backend = compiler.backend;
134 var handler = compiler.handler; 317 var handler = compiler.handler;
135 SourceFileProvider sourceFileProvider = handler.provider; 318 SourceFileProvider sourceFileProvider = handler.provider;
136 sourceFileManager = new ProviderSourceFileManager(sourceFileProvider); 319 sourceFileManager = new ProviderSourceFileManager(
320 sourceFileProvider,
321 outputProvider);
322 SourceInformationStrategyWrapper strategy =
323 new SourceInformationStrategyWrapper(backend.sourceInformationStrategy);
324 backend.sourceInformationStrategy = strategy;
137 await compiler.run(inputUri); 325 await compiler.run(inputUri);
138 326
139 List<SourceMapInfo> infoList = <SourceMapInfo>[]; 327 List<SourceMapInfo> infoList = <SourceMapInfo>[];
140 backend.generatedCode.forEach((Element element, js.Expression node) { 328 if (perElement) {
141 js.JavaScriptPrintingOptions options = 329 backend.generatedCode.forEach((Element element, js.Expression node) {
142 new js.JavaScriptPrintingOptions(); 330 SourceInformationSubProcess subProcess =
143 JavaScriptSourceInformationStrategy sourceInformationStrategy = 331 strategy.subProcessForNode(node);
144 compiler.backend.sourceInformationStrategy; 332 if (subProcess == null) {
145 NodeToSourceLocationsMap nodeMap = new NodeToSourceLocationsMap(); 333 // TODO(johnniwinther): Find out when this is happening and if it
146 SourceInformationProcessor sourceInformationProcessor = 334 // is benign. (Known to happen for `bool#fromString`)
147 sourceInformationStrategy.createProcessor(nodeMap); 335 print('No subProcess found for $element');
148 RecordingPrintingContext printingContext = 336 return;
149 new RecordingPrintingContext(sourceInformationProcessor); 337 }
150 new js.Printer(options, printingContext).visit(node); 338 NodeToSourceLocationsMap nodeMap = subProcess.nodeToSourceLocationsMap;
151 sourceInformationProcessor.process(node); 339 String code = subProcess.code;
152 340 CodePositionRecorder codePositions = subProcess.codePositions;
153 String code = printingContext.getText(); 341 CodePointComputer visitor =
342 new CodePointComputer(sourceFileManager, code, nodeMap);
343 visitor.apply(node);
344 List<CodePoint> codePoints = visitor.codePoints;
345 infoList.add(new SourceMapInfo(
346 element, code, node,
347 codePoints,
348 codePositions/*strategy.codePositions*/,
349 nodeMap));
350 });
351 } else {
352 // TODO(johnniwinther): Supported multiple output units.
353 SourceInformationSubProcess process = strategy.processMap.keys.first;
354 js.Node node = strategy.processMap[process];
355 String code;
356 NodeToSourceLocationsMap nodeMap;
357 CodePositionRecorder codePositions;
358 nodeMap = process.nodeToSourceLocationsMap;
359 code = process.code;
360 codePositions = process.codePositions;
154 CodePointComputer visitor = 361 CodePointComputer visitor =
155 new CodePointComputer(sourceFileManager, code, nodeMap); 362 new CodePointComputer(sourceFileManager, code, nodeMap);
156 visitor.apply(node); 363 visitor.apply(node);
157 List<CodePoint> codePoints = visitor.codePoints; 364 List<CodePoint> codePoints = visitor.codePoints;
158 infoList.add(new SourceMapInfo(element, code, node, codePoints, nodeMap)); 365 infoList.add(new SourceMapInfo(
159 }); 366 null, code, node,
367 codePoints,
368 codePositions,
369 nodeMap));
370 }
160 371
161 return infoList; 372 return infoList;
162 } 373 }
163 } 374 }
164 375
165 /// Source mapping information for the JavaScript code of an [Element]. 376 /// Source mapping information for the JavaScript code of an [Element].
166 class SourceMapInfo { 377 class SourceMapInfo {
167 final String name; 378 final String name;
168 final Element element; 379 final Element element;
169 final String code; 380 final String code;
170 final js.Expression node; 381 final js.Node node;
171 final List<CodePoint> codePoints; 382 final List<CodePoint> codePoints;
383 final CodePositionMap jsCodePositions;
172 final NodeToSourceLocationsMap nodeMap; 384 final NodeToSourceLocationsMap nodeMap;
173 385
174 SourceMapInfo( 386 SourceMapInfo(
175 Element element, this.code, this.node, this.codePoints, this.nodeMap) 387 Element element,
176 : this.name = computeElementNameForSourceMaps(element), 388 this.code,
389 this.node,
390 this.codePoints,
391 this.jsCodePositions,
392 this.nodeMap)
393 : this.name =
394 element != null ? computeElementNameForSourceMaps(element) : '',
177 this.element = element; 395 this.element = element;
396
397 String toString() {
398 return '$name:$element';
399 }
178 } 400 }
179 401
180 /// Collection of JavaScript nodes with their source mapped target offsets 402 /// Collection of JavaScript nodes with their source mapped target offsets
181 /// and source locations. 403 /// and source locations.
182 class NodeToSourceLocationsMap implements SourceMapper { 404 abstract class NodeToSourceLocationsMap {
405 Iterable<js.Node> get nodes;
406
407 Map<int, List<SourceLocation>> operator[] (js.Node node);
408 }
409
410 class NodeToSourceLocationRecorder
411 implements SourceMapper, NodeToSourceLocationsMap {
183 final Map<js.Node, Map<int, List<SourceLocation>>> _nodeMap = {}; 412 final Map<js.Node, Map<int, List<SourceLocation>>> _nodeMap = {};
184 413
185 @override 414 @override
186 void register(js.Node node, int codeOffset, SourceLocation sourceLocation) { 415 void register(js.Node node, int codeOffset, SourceLocation sourceLocation) {
187 _nodeMap.putIfAbsent(node, () => {}) 416 _nodeMap.putIfAbsent(node, () => {})
188 .putIfAbsent(codeOffset, () => []) 417 .putIfAbsent(codeOffset, () => [])
189 .add(sourceLocation); 418 .add(sourceLocation);
190 } 419 }
191 420
192 Iterable<js.Node> get nodes => _nodeMap.keys; 421 Iterable<js.Node> get nodes => _nodeMap.keys;
193 422
194 Map<int, List<SourceLocation>> operator[] (js.Node node) { 423 Map<int, List<SourceLocation>> operator[] (js.Node node) {
195 return _nodeMap[node]; 424 return _nodeMap[node];
196 } 425 }
197 } 426 }
198 427
428 class NodeToSourceLocationsMapWrapper implements NodeToSourceLocationsMap {
Siggi Cherem (dart-lang) 2016/01/21 18:18:35 nits/ideas: - rename to PerElementNodeToSourceLoc
429 final Set<js.Node> _nodes;
430 final NodeToSourceLocationsMap map;
431
432 NodeToSourceLocationsMapWrapper(this._nodes, this.map);
433
434 Iterable<js.Node> get nodes => map.nodes.where((n) => _nodes.contains(n));
435
436 Map<int, List<SourceLocation>> operator[] (js.Node node) {
437 return map[node];
438 }
439 }
440
441
199 /// Visitor that computes the [CodePoint]s for source mapping locations. 442 /// Visitor that computes the [CodePoint]s for source mapping locations.
200 class CodePointComputer extends js.BaseVisitor { 443 class CodePointComputer extends js.BaseVisitor {
201 final SourceFileManager sourceFileManager; 444 final SourceFileManager sourceFileManager;
202 final String code; 445 final String code;
203 final NodeToSourceLocationsMap nodeMap; 446 final NodeToSourceLocationsMap nodeMap;
204 List<CodePoint> codePoints = []; 447 List<CodePoint> codePoints = [];
205 448
206 CodePointComputer(this.sourceFileManager, this.code, this.nodeMap); 449 CodePointComputer(this.sourceFileManager, this.code, this.nodeMap);
207 450
208 String nodeToString(js.Node node) { 451 String nodeToString(js.Node node) {
(...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after
329 this.jsCode, 572 this.jsCode,
330 this.sourceLocation, 573 this.sourceLocation,
331 this.dartCode, 574 this.dartCode,
332 {this.isMissing: false}); 575 {this.isMissing: false});
333 576
334 String toString() { 577 String toString() {
335 return 'CodePoint[kind=$kind,js=$jsCode,dart=$dartCode,' 578 return 'CodePoint[kind=$kind,js=$jsCode,dart=$dartCode,'
336 'location=$sourceLocation]'; 579 'location=$sourceLocation]';
337 } 580 }
338 } 581 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698