blob: 2a62e6e232f63db6fe05e50664a6b3a8cee79b6f [file] [log] [blame]
// Copyright (c) 2018, 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.
/// Representation of a source-map file with dart2js-specific extensions, and
/// helper functions to parse them.
import 'dart:convert';
import 'dart:io';
import 'package:source_maps/source_maps.dart';
import 'util.dart';
/// Representation of a source-map file with dart2js-specific extensions.
///
/// Dart2js adds a special section that provides: tables of minified names and a
/// table of inlining frame data.
class Dart2jsMapping {
final SingleMapping sourceMap;
final Map<String, String> globalNames = {};
final Map<String, String> instanceNames = {};
final Map<int, List<FrameEntry>> frames = {};
List<int> _frameIndex;
List<int> get frameIndex {
if (_frameIndex == null) {
_frameIndex = frames.keys.toList()..sort();
}
return _frameIndex;
}
Dart2jsMapping(this.sourceMap, Map json) {
var extensions = json['x_org_dartlang_dart2js'];
if (extensions == null) return;
var minifiedNames = extensions['minified_names'];
if (minifiedNames != null) {
minifiedNames['global'].forEach((minifiedName, id) {
globalNames[minifiedName] = sourceMap.names[id];
});
minifiedNames['instance'].forEach((minifiedName, id) {
instanceNames[minifiedName] = sourceMap.names[id];
});
}
List jsonFrames = extensions['frames'];
if (jsonFrames != null) {
for (List values in jsonFrames) {
if (values.length < 2) {
warn("warning: incomplete frame data: $values");
continue;
}
int offset = values[0];
List<FrameEntry> entries = frames[offset] ??= [];
if (entries.length > 0) {
warn("warning: duplicate entries for $offset");
continue;
}
for (int i = 1; i < values.length; i++) {
var current = values[i];
if (current == -1) {
entries.add(new FrameEntry.pop(false));
} else if (current == 0) {
entries.add(new FrameEntry.pop(true));
} else {
if (current is List) {
if (current.length == 4) {
entries.add(new FrameEntry.push(sourceMap.urls[current[0]],
current[1], current[2], sourceMap.names[current[3]]));
} else {
warn("warning: unexpected entry $current");
}
} else {
warn("warning: unexpected entry $current");
}
}
}
}
}
}
}
class FrameEntry {
final String callUri;
final int callLine;
final int callColumn;
final String inlinedMethodName;
final bool isEmpty;
FrameEntry.push(
this.callUri, this.callLine, this.callColumn, this.inlinedMethodName)
: isEmpty = false;
FrameEntry.pop(this.isEmpty)
: callUri = null,
callLine = null,
callColumn = null,
inlinedMethodName = null;
bool get isPush => callUri != null;
bool get isPop => callUri == null;
toString() {
if (isPush)
return "push $inlinedMethodName @ $callUri:$callLine:$callColumn";
return isEmpty ? 'pop: empty' : 'pop';
}
}
const _marker = "\n//# sourceMappingURL=";
Dart2jsMapping parseMappingFor(Uri uri) {
var file = new File.fromUri(uri);
if (!file.existsSync()) {
warn('Error: no such file: $uri');
return null;
}
var contents = file.readAsStringSync();
var urlIndex = contents.indexOf(_marker);
var sourcemapPath;
if (urlIndex != -1) {
sourcemapPath = contents.substring(urlIndex + _marker.length).trim();
} else {
warn('Error: source-map url marker not found in $uri\n'
' trying $uri.map');
sourcemapPath = '${uri.pathSegments.last}.map';
}
assert(!sourcemapPath.contains('\n'));
var sourcemapFile = new File.fromUri(uri.resolve(sourcemapPath));
if (!sourcemapFile.existsSync()) {
warn('Error: no such file: $sourcemapFile');
return null;
}
var json = jsonDecode(sourcemapFile.readAsStringSync());
return new Dart2jsMapping(parseJson(json), json);
}