-
-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"eslint-plugin-import-x": patch | ||
--- | ||
|
||
Drastically improve `no-cycle`'s performance by skipping unnecessary BFSes using [Tarjan's SCC](https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm). |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,83 +1,88 @@ | ||
import calculateScc from '@rtsao/scc'; | ||
import { resolve } from './resolve'; | ||
import { ExportMap, childContext } from './export-map'; | ||
import type { ChildContext, RuleContext } from '../types'; | ||
import calculateScc from '@rtsao/scc' | ||
|
||
let cache = new Map<string, Record<string, number>>(); | ||
import type { ChildContext, RuleContext } from '../types' | ||
|
||
export class StronglyConnectedComponents { | ||
static clearCache() { | ||
import { ExportMap, childContext } from './export-map' | ||
import { resolve } from './resolve' | ||
|
||
const cache = new Map<string, Record<string, number>>() | ||
|
||
export const StronglyConnectedComponents = { | ||
clearCache() { | ||
cache.clear() | ||
} | ||
}, | ||
|
||
static get(source: string, context: RuleContext) { | ||
const path = resolve(source, context); | ||
if (path == null) { return null; } | ||
return StronglyConnectedComponents.for(childContext(path, context)); | ||
} | ||
get(source: string, context: RuleContext) { | ||
const path = resolve(source, context) | ||
if (path == null) { | ||
return null | ||
} | ||
return StronglyConnectedComponents.for(childContext(path, context)) | ||
}, | ||
|
||
static for(context: ChildContext) { | ||
for(context: ChildContext) { | ||
const cacheKey = context.cacheKey | ||
if (cache.has(cacheKey)) { | ||
return cache.get(cacheKey)!; | ||
return cache.get(cacheKey)! | ||
} | ||
const scc = StronglyConnectedComponents.calculate(context); | ||
cache.set(cacheKey, scc); | ||
return scc; | ||
} | ||
const scc = StronglyConnectedComponents.calculate(context) | ||
cache.set(cacheKey, scc) | ||
return scc | ||
}, | ||
|
||
static calculate(context: ChildContext) { | ||
const exportMap = ExportMap.for(context); | ||
const adjacencyList = StronglyConnectedComponents.exportMapToAdjacencyList(exportMap); | ||
const calculatedScc = calculateScc(adjacencyList); | ||
return StronglyConnectedComponents.calculatedSccToPlainObject(calculatedScc); | ||
} | ||
calculate(context: ChildContext) { | ||
const exportMap = ExportMap.for(context) | ||
const adjacencyList = | ||
StronglyConnectedComponents.exportMapToAdjacencyList(exportMap) | ||
const calculatedScc = calculateScc(adjacencyList) | ||
return StronglyConnectedComponents.calculatedSccToPlainObject(calculatedScc) | ||
}, | ||
|
||
static exportMapToAdjacencyList(initialExportMap: ExportMap | null) { | ||
exportMapToAdjacencyList(initialExportMap: ExportMap | null) { | ||
/** for each dep, what are its direct deps */ | ||
const adjacencyList = new Map<string, Set<string>>(); | ||
const adjacencyList = new Map<string, Set<string>>() | ||
// BFS | ||
function visitNode(exportMap: ExportMap | null) { | ||
if (!exportMap) { | ||
return; | ||
return | ||
} | ||
exportMap.imports.forEach((v, importedPath) => { | ||
const from = exportMap.path; | ||
const to = importedPath; | ||
for (const [importedPath, v] of exportMap.imports.entries()) { | ||
const from = exportMap.path | ||
const to = importedPath | ||
|
||
if (!adjacencyList.has(from)) { | ||
adjacencyList.set(from, new Set()); | ||
adjacencyList.set(from, new Set()) | ||
} | ||
|
||
const set = adjacencyList.get(from)!; | ||
const set = adjacencyList.get(from)! | ||
|
||
if (set.has(to)) { | ||
return; // prevent endless loop | ||
continue // prevent endless loop | ||
} | ||
set.add(to); | ||
visitNode(v.getter()); | ||
}); | ||
set.add(to) | ||
visitNode(v.getter()) | ||
} | ||
} | ||
visitNode(initialExportMap); | ||
visitNode(initialExportMap) | ||
// Fill gaps | ||
adjacencyList.forEach((values) => { | ||
values.forEach((value) => { | ||
for (const values of adjacencyList) { | ||
for (const value of values) { | ||
if (!adjacencyList.has(value)) { | ||
Check failure on line 70 in src/utils/scc.ts GitHub Actions / Lint and Test with Node.js 16 and ESLint 8.56 on macos-latest
Check failure on line 70 in src/utils/scc.ts GitHub Actions / Lint and Test with Node.js 18 and ESLint 8.56 on macos-latest
Check failure on line 70 in src/utils/scc.ts GitHub Actions / Lint and Test with Node.js 18 and ESLint 8 on macos-latest
Check failure on line 70 in src/utils/scc.ts GitHub Actions / Lint and Test with Node.js 20 and ESLint 8.56 on macos-latest
Check failure on line 70 in src/utils/scc.ts GitHub Actions / Lint and Test with Node.js 16 and ESLint 8.56 on ubuntu-latest
Check failure on line 70 in src/utils/scc.ts GitHub Actions / Lint and Test with Node.js 16 and ESLint 8 on ubuntu-latest
Check failure on line 70 in src/utils/scc.ts GitHub Actions / Lint and Test with Node.js 18 and ESLint 8.56 on ubuntu-latest
Check failure on line 70 in src/utils/scc.ts GitHub Actions / Lint and Test with Node.js 18 and ESLint 8 on ubuntu-latest
Check failure on line 70 in src/utils/scc.ts GitHub Actions / Lint and Test with Node.js 20 and ESLint 8.56 on ubuntu-latest
|
||
adjacencyList.set(value, new Set()); | ||
adjacencyList.set(value, new Set()) | ||
Check failure on line 71 in src/utils/scc.ts GitHub Actions / Lint and Test with Node.js 16 and ESLint 8.56 on macos-latest
Check failure on line 71 in src/utils/scc.ts GitHub Actions / Lint and Test with Node.js 18 and ESLint 8.56 on macos-latest
Check failure on line 71 in src/utils/scc.ts GitHub Actions / Lint and Test with Node.js 18 and ESLint 8 on macos-latest
Check failure on line 71 in src/utils/scc.ts GitHub Actions / Lint and Test with Node.js 20 and ESLint 8.56 on macos-latest
Check failure on line 71 in src/utils/scc.ts GitHub Actions / Lint and Test with Node.js 16 and ESLint 8.56 on ubuntu-latest
Check failure on line 71 in src/utils/scc.ts GitHub Actions / Lint and Test with Node.js 16 and ESLint 8 on ubuntu-latest
Check failure on line 71 in src/utils/scc.ts GitHub Actions / Lint and Test with Node.js 18 and ESLint 8.56 on ubuntu-latest
Check failure on line 71 in src/utils/scc.ts GitHub Actions / Lint and Test with Node.js 18 and ESLint 8 on ubuntu-latest
Check failure on line 71 in src/utils/scc.ts GitHub Actions / Lint and Test with Node.js 20 and ESLint 8.56 on ubuntu-latest
|
||
} | ||
}); | ||
}); | ||
return adjacencyList; | ||
} | ||
} | ||
} | ||
return adjacencyList | ||
}, | ||
|
||
static calculatedSccToPlainObject(sccs: Set<string>[]) { | ||
/** for each key, its SCC's index */ | ||
const obj: Record<string, number> = {}; | ||
sccs.forEach((scc, index) => { | ||
scc.forEach((node) => { | ||
obj[node] = index; | ||
}); | ||
}); | ||
return obj; | ||
} | ||
calculatedSccToPlainObject(sccs: Array<Set<string>>) { | ||
/** for each key, its SCC's index */ | ||
const obj: Record<string, number> = {} | ||
for (const [index, scc] of sccs.entries()) { | ||
for (const node of scc) { | ||
obj[node] = index | ||
} | ||
} | ||
return obj | ||
}, | ||
} |