forked from import-js/eslint-plugin-import
-
-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
216 additions
and
170 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,83 +1,91 @@ | ||
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) => { | ||
// eslint-disable-next-line unicorn/no-array-for-each -- Map.forEach, and it is way faster | ||
adjacencyList.forEach(values => { | ||
// eslint-disable-next-line unicorn/no-array-for-each -- Set.forEach | ||
values.forEach(value => { | ||
if (!adjacencyList.has(value)) { | ||
adjacencyList.set(value, new Set()); | ||
adjacencyList.set(value, new Set()) | ||
} | ||
}); | ||
}); | ||
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; | ||
} | ||
return adjacencyList | ||
}, | ||
|
||
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 | ||
}, | ||
} |
Oops, something went wrong.