mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-25 20:12:11 +08:00
fix(js,ts): an import of a package the repository declares itself links to that declaration (#2718)
A `declare module 'mammoth' { ... }` in a script .d.ts declares the module
outright, and TypeScript takes it before any file the specifier resolves to.
Nothing mapped a package name to a file in the repository, so an import of
such a package linked nothing, or, where the out-of-repo guard did not fire
(VS Code's extensions list no `vscode` dependency), a project namesake. A
bodiless function in an ambient context produced no node either, so
`convertToHtml` had nothing to link to.
- ambient-modules.ts: a token scanner for `declare module` blocks, a
per-context index of the project's declaration files, and the lookup the
import resolver makes: named, default (`export default`, `export =`),
export-clause and namespace-member imports, by position in the block.
- The import resolver consults a script declaration before a file a path
alias maps the specifier to (copilot's `paths` shim), and the import's
module reference and namespace or default binding link the declaring file.
- Extraction, both engines: a function_signature under `declare`, in
`declare global`, at a declaration file's top level or directly in its
`declare module` body is a function; never in a namespace, nor in an
ordinary module file's `declare module`, an augmentation.
- A module's function is reached from another file only through an import.
- A type merged across the files declaring one module is no single
declaration when a receiver is typed.
- Sync re-opens the importers of a package a changed declaration file
declares.
Augmentations, wildcard patterns, shorthands and test-fixture declarations
are left alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
642c7bb3a5
commit
a33040f786
@@ -94,9 +94,50 @@ function hasExportDeclaration(block: SyntaxNode): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** A TypeScript declaration file: `.d.ts`, `.d.mts`, `.d.cts`. */
|
||||
const DECLARATION_FILE = /\.d\.[cm]?ts$/i;
|
||||
|
||||
/**
|
||||
* Whether a bodiless `function_signature` is a function's declaration rather
|
||||
* than an overload of an implementation that follows it, and one a name or
|
||||
* an import reaches.
|
||||
*
|
||||
* TypeScript writes a function without a body in two places. Before the
|
||||
* implementation it overloads (`function f(a: number): void;`, then `function
|
||||
* f(a) {…}`), and there the implementation is the function's node. In an
|
||||
* ambient context nothing follows, and the signature is all the declaration
|
||||
* there is: a `declare function`, a function in a `declare global` body, a
|
||||
* declaration file's own functions, which are ambient with or without
|
||||
* `declare`, and a function a declaration file's `declare module 'x'` body
|
||||
* declares, which an import of `x` reaches (see resolution/ambient-modules).
|
||||
* Without a node, `import { convertToHtml } from 'mammoth'` had nothing to
|
||||
* bind to. Two are left out: a namespace's function is only ever reached
|
||||
* through the namespace (`semver.compare`, `vscode.window.showMessage`), which
|
||||
* the index doesn't model, so on its own it is only a bare name for unrelated
|
||||
* calls to land on; and a `declare module` body in an ordinary module file
|
||||
* augments the package's own module, whose functions the import means.
|
||||
* Mirrored in the kernel (tsjs/mod.rs `is_ambient_function_signature`).
|
||||
*/
|
||||
export function isAmbientFunctionSignature(node: SyntaxNode, filePath: string): boolean {
|
||||
if (node.type !== 'function_signature') return false;
|
||||
const declarationFile = DECLARATION_FILE.test(filePath);
|
||||
let ambient = false;
|
||||
for (let current = node.parent; current; current = current.parent) {
|
||||
if (current.type === 'internal_module') return false;
|
||||
if (current.type === 'module') {
|
||||
// `declare module 'x'`; `module Foo {}` is a namespace.
|
||||
if (getChildByField(current, 'name')?.type !== 'string') return false;
|
||||
return declarationFile;
|
||||
}
|
||||
if (current.type === 'ambient_declaration') ambient = true;
|
||||
}
|
||||
return ambient || declarationFile;
|
||||
}
|
||||
|
||||
export const typescriptExtractor: LanguageExtractor = {
|
||||
preParse: blankFlowSyntax,
|
||||
functionTypes: ['function_declaration', 'generator_function_declaration', 'arrow_function', 'function_expression', 'generator_function'],
|
||||
isAmbientFunction: isAmbientFunctionSignature,
|
||||
classTypes: ['class_declaration', 'abstract_class_declaration'],
|
||||
// `method_signature` is the interface/type-literal form of a method; without it
|
||||
// an interface's members never enter the graph, so a `.d.ts` platform API has
|
||||
|
||||
@@ -101,6 +101,14 @@ export interface LanguageExtractor {
|
||||
|
||||
/** Node types that represent functions */
|
||||
functionTypes: string[];
|
||||
/**
|
||||
* A bodiless function that is the function's whole declaration, not one
|
||||
* overload of an implementation that follows it: TypeScript's
|
||||
* `function_signature` in an ambient context (`declare function f(): void;`,
|
||||
* a declaration file's own or its `declare module` body's), never in a
|
||||
* namespace. Extracted as a function, like a `functionTypes` node.
|
||||
*/
|
||||
isAmbientFunction?: (node: SyntaxNode, filePath: string) => boolean;
|
||||
/** Node types that represent classes */
|
||||
classTypes: string[];
|
||||
/** Node types that represent methods */
|
||||
|
||||
@@ -1403,6 +1403,12 @@ export class TreeSitterExtractor {
|
||||
skipChildren = true; // extractFunction visits children via visitFunctionBody
|
||||
}
|
||||
}
|
||||
// A bodiless function that no implementation follows: a TypeScript
|
||||
// `declare function`, a `.d.ts` file's own or its `declare module` body's.
|
||||
else if (nodeType === 'function_signature' && this.extractor.isAmbientFunction?.(node, this.filePath)) {
|
||||
this.extractFunction(node);
|
||||
skipChildren = true;
|
||||
}
|
||||
// Check for class declarations
|
||||
else if (this.extractor.classTypes.includes(nodeType)) {
|
||||
// Some languages reuse class_declaration for structs/enums (e.g. Swift)
|
||||
|
||||
Reference in New Issue
Block a user