Line data Source code
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 'package:package_resolver/package_resolver.dart';
6 : import 'package:path/path.dart' as p;
7 : import 'package:source_maps/source_maps.dart';
8 : import 'package:stack_trace/stack_trace.dart';
9 :
10 : /// Convert [stackTrace], a stack trace generated by dart2js-compiled
11 : /// JavaScript, to a native-looking stack trace using [sourceMap].
12 : ///
13 : /// [minified] indicates whether or not the dart2js code was minified. If it
14 : /// hasn't, this tries to clean up the stack frame member names.
15 : ///
16 : /// If [packageResolver] is passed, it's used to reconstruct `package:` URIs for
17 : /// stack frames that come from packages.
18 : ///
19 : /// [sdkRoot] is the URI (usually a `file:` URI) for the SDK containing dart2js.
20 : /// It can be a [String] or a [Uri]. If it's passed, stack frames from the SDK
21 : /// will have `dart:` URLs.
22 : ///
23 : /// [packageRoot] is deprecated and shouldn't be used in new code. This throws
24 : /// an [ArgumentError] if [packageRoot] and [packageResolver] are both passed.
25 : StackTrace mapStackTrace(Mapping sourceMap, StackTrace stackTrace,
26 : {bool minified: false, SyncPackageResolver packageResolver, sdkRoot,
27 : @Deprecated("Use the packageResolver parameter instead.") packageRoot}) {
28 : if (packageRoot != null) {
29 : if (packageResolver != null) {
30 0 : throw new ArgumentError(
31 : "packageResolver and packageRoot may not both be passed.");
32 : }
33 :
34 0 : packageResolver = new SyncPackageResolver.root(packageRoot);
35 : }
36 :
37 0 : if (stackTrace is Chain) {
38 0 : return new Chain(stackTrace.traces.map((trace) {
39 0 : return new Trace.from(mapStackTrace(
40 : sourceMap, trace,
41 : minified: minified,
42 : packageResolver: packageResolver,
43 : sdkRoot: sdkRoot));
44 : }));
45 : }
46 :
47 0 : if (sdkRoot != null && sdkRoot is! String && sdkRoot is! Uri) {
48 0 : throw new ArgumentError(
49 0 : 'sdkRoot must be a String or a Uri, was "$sdkRoot".');
50 : }
51 :
52 0 : var sdkLib = sdkRoot == null ? null : "$sdkRoot/lib";
53 :
54 0 : var trace = new Trace.from(stackTrace);
55 0 : return new Trace(trace.frames.map((frame) {
56 : // If there's no line information, there's no way to translate this frame.
57 : // We could return it as-is, but these lines are usually not useful anyways.
58 0 : if (frame.line == null) return null;
59 :
60 : // If there's no column, try using the first column of the line.
61 0 : var column = frame.column == null ? 0 : frame.column;
62 :
63 : // Subtract 1 because stack traces use 1-indexed lines and columns and
64 : // source maps uses 0-indexed.
65 0 : var span = sourceMap.spanFor(frame.line - 1, column - 1,
66 0 : uri: frame.uri?.toString());
67 :
68 : // If we can't find a source span, ignore the frame. It's probably something
69 : // internal that the user doesn't care about.
70 : if (span == null) return null;
71 :
72 0 : var sourceUrl = span.sourceUrl.toString();
73 0 : if (sdkRoot != null && p.url.isWithin(sdkLib, sourceUrl)) {
74 0 : sourceUrl = "dart:" + p.url.relative(sourceUrl, from: sdkLib);
75 : } else if (packageResolver != null) {
76 0 : if (packageResolver.packageRoot != null &&
77 0 : p.url.isWithin(packageResolver.packageRoot.toString(), sourceUrl)) {
78 0 : sourceUrl = "package:" + p.url.relative(sourceUrl,
79 0 : from: packageResolver.packageRoot.toString());
80 0 : } else if (packageResolver.packageConfigMap != null) {
81 0 : for (var package in packageResolver.packageConfigMap.keys) {
82 0 : var packageUrl = packageResolver.packageConfigMap[package].toString();
83 0 : if (!p.url.isWithin(packageUrl, sourceUrl)) continue;
84 :
85 0 : sourceUrl = "package:$package/" +
86 0 : p.url.relative(sourceUrl, from: packageUrl);
87 : break;
88 : }
89 : }
90 : }
91 :
92 0 : return new Frame(
93 0 : Uri.parse(sourceUrl),
94 0 : span.start.line + 1,
95 0 : span.start.column + 1,
96 : // If the dart2js output is minified, there's no use trying to prettify
97 : // its member names. Use the span's identifier if available, otherwise
98 : // use the minified member name.
99 : minified
100 0 : ? (span.isIdentifier ? span.text : frame.member)
101 0 : : _prettifyMember(frame.member));
102 0 : }).where((frame) => frame != null));
103 : }
104 :
105 : /// Reformats a JS member name to make it look more Dart-like.
106 : String _prettifyMember(String member) {
107 : return member
108 : // Get rid of the noise that Firefox sometimes adds.
109 0 : .replaceAll(new RegExp(r"/?<$"), "")
110 : // Get rid of arity indicators and named arguments.
111 0 : .replaceAll(new RegExp(r"\$\d+(\$[a-zA-Z_0-9]+)*$"), "")
112 : // Convert closures to <fn>.
113 0 : .replaceAllMapped(new RegExp(r"(_+)closure\d*\.call$"),
114 : // The number of underscores before "closure" indicates how nested it
115 : // is.
116 0 : (match) => ".<fn>" * match[1].length)
117 : // Get rid of explicitly-generated calls.
118 0 : .replaceAll(new RegExp(r"\.call$"), "")
119 : // Get rid of the top-level method prefix.
120 0 : .replaceAll(new RegExp(r"^dart\."), "")
121 : // Get rid of library namespaces.
122 0 : .replaceAll(new RegExp(r"[a-zA-Z_0-9]+\$"), "")
123 : // Get rid of the static method prefix. The class name also exists in the
124 : // invocation, so we're not getting rid of any information.
125 0 : .replaceAll(new RegExp(r"^[a-zA-Z_0-9]+.(static|dart)."), "")
126 : // Convert underscores after identifiers to dots. This runs the risk of
127 : // incorrectly converting members that contain underscores, but those are
128 : // contrary to the style guide anyway.
129 0 : .replaceAllMapped(new RegExp(r"([a-zA-Z0-9]+)_"),
130 0 : (match) => match[1] + ".");
131 : }
|