-
Notifications
You must be signed in to change notification settings - Fork 362
/
Copy pathsource_map_buffer.dart
186 lines (157 loc) · 5.48 KB
/
source_map_buffer.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// Copyright 2018 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'package:charcode/charcode.dart';
import 'package:source_maps/source_maps.dart';
import 'package:source_span/source_span.dart';
import '../utils.dart';
/// A [StringBuffer] that builds a [SourceMap] for the file being written.
class SourceMapBuffer implements StringBuffer {
/// The buffer that contains the text of the target file.
final _buffer = StringBuffer();
/// The source map entries that map the source files to [_buffer].
final _entries = <Entry>[];
/// The index of the current line in [_buffer].
var _line = 0;
/// The index of the current column in [_buffer].
var _column = 0;
/// Whether the text currently being written should be encompassed by a
/// [SourceSpan].
var _inSpan = false;
/// The current location in [_buffer].
SourceLocation get _targetLocation =>
SourceLocation(_buffer.length, line: _line, column: _column);
bool get isEmpty => _buffer.isEmpty;
bool get isNotEmpty => _buffer.isNotEmpty;
int get length => _buffer.length;
/// Runs [callback] and associates all text written within it with [span].
///
/// Specifically, this associates the point at the beginning of the written
/// text with [span.start] and the point at the end of the written text with
/// [span.end].
T forSpan<T>(FileSpan span, T callback()) {
var wasInSpan = _inSpan;
_inSpan = true;
_addEntry(span.start, _targetLocation);
try {
return callback();
} finally {
// We could map [span.end] to [_targetLocation] here, but in practice
// browsers don't care about where a span ends as long as it covers at
// least the entity that they're looking up. Avoiding end mappings halves
// the size of the source maps we generate.
_inSpan = wasInSpan;
}
}
/// Adds an entry to [_entries] unless it's redundant with the last entry.
void _addEntry(FileLocation source, SourceLocation target) {
if (_entries.isNotEmpty) {
var entry = _entries.last;
// Browsers don't care about the position of a value within a line, so
// it's redundant to have two entries on the same target line that both
// point to the same source line, even if they point to different
// columns in that line.
if (entry.source.line == source.line &&
entry.target.line == target.line) {
return;
}
// Since source maps are only used to look up the source from the target
// and not vice versa, we don't need multiple mappings to the same target.
if (entry.target.offset == target.offset) return;
}
_entries.add(Entry(source, target, null));
}
void clear() =>
throw UnsupportedError("SourceMapBuffer.clear() is not supported.");
void write(Object? object) {
var string = object.toString();
_buffer.write(string);
for (var i = 0; i < string.length; i++) {
if (string.codeUnitAt(i) == $lf) {
_writeLine();
} else {
_column++;
}
}
}
void writeAll(Iterable<Object?> objects, [String separator = ""]) =>
write(objects.join(separator));
void writeCharCode(int charCode) {
_buffer.writeCharCode(charCode);
if (charCode == $lf) {
_writeLine();
} else {
_column++;
}
}
void writeln([Object? object = ""]) {
// Special-case the common case.
if (identical(object, "")) {
_buffer.writeln();
_writeLine();
return;
}
var string = object.toString();
_buffer.writeln(string);
var newlines = countOccurrences(string, $lf) + 1;
for (var i = 0; i < newlines; i++) {
_writeLine();
}
}
/// Records that a line has been passed.
///
/// If we're in the middle of a source span, indicate that at the beginning of
/// the new line. This is necessary because source maps consider each line
/// separately.
void _writeLine() {
// Trim useless entries.
if (_entries.last.target.line == _line &&
_entries.last.target.column == _column) {
_entries.removeLast();
}
_line++;
_column = 0;
if (_inSpan) {
_entries.add(Entry(_entries.last.source, _targetLocation, null));
}
}
String toString() => _buffer.toString();
/// Returns the source map for the file being written.
///
/// If [prefix] is passed, all the entries in the source map will be moved
/// forward by the number of characters and lines in [prefix].
///
/// [SingleMapping.targetUrl] will be `null`.
SingleMapping buildSourceMap({String? prefix}) {
if (prefix == null || prefix.isEmpty) {
return SingleMapping.fromEntries(_entries);
}
var prefixLength = prefix.length;
var prefixLines = 0;
var prefixColumn = 0;
for (var i = 0; i < prefix.length; i++) {
if (prefix.codeUnitAt(i) == $lf) {
prefixLines++;
prefixColumn = 0;
} else {
prefixColumn++;
}
}
return SingleMapping.fromEntries(
_entries.map(
(entry) => Entry(
entry.source,
SourceLocation(
entry.target.offset + prefixLength,
line: entry.target.line + prefixLines,
// Only adjust the column for entries that are on the same line as
// the last chunk of the prefix.
column: entry.target.column +
(entry.target.line == 0 ? prefixColumn : 0),
),
entry.identifierName,
),
),
);
}
}