blob: 9bcde36eade53213755400a90c2daa2008c601cb [file] [log] [blame] [edit]
// 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.
library;
import 'dart:convert';
import 'dart:io';
import 'package:source_maps/source_maps.dart';
// ignore: implementation_imports
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 = {};
late final List<int> frameIndex = frames.keys.toList()..sort();
Dart2jsMapping(this.sourceMap, Map json, {Logger? logger}) {
var extensions = json['x_org_dartlang_dart2js'] as Map?;
if (extensions == null) return;
var minifiedNames = extensions['minified_names'];
if (minifiedNames != null) {
_extractMinifiedNames(
minifiedNames['global'] as String,
sourceMap,
globalNames,
logger,
);
_extractMinifiedNames(
minifiedNames['instance'] as String,
sourceMap,
instanceNames,
logger,
);
}
var jsonFrames = extensions['frames'] as String?;
if (jsonFrames != null) {
_FrameDecoder(jsonFrames).parseFrames(frames, sourceMap);
}
}
Dart2jsMapping.json(Map json) : this(parseSingleMapping(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;
@override
toString() {
if (isPush) {
return "push $inlinedMethodName @ $callUri:$callLine:$callColumn";
}
return isEmpty ? 'pop: empty' : 'pop';
}
}
const _marker = "\n//# sourceMappingURL=";
Dart2jsMapping? parseMappingFor(Uri uri, {Logger? logger}) {
var file = File.fromUri(uri);
if (!file.existsSync()) {
logger?.log('Error: no such file: $uri');
return null;
}
var contents = file.readAsStringSync();
var urlIndex = contents.indexOf(_marker);
String sourcemapPath;
if (urlIndex != -1) {
sourcemapPath = contents.substring(urlIndex + _marker.length).trim();
} else {
logger?.log(
'Error: source-map url marker not found in $uri\n'
' trying $uri.map',
);
sourcemapPath = '${uri.pathSegments.last}.map';
}
assert(!sourcemapPath.contains('\n'));
var sourcemapFile = File.fromUri(uri.resolve(sourcemapPath));
if (!sourcemapFile.existsSync()) {
logger?.log('Error: no such file: $sourcemapFile');
return null;
}
var json = jsonDecode(sourcemapFile.readAsStringSync());
return Dart2jsMapping(parseSingleMapping(json), json, logger: logger);
}
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.
@override
bool moveNext() => ++index < _length;
@override
String get current => (index >= 0 && index < _length)
? _internal[index]
: throw StateError('No current value available.');
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(FrameEntry.pop(true));
index++;
continue;
} else if (marker == ',') {
entries.add(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(FrameEntry.push(uri, line, column, name));
}
}
}
}
void _extractMinifiedNames(
String encodedInput,
SingleMapping sourceMap,
Map<String, String> minifiedNames,
Logger? logger,
) {
if (encodedInput.isEmpty) return;
List<String> input = encodedInput.split(',');
if (input.length % 2 != 0) {
logger?.log("Error: expected an even number of entries");
}
for (int i = 0; i < input.length; i += 2) {
String minifiedName = input[i];
int id = int.parse(input[i + 1]);
minifiedNames[minifiedName] = sourceMap.names[id];
}
}