gitnexus 1.6.7-rc.2 → 1.6.7-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/analyze.js +7 -5
- package/dist/cli/optional-grammars.d.ts +18 -4
- package/dist/cli/optional-grammars.js +51 -8
- package/dist/core/ingestion/languages/dart/query.js +11 -3
- package/dist/core/ingestion/languages/kotlin/query.js +11 -3
- package/dist/core/ingestion/languages/swift/query.js +11 -3
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +11 -3
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +11 -0
- package/dist/core/ingestion/workers/parse-worker.js +13 -0
- package/dist/core/tree-sitter/parser-loader.d.ts +8 -0
- package/dist/core/tree-sitter/parser-loader.js +70 -0
- package/package.json +1 -1
package/dist/cli/analyze.js
CHANGED
|
@@ -19,7 +19,7 @@ import { getGitRoot, hasGitDir, getDefaultBranch } from '../storage/git.js';
|
|
|
19
19
|
import { loadAnalyzeConfig, mergeAnalyzeOptions, resolveDefaultBranch, validateBranchName, GitNexusRcError, } from './analyze-config.js';
|
|
20
20
|
import { runFullAnalysis } from '../core/run-analyze.js';
|
|
21
21
|
import { getMaxFileSizeBannerMessage } from '../core/ingestion/utils/max-file-size.js';
|
|
22
|
-
import { warnMissingOptionalGrammars } from './optional-grammars.js';
|
|
22
|
+
import { warnMissingOptionalGrammars, getOptionalGrammarExtensions } from './optional-grammars.js';
|
|
23
23
|
import { glob } from 'glob';
|
|
24
24
|
import fs from 'fs/promises';
|
|
25
25
|
import { cliError } from './cli-message.js';
|
|
@@ -691,11 +691,13 @@ const analyzeCommandImpl = async (inputPath, cliOptions) => {
|
|
|
691
691
|
console.log(' Note: --index-only overrides --skills; community skill files will not be written.\n');
|
|
692
692
|
}
|
|
693
693
|
// If the target repo contains files an optional grammar would parse but
|
|
694
|
-
// that grammar's native binding is absent
|
|
695
|
-
//
|
|
696
|
-
// degraded index.
|
|
694
|
+
// that grammar's native binding is absent (or disabled via
|
|
695
|
+
// GITNEXUS_SKIP_OPTIONAL_GRAMMARS), warn before analysis so users learn why
|
|
696
|
+
// those files end up unparsed instead of silently getting a degraded index.
|
|
697
|
+
// The extension set is derived from OPTIONAL_GRAMMARS so it can't drift.
|
|
697
698
|
try {
|
|
698
|
-
const
|
|
699
|
+
const optionalGlobs = getOptionalGrammarExtensions().map((e) => `**/*${e}`);
|
|
700
|
+
const matches = await glob(optionalGlobs, {
|
|
699
701
|
cwd: repoPath,
|
|
700
702
|
ignore: ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**'],
|
|
701
703
|
dot: false,
|
|
@@ -4,19 +4,33 @@
|
|
|
4
4
|
* tree-sitter-dart, tree-sitter-proto, and tree-sitter-swift are vendored
|
|
5
5
|
* under vendor/ and materialized into node_modules/ at postinstall. Dart
|
|
6
6
|
* and Proto are built from source with node-gyp; Swift ships platform
|
|
7
|
-
* prebuilds activated via node-gyp-build.
|
|
7
|
+
* prebuilds activated via node-gyp-build. tree-sitter-kotlin is a declared
|
|
8
|
+
* optionalDependency (not vendored). All can be skipped via
|
|
8
9
|
* GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1 (postinstall scripts), or can silently
|
|
9
|
-
* soft-fail when the toolchain is missing (Dart/Proto)
|
|
10
|
-
* matches the host platform (Swift)
|
|
10
|
+
* soft-fail when the toolchain is missing (Dart/Proto), when no prebuild
|
|
11
|
+
* matches the host platform (Swift), or when the optional install was
|
|
12
|
+
* skipped or its native build failed (Kotlin).
|
|
11
13
|
*
|
|
12
14
|
* Either path produces the same observable: the .node binding is absent
|
|
13
15
|
* at runtime. This helper detects that condition and surfaces a single
|
|
14
|
-
* stderr line per missing grammar so users learn why .dart/.proto/.swift
|
|
16
|
+
* stderr line per missing grammar so users learn why .dart/.proto/.swift/.kt
|
|
15
17
|
* support is unavailable instead of silently getting a degraded index.
|
|
16
18
|
*/
|
|
19
|
+
/**
|
|
20
|
+
* The file extensions backed by an optional grammar — the single source for
|
|
21
|
+
* the `analyze` preflight glob (so the glob can't drift from this list).
|
|
22
|
+
*/
|
|
23
|
+
export declare function getOptionalGrammarExtensions(): string[];
|
|
17
24
|
export interface MissingGrammar {
|
|
18
25
|
name: string;
|
|
19
26
|
extensions: string[];
|
|
27
|
+
/**
|
|
28
|
+
* `missing` — the native binding could not be loaded (not installed / build
|
|
29
|
+
* soft-failed / no prebuild). `skipped` — the binding is fine but the user
|
|
30
|
+
* disabled it via `GITNEXUS_SKIP_OPTIONAL_GRAMMARS`. Drives the warning text
|
|
31
|
+
* so a deliberate opt-out is not told to reinstall.
|
|
32
|
+
*/
|
|
33
|
+
reason: 'missing' | 'skipped';
|
|
20
34
|
}
|
|
21
35
|
/**
|
|
22
36
|
* Returns the list of optional grammars whose native binding cannot be
|
|
@@ -4,24 +4,51 @@
|
|
|
4
4
|
* tree-sitter-dart, tree-sitter-proto, and tree-sitter-swift are vendored
|
|
5
5
|
* under vendor/ and materialized into node_modules/ at postinstall. Dart
|
|
6
6
|
* and Proto are built from source with node-gyp; Swift ships platform
|
|
7
|
-
* prebuilds activated via node-gyp-build.
|
|
7
|
+
* prebuilds activated via node-gyp-build. tree-sitter-kotlin is a declared
|
|
8
|
+
* optionalDependency (not vendored). All can be skipped via
|
|
8
9
|
* GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1 (postinstall scripts), or can silently
|
|
9
|
-
* soft-fail when the toolchain is missing (Dart/Proto)
|
|
10
|
-
* matches the host platform (Swift)
|
|
10
|
+
* soft-fail when the toolchain is missing (Dart/Proto), when no prebuild
|
|
11
|
+
* matches the host platform (Swift), or when the optional install was
|
|
12
|
+
* skipped or its native build failed (Kotlin).
|
|
11
13
|
*
|
|
12
14
|
* Either path produces the same observable: the .node binding is absent
|
|
13
15
|
* at runtime. This helper detects that condition and surfaces a single
|
|
14
|
-
* stderr line per missing grammar so users learn why .dart/.proto/.swift
|
|
16
|
+
* stderr line per missing grammar so users learn why .dart/.proto/.swift/.kt
|
|
15
17
|
* support is unavailable instead of silently getting a degraded index.
|
|
16
18
|
*/
|
|
17
19
|
import { createRequire } from 'module';
|
|
20
|
+
import { SupportedLanguages } from '../_shared/index.js';
|
|
21
|
+
import { isGrammarRuntimeSkipped } from '../core/tree-sitter/parser-loader.js';
|
|
18
22
|
import { cliWarn } from './cli-message.js';
|
|
19
23
|
const _require = createRequire(import.meta.url);
|
|
20
24
|
const OPTIONAL_GRAMMARS = [
|
|
21
|
-
{
|
|
25
|
+
{
|
|
26
|
+
name: 'tree-sitter-dart',
|
|
27
|
+
pkg: 'tree-sitter-dart',
|
|
28
|
+
extensions: ['.dart'],
|
|
29
|
+
language: SupportedLanguages.Dart,
|
|
30
|
+
},
|
|
22
31
|
{ name: 'tree-sitter-proto', pkg: 'tree-sitter-proto', extensions: ['.proto'] },
|
|
23
|
-
{
|
|
32
|
+
{
|
|
33
|
+
name: 'tree-sitter-swift',
|
|
34
|
+
pkg: 'tree-sitter-swift',
|
|
35
|
+
extensions: ['.swift'],
|
|
36
|
+
language: SupportedLanguages.Swift,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'tree-sitter-kotlin',
|
|
40
|
+
pkg: 'tree-sitter-kotlin',
|
|
41
|
+
extensions: ['.kt', '.kts'],
|
|
42
|
+
language: SupportedLanguages.Kotlin,
|
|
43
|
+
},
|
|
24
44
|
];
|
|
45
|
+
/**
|
|
46
|
+
* The file extensions backed by an optional grammar — the single source for
|
|
47
|
+
* the `analyze` preflight glob (so the glob can't drift from this list).
|
|
48
|
+
*/
|
|
49
|
+
export function getOptionalGrammarExtensions() {
|
|
50
|
+
return [...new Set(OPTIONAL_GRAMMARS.flatMap((g) => g.extensions))];
|
|
51
|
+
}
|
|
25
52
|
/**
|
|
26
53
|
* Returns the list of optional grammars whose native binding cannot be
|
|
27
54
|
* loaded. Actually `require()`s the package — `require.resolve` would
|
|
@@ -41,6 +68,13 @@ const OPTIONAL_GRAMMARS = [
|
|
|
41
68
|
export function detectMissingOptionalGrammars() {
|
|
42
69
|
const missing = [];
|
|
43
70
|
for (const g of OPTIONAL_GRAMMARS) {
|
|
71
|
+
// Deliberate runtime opt-out comes first: even an installed binding is
|
|
72
|
+
// treated as unavailable, with a `skipped` reason so the warning says so
|
|
73
|
+
// instead of suggesting a reinstall (#2101 review).
|
|
74
|
+
if (g.language !== undefined && isGrammarRuntimeSkipped(g.language)) {
|
|
75
|
+
missing.push({ name: g.name, extensions: g.extensions, reason: 'skipped' });
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
44
78
|
try {
|
|
45
79
|
_require(g.pkg);
|
|
46
80
|
}
|
|
@@ -59,7 +93,7 @@ export function detectMissingOptionalGrammars() {
|
|
|
59
93
|
// there for tool-data stdout output).
|
|
60
94
|
cliWarn(`GitNexus: optional grammar "${g.name}" is installed but failed to load (${msg.slice(0, 200)}). ${g.extensions.join('/')} files will not be parsed.`, { grammar: g.name, extensions: g.extensions, error: msg });
|
|
61
95
|
}
|
|
62
|
-
missing.push({ name: g.name, extensions: g.extensions });
|
|
96
|
+
missing.push({ name: g.name, extensions: g.extensions, reason: 'missing' });
|
|
63
97
|
}
|
|
64
98
|
}
|
|
65
99
|
return missing;
|
|
@@ -86,6 +120,15 @@ export function warnMissingOptionalGrammars(opts) {
|
|
|
86
120
|
if (relevantExtensions && !g.extensions.some((e) => relevantExtensions.has(e))) {
|
|
87
121
|
continue;
|
|
88
122
|
}
|
|
89
|
-
|
|
123
|
+
const exts = g.extensions.join('/');
|
|
124
|
+
const message = g.reason === 'skipped'
|
|
125
|
+
? `GitNexus${ctx}: optional grammar "${g.name}" is disabled via GITNEXUS_SKIP_OPTIONAL_GRAMMARS — ${exts} files will not be parsed. Unset the variable to re-enable.`
|
|
126
|
+
: `GitNexus${ctx}: optional grammar "${g.name}" is unavailable — ${exts} files will not be parsed. Reinstall without GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1 (and ensure python3, make, g++) to enable.`;
|
|
127
|
+
cliWarn(message, {
|
|
128
|
+
grammar: g.name,
|
|
129
|
+
extensions: g.extensions,
|
|
130
|
+
reason: g.reason,
|
|
131
|
+
context: opts?.context,
|
|
132
|
+
});
|
|
90
133
|
}
|
|
91
134
|
}
|
|
@@ -22,7 +22,15 @@
|
|
|
22
22
|
* - Parameter / return / receiver type bindings and arity metadata.
|
|
23
23
|
*/
|
|
24
24
|
import Parser from 'tree-sitter';
|
|
25
|
-
import
|
|
25
|
+
import { SupportedLanguages } from '../../../../_shared/index.js';
|
|
26
|
+
// `tree-sitter-dart` is an optional/vendored grammar that may be absent on a
|
|
27
|
+
// default install. Loaded lazily + guarded via parser-loader rather than
|
|
28
|
+
// statically imported: this module is pulled onto the main thread eagerly by
|
|
29
|
+
// the scope-resolution registry and the language-provider index, so a top-level
|
|
30
|
+
// `import Dart from 'tree-sitter-dart'` would throw ERR_MODULE_NOT_FOUND at
|
|
31
|
+
// module-load and crash `analyze` even for repos with no Dart files (#2091,
|
|
32
|
+
// #2093). The grammar is only ever needed inside the lazy getters below.
|
|
33
|
+
import { getLanguageGrammar } from '../../../tree-sitter/parser-loader.js';
|
|
26
34
|
const DART_SCOPE_QUERY = `
|
|
27
35
|
; ── Scopes ───────────────────────────────────────────────────────────────────
|
|
28
36
|
(program) @scope.module
|
|
@@ -130,13 +138,13 @@ let _query = null;
|
|
|
130
138
|
export function getDartParser() {
|
|
131
139
|
if (_parser === null) {
|
|
132
140
|
_parser = new Parser();
|
|
133
|
-
_parser.setLanguage(Dart);
|
|
141
|
+
_parser.setLanguage(getLanguageGrammar(SupportedLanguages.Dart));
|
|
134
142
|
}
|
|
135
143
|
return _parser;
|
|
136
144
|
}
|
|
137
145
|
export function getDartScopeQuery() {
|
|
138
146
|
if (_query === null) {
|
|
139
|
-
_query = new Parser.Query(Dart, DART_SCOPE_QUERY);
|
|
147
|
+
_query = new Parser.Query(getLanguageGrammar(SupportedLanguages.Dart), DART_SCOPE_QUERY);
|
|
140
148
|
}
|
|
141
149
|
return _query;
|
|
142
150
|
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import Parser from 'tree-sitter';
|
|
2
|
-
import
|
|
2
|
+
import { SupportedLanguages } from '../../../../_shared/index.js';
|
|
3
|
+
// `tree-sitter-kotlin` is an optionalDependency that may be absent on a default
|
|
4
|
+
// install (or fail its native build). Loaded lazily + guarded via parser-loader
|
|
5
|
+
// rather than statically imported: this module is pulled onto the main thread
|
|
6
|
+
// eagerly by the scope-resolution registry and the language-provider index, so
|
|
7
|
+
// a top-level `import Kotlin from 'tree-sitter-kotlin'` would throw
|
|
8
|
+
// ERR_MODULE_NOT_FOUND at module-load and crash `analyze` even for repos with no
|
|
9
|
+
// Kotlin files (#2091, #2093). The grammar is only ever needed in the getters.
|
|
10
|
+
import { getLanguageGrammar } from '../../../tree-sitter/parser-loader.js';
|
|
3
11
|
const KOTLIN_SCOPE_QUERY = `
|
|
4
12
|
;; Scopes
|
|
5
13
|
(source_file) @scope.module
|
|
@@ -176,13 +184,13 @@ let query = null;
|
|
|
176
184
|
export function getKotlinParser() {
|
|
177
185
|
if (parser === null) {
|
|
178
186
|
parser = new Parser();
|
|
179
|
-
parser.setLanguage(Kotlin);
|
|
187
|
+
parser.setLanguage(getLanguageGrammar(SupportedLanguages.Kotlin));
|
|
180
188
|
}
|
|
181
189
|
return parser;
|
|
182
190
|
}
|
|
183
191
|
export function getKotlinScopeQuery() {
|
|
184
192
|
if (query === null) {
|
|
185
|
-
query = new Parser.Query(Kotlin, KOTLIN_SCOPE_QUERY);
|
|
193
|
+
query = new Parser.Query(getLanguageGrammar(SupportedLanguages.Kotlin), KOTLIN_SCOPE_QUERY);
|
|
186
194
|
}
|
|
187
195
|
return query;
|
|
188
196
|
}
|
|
@@ -42,7 +42,15 @@
|
|
|
42
42
|
* tree-sitter init cost per file.
|
|
43
43
|
*/
|
|
44
44
|
import Parser from 'tree-sitter';
|
|
45
|
-
import
|
|
45
|
+
import { SupportedLanguages } from '../../../../_shared/index.js';
|
|
46
|
+
// `tree-sitter-swift` is an optional/vendored grammar that may be absent on a
|
|
47
|
+
// default install. It is loaded lazily + guarded via parser-loader rather than
|
|
48
|
+
// statically imported: this module is pulled onto the main thread eagerly by
|
|
49
|
+
// the scope-resolution registry and the language-provider index, so a top-level
|
|
50
|
+
// `import Swift from 'tree-sitter-swift'` would throw ERR_MODULE_NOT_FOUND at
|
|
51
|
+
// module-load and crash `analyze` even for repos with no Swift files (#2091,
|
|
52
|
+
// #2093). The grammar is only ever needed inside the lazy getters below.
|
|
53
|
+
import { getLanguageGrammar } from '../../../tree-sitter/parser-loader.js';
|
|
46
54
|
const SWIFT_SCOPE_QUERY = `
|
|
47
55
|
;; ── Scopes ──────────────────────────────────────────────────────────
|
|
48
56
|
(source_file) @scope.module
|
|
@@ -182,13 +190,13 @@ let _query = null;
|
|
|
182
190
|
export function getSwiftParser() {
|
|
183
191
|
if (_parser === null) {
|
|
184
192
|
_parser = new Parser();
|
|
185
|
-
_parser.setLanguage(Swift);
|
|
193
|
+
_parser.setLanguage(getLanguageGrammar(SupportedLanguages.Swift));
|
|
186
194
|
}
|
|
187
195
|
return _parser;
|
|
188
196
|
}
|
|
189
197
|
export function getSwiftScopeQuery() {
|
|
190
198
|
if (_query === null) {
|
|
191
|
-
_query = new Parser.Query(Swift, SWIFT_SCOPE_QUERY);
|
|
199
|
+
_query = new Parser.Query(getLanguageGrammar(SupportedLanguages.Swift), SWIFT_SCOPE_QUERY);
|
|
192
200
|
}
|
|
193
201
|
return _query;
|
|
194
202
|
}
|
|
@@ -20,9 +20,9 @@ import { fileContentHash, computeChunkHash, loadParseCacheChunk, persistParseCac
|
|
|
20
20
|
import { clearParsedFileStore, persistParsedFileChunk, getDurableParsedFileDir, loadDurableParsedFileIndex, restoreDurableParsedFileShard, } from '../../../storage/parsedfile-store.js';
|
|
21
21
|
import { processRoutesFromExtracted, buildExportedTypeMapFromGraph, } from '../call-processor.js';
|
|
22
22
|
import { createSemanticModel } from '../model/index.js';
|
|
23
|
-
import { getLanguageFromFilename } from '../../../_shared/index.js';
|
|
23
|
+
import { getLanguageFromFilename, } from '../../../_shared/index.js';
|
|
24
24
|
import { readFileContents } from '../filesystem-walker.js';
|
|
25
|
-
import { isLanguageAvailable } from '../../tree-sitter/parser-loader.js';
|
|
25
|
+
import { isLanguageAvailable, isGrammarRuntimeSkipped } from '../../tree-sitter/parser-loader.js';
|
|
26
26
|
import { createWorkerPool, workerPoolDisabledByEnv, resolveAutoPoolSize, WorkerPoolInitializationError, WorkerPoolDisabledError, } from '../workers/worker-pool.js';
|
|
27
27
|
import fs from 'node:fs';
|
|
28
28
|
import path from 'node:path';
|
|
@@ -167,7 +167,15 @@ export async function runChunkedParseAndResolve(graph, scannedFiles, allPaths, t
|
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
for (const [lang, count] of skippedByLang) {
|
|
170
|
-
|
|
170
|
+
// Distinguish a deliberate runtime opt-out from a genuinely-missing binding
|
|
171
|
+
// so we don't tell a user who set GITNEXUS_SKIP_OPTIONAL_GRAMMARS to
|
|
172
|
+
// `npm rebuild` a grammar that built fine (#2091/#2093 review).
|
|
173
|
+
if (isGrammarRuntimeSkipped(lang)) {
|
|
174
|
+
logger.warn(`Skipping ${count} ${lang} file(s) — ${lang} parsing disabled via GITNEXUS_SKIP_OPTIONAL_GRAMMARS.`);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
logger.warn(`Skipping ${count} ${lang} file(s) — ${lang} parser not available (native binding may not have built). Try: npm rebuild tree-sitter-${lang}`);
|
|
178
|
+
}
|
|
171
179
|
}
|
|
172
180
|
// Sort parseableScanned alphabetically for stable chunk membership
|
|
173
181
|
// across runs (Finding 4). Without this, filesystem-scan order can
|
|
@@ -27,6 +27,7 @@ import { getPhaseOutput } from '../../pipeline-phases/types.js';
|
|
|
27
27
|
import { getLanguageFromFilename } from '../../../../_shared/index.js';
|
|
28
28
|
import { readFileContents } from '../../filesystem-walker.js';
|
|
29
29
|
import { runScopeResolution } from './run.js';
|
|
30
|
+
import { isLanguageAvailable } from '../../../tree-sitter/parser-loader.js';
|
|
30
31
|
import { buildGraphNodeLookup } from '../graph-bridge/node-lookup.js';
|
|
31
32
|
import { SCOPE_RESOLVERS } from './registry.js';
|
|
32
33
|
import { isDev, isSemanticModelValidatorEnabled } from '../../utils/env.js';
|
|
@@ -120,6 +121,16 @@ export const scopeResolutionPhase = {
|
|
|
120
121
|
const fileLang = getLanguageFromFilename(f.path);
|
|
121
122
|
if (fileLang === null)
|
|
122
123
|
continue;
|
|
124
|
+
// Skip files whose grammar isn't available (optional grammars like
|
|
125
|
+
// swift/dart/kotlin on an install where the binding is absent or the
|
|
126
|
+
// user set GITNEXUS_SKIP_OPTIONAL_GRAMMARS). The parse phase already
|
|
127
|
+
// excluded and warned about these (parse-impl.ts); without this guard the
|
|
128
|
+
// file would fall through to the main-thread re-extract in run.ts and
|
|
129
|
+
// throw "Unsupported language" (caught, but noisy, and it needlessly
|
|
130
|
+
// loads the grammar on the main thread). `isLanguageAvailable` is
|
|
131
|
+
// memoized, so this stays O(1) per language. (#2091, #2093)
|
|
132
|
+
if (!isLanguageAvailable(fileLang))
|
|
133
|
+
continue;
|
|
123
134
|
let bucket = filesByLang.get(fileLang);
|
|
124
135
|
if (bucket === undefined) {
|
|
125
136
|
bucket = [];
|
|
@@ -18,6 +18,19 @@ import { getProvider } from '../languages/index.js';
|
|
|
18
18
|
import { getTreeSitterBufferSize, getTreeSitterContentByteLength, TREE_SITTER_MAX_BUFFER, } from '../constants.js';
|
|
19
19
|
import { ARRAY_METHOD_HOC_BLOCKLIST_SET, DEFAULT_EXPORT_IDENTIFIER_BLOCKLIST_SET, deriveDefaultExportHocName, } from '../ts-js-hoc-utils.js';
|
|
20
20
|
import { parseSourceSafe } from '../../tree-sitter/safe-parse.js';
|
|
21
|
+
// ── Worker grammar loading — enforcement boundary (#2091/#2093, #2101) ───────
|
|
22
|
+
// The worker maintains its own grammar table (the guarded `_require`s below +
|
|
23
|
+
// `languageMap`) and intentionally does NOT consult the runtime
|
|
24
|
+
// `GITNEXUS_SKIP_OPTIONAL_GRAMMARS` opt-out. It does not need to: the MAIN
|
|
25
|
+
// THREAD's `parseableScanned` filter (pipeline-phases/parse-impl.ts, gated on
|
|
26
|
+
// `parser-loader.isLanguageAvailable`, which honors the runtime opt-out and a
|
|
27
|
+
// genuinely-absent binding alike) excludes files of an unavailable/opted-out
|
|
28
|
+
// language BEFORE any chunk is dispatched, so the worker never receives them.
|
|
29
|
+
// That main-thread filter is the single enforcement point. Any future change
|
|
30
|
+
// that dispatches files to the worker WITHOUT first passing them through
|
|
31
|
+
// `isLanguageAvailable` must re-introduce the gate here. (The cleaner end-state
|
|
32
|
+
// — routing this table through `parser-loader.getLanguageGrammar` so there is
|
|
33
|
+
// one loader — is the deferred Tier-1 consolidation.)
|
|
21
34
|
// tree-sitter-swift is an optionalDependency — may not be installed
|
|
22
35
|
const _require = createRequire(import.meta.url);
|
|
23
36
|
let Swift = null;
|
|
@@ -15,6 +15,14 @@ export interface GrammarSourceDescriptor {
|
|
|
15
15
|
export declare const listGrammarSources: () => GrammarSourceDescriptor[];
|
|
16
16
|
export declare const resolveLanguageKey: (language: SupportedLanguages, filePath?: string) => string;
|
|
17
17
|
export declare const isLanguageAvailable: (language: SupportedLanguages, filePath?: string) => boolean;
|
|
18
|
+
/**
|
|
19
|
+
* True when `language`'s grammar is being treated as unavailable specifically
|
|
20
|
+
* because of the runtime GITNEXUS_SKIP_OPTIONAL_GRAMMARS opt-out — as opposed
|
|
21
|
+
* to a genuinely-missing/broken native binding. Lets callers surface an
|
|
22
|
+
* accurate "skipped on purpose" message instead of a spurious "npm rebuild"
|
|
23
|
+
* recovery hint. Returns false for required grammars and for an absent env.
|
|
24
|
+
*/
|
|
25
|
+
export declare const isGrammarRuntimeSkipped: (language: SupportedLanguages, filePath?: string) => boolean;
|
|
18
26
|
export declare const getLanguageGrammar: (language: SupportedLanguages, filePath?: string) => unknown;
|
|
19
27
|
export declare const loadParser: () => Promise<Parser>;
|
|
20
28
|
export declare const loadLanguage: (language: SupportedLanguages, filePath?: string) => Promise<void>;
|
|
@@ -86,6 +86,7 @@ const SOURCES = {
|
|
|
86
86
|
[SupportedLanguages.Swift]: {
|
|
87
87
|
load: () => _require('tree-sitter-swift'),
|
|
88
88
|
optional: true,
|
|
89
|
+
userSkippable: true,
|
|
89
90
|
unavailableNote: 'Swift parsing disabled: vendored `tree-sitter-swift` (under ' +
|
|
90
91
|
'`gitnexus/vendor/tree-sitter-swift`) failed to load. ' +
|
|
91
92
|
'Likely cause: no prebuilt `.node` for this platform/architecture. ' +
|
|
@@ -94,6 +95,7 @@ const SOURCES = {
|
|
|
94
95
|
[SupportedLanguages.Dart]: {
|
|
95
96
|
load: () => _require('tree-sitter-dart'),
|
|
96
97
|
optional: true,
|
|
98
|
+
userSkippable: true,
|
|
97
99
|
unavailableNote: 'Dart parsing disabled: vendored `tree-sitter-dart` (under ' +
|
|
98
100
|
'`gitnexus/vendor/tree-sitter-dart`) failed to load. ' +
|
|
99
101
|
'Likely cause: native compile failed at install (missing python3/make/g++). ' +
|
|
@@ -102,6 +104,7 @@ const SOURCES = {
|
|
|
102
104
|
[SupportedLanguages.Kotlin]: {
|
|
103
105
|
load: () => _require('tree-sitter-kotlin'),
|
|
104
106
|
optional: true,
|
|
107
|
+
userSkippable: true,
|
|
105
108
|
unavailableNote: 'Kotlin parsing disabled: `tree-sitter-kotlin` is an optionalDependency ' +
|
|
106
109
|
'and is not installed (or its native binding failed to build).',
|
|
107
110
|
},
|
|
@@ -112,6 +115,43 @@ export const listGrammarSources = () => Object.entries(SOURCES).map(([key, sourc
|
|
|
112
115
|
}));
|
|
113
116
|
const loadCache = new Map();
|
|
114
117
|
const logged = new Set();
|
|
118
|
+
// Parsed form of GITNEXUS_SKIP_OPTIONAL_GRAMMARS, resolved lazily ONCE per
|
|
119
|
+
// process. The env is set before analyze runs, so re-reading + re-allocating a
|
|
120
|
+
// Set on every call was wasted work (and a latent trap for any future per-file
|
|
121
|
+
// caller). `vi.resetModules()` gives the unit tests a fresh module — and thus a
|
|
122
|
+
// fresh memo — per case, so this stays test-friendly.
|
|
123
|
+
// 'all' → every userSkippable grammar; Set → only the named ids
|
|
124
|
+
// (and `tree-sitter-<id>` spellings); null → env unset/empty (nothing).
|
|
125
|
+
let _skipDirective;
|
|
126
|
+
const skipDirective = () => {
|
|
127
|
+
if (_skipDirective !== undefined)
|
|
128
|
+
return _skipDirective;
|
|
129
|
+
const raw = (process.env.GITNEXUS_SKIP_OPTIONAL_GRAMMARS ?? '').trim().toLowerCase();
|
|
130
|
+
if (raw === '')
|
|
131
|
+
return (_skipDirective = null);
|
|
132
|
+
if (raw === '1' || raw === 'true' || raw === 'all' || raw === '*')
|
|
133
|
+
return (_skipDirective = 'all');
|
|
134
|
+
return (_skipDirective = new Set(raw
|
|
135
|
+
.split(',')
|
|
136
|
+
.map((s) => s.trim())
|
|
137
|
+
.filter(Boolean)
|
|
138
|
+
.flatMap((s) => [s, s.replace(/^tree-sitter-/, '')])));
|
|
139
|
+
};
|
|
140
|
+
const isRuntimeSkippedGrammar = (key, source) => {
|
|
141
|
+
// Only grammars explicitly flagged user-skippable (swift/dart/kotlin) — never
|
|
142
|
+
// required deps that use the optional machinery for ABI safety (C carries no
|
|
143
|
+
// `userSkippable`).
|
|
144
|
+
if (source.userSkippable !== true)
|
|
145
|
+
return false;
|
|
146
|
+
const directive = skipDirective();
|
|
147
|
+
if (directive === null)
|
|
148
|
+
return false;
|
|
149
|
+
if (directive === 'all')
|
|
150
|
+
return true;
|
|
151
|
+
// `key` is the SupportedLanguages value (e.g. `swift`); the directive Set
|
|
152
|
+
// already holds both the bare id and the `tree-sitter-<id>` spelling.
|
|
153
|
+
return directive.has(key) || directive.has(`tree-sitter-${key}`);
|
|
154
|
+
};
|
|
115
155
|
const logFailure = (key, result) => {
|
|
116
156
|
if (result.ok === true)
|
|
117
157
|
return;
|
|
@@ -148,6 +188,24 @@ const loadGrammar = (key) => {
|
|
|
148
188
|
loadCache.set(key, result);
|
|
149
189
|
return result;
|
|
150
190
|
}
|
|
191
|
+
// Runtime opt-out: treat a user-skipped optional grammar exactly like an
|
|
192
|
+
// absent binding (non-fatal unavailable + one warning), without attempting
|
|
193
|
+
// the native load. See `isRuntimeSkippedGrammar`.
|
|
194
|
+
if (isRuntimeSkippedGrammar(key, source)) {
|
|
195
|
+
// Deliberate opt-out: emit an accurate "disabled on purpose" note rather
|
|
196
|
+
// than `source.unavailableNote` (which blames a missing/unbuilt binding and
|
|
197
|
+
// would mislead a user who set the env intentionally — #2101 review).
|
|
198
|
+
const result = {
|
|
199
|
+
ok: false,
|
|
200
|
+
error: new Error('runtime opt-out'),
|
|
201
|
+
note: `${key} parsing disabled via GITNEXUS_SKIP_OPTIONAL_GRAMMARS (unset it to re-enable).`,
|
|
202
|
+
fatal: false,
|
|
203
|
+
severity: 'warn',
|
|
204
|
+
};
|
|
205
|
+
loadCache.set(key, result);
|
|
206
|
+
logFailure(key, result);
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
151
209
|
let result;
|
|
152
210
|
try {
|
|
153
211
|
result = { ok: true, grammar: source.load() };
|
|
@@ -168,6 +226,18 @@ const loadGrammar = (key) => {
|
|
|
168
226
|
return result;
|
|
169
227
|
};
|
|
170
228
|
export const isLanguageAvailable = (language, filePath) => loadGrammar(resolveLanguageKey(language, filePath)).ok;
|
|
229
|
+
/**
|
|
230
|
+
* True when `language`'s grammar is being treated as unavailable specifically
|
|
231
|
+
* because of the runtime GITNEXUS_SKIP_OPTIONAL_GRAMMARS opt-out — as opposed
|
|
232
|
+
* to a genuinely-missing/broken native binding. Lets callers surface an
|
|
233
|
+
* accurate "skipped on purpose" message instead of a spurious "npm rebuild"
|
|
234
|
+
* recovery hint. Returns false for required grammars and for an absent env.
|
|
235
|
+
*/
|
|
236
|
+
export const isGrammarRuntimeSkipped = (language, filePath) => {
|
|
237
|
+
const key = resolveLanguageKey(language, filePath);
|
|
238
|
+
const source = SOURCES[key];
|
|
239
|
+
return source !== undefined && isRuntimeSkippedGrammar(key, source);
|
|
240
|
+
};
|
|
171
241
|
export const getLanguageGrammar = (language, filePath) => {
|
|
172
242
|
const key = resolveLanguageKey(language, filePath);
|
|
173
243
|
const result = loadGrammar(key);
|
package/package.json
CHANGED