mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 08:03:28 +00:00
128 lines
3.7 KiB
JavaScript
128 lines
3.7 KiB
JavaScript
// TypeScript 7 is a native CLI; AST consumers still need the legacy JavaScript API.
|
|
import ts from 'typescript-api'
|
|
|
|
function bindingDeclarations(name, declaration, entries = []) {
|
|
if (!name) {
|
|
return entries
|
|
}
|
|
if (ts.isIdentifier(name)) {
|
|
entries.push([name.text, declaration])
|
|
} else {
|
|
for (const element of name.elements) {
|
|
bindingDeclarations(element.name, element, entries)
|
|
}
|
|
}
|
|
return entries
|
|
}
|
|
|
|
function isLexicalScope(node) {
|
|
return (
|
|
ts.isSourceFile(node) ||
|
|
ts.isFunctionLike(node) ||
|
|
ts.isModuleBlock(node) ||
|
|
ts.isBlock(node) ||
|
|
ts.isCaseBlock(node) ||
|
|
ts.isCatchClause(node) ||
|
|
ts.isForStatement(node) ||
|
|
ts.isForInStatement(node) ||
|
|
ts.isForOfStatement(node)
|
|
)
|
|
}
|
|
|
|
function nearestScope(node, sourceFile, predicate = isLexicalScope) {
|
|
let current = node.parent
|
|
while (current && current !== sourceFile) {
|
|
if (predicate(current)) {
|
|
return current
|
|
}
|
|
current = current.parent
|
|
}
|
|
return sourceFile
|
|
}
|
|
|
|
function varScope(node, sourceFile) {
|
|
return nearestScope(
|
|
node,
|
|
sourceFile,
|
|
(scope) => ts.isFunctionLike(scope) || ts.isModuleBlock(scope)
|
|
)
|
|
}
|
|
|
|
function collectDeclarations(sourceFile) {
|
|
const declarations = new Map()
|
|
|
|
function add(scope, name, declaration) {
|
|
const byName = declarations.get(scope) ?? new Map()
|
|
const matches = byName.get(name) ?? []
|
|
matches.push(declaration)
|
|
byName.set(name, matches)
|
|
declarations.set(scope, byName)
|
|
}
|
|
|
|
function visit(node) {
|
|
if (ts.isImportDeclaration(node)) {
|
|
const clause = node.importClause
|
|
if (clause?.name) {
|
|
add(sourceFile, clause.name.text, clause.name)
|
|
}
|
|
if (clause?.namedBindings && ts.isNamespaceImport(clause.namedBindings)) {
|
|
add(sourceFile, clause.namedBindings.name.text, clause.namedBindings)
|
|
}
|
|
if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings)) {
|
|
for (const element of clause.namedBindings.elements) {
|
|
add(sourceFile, element.name.text, element)
|
|
}
|
|
}
|
|
} else if (ts.isParameter(node)) {
|
|
for (const [name, declaration] of bindingDeclarations(node.name, node)) {
|
|
add(node.parent, name, declaration)
|
|
}
|
|
} else if (ts.isVariableDeclaration(node)) {
|
|
const declarationList = node.parent
|
|
const scope =
|
|
ts.isVariableDeclarationList(declarationList) &&
|
|
(declarationList.flags & ts.NodeFlags.BlockScoped) === 0
|
|
? varScope(node, sourceFile)
|
|
: nearestScope(node, sourceFile)
|
|
for (const [name, declaration] of bindingDeclarations(node.name, node)) {
|
|
add(scope, name, declaration)
|
|
}
|
|
} else if ((ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) && node.name) {
|
|
add(nearestScope(node, sourceFile), node.name.text, node)
|
|
} else if ((ts.isFunctionExpression(node) || ts.isClassExpression(node)) && node.name) {
|
|
add(node, node.name.text, node)
|
|
} else if (ts.isCatchClause(node) && node.variableDeclaration) {
|
|
for (const [name, declaration] of bindingDeclarations(
|
|
node.variableDeclaration.name,
|
|
node.variableDeclaration
|
|
)) {
|
|
add(node, name, declaration)
|
|
}
|
|
}
|
|
ts.forEachChild(node, visit)
|
|
}
|
|
|
|
visit(sourceFile)
|
|
return declarations
|
|
}
|
|
|
|
export function collectSourceBindings(sourceFile) {
|
|
const declarations = collectDeclarations(sourceFile)
|
|
return {
|
|
resolveBinding(identifier) {
|
|
let current = identifier.parent
|
|
while (current) {
|
|
const matches = declarations.get(current)?.get(identifier.text)
|
|
if (matches?.length) {
|
|
return matches[0]
|
|
}
|
|
if (current === sourceFile) {
|
|
break
|
|
}
|
|
current = current.parent
|
|
}
|
|
return undefined
|
|
}
|
|
}
|
|
}
|