mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-04 02:31:36 +00:00
fix(ingestion): lazy-load tree-sitter-c to prevent module-load crash
tree-sitter-c is now vendored prebuild-only (#2116) with 0/6 committed prebuilds, so on a toolchain-less or `--ignore-scripts` install C has no native binding. Three modules loaded it via a hard top-level `import C from 'tree-sitter-c'`, which throws ERR_MODULE_NOT_FOUND at module-load — crashing `analyze` before parser-loader's optional/severity:error degradation can run. This is the #2091/#2093 bug class (previously fixed for swift/dart/kotlin); C was left static because it used to be an always-present npm dependency. - languages/c/query.ts: load via the lazy guarded getLanguageGrammar(C), mirroring swift/query.ts; the main-thread isLanguageAvailable filter ensures the getters are reached only when C is present. - workers/parse-worker.ts: guarded `_require('tree-sitter-c')` + conditional languageMap spread, like swift/dart/kotlin. - group/extractors/include-extractor.ts: guarded `_require`; getLanguageForFile returns null for .c/.h when absent, so C include-extraction degrades to a no-op (C++ unaffected). - extend the registry-import-closure regression test (#2091/#2093) to assert C also loads lazily at registry static-import time.
This commit is contained in:
parent
dd55d14ab8
commit
d0793f4dde
4 changed files with 61 additions and 18 deletions
|
|
@ -1,9 +1,23 @@
|
|||
import * as path from 'node:path';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import { createRequire } from 'node:module';
|
||||
import { glob } from 'glob';
|
||||
import Parser from 'tree-sitter';
|
||||
import C from 'tree-sitter-c';
|
||||
import Cpp from 'tree-sitter-cpp';
|
||||
|
||||
// `tree-sitter-c` is vendored prebuild-only (#2116) and may be absent on a
|
||||
// toolchain-less / `--ignore-scripts` install. Load it via a guarded `_require`
|
||||
// rather than a top-level `import C from 'tree-sitter-c'`, which would throw
|
||||
// ERR_MODULE_NOT_FOUND at module-load and crash analyze (#2091/#2093). When the
|
||||
// binding is absent, `getLanguageForFile` returns null for `.c`/`.h` so C
|
||||
// include-extraction is skipped (C++ is unaffected — its binding always ships).
|
||||
const _require = createRequire(import.meta.url);
|
||||
let C: unknown = null;
|
||||
try {
|
||||
C = _require('tree-sitter-c');
|
||||
} catch {
|
||||
/* C grammar unavailable — C include extraction degrades to a no-op. */
|
||||
}
|
||||
import type { ContractExtractor, CypherExecutor } from '../contract-extractor.js';
|
||||
import type { ExtractedContract, RepoHandle } from '../types.js';
|
||||
import { readSafe } from './fs-utils.js';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,15 @@
|
|||
import Parser from 'tree-sitter';
|
||||
import C from 'tree-sitter-c';
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
// `tree-sitter-c` is vendored prebuild-only (#2116) and may be absent on a
|
||||
// toolchain-less / `--ignore-scripts` install. It is loaded lazily + guarded via
|
||||
// parser-loader rather than statically imported: this module is pulled onto the
|
||||
// main thread eagerly by the scope-resolution registry and the language-provider
|
||||
// index, so a top-level `import C from 'tree-sitter-c'` would throw
|
||||
// ERR_MODULE_NOT_FOUND at module-load and crash `analyze` even for repos with no
|
||||
// C files (#2091, #2093). The grammar is only ever needed inside the lazy getters
|
||||
// below, and the main-thread `isLanguageAvailable` filter ensures they are
|
||||
// reached only when the binding is present.
|
||||
import { getLanguageGrammar } from '../../../tree-sitter/parser-loader.js';
|
||||
|
||||
const C_SCOPE_QUERY = `
|
||||
;; Scopes
|
||||
|
|
@ -167,14 +177,19 @@ let _query: Parser.Query | null = null;
|
|||
export function getCParser(): Parser {
|
||||
if (_parser === null) {
|
||||
_parser = new Parser();
|
||||
_parser.setLanguage(C as Parameters<Parser['setLanguage']>[0]);
|
||||
_parser.setLanguage(
|
||||
getLanguageGrammar(SupportedLanguages.C) as Parameters<Parser['setLanguage']>[0],
|
||||
);
|
||||
}
|
||||
return _parser;
|
||||
}
|
||||
|
||||
export function getCScopeQuery(): Parser.Query {
|
||||
if (_query === null) {
|
||||
_query = new Parser.Query(C as Parameters<Parser['setLanguage']>[0], C_SCOPE_QUERY);
|
||||
_query = new Parser.Query(
|
||||
getLanguageGrammar(SupportedLanguages.C) as Parameters<Parser['setLanguage']>[0],
|
||||
C_SCOPE_QUERY,
|
||||
);
|
||||
}
|
||||
return _query;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import JavaScript from 'tree-sitter-javascript';
|
|||
import TypeScript from 'tree-sitter-typescript';
|
||||
import Python from 'tree-sitter-python';
|
||||
import Java from 'tree-sitter-java';
|
||||
import C from 'tree-sitter-c';
|
||||
import CPP from 'tree-sitter-cpp';
|
||||
// Explicit subpath import — see parser-loader.ts for rationale (#1013).
|
||||
import CSharp from 'tree-sitter-c-sharp/bindings/node/index.js';
|
||||
|
|
@ -67,6 +66,16 @@ let Kotlin: TreeSitterLanguage | null = null;
|
|||
try {
|
||||
Kotlin = _require('tree-sitter-kotlin');
|
||||
} catch {}
|
||||
|
||||
// tree-sitter-c is now vendored prebuild-only (#2116) and may be absent on a
|
||||
// toolchain-less / `--ignore-scripts` install. Guard it like Swift/Dart/Kotlin so
|
||||
// a missing binding cannot crash the worker at module-load (#2091/#2093); the
|
||||
// main-thread `isLanguageAvailable` filter keeps C files from being dispatched
|
||||
// here when the entry is absent.
|
||||
let C: TreeSitterLanguage | null = null;
|
||||
try {
|
||||
C = _require('tree-sitter-c');
|
||||
} catch {}
|
||||
import { getLanguageFromFilename } from 'gitnexus-shared';
|
||||
import {
|
||||
buildConcreteTypedefDefinitionRanges,
|
||||
|
|
@ -404,7 +413,7 @@ const languageMap: Record<string, TreeSitterLanguage> = {
|
|||
[`${SupportedLanguages.TypeScript}:tsx`]: TypeScript.tsx,
|
||||
[SupportedLanguages.Python]: Python,
|
||||
[SupportedLanguages.Java]: Java,
|
||||
[SupportedLanguages.C]: C,
|
||||
...(C ? { [SupportedLanguages.C]: C } : {}),
|
||||
[SupportedLanguages.CPlusPlus]: CPP,
|
||||
[SupportedLanguages.CSharp]: CSharp,
|
||||
[SupportedLanguages.Go]: Go,
|
||||
|
|
|
|||
|
|
@ -4,15 +4,17 @@
|
|||
* The scope-resolution registry (`scope-resolution/pipeline/registry.ts`) and
|
||||
* the language-provider index statically import all 16 language providers. Each
|
||||
* per-language `query.ts` used to do a top-level `import X from 'tree-sitter-Y'`.
|
||||
* For the OPTIONAL grammars (swift/dart/kotlin) that import resolved — and on a
|
||||
* default install where the vendored/optional binding is absent, THREW
|
||||
* `ERR_MODULE_NOT_FOUND` — at module-load on the main thread, before any runtime
|
||||
* gate, crashing `gitnexus analyze` regardless of the repo's actual languages.
|
||||
* For the prebuild-only / optional grammars (swift/dart/kotlin, and — since
|
||||
* #2116 — vendored-prebuild-only C) that import resolved — and on a default
|
||||
* install where the binding is absent, THREW `ERR_MODULE_NOT_FOUND` — at
|
||||
* module-load on the main thread, before any runtime gate, crashing
|
||||
* `gitnexus analyze` regardless of the repo's actual languages.
|
||||
*
|
||||
* The fix routes those three `query.ts` modules through the lazy, guarded
|
||||
* The fix routes those `query.ts` modules through the lazy, guarded
|
||||
* `parser-loader.getLanguageGrammar()` so the grammar binding is only required
|
||||
* at first use (inside the worker, for a file of that language) — never at
|
||||
* module-load.
|
||||
* module-load. (C joined this set when it became vendored prebuild-only; it used
|
||||
* to be an always-present npm dependency.)
|
||||
*
|
||||
* This test locks the fix in WITHOUT needing to simulate a missing grammar:
|
||||
* spawn a child Node process, import the built scope-resolution `registry.js`
|
||||
|
|
@ -57,10 +59,13 @@ const PROBE = `
|
|||
process.stdout.write(JSON.stringify([...after].filter((k) => !before.has(k))));
|
||||
`;
|
||||
|
||||
const OPTIONAL_GRAMMAR_RE = /tree-sitter-(swift|dart|kotlin)[\\/]/;
|
||||
// `tree-sitter-c[\\/]` matches only the exact `tree-sitter-c/` package — NOT
|
||||
// `tree-sitter-cpp/` or `tree-sitter-c-sharp/` (those need a non-separator after
|
||||
// the `c`), so the required C++/C# eager loads are unaffected.
|
||||
const OPTIONAL_GRAMMAR_RE = /tree-sitter-(swift|dart|kotlin|c)[\\/]/;
|
||||
|
||||
describe('optional-grammar static-import closure (#2091/#2093)', () => {
|
||||
it('importing the scope-resolution registry loads NO optional grammar binding', () => {
|
||||
describe('optional-grammar static-import closure (#2091/#2093, #2116)', () => {
|
||||
it('importing the scope-resolution registry loads NO lazy grammar binding (swift/dart/kotlin/c)', () => {
|
||||
if (!fs.existsSync(DIST_REGISTRY)) {
|
||||
throw new Error(
|
||||
`${DIST_REGISTRY} missing — run \`npm run build\` first (or \`npm run test:integration\`, ` +
|
||||
|
|
@ -117,13 +122,13 @@ describe('optional-grammar static-import closure (#2091/#2093)', () => {
|
|||
`Newly-loaded (${newlyLoaded.length}):\n${newlyLoaded.join('\n')}`,
|
||||
).toBeGreaterThan(0);
|
||||
|
||||
// Headline assertion: no OPTIONAL grammar binding (swift/dart/kotlin) is
|
||||
// Headline assertion: no lazy grammar binding (swift/dart/kotlin/c) is
|
||||
// loaded at registry static-import time — they must load lazily.
|
||||
const optionalLoaded = newlyLoaded.filter((p) => OPTIONAL_GRAMMAR_RE.test(p));
|
||||
expect(
|
||||
optionalLoaded,
|
||||
`Optional tree-sitter grammar binding(s) loaded at registry static-import time. ` +
|
||||
`query.ts must load swift/dart/kotlin lazily via parser-loader, not via a ` +
|
||||
`Lazy tree-sitter grammar binding(s) loaded at registry static-import time. ` +
|
||||
`query.ts must load swift/dart/kotlin/c lazily via parser-loader, not via a ` +
|
||||
`top-level \`import\`. Offending paths:\n${optionalLoaded.join('\n')}`,
|
||||
).toEqual([]);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue