Skip to content

Commit 73a5bfb

Browse files
authored
Merge pull request microsoft#251003 from microsoft/joh/systematic-mockingbird
Detect and fix cyclic runtime dependencies
2 parents be48993 + 35911fd commit 73a5bfb

File tree

101 files changed

+1692
-1688
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+1692
-1688
lines changed

build/lib/tsb/builder.js

Lines changed: 24 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/lib/tsb/builder.ts

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str
4444
const host = new LanguageServiceHost(cmd, projectFile, _log);
4545

4646
const outHost = new LanguageServiceHost({ ...cmd, options: { ...cmd.options, sourceRoot: cmd.options.outDir } }, cmd.options.outDir ?? '', _log);
47-
let lastCycleCheckVersion: string;
47+
const toBeCheckedForCycles: string[] = [];
4848

4949
const service = ts.createLanguageService(host, ts.createDocumentRegistry());
5050
const lastBuildVersion: { [path: string]: string } = Object.create(null);
@@ -315,6 +315,7 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str
315315
const jsValue = value.files.find(candidate => candidate.basename.endsWith('.js'));
316316
if (jsValue) {
317317
outHost.addScriptSnapshot(jsValue.path, new ScriptSnapshot(String(jsValue.contents), new Date()));
318+
toBeCheckedForCycles.push(normalize(jsValue.path));
318319
}
319320

320321
}).catch(e => {
@@ -424,25 +425,22 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str
424425

425426
}).then(() => {
426427
// check for cyclic dependencies
427-
const thisCycleCheckVersion = outHost.getProjectVersion();
428-
if (thisCycleCheckVersion === lastCycleCheckVersion) {
429-
return;
430-
}
431-
const oneCycle = outHost.hasCyclicDependency();
432-
lastCycleCheckVersion = thisCycleCheckVersion;
433-
delete oldErrors[projectFile];
434-
435-
if (oneCycle) {
436-
const cycleError: ts.Diagnostic = {
437-
category: ts.DiagnosticCategory.Error,
438-
code: 1,
439-
file: undefined,
440-
start: undefined,
441-
length: undefined,
442-
messageText: `CYCLIC dependency between ${oneCycle}`
443-
};
444-
onError(cycleError);
445-
newErrors[projectFile] = [cycleError];
428+
const cycles = outHost.getCyclicDependencies(toBeCheckedForCycles);
429+
toBeCheckedForCycles.length = 0;
430+
431+
for (const [filename, error] of cycles) {
432+
const cyclicDepErrors: ts.Diagnostic[] = [];
433+
if (error) {
434+
cyclicDepErrors.push({
435+
category: ts.DiagnosticCategory.Error,
436+
code: 1,
437+
file: undefined,
438+
start: undefined,
439+
length: undefined,
440+
messageText: `CYCLIC dependency: ${error}`
441+
});
442+
}
443+
newErrors[filename] = cyclicDepErrors;
446444
}
447445

448446
}).then(() => {
@@ -666,15 +664,17 @@ class LanguageServiceHost implements ts.LanguageServiceHost {
666664
}
667665
}
668666

669-
hasCyclicDependency(): string | undefined {
667+
getCyclicDependencies(filenames: string[]): Map<string, string | undefined> {
670668
// Ensure dependencies are up to date
671669
while (this._dependenciesRecomputeList.length) {
672670
this._processFile(this._dependenciesRecomputeList.pop()!);
673671
}
674-
const cycle = this._dependencies.findCycle();
675-
return cycle
676-
? cycle.join(' -> ')
677-
: undefined;
672+
const cycles = this._dependencies.findCycles(filenames.sort((a, b) => a.localeCompare(b)));
673+
const result = new Map<string, string | undefined>();
674+
for (const [key, value] of cycles) {
675+
result.set(key, value?.join(' -> '));
676+
}
677+
return result;
678678
}
679679

680680
_processFile(filename: string): void {

build/lib/tsb/utils.js

Lines changed: 26 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/lib/tsb/utils.ts

Lines changed: 28 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -63,53 +63,42 @@ export namespace graph {
6363
return this._nodes.get(data) ?? null;
6464
}
6565

66-
findCycle(): T[] | undefined {
66+
findCycles(allData: T[]): Map<T, T[] | undefined> {
67+
const result = new Map<T, T[] | undefined>();
68+
const checked = new Set<T>();
69+
for (const data of allData) {
70+
const node = this.lookup(data);
71+
if (!node) {
72+
continue;
73+
}
74+
const r = this._findCycle(node, checked, new Set());
75+
result.set(node.data, r);
76+
}
77+
return result;
78+
}
6779

68-
let result: T[] | undefined;
69-
let foundStartNodes = false;
70-
const checked = new Set<Node<T>>();
80+
private _findCycle(node: Node<T>, checked: Set<T>, seen: Set<T>): T[] | undefined {
7181

72-
for (const [_start, value] of this._nodes) {
82+
if (checked.has(node.data)) {
83+
return undefined;
84+
}
7385

74-
if (Object.values(value.incoming).length > 0) {
75-
continue;
86+
let result: T[] | undefined;
87+
for (const child of node.outgoing.values()) {
88+
if (seen.has(child.data)) {
89+
const seenArr = Array.from(seen);
90+
const idx = seenArr.indexOf(child.data);
91+
seenArr.push(child.data);
92+
return idx > 0 ? seenArr.slice(idx) : seenArr;
7693
}
77-
78-
foundStartNodes = true;
79-
80-
const dfs = (node: Node<T>, visited: Set<Node<T>>) => {
81-
82-
if (checked.has(node)) {
83-
return;
84-
}
85-
86-
if (visited.has(node)) {
87-
result = [...visited, node].map(n => n.data);
88-
const idx = result.indexOf(node.data);
89-
result = result.slice(idx);
90-
return;
91-
}
92-
visited.add(node);
93-
for (const child of Object.values(node.outgoing)) {
94-
dfs(child, visited);
95-
if (result) {
96-
break;
97-
}
98-
}
99-
visited.delete(node);
100-
checked.add(node);
101-
};
102-
dfs(value, new Set());
94+
seen.add(child.data);
95+
result = this._findCycle(child, checked, seen);
96+
seen.delete(child.data);
10397
if (result) {
10498
break;
10599
}
106100
}
107-
108-
if (!foundStartNodes) {
109-
// everything is a cycle
110-
return Array.from(this._nodes.keys());
111-
}
112-
101+
checked.add(node.data);
113102
return result;
114103
}
115104
}

0 commit comments

Comments
 (0)