Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/build/lib/nls.js
3520 views
1
"use strict";
2
var __importDefault = (this && this.__importDefault) || function (mod) {
3
return (mod && mod.__esModule) ? mod : { "default": mod };
4
};
5
Object.defineProperty(exports, "__esModule", { value: true });
6
exports.nls = nls;
7
const lazy_js_1 = __importDefault(require("lazy.js"));
8
const event_stream_1 = require("event-stream");
9
const vinyl_1 = __importDefault(require("vinyl"));
10
const source_map_1 = __importDefault(require("source-map"));
11
const path_1 = __importDefault(require("path"));
12
const gulp_sort_1 = __importDefault(require("gulp-sort"));
13
var CollectStepResult;
14
(function (CollectStepResult) {
15
CollectStepResult[CollectStepResult["Yes"] = 0] = "Yes";
16
CollectStepResult[CollectStepResult["YesAndRecurse"] = 1] = "YesAndRecurse";
17
CollectStepResult[CollectStepResult["No"] = 2] = "No";
18
CollectStepResult[CollectStepResult["NoAndRecurse"] = 3] = "NoAndRecurse";
19
})(CollectStepResult || (CollectStepResult = {}));
20
function collect(ts, node, fn) {
21
const result = [];
22
function loop(node) {
23
const stepResult = fn(node);
24
if (stepResult === CollectStepResult.Yes || stepResult === CollectStepResult.YesAndRecurse) {
25
result.push(node);
26
}
27
if (stepResult === CollectStepResult.YesAndRecurse || stepResult === CollectStepResult.NoAndRecurse) {
28
ts.forEachChild(node, loop);
29
}
30
}
31
loop(node);
32
return result;
33
}
34
function clone(object) {
35
const result = {};
36
for (const id in object) {
37
result[id] = object[id];
38
}
39
return result;
40
}
41
/**
42
* Returns a stream containing the patched JavaScript and source maps.
43
*/
44
function nls(options) {
45
let base;
46
const input = (0, event_stream_1.through)();
47
const output = input
48
.pipe((0, gulp_sort_1.default)()) // IMPORTANT: to ensure stable NLS metadata generation, we must sort the files because NLS messages are globally extracted and indexed across all files
49
.pipe((0, event_stream_1.through)(function (f) {
50
if (!f.sourceMap) {
51
return this.emit('error', new Error(`File ${f.relative} does not have sourcemaps.`));
52
}
53
let source = f.sourceMap.sources[0];
54
if (!source) {
55
return this.emit('error', new Error(`File ${f.relative} does not have a source in the source map.`));
56
}
57
const root = f.sourceMap.sourceRoot;
58
if (root) {
59
source = path_1.default.join(root, source);
60
}
61
const typescript = f.sourceMap.sourcesContent[0];
62
if (!typescript) {
63
return this.emit('error', new Error(`File ${f.relative} does not have the original content in the source map.`));
64
}
65
base = f.base;
66
this.emit('data', _nls.patchFile(f, typescript, options));
67
}, function () {
68
for (const file of [
69
new vinyl_1.default({
70
contents: Buffer.from(JSON.stringify({
71
keys: _nls.moduleToNLSKeys,
72
messages: _nls.moduleToNLSMessages,
73
}, null, '\t')),
74
base,
75
path: `${base}/nls.metadata.json`
76
}),
77
new vinyl_1.default({
78
contents: Buffer.from(JSON.stringify(_nls.allNLSMessages)),
79
base,
80
path: `${base}/nls.messages.json`
81
}),
82
new vinyl_1.default({
83
contents: Buffer.from(JSON.stringify(_nls.allNLSModulesAndKeys)),
84
base,
85
path: `${base}/nls.keys.json`
86
}),
87
new vinyl_1.default({
88
contents: Buffer.from(`/*---------------------------------------------------------
89
* Copyright (C) Microsoft Corporation. All rights reserved.
90
*--------------------------------------------------------*/
91
globalThis._VSCODE_NLS_MESSAGES=${JSON.stringify(_nls.allNLSMessages)};`),
92
base,
93
path: `${base}/nls.messages.js`
94
})
95
]) {
96
this.emit('data', file);
97
}
98
this.emit('end');
99
}));
100
return (0, event_stream_1.duplex)(input, output);
101
}
102
function isImportNode(ts, node) {
103
return node.kind === ts.SyntaxKind.ImportDeclaration || node.kind === ts.SyntaxKind.ImportEqualsDeclaration;
104
}
105
var _nls;
106
(function (_nls) {
107
_nls.moduleToNLSKeys = {};
108
_nls.moduleToNLSMessages = {};
109
_nls.allNLSMessages = [];
110
_nls.allNLSModulesAndKeys = [];
111
let allNLSMessagesIndex = 0;
112
function fileFrom(file, contents, path = file.path) {
113
return new vinyl_1.default({
114
contents: Buffer.from(contents),
115
base: file.base,
116
cwd: file.cwd,
117
path: path
118
});
119
}
120
function mappedPositionFrom(source, lc) {
121
return { source, line: lc.line + 1, column: lc.character };
122
}
123
function lcFrom(position) {
124
return { line: position.line - 1, character: position.column };
125
}
126
class SingleFileServiceHost {
127
options;
128
filename;
129
file;
130
lib;
131
constructor(ts, options, filename, contents) {
132
this.options = options;
133
this.filename = filename;
134
this.file = ts.ScriptSnapshot.fromString(contents);
135
this.lib = ts.ScriptSnapshot.fromString('');
136
}
137
getCompilationSettings = () => this.options;
138
getScriptFileNames = () => [this.filename];
139
getScriptVersion = () => '1';
140
getScriptSnapshot = (name) => name === this.filename ? this.file : this.lib;
141
getCurrentDirectory = () => '';
142
getDefaultLibFileName = () => 'lib.d.ts';
143
readFile(path, _encoding) {
144
if (path === this.filename) {
145
return this.file.getText(0, this.file.getLength());
146
}
147
return undefined;
148
}
149
fileExists(path) {
150
return path === this.filename;
151
}
152
}
153
function isCallExpressionWithinTextSpanCollectStep(ts, textSpan, node) {
154
if (!ts.textSpanContainsTextSpan({ start: node.pos, length: node.end - node.pos }, textSpan)) {
155
return CollectStepResult.No;
156
}
157
return node.kind === ts.SyntaxKind.CallExpression ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse;
158
}
159
function analyze(ts, contents, functionName, options = {}) {
160
const filename = 'file.ts';
161
const serviceHost = new SingleFileServiceHost(ts, Object.assign(clone(options), { noResolve: true }), filename, contents);
162
const service = ts.createLanguageService(serviceHost);
163
const sourceFile = ts.createSourceFile(filename, contents, ts.ScriptTarget.ES5, true);
164
// all imports
165
const imports = (0, lazy_js_1.default)(collect(ts, sourceFile, n => isImportNode(ts, n) ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse));
166
// import nls = require('vs/nls');
167
const importEqualsDeclarations = imports
168
.filter(n => n.kind === ts.SyntaxKind.ImportEqualsDeclaration)
169
.map(n => n)
170
.filter(d => d.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference)
171
.filter(d => d.moduleReference.expression.getText().endsWith(`/nls.js'`));
172
// import ... from 'vs/nls';
173
const importDeclarations = imports
174
.filter(n => n.kind === ts.SyntaxKind.ImportDeclaration)
175
.map(n => n)
176
.filter(d => d.moduleSpecifier.kind === ts.SyntaxKind.StringLiteral)
177
.filter(d => d.moduleSpecifier.getText().endsWith(`/nls.js'`))
178
.filter(d => !!d.importClause && !!d.importClause.namedBindings);
179
// `nls.localize(...)` calls
180
const nlsLocalizeCallExpressions = importDeclarations
181
.filter(d => !!(d.importClause && d.importClause.namedBindings && d.importClause.namedBindings.kind === ts.SyntaxKind.NamespaceImport))
182
.map(d => d.importClause.namedBindings.name)
183
.concat(importEqualsDeclarations.map(d => d.name))
184
// find read-only references to `nls`
185
.map(n => service.getReferencesAtPosition(filename, n.pos + 1) ?? [])
186
.flatten()
187
.filter(r => !r.isWriteAccess)
188
// find the deepest call expressions AST nodes that contain those references
189
.map(r => collect(ts, sourceFile, n => isCallExpressionWithinTextSpanCollectStep(ts, r.textSpan, n)))
190
.map(a => (0, lazy_js_1.default)(a).last())
191
.filter(n => !!n)
192
.map(n => n)
193
// only `localize` calls
194
.filter(n => n.expression.kind === ts.SyntaxKind.PropertyAccessExpression && n.expression.name.getText() === functionName);
195
// `localize` named imports
196
const allLocalizeImportDeclarations = importDeclarations
197
.filter(d => !!(d.importClause && d.importClause.namedBindings && d.importClause.namedBindings.kind === ts.SyntaxKind.NamedImports))
198
.map(d => [].concat(d.importClause.namedBindings.elements))
199
.flatten();
200
// `localize` read-only references
201
const localizeReferences = allLocalizeImportDeclarations
202
.filter(d => d.name.getText() === functionName)
203
.map(n => service.getReferencesAtPosition(filename, n.pos + 1) ?? [])
204
.flatten()
205
.filter(r => !r.isWriteAccess);
206
// custom named `localize` read-only references
207
const namedLocalizeReferences = allLocalizeImportDeclarations
208
.filter(d => d.propertyName && d.propertyName.getText() === functionName)
209
.map(n => service.getReferencesAtPosition(filename, n.name.pos + 1) ?? [])
210
.flatten()
211
.filter(r => !r.isWriteAccess);
212
// find the deepest call expressions AST nodes that contain those references
213
const localizeCallExpressions = localizeReferences
214
.concat(namedLocalizeReferences)
215
.map(r => collect(ts, sourceFile, n => isCallExpressionWithinTextSpanCollectStep(ts, r.textSpan, n)))
216
.map(a => (0, lazy_js_1.default)(a).last())
217
.filter(n => !!n)
218
.map(n => n);
219
// collect everything
220
const localizeCalls = nlsLocalizeCallExpressions
221
.concat(localizeCallExpressions)
222
.map(e => e.arguments)
223
.filter(a => a.length > 1)
224
.sort((a, b) => a[0].getStart() - b[0].getStart())
225
.map(a => ({
226
keySpan: { start: ts.getLineAndCharacterOfPosition(sourceFile, a[0].getStart()), end: ts.getLineAndCharacterOfPosition(sourceFile, a[0].getEnd()) },
227
key: a[0].getText(),
228
valueSpan: { start: ts.getLineAndCharacterOfPosition(sourceFile, a[1].getStart()), end: ts.getLineAndCharacterOfPosition(sourceFile, a[1].getEnd()) },
229
value: a[1].getText()
230
}));
231
return {
232
localizeCalls: localizeCalls.toArray()
233
};
234
}
235
class TextModel {
236
lines;
237
lineEndings;
238
constructor(contents) {
239
const regex = /\r\n|\r|\n/g;
240
let index = 0;
241
let match;
242
this.lines = [];
243
this.lineEndings = [];
244
while (match = regex.exec(contents)) {
245
this.lines.push(contents.substring(index, match.index));
246
this.lineEndings.push(match[0]);
247
index = regex.lastIndex;
248
}
249
if (contents.length > 0) {
250
this.lines.push(contents.substring(index, contents.length));
251
this.lineEndings.push('');
252
}
253
}
254
get(index) {
255
return this.lines[index];
256
}
257
set(index, line) {
258
this.lines[index] = line;
259
}
260
get lineCount() {
261
return this.lines.length;
262
}
263
/**
264
* Applies patch(es) to the model.
265
* Multiple patches must be ordered.
266
* Does not support patches spanning multiple lines.
267
*/
268
apply(patch) {
269
const startLineNumber = patch.span.start.line;
270
const endLineNumber = patch.span.end.line;
271
const startLine = this.lines[startLineNumber] || '';
272
const endLine = this.lines[endLineNumber] || '';
273
this.lines[startLineNumber] = [
274
startLine.substring(0, patch.span.start.character),
275
patch.content,
276
endLine.substring(patch.span.end.character)
277
].join('');
278
for (let i = startLineNumber + 1; i <= endLineNumber; i++) {
279
this.lines[i] = '';
280
}
281
}
282
toString() {
283
return (0, lazy_js_1.default)(this.lines).zip(this.lineEndings)
284
.flatten().toArray().join('');
285
}
286
}
287
function patchJavascript(patches, contents) {
288
const model = new TextModel(contents);
289
// patch the localize calls
290
(0, lazy_js_1.default)(patches).reverse().each(p => model.apply(p));
291
return model.toString();
292
}
293
function patchSourcemap(patches, rsm, smc) {
294
const smg = new source_map_1.default.SourceMapGenerator({
295
file: rsm.file,
296
sourceRoot: rsm.sourceRoot
297
});
298
patches = patches.reverse();
299
let currentLine = -1;
300
let currentLineDiff = 0;
301
let source = null;
302
smc.eachMapping(m => {
303
const patch = patches[patches.length - 1];
304
const original = { line: m.originalLine, column: m.originalColumn };
305
const generated = { line: m.generatedLine, column: m.generatedColumn };
306
if (currentLine !== generated.line) {
307
currentLineDiff = 0;
308
}
309
currentLine = generated.line;
310
generated.column += currentLineDiff;
311
if (patch && m.generatedLine - 1 === patch.span.end.line && m.generatedColumn === patch.span.end.character) {
312
const originalLength = patch.span.end.character - patch.span.start.character;
313
const modifiedLength = patch.content.length;
314
const lengthDiff = modifiedLength - originalLength;
315
currentLineDiff += lengthDiff;
316
generated.column += lengthDiff;
317
patches.pop();
318
}
319
source = rsm.sourceRoot ? path_1.default.relative(rsm.sourceRoot, m.source) : m.source;
320
source = source.replace(/\\/g, '/');
321
smg.addMapping({ source, name: m.name, original, generated });
322
}, null, source_map_1.default.SourceMapConsumer.GENERATED_ORDER);
323
if (source) {
324
smg.setSourceContent(source, smc.sourceContentFor(source));
325
}
326
return JSON.parse(smg.toString());
327
}
328
function parseLocalizeKeyOrValue(sourceExpression) {
329
// sourceValue can be "foo", 'foo', `foo` or { .... }
330
// in its evalulated form
331
// we want to return either the string or the object
332
// eslint-disable-next-line no-eval
333
return eval(`(${sourceExpression})`);
334
}
335
function patch(ts, typescript, javascript, sourcemap, options) {
336
const { localizeCalls } = analyze(ts, typescript, 'localize');
337
const { localizeCalls: localize2Calls } = analyze(ts, typescript, 'localize2');
338
if (localizeCalls.length === 0 && localize2Calls.length === 0) {
339
return { javascript, sourcemap };
340
}
341
const nlsKeys = localizeCalls.map(lc => parseLocalizeKeyOrValue(lc.key)).concat(localize2Calls.map(lc => parseLocalizeKeyOrValue(lc.key)));
342
const nlsMessages = localizeCalls.map(lc => parseLocalizeKeyOrValue(lc.value)).concat(localize2Calls.map(lc => parseLocalizeKeyOrValue(lc.value)));
343
const smc = new source_map_1.default.SourceMapConsumer(sourcemap);
344
const positionFrom = mappedPositionFrom.bind(null, sourcemap.sources[0]);
345
// build patches
346
const toPatch = (c) => {
347
const start = lcFrom(smc.generatedPositionFor(positionFrom(c.range.start)));
348
const end = lcFrom(smc.generatedPositionFor(positionFrom(c.range.end)));
349
return { span: { start, end }, content: c.content };
350
};
351
const localizePatches = (0, lazy_js_1.default)(localizeCalls)
352
.map(lc => (options.preserveEnglish ? [
353
{ range: lc.keySpan, content: `${allNLSMessagesIndex++}` } // localize('key', "message") => localize(<index>, "message")
354
] : [
355
{ range: lc.keySpan, content: `${allNLSMessagesIndex++}` }, // localize('key', "message") => localize(<index>, null)
356
{ range: lc.valueSpan, content: 'null' }
357
]))
358
.flatten()
359
.map(toPatch);
360
const localize2Patches = (0, lazy_js_1.default)(localize2Calls)
361
.map(lc => ({ range: lc.keySpan, content: `${allNLSMessagesIndex++}` } // localize2('key', "message") => localize(<index>, "message")
362
))
363
.map(toPatch);
364
// Sort patches by their start position
365
const patches = localizePatches.concat(localize2Patches).toArray().sort((a, b) => {
366
if (a.span.start.line < b.span.start.line) {
367
return -1;
368
}
369
else if (a.span.start.line > b.span.start.line) {
370
return 1;
371
}
372
else if (a.span.start.character < b.span.start.character) {
373
return -1;
374
}
375
else if (a.span.start.character > b.span.start.character) {
376
return 1;
377
}
378
else {
379
return 0;
380
}
381
});
382
javascript = patchJavascript(patches, javascript);
383
sourcemap = patchSourcemap(patches, sourcemap, smc);
384
return { javascript, sourcemap, nlsKeys, nlsMessages };
385
}
386
function patchFile(javascriptFile, typescript, options) {
387
const ts = require('typescript');
388
// hack?
389
const moduleId = javascriptFile.relative
390
.replace(/\.js$/, '')
391
.replace(/\\/g, '/');
392
const { javascript, sourcemap, nlsKeys, nlsMessages } = patch(ts, typescript, javascriptFile.contents.toString(), javascriptFile.sourceMap, options);
393
const result = fileFrom(javascriptFile, javascript);
394
result.sourceMap = sourcemap;
395
if (nlsKeys) {
396
_nls.moduleToNLSKeys[moduleId] = nlsKeys;
397
_nls.allNLSModulesAndKeys.push([moduleId, nlsKeys.map(nlsKey => typeof nlsKey === 'string' ? nlsKey : nlsKey.key)]);
398
}
399
if (nlsMessages) {
400
_nls.moduleToNLSMessages[moduleId] = nlsMessages;
401
_nls.allNLSMessages.push(...nlsMessages);
402
}
403
return result;
404
}
405
_nls.patchFile = patchFile;
406
})(_nls || (_nls = {}));
407
//# sourceMappingURL=nls.js.map
408