forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 3
/
source_map.ts
190 lines (159 loc) · 5.67 KB
/
source_map.ts
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
187
188
189
190
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {utf8Encode} from '../util';
// https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
const VERSION = 3;
const JS_B64_PREFIX = '# sourceMappingURL=data:application/json;base64,';
type Segment = {
col0: number,
sourceUrl?: string,
sourceLine0?: number,
sourceCol0?: number,
};
export type SourceMap = {
version: number,
file?: string,
sourceRoot: string,
sources: string[],
sourcesContent: (string | null)[],
mappings: string,
};
export class SourceMapGenerator {
private sourcesContent: Map<string, string|null> = new Map();
private lines: Segment[][] = [];
private lastCol0: number = 0;
private hasMappings = false;
constructor(private file: string|null = null) {}
// The content is `null` when the content is expected to be loaded using the URL
addSource(url: string, content: string|null = null): this {
if (!this.sourcesContent.has(url)) {
this.sourcesContent.set(url, content);
}
return this;
}
addLine(): this {
this.lines.push([]);
this.lastCol0 = 0;
return this;
}
addMapping(col0: number, sourceUrl?: string, sourceLine0?: number, sourceCol0?: number): this {
if (!this.currentLine) {
throw new Error(`A line must be added before mappings can be added`);
}
if (sourceUrl != null && !this.sourcesContent.has(sourceUrl)) {
throw new Error(`Unknown source file "${sourceUrl}"`);
}
if (col0 == null) {
throw new Error(`The column in the generated code must be provided`);
}
if (col0 < this.lastCol0) {
throw new Error(`Mapping should be added in output order`);
}
if (sourceUrl && (sourceLine0 == null || sourceCol0 == null)) {
throw new Error(`The source location must be provided when a source url is provided`);
}
this.hasMappings = true;
this.lastCol0 = col0;
this.currentLine.push({col0, sourceUrl, sourceLine0, sourceCol0});
return this;
}
/**
* @internal strip this from published d.ts files due to
* https://github.com/microsoft/TypeScript/issues/36216
*/
private get currentLine(): Segment[]|null { return this.lines.slice(-1)[0]; }
toJSON(): SourceMap|null {
if (!this.hasMappings) {
return null;
}
const sourcesIndex = new Map<string, number>();
const sources: string[] = [];
const sourcesContent: (string | null)[] = [];
Array.from(this.sourcesContent.keys()).forEach((url: string, i: number) => {
sourcesIndex.set(url, i);
sources.push(url);
sourcesContent.push(this.sourcesContent.get(url) || null);
});
let mappings: string = '';
let lastCol0: number = 0;
let lastSourceIndex: number = 0;
let lastSourceLine0: number = 0;
let lastSourceCol0: number = 0;
this.lines.forEach(segments => {
lastCol0 = 0;
mappings += segments
.map(segment => {
// zero-based starting column of the line in the generated code
let segAsStr = toBase64VLQ(segment.col0 - lastCol0);
lastCol0 = segment.col0;
if (segment.sourceUrl != null) {
// zero-based index into the “sources” list
segAsStr +=
toBase64VLQ(sourcesIndex.get(segment.sourceUrl) ! - lastSourceIndex);
lastSourceIndex = sourcesIndex.get(segment.sourceUrl) !;
// the zero-based starting line in the original source
segAsStr += toBase64VLQ(segment.sourceLine0 ! - lastSourceLine0);
lastSourceLine0 = segment.sourceLine0 !;
// the zero-based starting column in the original source
segAsStr += toBase64VLQ(segment.sourceCol0 ! - lastSourceCol0);
lastSourceCol0 = segment.sourceCol0 !;
}
return segAsStr;
})
.join(',');
mappings += ';';
});
mappings = mappings.slice(0, -1);
return {
'file': this.file || '',
'version': VERSION,
'sourceRoot': '',
'sources': sources,
'sourcesContent': sourcesContent,
'mappings': mappings,
};
}
toJsComment(): string {
return this.hasMappings ? '//' + JS_B64_PREFIX + toBase64String(JSON.stringify(this, null, 0)) :
'';
}
}
export function toBase64String(value: string): string {
let b64 = '';
value = utf8Encode(value);
for (let i = 0; i < value.length;) {
const i1 = value.charCodeAt(i++);
const i2 = value.charCodeAt(i++);
const i3 = value.charCodeAt(i++);
b64 += toBase64Digit(i1 >> 2);
b64 += toBase64Digit(((i1 & 3) << 4) | (isNaN(i2) ? 0 : i2 >> 4));
b64 += isNaN(i2) ? '=' : toBase64Digit(((i2 & 15) << 2) | (i3 >> 6));
b64 += isNaN(i2) || isNaN(i3) ? '=' : toBase64Digit(i3 & 63);
}
return b64;
}
function toBase64VLQ(value: number): string {
value = value < 0 ? ((-value) << 1) + 1 : value << 1;
let out = '';
do {
let digit = value & 31;
value = value >> 5;
if (value > 0) {
digit = digit | 32;
}
out += toBase64Digit(digit);
} while (value > 0);
return out;
}
const B64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
function toBase64Digit(value: number): string {
if (value < 0 || value >= 64) {
throw new Error(`Can only encode value in the range [0, 63]`);
}
return B64_DIGITS[value];
}