blob: 2ba789c0e8e6bf48b28f40c5285eb5901714594c [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 'package:source_maps/src/vlq.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) {
_extractMinifedNames(minifiedNames['global'], sourceMap, globalNames);
_extractMinifedNames(minifiedNames['instance'], sourceMap, instanceNames);
}
String jsonFrames = extensions['frames'];
if (jsonFrames != null) {
new _FrameDecoder(jsonFrames).parseFrames(frames, sourceMap);
}
}
Dart2jsMapping.json(Map json) : this(parseJson(json), json);
}
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);
}
class _FrameDecoder implements Iterator<String> {
final String _internal;
final int _length;
int index = -1;
_FrameDecoder(this._internal) : _length = _internal.length;
// Iterator API is used by decodeVlq to consume VLQ entries.
bool moveNext() => ++index < _length;
String get current =>
(index >= 0 && index < _length) ? _internal[index] : null;
bool get hasTokens => index < _length - 1 && _length > 0;
int _readDelta() => decodeVlq(this);
void parseFrames(Map<int, List<FrameEntry>> frames, SingleMapping sourceMap) {
var offset = 0;
var uriId = 0;
var nameId = 0;
var line = 0;
var column = 0;
while (hasTokens) {
offset += _readDelta();
List<FrameEntry> entries = frames[offset] ??= [];
var marker = _internal[index + 1];
if (marker == ';') {
entries.add(new FrameEntry.pop(true));
index++;
continue;
} else if (marker == ',') {
entries.add(new FrameEntry.pop(false));
index++;
continue;
} else {
uriId += _readDelta();
var uri = sourceMap.urls[uriId];
line += _readDelta();
column += _readDelta();
nameId += _readDelta();
var name = sourceMap.names[nameId];
entries.add(new FrameEntry.push(uri, line, column, name));
}
}
}
}
_extractMinifedNames(String encodedInput, SingleMapping sourceMap,
Map<String, String> minifiedNames) {
List<String> input = encodedInput.split(',');
if (input.length % 2 != 0) {
warn("expected an even number of entries");
}
for (int i = 0; i < input.length; i += 2) {
String minifiedName = input[i];
int id = int.tryParse(input[i + 1]);
minifiedNames[minifiedName] = sourceMap.names[id];
}
}