mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 09:07:40 +00:00
Reduce import-scanner work by omitting unused AST parents and the duplicate traversal. Run the mandatory process-host import check alongside static work and explicitly join it before CI can succeed. Update both workflow phase/enforcement contracts and migrate the newer benchmark to the public subprocess package API. Keep the original package build path, manifests, runtime, test selection, and shard count. Independent native review is clean; validation and timing evidence are in the PR.
259 lines
8.9 KiB
JavaScript
259 lines
8.9 KiB
JavaScript
import ts from 'typescript-api'
|
|
|
|
const MODULE_BUILTINS = new Set(['module', 'node:module'])
|
|
const PROCESS_BUILTINS = new Set(['process', 'node:process'])
|
|
|
|
function unwrap(node) {
|
|
while (
|
|
node &&
|
|
(ts.isParenthesizedExpression(node) ||
|
|
ts.isAsExpression(node) ||
|
|
ts.isSatisfiesExpression(node) ||
|
|
ts.isNonNullExpression(node) ||
|
|
ts.isTypeAssertionExpression(node) ||
|
|
ts.isAwaitExpression(node))
|
|
) {
|
|
node = node.expression
|
|
}
|
|
return node
|
|
}
|
|
|
|
function isNamed(node, name) {
|
|
return ts.isIdentifier(node) && node.text === name
|
|
}
|
|
|
|
function literalString(node) {
|
|
node = unwrap(node)
|
|
return node && (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node))
|
|
? node.text
|
|
: null
|
|
}
|
|
|
|
function propertyName(node) {
|
|
if (ts.isPropertyAccessExpression(node)) {
|
|
return node.name.text
|
|
}
|
|
return ts.isElementAccessExpression(node) ? literalString(node.argumentExpression) : null
|
|
}
|
|
|
|
function collectBindings(source) {
|
|
const strings = new Map()
|
|
const declarations = []
|
|
const destructures = []
|
|
const imports = []
|
|
const modules = []
|
|
function visit(node, isConst = false) {
|
|
switch (node.kind) {
|
|
case ts.SyntaxKind.VariableDeclarationList:
|
|
for (const declaration of node.declarations) {
|
|
visit(declaration, (node.flags & ts.NodeFlags.Const) !== 0)
|
|
}
|
|
return
|
|
case ts.SyntaxKind.VariableDeclaration: {
|
|
if (!node.initializer) {
|
|
break
|
|
}
|
|
const initializer = unwrap(node.initializer)
|
|
if (ts.isIdentifier(node.name)) {
|
|
declarations.push({ name: node.name.text, initializer })
|
|
const value = literalString(initializer)
|
|
if (isConst && value !== null) {
|
|
strings.set(node.name.text, [...(strings.get(node.name.text) ?? []), value])
|
|
}
|
|
} else if (ts.isObjectBindingPattern(node.name)) {
|
|
for (const element of node.name.elements) {
|
|
const key = element.propertyName ?? element.name
|
|
const name = ts.isIdentifier(key) ? key.text : literalString(key)
|
|
if (ts.isIdentifier(element.name) && name !== null) {
|
|
destructures.push({ name: element.name.text, key: name, initializer })
|
|
}
|
|
}
|
|
}
|
|
break
|
|
}
|
|
case ts.SyntaxKind.ImportDeclaration:
|
|
if (ts.isStringLiteral(node.moduleSpecifier)) {
|
|
imports.push({ module: node.moduleSpecifier.text, clause: node.importClause })
|
|
}
|
|
modules.push(node)
|
|
break
|
|
case ts.SyntaxKind.ImportEqualsDeclaration:
|
|
if (
|
|
ts.isExternalModuleReference(node.moduleReference) &&
|
|
ts.isStringLiteral(node.moduleReference.expression)
|
|
) {
|
|
imports.push({ module: node.moduleReference.expression.text, namespace: node.name })
|
|
}
|
|
modules.push(node)
|
|
break
|
|
case ts.SyntaxKind.ExportDeclaration:
|
|
case ts.SyntaxKind.ImportType:
|
|
case ts.SyntaxKind.CallExpression:
|
|
modules.push(node)
|
|
break
|
|
}
|
|
ts.forEachChild(node, visit)
|
|
}
|
|
visit(source)
|
|
return { strings, declarations, destructures, imports, modules }
|
|
}
|
|
|
|
/**
|
|
* Specifiers a file statically loads: imports, re-exports, `import()`, `require`/`require.resolve`,
|
|
* `module.require`, `createRequire(...)` loaders and their aliases, and `process.getBuiltinModule`.
|
|
* A specifier argument may be a literal or a file-level `const` string. Name bindings are
|
|
* file-wide, not scope-aware. Literal property keys are resolved; computed key values are not.
|
|
* Computed specifiers (concatenation, templates with substitutions, parameters, values imported
|
|
* from other files), loaders stored in objects or passed as arguments, and `eval`/`Function`
|
|
* are not resolved.
|
|
*/
|
|
export function collectModuleSpecifiers(file, contents) {
|
|
const source = ts.createSourceFile(file, contents, ts.ScriptTarget.Latest, false)
|
|
if (source.parseDiagnostics.length > 0) {
|
|
throw new Error(`Cannot parse ${file}; the process-host import check must not skip it.`)
|
|
}
|
|
const { strings, declarations, destructures, imports, modules } = collectBindings(source)
|
|
const loaders = new Set(['require'])
|
|
const createRequires = new Set()
|
|
const moduleNamespaces = new Set()
|
|
const processes = new Set(['process'])
|
|
const builtinLoaders = new Set()
|
|
|
|
function specifierValues(node) {
|
|
node = unwrap(node)
|
|
const value = literalString(node)
|
|
if (value !== null) {
|
|
return [value]
|
|
}
|
|
return node && ts.isIdentifier(node) ? (strings.get(node.text) ?? []) : []
|
|
}
|
|
function isProcess(node) {
|
|
node = unwrap(node)
|
|
return (
|
|
(ts.isIdentifier(node) && processes.has(node.text)) ||
|
|
(propertyName(node) === 'process' && isNamed(node.expression, 'globalThis')) ||
|
|
loadsAny(node, PROCESS_BUILTINS)
|
|
)
|
|
}
|
|
function isBuiltinLoader(node) {
|
|
node = unwrap(node)
|
|
return (
|
|
(ts.isIdentifier(node) && builtinLoaders.has(node.text)) ||
|
|
(propertyName(node) === 'getBuiltinModule' && isProcess(node.expression))
|
|
)
|
|
}
|
|
function isCreateRequire(node) {
|
|
node = unwrap(node)
|
|
return (
|
|
(ts.isIdentifier(node) && createRequires.has(node.text)) ||
|
|
(propertyName(node) === 'createRequire' && isModuleNamespace(node.expression))
|
|
)
|
|
}
|
|
function isLoader(node) {
|
|
node = unwrap(node)
|
|
return (
|
|
(ts.isIdentifier(node) && loaders.has(node.text)) ||
|
|
(propertyName(node) === 'require' && isNamed(node.expression, 'module')) ||
|
|
(ts.isCallExpression(node) && isCreateRequire(node.expression))
|
|
)
|
|
}
|
|
function loadsAny(node, specifiers) {
|
|
return (
|
|
ts.isCallExpression(node) &&
|
|
(node.expression.kind === ts.SyntaxKind.ImportKeyword ||
|
|
isLoader(node.expression) ||
|
|
isBuiltinLoader(node.expression)) &&
|
|
specifierValues(node.arguments[0]).some((specifier) => specifiers.has(specifier))
|
|
)
|
|
}
|
|
function isModuleNamespace(node) {
|
|
node = unwrap(node)
|
|
return (
|
|
(ts.isIdentifier(node) && moduleNamespaces.has(node.text)) || loadsAny(node, MODULE_BUILTINS)
|
|
)
|
|
}
|
|
|
|
for (const { module, clause, namespace } of imports) {
|
|
const named = clause?.namedBindings
|
|
const local = [
|
|
namespace,
|
|
clause?.name,
|
|
named && ts.isNamespaceImport(named) ? named.name : null
|
|
]
|
|
for (const name of local.filter(Boolean)) {
|
|
if (MODULE_BUILTINS.has(module)) {
|
|
moduleNamespaces.add(name.text)
|
|
}
|
|
if (PROCESS_BUILTINS.has(module)) {
|
|
processes.add(name.text)
|
|
}
|
|
}
|
|
for (const element of named && ts.isNamedImports(named) ? named.elements : []) {
|
|
const imported = (element.propertyName ?? element.name).text
|
|
if (MODULE_BUILTINS.has(module) && (imported === 'default' || imported === 'Module')) {
|
|
moduleNamespaces.add(element.name.text)
|
|
}
|
|
if (PROCESS_BUILTINS.has(module) && imported === 'default') {
|
|
processes.add(element.name.text)
|
|
}
|
|
if (MODULE_BUILTINS.has(module) && imported === 'createRequire') {
|
|
createRequires.add(element.name.text)
|
|
}
|
|
if (PROCESS_BUILTINS.has(module) && imported === 'getBuiltinModule') {
|
|
builtinLoaders.add(element.name.text)
|
|
}
|
|
}
|
|
}
|
|
// Aliases can chain (`const m = require('module'); const r = m.createRequire(x)`).
|
|
for (let changed = true; changed;) {
|
|
changed = false
|
|
const bind = (set, name, matches) => {
|
|
if (matches && !set.has(name)) {
|
|
set.add(name)
|
|
changed = true
|
|
}
|
|
}
|
|
for (const { name, initializer } of declarations) {
|
|
bind(loaders, name, isLoader(initializer))
|
|
bind(createRequires, name, isCreateRequire(initializer))
|
|
bind(moduleNamespaces, name, isModuleNamespace(initializer))
|
|
bind(processes, name, isProcess(initializer))
|
|
bind(builtinLoaders, name, isBuiltinLoader(initializer))
|
|
}
|
|
for (const { name, key, initializer } of destructures) {
|
|
bind(createRequires, name, key === 'createRequire' && isModuleNamespace(initializer))
|
|
bind(builtinLoaders, name, key === 'getBuiltinModule' && isProcess(initializer))
|
|
}
|
|
}
|
|
|
|
const specifiers = new Set()
|
|
function add(node) {
|
|
for (const specifier of specifierValues(node)) {
|
|
specifiers.add(specifier)
|
|
}
|
|
}
|
|
for (const node of modules) {
|
|
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
|
|
add(node.moduleSpecifier)
|
|
} else if (
|
|
ts.isImportEqualsDeclaration(node) &&
|
|
ts.isExternalModuleReference(node.moduleReference)
|
|
) {
|
|
add(node.moduleReference.expression)
|
|
} else if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument)) {
|
|
add(node.argument.literal)
|
|
} else if (ts.isCallExpression(node)) {
|
|
const callee = unwrap(node.expression)
|
|
if (
|
|
callee.kind === ts.SyntaxKind.ImportKeyword ||
|
|
isLoader(callee) ||
|
|
isBuiltinLoader(callee) ||
|
|
(propertyName(callee) === 'resolve' && isLoader(callee.expression))
|
|
) {
|
|
add(node.arguments[0])
|
|
}
|
|
}
|
|
}
|
|
return [...specifiers]
|
|
}
|