Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/build/lib/tsb/builder.ts
4772 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import fs from 'fs';
7
import path from 'path';
8
import crypto from 'crypto';
9
import * as utils from './utils.ts';
10
import colors from 'ansi-colors';
11
import ts from 'typescript';
12
import Vinyl from 'vinyl';
13
import { type RawSourceMap, SourceMapConsumer, SourceMapGenerator } from 'source-map';
14
15
export interface IConfiguration {
16
logFn: (topic: string, message: string) => void;
17
_emitWithoutBasePath?: boolean;
18
}
19
20
export interface CancellationToken {
21
isCancellationRequested(): boolean;
22
}
23
24
export const CancellationToken = new class {
25
None: CancellationToken = {
26
isCancellationRequested() { return false; }
27
};
28
};
29
30
export interface ITypeScriptBuilder {
31
build(out: (file: Vinyl) => void, onError: (err: ts.Diagnostic) => void, token?: CancellationToken): Promise<any>;
32
file(file: Vinyl): void;
33
languageService: ts.LanguageService;
34
}
35
36
function normalize(path: string): string {
37
return path.replace(/\\/g, '/');
38
}
39
40
export function createTypeScriptBuilder(config: IConfiguration, projectFile: string, cmd: ts.ParsedCommandLine): ITypeScriptBuilder {
41
42
const _log = config.logFn;
43
44
const host = new LanguageServiceHost(cmd, projectFile, _log);
45
46
const outHost = new LanguageServiceHost({ ...cmd, options: { ...cmd.options, sourceRoot: cmd.options.outDir } }, cmd.options.outDir ?? '', _log);
47
const toBeCheckedForCycles: string[] = [];
48
49
const service = ts.createLanguageService(host, ts.createDocumentRegistry());
50
const lastBuildVersion: { [path: string]: string } = Object.create(null);
51
const lastDtsHash: { [path: string]: string } = Object.create(null);
52
const userWantsDeclarations = cmd.options.declaration;
53
let oldErrors: { [path: string]: ts.Diagnostic[] } = Object.create(null);
54
let headUsed = process.memoryUsage().heapUsed;
55
let emitSourceMapsInStream = true;
56
57
// always emit declaraction files
58
host.getCompilationSettings().declaration = true;
59
60
function file(file: Vinyl): void {
61
// support gulp-sourcemaps
62
if (file.sourceMap) {
63
emitSourceMapsInStream = false;
64
}
65
66
if (!file.contents) {
67
host.removeScriptSnapshot(file.path);
68
delete lastBuildVersion[normalize(file.path)];
69
} else {
70
host.addScriptSnapshot(file.path, new VinylScriptSnapshot(file));
71
}
72
}
73
74
function baseFor(snapshot: ScriptSnapshot): string {
75
if (snapshot instanceof VinylScriptSnapshot) {
76
return cmd.options.outDir || snapshot.getBase();
77
} else {
78
return '';
79
}
80
}
81
82
function isExternalModule(sourceFile: ts.SourceFile): boolean {
83
interface SourceFileWithModuleIndicator extends ts.SourceFile {
84
externalModuleIndicator?: unknown;
85
}
86
return !!(sourceFile as SourceFileWithModuleIndicator).externalModuleIndicator
87
|| /declare\s+module\s+('|")(.+)\1/.test(sourceFile.getText());
88
}
89
90
function build(out: (file: Vinyl) => void, onError: (err: any) => void, token = CancellationToken.None): Promise<any> {
91
92
function checkSyntaxSoon(fileName: string): Promise<ts.Diagnostic[]> {
93
return new Promise<ts.Diagnostic[]>(resolve => {
94
process.nextTick(function () {
95
if (!host.getScriptSnapshot(fileName, false)) {
96
resolve([]); // no script, no problems
97
} else {
98
resolve(service.getSyntacticDiagnostics(fileName));
99
}
100
});
101
});
102
}
103
104
function checkSemanticsSoon(fileName: string): Promise<ts.Diagnostic[]> {
105
return new Promise<ts.Diagnostic[]>(resolve => {
106
process.nextTick(function () {
107
if (!host.getScriptSnapshot(fileName, false)) {
108
resolve([]); // no script, no problems
109
} else {
110
resolve(service.getSemanticDiagnostics(fileName));
111
}
112
});
113
});
114
}
115
116
function emitSoon(fileName: string): Promise<{ fileName: string; signature?: string; files: Vinyl[] }> {
117
118
return new Promise(resolve => {
119
process.nextTick(function () {
120
121
if (/\.d\.ts$/.test(fileName)) {
122
// if it's already a d.ts file just emit it signature
123
const snapshot = host.getScriptSnapshot(fileName);
124
const signature = crypto.createHash('sha256')
125
.update(snapshot.getText(0, snapshot.getLength()))
126
.digest('base64');
127
128
return resolve({
129
fileName,
130
signature,
131
files: []
132
});
133
}
134
135
const output = service.getEmitOutput(fileName);
136
const files: Vinyl[] = [];
137
let signature: string | undefined;
138
139
for (const file of output.outputFiles) {
140
if (!emitSourceMapsInStream && /\.js\.map$/.test(file.name)) {
141
continue;
142
}
143
144
if (/\.d\.ts$/.test(file.name)) {
145
signature = crypto.createHash('sha256')
146
.update(file.text)
147
.digest('base64');
148
149
if (!userWantsDeclarations) {
150
// don't leak .d.ts files if users don't want them
151
continue;
152
}
153
}
154
155
const vinyl = new Vinyl({
156
path: file.name,
157
contents: Buffer.from(file.text),
158
base: !config._emitWithoutBasePath && baseFor(host.getScriptSnapshot(fileName)) || undefined
159
});
160
161
if (!emitSourceMapsInStream && /\.js$/.test(file.name)) {
162
const sourcemapFile = output.outputFiles.filter(f => /\.js\.map$/.test(f.name))[0];
163
164
if (sourcemapFile) {
165
const extname = path.extname(vinyl.relative);
166
const basename = path.basename(vinyl.relative, extname);
167
const dirname = path.dirname(vinyl.relative);
168
const tsname = (dirname === '.' ? '' : dirname + '/') + basename + '.ts';
169
170
let sourceMap = JSON.parse(sourcemapFile.text) as RawSourceMap;
171
sourceMap.sources[0] = tsname.replace(/\\/g, '/');
172
173
// check for an "input source" map and combine them
174
// in step 1 we extract all line edit from the input source map, and
175
// in step 2 we apply the line edits to the typescript source map
176
const snapshot = host.getScriptSnapshot(fileName);
177
if (snapshot instanceof VinylScriptSnapshot && snapshot.sourceMap) {
178
const inputSMC = new SourceMapConsumer(snapshot.sourceMap);
179
const tsSMC = new SourceMapConsumer(sourceMap);
180
let didChange = false;
181
const smg = new SourceMapGenerator({
182
file: sourceMap.file,
183
sourceRoot: sourceMap.sourceRoot
184
});
185
186
// step 1
187
const lineEdits = new Map<number, [from: number, to: number][]>();
188
inputSMC.eachMapping(m => {
189
if (m.originalLine === m.generatedLine) {
190
// same line mapping
191
let array = lineEdits.get(m.originalLine);
192
if (!array) {
193
array = [];
194
lineEdits.set(m.originalLine, array);
195
}
196
array.push([m.originalColumn, m.generatedColumn]);
197
} else {
198
// NOT SUPPORTED
199
}
200
});
201
202
// step 2
203
tsSMC.eachMapping(m => {
204
didChange = true;
205
const edits = lineEdits.get(m.originalLine);
206
let originalColumnDelta = 0;
207
if (edits) {
208
for (const [from, to] of edits) {
209
if (to >= m.originalColumn) {
210
break;
211
}
212
originalColumnDelta = from - to;
213
}
214
}
215
smg.addMapping({
216
source: m.source,
217
name: m.name,
218
generated: { line: m.generatedLine, column: m.generatedColumn },
219
original: { line: m.originalLine, column: m.originalColumn + originalColumnDelta }
220
});
221
});
222
223
if (didChange) {
224
225
interface SourceMapGeneratorWithSources extends SourceMapGenerator {
226
_sources: { add(source: string): void };
227
}
228
229
[tsSMC, inputSMC].forEach((consumer) => {
230
(consumer as SourceMapConsumer & { sources: string[] }).sources.forEach((sourceFile: string) => {
231
(smg as SourceMapGeneratorWithSources)._sources.add(sourceFile);
232
const sourceContent = consumer.sourceContentFor(sourceFile);
233
if (sourceContent !== null) {
234
smg.setSourceContent(sourceFile, sourceContent);
235
}
236
});
237
}); sourceMap = JSON.parse(smg.toString());
238
239
// const filename = '/Users/jrieken/Code/vscode/src2/' + vinyl.relative + '.map';
240
// fs.promises.mkdir(path.dirname(filename), { recursive: true }).then(async () => {
241
// await fs.promises.writeFile(filename, smg.toString());
242
// await fs.promises.writeFile('/Users/jrieken/Code/vscode/src2/' + vinyl.relative, vinyl.contents);
243
// });
244
}
245
}
246
247
(vinyl as Vinyl & { sourceMap?: RawSourceMap }).sourceMap = sourceMap;
248
}
249
} files.push(vinyl);
250
}
251
252
resolve({
253
fileName,
254
signature,
255
files
256
});
257
});
258
});
259
}
260
261
const newErrors: { [path: string]: ts.Diagnostic[] } = Object.create(null);
262
const t1 = Date.now();
263
264
const toBeEmitted: string[] = [];
265
const toBeCheckedSyntactically: string[] = [];
266
const toBeCheckedSemantically: string[] = [];
267
const filesWithChangedSignature: string[] = [];
268
const dependentFiles: string[] = [];
269
const newLastBuildVersion = new Map<string, string>();
270
271
for (const fileName of host.getScriptFileNames()) {
272
if (lastBuildVersion[fileName] !== host.getScriptVersion(fileName)) {
273
274
toBeEmitted.push(fileName);
275
toBeCheckedSyntactically.push(fileName);
276
toBeCheckedSemantically.push(fileName);
277
}
278
}
279
280
return new Promise<void>(resolve => {
281
282
const semanticCheckInfo = new Map<string, number>();
283
const seenAsDependentFile = new Set<string>();
284
285
function workOnNext() {
286
287
let promise: Promise<any> | undefined;
288
// let fileName: string;
289
290
// someone told us to stop this
291
if (token.isCancellationRequested()) {
292
_log('[CANCEL]', '>>This compile run was cancelled<<');
293
newLastBuildVersion.clear();
294
resolve();
295
return;
296
}
297
298
// (1st) emit code
299
else if (toBeEmitted.length) {
300
const fileName = toBeEmitted.pop()!;
301
promise = emitSoon(fileName).then(value => {
302
303
for (const file of value.files) {
304
_log('[emit code]', file.path);
305
out(file);
306
}
307
308
// remember when this was build
309
newLastBuildVersion.set(fileName, host.getScriptVersion(fileName));
310
311
// remeber the signature
312
if (value.signature && lastDtsHash[fileName] !== value.signature) {
313
lastDtsHash[fileName] = value.signature;
314
filesWithChangedSignature.push(fileName);
315
}
316
317
// line up for cycle check
318
const jsValue = value.files.find(candidate => candidate.basename.endsWith('.js'));
319
if (jsValue) {
320
outHost.addScriptSnapshot(jsValue.path, new ScriptSnapshot(String(jsValue.contents), new Date()));
321
toBeCheckedForCycles.push(normalize(jsValue.path));
322
}
323
324
}).catch(e => {
325
// can't just skip this or make a result up..
326
host.error(`ERROR emitting ${fileName}`);
327
host.error(e);
328
});
329
}
330
331
// (2nd) check syntax
332
else if (toBeCheckedSyntactically.length) {
333
const fileName = toBeCheckedSyntactically.pop()!;
334
_log('[check syntax]', fileName);
335
promise = checkSyntaxSoon(fileName).then(diagnostics => {
336
delete oldErrors[fileName];
337
if (diagnostics.length > 0) {
338
diagnostics.forEach(d => onError(d));
339
newErrors[fileName] = diagnostics;
340
341
// stop the world when there are syntax errors
342
toBeCheckedSyntactically.length = 0;
343
toBeCheckedSemantically.length = 0;
344
filesWithChangedSignature.length = 0;
345
}
346
});
347
}
348
349
// (3rd) check semantics
350
else if (toBeCheckedSemantically.length) {
351
352
let fileName = toBeCheckedSemantically.pop();
353
while (fileName && semanticCheckInfo.has(fileName)) {
354
fileName = toBeCheckedSemantically.pop()!;
355
}
356
357
if (fileName) {
358
_log('[check semantics]', fileName);
359
promise = checkSemanticsSoon(fileName).then(diagnostics => {
360
delete oldErrors[fileName!];
361
semanticCheckInfo.set(fileName!, diagnostics.length);
362
if (diagnostics.length > 0) {
363
diagnostics.forEach(d => onError(d));
364
newErrors[fileName!] = diagnostics;
365
}
366
});
367
}
368
}
369
370
// (4th) check dependents
371
else if (filesWithChangedSignature.length) {
372
while (filesWithChangedSignature.length) {
373
const fileName = filesWithChangedSignature.pop()!;
374
375
if (!isExternalModule(service.getProgram()!.getSourceFile(fileName)!)) {
376
_log('[check semantics*]', fileName + ' is an internal module and it has changed shape -> check whatever hasn\'t been checked yet');
377
toBeCheckedSemantically.push(...host.getScriptFileNames());
378
filesWithChangedSignature.length = 0;
379
dependentFiles.length = 0;
380
break;
381
}
382
383
host.collectDependents(fileName, dependentFiles);
384
}
385
}
386
387
// (5th) dependents contd
388
else if (dependentFiles.length) {
389
let fileName = dependentFiles.pop();
390
while (fileName && seenAsDependentFile.has(fileName)) {
391
fileName = dependentFiles.pop();
392
}
393
if (fileName) {
394
seenAsDependentFile.add(fileName);
395
const value = semanticCheckInfo.get(fileName);
396
if (value === 0) {
397
// already validated successfully -> look at dependents next
398
host.collectDependents(fileName, dependentFiles);
399
400
} else if (typeof value === 'undefined') {
401
// first validate -> look at dependents next
402
dependentFiles.push(fileName);
403
toBeCheckedSemantically.push(fileName);
404
}
405
}
406
}
407
408
409
// (last) done
410
else {
411
resolve();
412
return;
413
}
414
415
if (!promise) {
416
promise = Promise.resolve();
417
}
418
419
promise.then(function () {
420
// change to change
421
process.nextTick(workOnNext);
422
}).catch(err => {
423
console.error(err);
424
});
425
}
426
427
workOnNext();
428
429
}).then(() => {
430
// check for cyclic dependencies
431
const cycles = outHost.getCyclicDependencies(toBeCheckedForCycles);
432
toBeCheckedForCycles.length = 0;
433
434
for (const [filename, error] of cycles) {
435
const cyclicDepErrors: ts.Diagnostic[] = [];
436
if (error) {
437
cyclicDepErrors.push({
438
category: ts.DiagnosticCategory.Error,
439
code: 1,
440
file: undefined,
441
start: undefined,
442
length: undefined,
443
messageText: `CYCLIC dependency: ${error}`
444
});
445
}
446
delete oldErrors[filename];
447
newErrors[filename] = cyclicDepErrors;
448
cyclicDepErrors.forEach(d => onError(d));
449
}
450
451
}).then(() => {
452
453
// store the build versions to not rebuilt the next time
454
newLastBuildVersion.forEach((value, key) => {
455
lastBuildVersion[key] = value;
456
});
457
458
// print old errors and keep them
459
for (const [key, value] of Object.entries(oldErrors)) {
460
value.forEach(diag => onError(diag));
461
newErrors[key] = value;
462
}
463
oldErrors = newErrors;
464
465
// print stats
466
const headNow = process.memoryUsage().heapUsed;
467
const MB = 1024 * 1024;
468
_log(
469
'[tsb]',
470
`time: ${colors.yellow((Date.now() - t1) + 'ms')} + \nmem: ${colors.cyan(Math.ceil(headNow / MB) + 'MB')} ${colors.bgCyan('delta: ' + Math.ceil((headNow - headUsed) / MB))}`
471
);
472
headUsed = headNow;
473
});
474
}
475
476
return {
477
file,
478
build,
479
languageService: service
480
};
481
}
482
483
class ScriptSnapshot implements ts.IScriptSnapshot {
484
485
private readonly _text: string;
486
private readonly _mtime: Date;
487
488
constructor(text: string, mtime: Date) {
489
this._text = text;
490
this._mtime = mtime;
491
}
492
493
getVersion(): string {
494
return this._mtime.toUTCString();
495
}
496
497
getText(start: number, end: number): string {
498
return this._text.substring(start, end);
499
}
500
501
getLength(): number {
502
return this._text.length;
503
}
504
505
getChangeRange(_oldSnapshot: ts.IScriptSnapshot): ts.TextChangeRange | undefined {
506
return undefined;
507
}
508
}
509
510
class VinylScriptSnapshot extends ScriptSnapshot {
511
512
private readonly _base: string;
513
514
readonly sourceMap?: RawSourceMap;
515
516
constructor(file: Vinyl & { sourceMap?: RawSourceMap }) {
517
super(file.contents!.toString(), file.stat!.mtime);
518
this._base = file.base;
519
this.sourceMap = file.sourceMap;
520
}
521
522
getBase(): string {
523
return this._base;
524
}
525
}
526
527
class LanguageServiceHost implements ts.LanguageServiceHost {
528
529
private readonly _snapshots: { [path: string]: ScriptSnapshot };
530
private readonly _filesInProject: Set<string>;
531
private readonly _filesAdded: Set<string>;
532
private readonly _dependencies: InstanceType<typeof utils.graph.Graph<string>>;
533
private readonly _dependenciesRecomputeList: string[];
534
private readonly _fileNameToDeclaredModule: { [path: string]: string[] };
535
536
private _projectVersion: number;
537
private readonly _cmdLine: ts.ParsedCommandLine;
538
private readonly _projectPath: string;
539
private readonly _log: (topic: string, message: string) => void;
540
541
constructor(
542
cmdLine: ts.ParsedCommandLine,
543
projectPath: string,
544
log: (topic: string, message: string) => void
545
) {
546
this._cmdLine = cmdLine;
547
this._projectPath = projectPath;
548
this._log = log;
549
this._snapshots = Object.create(null);
550
this._filesInProject = new Set(this._cmdLine.fileNames);
551
this._filesAdded = new Set();
552
this._dependencies = new utils.graph.Graph<string>();
553
this._dependenciesRecomputeList = [];
554
this._fileNameToDeclaredModule = Object.create(null);
555
556
this._projectVersion = 1;
557
}
558
559
log(_s: string): void {
560
// console.log(s);
561
}
562
563
trace(_s: string): void {
564
// console.log(s);
565
}
566
567
error(s: string): void {
568
console.error(s);
569
}
570
571
getCompilationSettings(): ts.CompilerOptions {
572
return this._cmdLine.options;
573
}
574
575
getProjectVersion(): string {
576
return String(this._projectVersion);
577
}
578
579
getScriptFileNames(): string[] {
580
const res = Object.keys(this._snapshots).filter(path => this._filesInProject.has(path) || this._filesAdded.has(path));
581
return res;
582
}
583
584
getScriptVersion(filename: string): string {
585
filename = normalize(filename);
586
const result = this._snapshots[filename];
587
if (result) {
588
return result.getVersion();
589
}
590
return 'UNKNWON_FILE_' + Math.random().toString(16).slice(2);
591
}
592
593
getScriptSnapshot(filename: string, resolve: boolean = true): ScriptSnapshot {
594
filename = normalize(filename);
595
let result = this._snapshots[filename];
596
if (!result && resolve) {
597
try {
598
result = new VinylScriptSnapshot(new Vinyl({
599
path: filename,
600
contents: fs.readFileSync(filename),
601
base: this.getCompilationSettings().outDir,
602
stat: fs.statSync(filename)
603
}));
604
this.addScriptSnapshot(filename, result);
605
} catch (e) {
606
// ignore
607
}
608
}
609
return result;
610
}
611
612
private static _declareModule = /declare\s+module\s+('|")(.+)\1/g;
613
614
addScriptSnapshot(filename: string, snapshot: ScriptSnapshot): ScriptSnapshot {
615
this._projectVersion++;
616
filename = normalize(filename);
617
const old = this._snapshots[filename];
618
if (!old && !this._filesInProject.has(filename) && !filename.endsWith('.d.ts')) {
619
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
620
// not very proper!
621
this._filesAdded.add(filename);
622
}
623
if (!old || old.getVersion() !== snapshot.getVersion()) {
624
this._dependenciesRecomputeList.push(filename);
625
626
// (cheap) check for declare module
627
LanguageServiceHost._declareModule.lastIndex = 0;
628
let match: RegExpExecArray | null | undefined;
629
while ((match = LanguageServiceHost._declareModule.exec(snapshot.getText(0, snapshot.getLength())))) {
630
let declaredModules = this._fileNameToDeclaredModule[filename];
631
if (!declaredModules) {
632
this._fileNameToDeclaredModule[filename] = declaredModules = [];
633
}
634
declaredModules.push(match[2]);
635
}
636
}
637
this._snapshots[filename] = snapshot;
638
return old;
639
}
640
641
removeScriptSnapshot(filename: string): boolean {
642
filename = normalize(filename);
643
this._log('removeScriptSnapshot', filename);
644
this._filesInProject.delete(filename);
645
this._filesAdded.delete(filename);
646
this._projectVersion++;
647
delete this._fileNameToDeclaredModule[filename];
648
return delete this._snapshots[filename];
649
}
650
651
getCurrentDirectory(): string {
652
return path.dirname(this._projectPath);
653
}
654
655
getDefaultLibFileName(options: ts.CompilerOptions): string {
656
return ts.getDefaultLibFilePath(options);
657
}
658
659
readonly directoryExists = ts.sys.directoryExists;
660
readonly getDirectories = ts.sys.getDirectories;
661
readonly fileExists = ts.sys.fileExists;
662
readonly readFile = ts.sys.readFile;
663
readonly readDirectory = ts.sys.readDirectory;
664
665
// ---- dependency management
666
667
collectDependents(filename: string, target: string[]): void {
668
while (this._dependenciesRecomputeList.length) {
669
this._processFile(this._dependenciesRecomputeList.pop()!);
670
}
671
filename = normalize(filename);
672
const node = this._dependencies.lookup(filename);
673
if (node) {
674
node.incoming.forEach((entry: any) => target.push(entry.data));
675
}
676
}
677
678
getCyclicDependencies(filenames: string[]): Map<string, string | undefined> {
679
// Ensure dependencies are up to date
680
while (this._dependenciesRecomputeList.length) {
681
this._processFile(this._dependenciesRecomputeList.pop()!);
682
}
683
const cycles = this._dependencies.findCycles(filenames.sort((a, b) => a.localeCompare(b)));
684
const result = new Map<string, string | undefined>();
685
for (const [key, value] of cycles) {
686
result.set(key, value?.join(' -> '));
687
}
688
return result;
689
}
690
691
_processFile(filename: string): void {
692
if (filename.match(/.*\.d\.ts$/)) {
693
return;
694
}
695
filename = normalize(filename);
696
const snapshot = this.getScriptSnapshot(filename);
697
if (!snapshot) {
698
this._log('processFile', `Missing snapshot for: ${filename}`);
699
return;
700
}
701
const info = ts.preProcessFile(snapshot.getText(0, snapshot.getLength()), true);
702
703
// (0) clear out old dependencies
704
this._dependencies.resetNode(filename);
705
706
// (1) ///-references
707
info.referencedFiles.forEach(ref => {
708
const resolvedPath = path.resolve(path.dirname(filename), ref.fileName);
709
const normalizedPath = normalize(resolvedPath);
710
711
this._dependencies.inertEdge(filename, normalizedPath);
712
});
713
714
// (2) import-require statements
715
info.importedFiles.forEach(ref => {
716
717
if (!ref.fileName.startsWith('.')) {
718
// node module?
719
return;
720
}
721
if (ref.fileName.endsWith('.css')) {
722
return;
723
}
724
725
const stopDirname = normalize(this.getCurrentDirectory());
726
let dirname = filename;
727
let found = false;
728
729
730
while (!found && dirname.indexOf(stopDirname) === 0) {
731
dirname = path.dirname(dirname);
732
let resolvedPath = path.resolve(dirname, ref.fileName);
733
if (resolvedPath.endsWith('.js')) {
734
resolvedPath = resolvedPath.slice(0, -3);
735
}
736
const normalizedPath = normalize(resolvedPath);
737
738
if (this.getScriptSnapshot(normalizedPath + '.ts')) {
739
this._dependencies.inertEdge(filename, normalizedPath + '.ts');
740
found = true;
741
742
} else if (this.getScriptSnapshot(normalizedPath + '.d.ts')) {
743
this._dependencies.inertEdge(filename, normalizedPath + '.d.ts');
744
found = true;
745
746
} else if (this.getScriptSnapshot(normalizedPath + '.js')) {
747
this._dependencies.inertEdge(filename, normalizedPath + '.js');
748
found = true;
749
}
750
}
751
752
if (!found) {
753
for (const key in this._fileNameToDeclaredModule) {
754
if (this._fileNameToDeclaredModule[key] && ~this._fileNameToDeclaredModule[key].indexOf(ref.fileName)) {
755
this._dependencies.inertEdge(filename, key);
756
}
757
}
758
}
759
});
760
}
761
}
762
763