gitnexus 1.6.12-rc.2 → 1.6.12-rc.21
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/_shared/graph/types.d.ts +1 -1
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/language-detection.d.ts.map +1 -1
- package/dist/_shared/language-detection.js +2 -0
- package/dist/_shared/language-detection.js.map +1 -1
- package/dist/_shared/languages.d.ts +1 -0
- package/dist/_shared/languages.d.ts.map +1 -1
- package/dist/_shared/languages.js +1 -0
- package/dist/_shared/languages.js.map +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
- package/dist/_shared/lbug/schema-constants.js +2 -0
- package/dist/_shared/lbug/schema-constants.js.map +1 -1
- package/dist/_shared/scope-resolution/language-classification.d.ts +2 -1
- package/dist/_shared/scope-resolution/language-classification.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/language-classification.js +3 -1
- package/dist/_shared/scope-resolution/language-classification.js.map +1 -1
- package/dist/_shared/scope-resolution/registries/class-registry.d.ts +3 -3
- package/dist/_shared/scope-resolution/registries/class-registry.js +3 -3
- package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/registries/context.js +2 -0
- package/dist/_shared/scope-resolution/registries/context.js.map +1 -1
- package/dist/_shared/scope-resolution/resolve-type-ref.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/resolve-type-ref.js +2 -0
- package/dist/_shared/scope-resolution/resolve-type-ref.js.map +1 -1
- package/dist/cli/analyze-config.d.ts +2 -2
- package/dist/cli/analyze-config.js +10 -33
- package/dist/cli/doctor.js +6 -4
- package/dist/cli/eval-server.js +13 -1
- package/dist/cli/i18n/en.d.ts +4 -2
- package/dist/cli/i18n/en.js +4 -2
- package/dist/cli/i18n/resources.d.ts +6 -2
- package/dist/cli/i18n/zh-CN.d.ts +2 -0
- package/dist/cli/i18n/zh-CN.js +4 -2
- package/dist/core/analysis-feature-registry.d.ts +1 -1
- package/dist/core/analysis-feature-registry.js +2 -0
- package/dist/core/auto-sync/starter.js +2 -2
- package/dist/core/embeddings/ast-utils.js +8 -3
- package/dist/core/embeddings/chunker.js +67 -10
- package/dist/core/embeddings/embedding-pipeline.d.ts +1 -1
- package/dist/core/embeddings/embedding-pipeline.js +1 -1
- package/dist/core/embeddings/structural-extractor.js +2 -2
- package/dist/core/embeddings/types.d.ts +4 -2
- package/dist/core/embeddings/types.js +18 -0
- package/dist/core/git-ref.d.ts +22 -0
- package/dist/core/git-ref.js +114 -0
- package/dist/core/git-staleness.js +14 -0
- package/dist/core/group/extractors/graphql-extractor.d.ts +1 -1
- package/dist/core/group/extractors/graphql-extractor.js +149 -13
- package/dist/core/group/extractors/manifest-extractor.d.ts +1 -1
- package/dist/core/group/extractors/manifest-extractor.js +2 -2
- package/dist/core/ingestion/call-extractors/zig-static-gating.d.ts +5 -8
- package/dist/core/ingestion/content-language-classification.d.ts +10 -0
- package/dist/core/ingestion/content-language-classification.js +30 -0
- package/dist/core/ingestion/filesystem-walker.d.ts +1 -0
- package/dist/core/ingestion/filesystem-walker.js +1 -1
- package/dist/core/ingestion/import-resolvers/node-workspace-packages.js +3 -2
- package/dist/core/ingestion/language-config.d.ts +82 -13
- package/dist/core/ingestion/language-config.js +217 -13
- package/dist/core/ingestion/language-provider.d.ts +88 -1
- package/dist/core/ingestion/languages/index.d.ts +7 -0
- package/dist/core/ingestion/languages/index.js +30 -1
- package/dist/core/ingestion/languages/objective-c/analysis-features.d.ts +10 -0
- package/dist/core/ingestion/languages/objective-c/analysis-features.js +19 -0
- package/dist/core/ingestion/languages/objective-c/compilation-unit-siblings.d.ts +13 -0
- package/dist/core/ingestion/languages/objective-c/compilation-unit-siblings.js +124 -0
- package/dist/core/ingestion/languages/objective-c/facts.d.ts +148 -0
- package/dist/core/ingestion/languages/objective-c/facts.js +1150 -0
- package/dist/core/ingestion/languages/objective-c/import-target.d.ts +11 -0
- package/dist/core/ingestion/languages/objective-c/import-target.js +122 -0
- package/dist/core/ingestion/languages/objective-c/macro-marker-preprocess.d.ts +24 -0
- package/dist/core/ingestion/languages/objective-c/macro-marker-preprocess.js +261 -0
- package/dist/core/ingestion/languages/objective-c/resolution-config.d.ts +41 -0
- package/dist/core/ingestion/languages/objective-c/resolution-config.js +282 -0
- package/dist/core/ingestion/languages/objective-c/scope-resolver.d.ts +2 -0
- package/dist/core/ingestion/languages/objective-c/scope-resolver.js +503 -0
- package/dist/core/ingestion/languages/objective-c.d.ts +2 -0
- package/dist/core/ingestion/languages/objective-c.js +300 -0
- package/dist/core/ingestion/languages/typescript/query.d.ts +31 -0
- package/dist/core/ingestion/languages/typescript/query.js +7 -1
- package/dist/core/ingestion/languages/typescript/tsconfig.js +3 -2
- package/dist/core/ingestion/languages/typescript.js +1 -1
- package/dist/core/ingestion/languages/zig/query.d.ts +26 -0
- package/dist/core/ingestion/languages/zig/query.js +61 -1
- package/dist/core/ingestion/languages/zig/range-binding.d.ts +8 -0
- package/dist/core/ingestion/languages/zig/range-binding.js +24 -0
- package/dist/core/ingestion/languages/zig/scope-resolver.d.ts +3 -2
- package/dist/core/ingestion/languages/zig/scope-resolver.js +18 -5
- package/dist/core/ingestion/languages/zig/this-alias-bindings.d.ts +58 -0
- package/dist/core/ingestion/languages/zig/this-alias-bindings.js +176 -0
- package/dist/core/ingestion/languages/zig/workspace-static-gating.d.ts +8 -0
- package/dist/core/ingestion/languages/zig/workspace-static-gating.js +82 -0
- package/dist/core/ingestion/model/registration-table.d.ts +3 -3
- package/dist/core/ingestion/model/registration-table.js +9 -5
- package/dist/core/ingestion/model/symbol-table.d.ts +1 -1
- package/dist/core/ingestion/model/symbol-table.js +2 -0
- package/dist/core/ingestion/model/type-registry.d.ts +1 -1
- package/dist/core/ingestion/parsing-processor.d.ts +22 -6
- package/dist/core/ingestion/parsing-processor.js +39 -20
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +9 -1
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +307 -96
- package/dist/core/ingestion/pipeline-phases/parse-round-budget.d.ts +42 -0
- package/dist/core/ingestion/pipeline-phases/parse-round-budget.js +50 -0
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +7 -1
- package/dist/core/ingestion/scope-extractor.js +4 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +13 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +2 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +2 -0
- package/dist/core/ingestion/scope-resolution/passes/property-dispatch.d.ts +78 -5
- package/dist/core/ingestion/scope-resolution/passes/property-dispatch.js +262 -7
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +5 -2
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +10 -1
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +39 -3
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +82 -3
- package/dist/core/ingestion/scope-resolution/value-ref-edges.d.ts +14 -0
- package/dist/core/ingestion/scope-resolution/value-ref-edges.js +14 -0
- package/dist/core/ingestion/tree-sitter-queries.js +2 -0
- package/dist/core/ingestion/utils/symbol-labels.js +2 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +1 -1
- package/dist/core/ingestion/workers/parse-worker.js +63 -3
- package/dist/core/ingestion/workers/worker-pool.d.ts +56 -2
- package/dist/core/ingestion/workers/worker-pool.js +117 -26
- package/dist/core/lbug/csv-generator.js +2 -0
- package/dist/core/lbug/schema.d.ts +3 -1
- package/dist/core/lbug/schema.js +27 -0
- package/dist/core/search/fts-indexes.js +13 -4
- package/dist/core/search/fts-schema.js +2 -0
- package/dist/core/tree-sitter/parser-loader.js +10 -0
- package/dist/core/tree-sitter/vendored-grammars.d.ts +1 -1
- package/dist/core/tree-sitter/vendored-grammars.js +2 -1
- package/dist/mcp/local/local-backend.d.ts +33 -0
- package/dist/mcp/local/local-backend.js +166 -5
- package/dist/mcp/tools.js +6 -4
- package/dist/server/analyze-job.d.ts +19 -1
- package/dist/server/analyze-job.js +14 -3
- package/dist/server/analyze-launch.d.ts +14 -0
- package/dist/server/analyze-launch.js +59 -14
- package/dist/server/analyze-worker-ipc.d.ts +6 -5
- package/dist/server/analyze-worker-ipc.js +4 -0
- package/dist/server/api.js +68 -21
- package/dist/server/git-clone.d.ts +38 -5
- package/dist/server/git-clone.js +216 -38
- package/dist/server/repo-projection.d.ts +88 -0
- package/dist/server/repo-projection.js +53 -0
- package/dist/storage/file-lock.js +5 -3
- package/dist/storage/parse-cache.js +32 -1
- package/dist/storage/parsedfile-store.js +53 -1
- package/dist/storage/v8-sidecar.d.ts +11 -0
- package/dist/storage/v8-sidecar.js +15 -7
- package/dist/utils/process-identity.d.ts +13 -0
- package/dist/utils/process-identity.js +18 -0
- package/package.json +1 -1
- package/scripts/build-tree-sitter-grammars.cjs +4 -3
- package/vendor/tree-sitter-objc/LICENSE +21 -0
- package/vendor/tree-sitter-objc/README.md +18 -0
- package/vendor/tree-sitter-objc/binding.gyp +35 -0
- package/vendor/tree-sitter-objc/bindings/node/binding.cc +19 -0
- package/vendor/tree-sitter-objc/bindings/node/binding_test.js +9 -0
- package/vendor/tree-sitter-objc/bindings/node/index.d.ts +27 -0
- package/vendor/tree-sitter-objc/bindings/node/index.js +11 -0
- package/vendor/tree-sitter-objc/grammar.js +1286 -0
- package/vendor/tree-sitter-objc/package.json +56 -0
- package/vendor/tree-sitter-objc/prebuilds/SHA256SUMS +6 -0
- package/vendor/tree-sitter-objc/prebuilds/darwin-arm64/tree-sitter-objc.node +0 -0
- package/vendor/tree-sitter-objc/prebuilds/darwin-x64/tree-sitter-objc.node +0 -0
- package/vendor/tree-sitter-objc/prebuilds/linux-arm64/tree-sitter-objc.node +0 -0
- package/vendor/tree-sitter-objc/prebuilds/linux-x64/tree-sitter-objc.node +0 -0
- package/vendor/tree-sitter-objc/prebuilds/win32-arm64/tree-sitter-objc.node +0 -0
- package/vendor/tree-sitter-objc/prebuilds/win32-x64/tree-sitter-objc.node +0 -0
- package/vendor/tree-sitter-objc/queries/folds.scm +20 -0
- package/vendor/tree-sitter-objc/queries/highlights.scm +216 -0
- package/vendor/tree-sitter-objc/queries/indents.scm +1 -0
- package/vendor/tree-sitter-objc/queries/injections.scm +10 -0
- package/vendor/tree-sitter-objc/queries/locals.scm +1 -0
- package/vendor/tree-sitter-objc/src/grammar.json +16292 -0
- package/vendor/tree-sitter-objc/src/node-types.json +7524 -0
- package/vendor/tree-sitter-objc/src/parser.c +684023 -0
- package/vendor/tree-sitter-objc/src/tree_sitter/alloc.h +54 -0
- package/vendor/tree-sitter-objc/src/tree_sitter/array.h +291 -0
- package/vendor/tree-sitter-objc/src/tree_sitter/parser.h +266 -0
- package/vendor/tree-sitter-objc/tree-sitter-objc.wasm +0 -0
- package/vendor/tree-sitter-objc/tree-sitter.json +38 -0
- package/web/assets/{agent-BMRqXorA.js → agent-C2n33ANd.js} +1 -1
- package/web/assets/{index-Dj3vQOHK.js → index-CHJoW_P1.js} +18 -18
- package/web/assets/src-jc8Ffy3T.js +1 -0
- package/web/index.html +2 -2
- package/web/assets/src-Df5C1Nz4.js +0 -1
|
@@ -89,8 +89,32 @@ const DECLARATION_BODY_NODE_TYPES = new Set([
|
|
|
89
89
|
'class_body',
|
|
90
90
|
'object_type',
|
|
91
91
|
'declaration_list',
|
|
92
|
+
'implementation_definition',
|
|
92
93
|
'interface_body',
|
|
93
94
|
]);
|
|
95
|
+
const DIRECT_MEMBER_DECLARATION_TYPES = new Set([
|
|
96
|
+
'protocol_declaration',
|
|
97
|
+
'class_interface',
|
|
98
|
+
// tree-sitter-objc exposes each implementation_definition directly under
|
|
99
|
+
// class_implementation, rather than grouping them in a shared body node.
|
|
100
|
+
'class_implementation',
|
|
101
|
+
]);
|
|
102
|
+
const DIRECT_MEMBER_HEADER_NODE_TYPES = new Set([
|
|
103
|
+
'identifier',
|
|
104
|
+
'parameterized_arguments',
|
|
105
|
+
'protocol_reference_list',
|
|
106
|
+
// tree-sitter-objc `_type_params` hides itself and exposes this named child
|
|
107
|
+
// for `(T)` generics and category-shaped argument lists.
|
|
108
|
+
'generic_arguments',
|
|
109
|
+
// `_class_interface_header` / `_class_implementation_header` expose these
|
|
110
|
+
// named children before members (`NS_ROOT_CLASS @interface …`).
|
|
111
|
+
'attribute_declaration',
|
|
112
|
+
'attribute_specifier',
|
|
113
|
+
'availability_attribute_specifier',
|
|
114
|
+
'ms_declspec_modifier',
|
|
115
|
+
'storage_class_specifier',
|
|
116
|
+
'type_qualifier',
|
|
117
|
+
]);
|
|
94
118
|
const FIELD_LIKE_MEMBER_TYPES = new Set([
|
|
95
119
|
'field_definition',
|
|
96
120
|
'public_field_definition',
|
|
@@ -100,7 +124,15 @@ const FIELD_LIKE_MEMBER_TYPES = new Set([
|
|
|
100
124
|
'lexical_declaration',
|
|
101
125
|
'pair',
|
|
102
126
|
'enum_assignment',
|
|
127
|
+
// tree-sitter-objc wraps each ivar as `instance_variable`, not field_definition.
|
|
128
|
+
'instance_variable',
|
|
103
129
|
]);
|
|
130
|
+
const DECLARATION_MEMBER_WRAPPER_TYPES = new Set([
|
|
131
|
+
'qualified_protocol_interface_declaration',
|
|
132
|
+
'instance_variables',
|
|
133
|
+
]);
|
|
134
|
+
/** Named prefixes the ObjC grammar allows immediately before each ivar. */
|
|
135
|
+
const IVAR_ATTRIBUTE_PREFIX_TYPES = new Set(['attribute_specifier', 'attribute_declaration']);
|
|
104
136
|
const declarationChunk = async (content, filePath, startLine, endLine, chunkSize, overlap, rule) => {
|
|
105
137
|
const tree = await ensureAndParse(content, filePath);
|
|
106
138
|
if (!tree)
|
|
@@ -196,6 +228,8 @@ const getDeclarationBodyNode = (node) => {
|
|
|
196
228
|
const bodyNode = node.childForFieldName?.('body');
|
|
197
229
|
if (bodyNode)
|
|
198
230
|
return bodyNode;
|
|
231
|
+
if (DIRECT_MEMBER_DECLARATION_TYPES.has(node.type))
|
|
232
|
+
return node;
|
|
199
233
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
200
234
|
const child = node.namedChild(i);
|
|
201
235
|
if (!child)
|
|
@@ -207,16 +241,39 @@ const getDeclarationBodyNode = (node) => {
|
|
|
207
241
|
};
|
|
208
242
|
const collectDeclarationUnits = (bodyNode, groupFields) => {
|
|
209
243
|
const members = [];
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
244
|
+
const collectMembers = (node, skipHeaderChildren, includeNodePrefixOnFirstMember = false) => {
|
|
245
|
+
const firstMemberIndex = members.length;
|
|
246
|
+
let ivarAttributePrefixStart;
|
|
247
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
248
|
+
const child = node.namedChild(i);
|
|
249
|
+
if (!child)
|
|
250
|
+
continue;
|
|
251
|
+
if (DECLARATION_MEMBER_WRAPPER_TYPES.has(child.type)) {
|
|
252
|
+
collectMembers(child, false, true);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (skipHeaderChildren && DIRECT_MEMBER_HEADER_NODE_TYPES.has(child.type))
|
|
256
|
+
continue;
|
|
257
|
+
if (node.type === 'instance_variables' && IVAR_ATTRIBUTE_PREFIX_TYPES.has(child.type)) {
|
|
258
|
+
ivarAttributePrefixStart ??= child.startIndex;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
members.push({
|
|
262
|
+
startIndex: ivarAttributePrefixStart ?? child.startIndex,
|
|
263
|
+
endIndex: child.endIndex,
|
|
264
|
+
groupable: groupFields && FIELD_LIKE_MEMBER_TYPES.has(child.type),
|
|
265
|
+
});
|
|
266
|
+
ivarAttributePrefixStart = undefined;
|
|
267
|
+
}
|
|
268
|
+
const firstMember = members[firstMemberIndex];
|
|
269
|
+
if (includeNodePrefixOnFirstMember && firstMember) {
|
|
270
|
+
firstMember.startIndex = node.startIndex;
|
|
271
|
+
if (node.type === 'instance_variables') {
|
|
272
|
+
members[members.length - 1].endIndex = node.endIndex;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
collectMembers(bodyNode, DIRECT_MEMBER_DECLARATION_TYPES.has(bodyNode.type));
|
|
220
277
|
if (members.length === 0)
|
|
221
278
|
return [];
|
|
222
279
|
const grouped = [];
|
|
@@ -28,7 +28,7 @@ export declare const resolveEmbeddingInstallPolicy: () => ExtensionInstallPolicy
|
|
|
28
28
|
* invalidate existing vectors, such as metadata/header shape changes,
|
|
29
29
|
* structural container context changes, or preceding-context formatting rules.
|
|
30
30
|
*/
|
|
31
|
-
export declare const EMBEDDING_TEXT_VERSION = "
|
|
31
|
+
export declare const EMBEDDING_TEXT_VERSION = "v5";
|
|
32
32
|
/**
|
|
33
33
|
* Compute a stable content fingerprint for an embeddable node.
|
|
34
34
|
* Used to detect when the underlying text has changed so stale vectors
|
|
@@ -53,7 +53,7 @@ const ensureVectorExtensionAvailable = async () => {
|
|
|
53
53
|
* invalidate existing vectors, such as metadata/header shape changes,
|
|
54
54
|
* structural container context changes, or preceding-context formatting rules.
|
|
55
55
|
*/
|
|
56
|
-
export const EMBEDDING_TEXT_VERSION = '
|
|
56
|
+
export const EMBEDDING_TEXT_VERSION = 'v5';
|
|
57
57
|
/**
|
|
58
58
|
* Compute a stable content fingerprint for an embeddable node.
|
|
59
59
|
* Used to detect when the underlying text has changed so stale vectors
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Reuses ingestion pipeline's AST-based MethodExtractor / FieldExtractor
|
|
5
5
|
* to extract method and field names for embedding text generation.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { getProviderForFileContent } from '../ingestion/languages/index.js';
|
|
8
8
|
import { buildTypeEnv } from '../ingestion/type-env.js';
|
|
9
9
|
import { ensureAndParse, findDeclarationNode } from './ast-utils.js';
|
|
10
10
|
const NOOP_SYMBOL_TABLE = {
|
|
@@ -17,7 +17,7 @@ const NOOP_SYMBOL_TABLE = {
|
|
|
17
17
|
* using the ingestion pipeline's AST extractors.
|
|
18
18
|
*/
|
|
19
19
|
export const extractStructuralNames = async (content, filePath) => {
|
|
20
|
-
const provider =
|
|
20
|
+
const provider = getProviderForFileContent(filePath, content);
|
|
21
21
|
if (!provider)
|
|
22
22
|
return { methodNames: [], fieldNames: [] };
|
|
23
23
|
const tree = await ensureAndParse(content, filePath);
|
|
@@ -7,6 +7,8 @@ export declare const LABEL_FUNCTION: "Function";
|
|
|
7
7
|
export declare const LABEL_METHOD: "Method";
|
|
8
8
|
export declare const LABEL_CONSTRUCTOR: "Constructor";
|
|
9
9
|
export declare const LABEL_CLASS: "Class";
|
|
10
|
+
export declare const LABEL_PROTOCOL: "Protocol";
|
|
11
|
+
export declare const LABEL_CATEGORY: "Category";
|
|
10
12
|
export declare const LABEL_INTERFACE: "Interface";
|
|
11
13
|
export declare const LABEL_STRUCT: "Struct";
|
|
12
14
|
export declare const LABEL_ENUM: "Enum";
|
|
@@ -38,7 +40,7 @@ export interface ChunkingRule {
|
|
|
38
40
|
/**
|
|
39
41
|
* Node labels that need chunking (have code body, potentially long)
|
|
40
42
|
*/
|
|
41
|
-
export declare const CHUNKABLE_LABELS: readonly ["Function", "Method", "Constructor", "Class", "Interface", "Struct", "Enum", "Trait", "Impl", "Macro", "Namespace"];
|
|
43
|
+
export declare const CHUNKABLE_LABELS: readonly ["Function", "Method", "Constructor", "Class", "Protocol", "Category", "Interface", "Struct", "Enum", "Trait", "Impl", "Macro", "Namespace"];
|
|
42
44
|
/**
|
|
43
45
|
* Node labels that are short (no chunking needed, embed directly)
|
|
44
46
|
*/
|
|
@@ -46,7 +48,7 @@ export declare const SHORT_LABELS: readonly ["TypeAlias", "Typedef", "Const", "P
|
|
|
46
48
|
/**
|
|
47
49
|
* All embeddable labels (union of CHUNKABLE + SHORT)
|
|
48
50
|
*/
|
|
49
|
-
export declare const EMBEDDABLE_LABELS: readonly ["Function", "Method", "Constructor", "Class", "Interface", "Struct", "Enum", "Trait", "Impl", "Macro", "Namespace", "TypeAlias", "Typedef", "Const", "Property", "Record", "Union", "Static", "Variable"];
|
|
51
|
+
export declare const EMBEDDABLE_LABELS: readonly ["Function", "Method", "Constructor", "Class", "Protocol", "Category", "Interface", "Struct", "Enum", "Trait", "Impl", "Macro", "Namespace", "TypeAlias", "Typedef", "Const", "Property", "Record", "Union", "Static", "Variable"];
|
|
50
52
|
export type EmbeddableLabel = (typeof EMBEDDABLE_LABELS)[number];
|
|
51
53
|
/**
|
|
52
54
|
* Check if a label should be embedded
|
|
@@ -7,6 +7,8 @@ export const LABEL_FUNCTION = 'Function';
|
|
|
7
7
|
export const LABEL_METHOD = 'Method';
|
|
8
8
|
export const LABEL_CONSTRUCTOR = 'Constructor';
|
|
9
9
|
export const LABEL_CLASS = 'Class';
|
|
10
|
+
export const LABEL_PROTOCOL = 'Protocol';
|
|
11
|
+
export const LABEL_CATEGORY = 'Category';
|
|
10
12
|
export const LABEL_INTERFACE = 'Interface';
|
|
11
13
|
export const LABEL_STRUCT = 'Struct';
|
|
12
14
|
export const LABEL_ENUM = 'Enum';
|
|
@@ -38,6 +40,8 @@ export const CHUNKABLE_LABELS = [
|
|
|
38
40
|
LABEL_METHOD,
|
|
39
41
|
LABEL_CONSTRUCTOR,
|
|
40
42
|
LABEL_CLASS,
|
|
43
|
+
LABEL_PROTOCOL,
|
|
44
|
+
LABEL_CATEGORY,
|
|
41
45
|
LABEL_INTERFACE,
|
|
42
46
|
LABEL_STRUCT,
|
|
43
47
|
LABEL_ENUM,
|
|
@@ -131,6 +135,20 @@ export const CHUNKING_RULES = {
|
|
|
131
135
|
groupFields: false,
|
|
132
136
|
structuralTextMode: STRUCTURAL_TEXT_MODE_DECLARATION,
|
|
133
137
|
},
|
|
138
|
+
[LABEL_PROTOCOL]: {
|
|
139
|
+
mode: CHUNK_MODE_AST_DECLARATION,
|
|
140
|
+
includePrefix: true,
|
|
141
|
+
includeSuffix: false,
|
|
142
|
+
groupFields: false,
|
|
143
|
+
structuralTextMode: STRUCTURAL_TEXT_MODE_NONE,
|
|
144
|
+
},
|
|
145
|
+
[LABEL_CATEGORY]: {
|
|
146
|
+
mode: CHUNK_MODE_AST_DECLARATION,
|
|
147
|
+
includePrefix: true,
|
|
148
|
+
includeSuffix: false,
|
|
149
|
+
groupFields: true,
|
|
150
|
+
structuralTextMode: STRUCTURAL_TEXT_MODE_NONE,
|
|
151
|
+
},
|
|
134
152
|
[LABEL_STRUCT]: {
|
|
135
153
|
mode: CHUNK_MODE_AST_DECLARATION,
|
|
136
154
|
includePrefix: true,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git ref-name validation used by both the CLI and the HTTP analyze route.
|
|
3
|
+
*
|
|
4
|
+
* Lives in `core/` so `server/api.ts` does not import `cli/analyze-config`
|
|
5
|
+
* (that import closed a cli → server → cli cycle: `cli/serve.ts` already
|
|
6
|
+
* imports `createServer`). The CLI keeps a thin wrapper that rethrows
|
|
7
|
+
* {@link InvalidBranchError} as `GitNexusRcError`.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Thrown when a user-supplied branch name fails {@link validateBranchName}.
|
|
11
|
+
* Callers at a product boundary map this to their own error type (CLI:
|
|
12
|
+
* `GitNexusRcError`; HTTP: 400).
|
|
13
|
+
*/
|
|
14
|
+
export declare class InvalidBranchError extends Error {
|
|
15
|
+
constructor(message: string);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Validate a user-supplied branch name. Returns the trimmed name or throws
|
|
19
|
+
* {@link InvalidBranchError}. Conservative but accepts the shapes real
|
|
20
|
+
* branches use (`feature/foo-bar`, `release/1.2`, `develop`).
|
|
21
|
+
*/
|
|
22
|
+
export declare function validateBranchName(value: string, source: string): string;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git ref-name validation used by both the CLI and the HTTP analyze route.
|
|
3
|
+
*
|
|
4
|
+
* Lives in `core/` so `server/api.ts` does not import `cli/analyze-config`
|
|
5
|
+
* (that import closed a cli → server → cli cycle: `cli/serve.ts` already
|
|
6
|
+
* imports `createServer`). The CLI keeps a thin wrapper that rethrows
|
|
7
|
+
* {@link InvalidBranchError} as `GitNexusRcError`.
|
|
8
|
+
*/
|
|
9
|
+
/** Git refs longer than this are almost certainly a mistake / injection attempt. */
|
|
10
|
+
const BRANCH_MAX_LENGTH = 255;
|
|
11
|
+
/**
|
|
12
|
+
* Thrown when a user-supplied branch name fails {@link validateBranchName}.
|
|
13
|
+
* Callers at a product boundary map this to their own error type (CLI:
|
|
14
|
+
* `GitNexusRcError`; HTTP: 400).
|
|
15
|
+
*/
|
|
16
|
+
export class InvalidBranchError extends Error {
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = 'InvalidBranchError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Reject control characters and hidden / bidirectional Unicode in a string
|
|
24
|
+
* value. These have no legitimate place in a branch name and would otherwise
|
|
25
|
+
* let a committed config or HTTP body smuggle invisible controls into
|
|
26
|
+
* generated AGENTS.md / CLAUDE.md content.
|
|
27
|
+
*/
|
|
28
|
+
const isHiddenOrControl = (codePoint) => codePoint < 0x20 ||
|
|
29
|
+
codePoint === 0x7f ||
|
|
30
|
+
(codePoint >= 0x200b && codePoint <= 0x200f) || // zero-width + LRM/RLM
|
|
31
|
+
(codePoint >= 0x202a && codePoint <= 0x202e) || // bidi embeddings/overrides
|
|
32
|
+
(codePoint >= 0x2060 && codePoint <= 0x2064) || // word-joiner + invisible math
|
|
33
|
+
(codePoint >= 0x2066 && codePoint <= 0x206f) || // bidi isolates + deprecated
|
|
34
|
+
codePoint === 0xfeff; // BOM / zero-width no-break space
|
|
35
|
+
const assertNoHiddenChars = (value, source) => {
|
|
36
|
+
for (const ch of value) {
|
|
37
|
+
const cp = ch.codePointAt(0);
|
|
38
|
+
if (cp !== undefined && isHiddenOrControl(cp)) {
|
|
39
|
+
throw new InvalidBranchError(`${source}: value contains control or hidden/bidirectional characters, which are not allowed.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Validate a user-supplied branch name. Returns the trimmed name or throws
|
|
45
|
+
* {@link InvalidBranchError}. Conservative but accepts the shapes real
|
|
46
|
+
* branches use (`feature/foo-bar`, `release/1.2`, `develop`).
|
|
47
|
+
*/
|
|
48
|
+
export function validateBranchName(value, source) {
|
|
49
|
+
const trimmed = value.trim();
|
|
50
|
+
if (!trimmed) {
|
|
51
|
+
throw new InvalidBranchError(`${source}: branch name must not be empty.`);
|
|
52
|
+
}
|
|
53
|
+
if (trimmed.length > BRANCH_MAX_LENGTH) {
|
|
54
|
+
throw new InvalidBranchError(`${source}: branch name is too long (max ${BRANCH_MAX_LENGTH}).`);
|
|
55
|
+
}
|
|
56
|
+
assertNoHiddenChars(trimmed, source);
|
|
57
|
+
if (/\s/.test(trimmed)) {
|
|
58
|
+
throw new InvalidBranchError(`${source}: branch name must not contain whitespace.`);
|
|
59
|
+
}
|
|
60
|
+
// git ref-name rules (subset): reject characters git itself forbids in refs.
|
|
61
|
+
if (/[~^:?*[\\]/.test(trimmed)) {
|
|
62
|
+
throw new InvalidBranchError(`${source}: branch name contains characters not allowed in a git ref (~ ^ : ? * [ \\).`);
|
|
63
|
+
}
|
|
64
|
+
if (trimmed.startsWith('-')) {
|
|
65
|
+
throw new InvalidBranchError(`${source}: branch name must not start with "-".`);
|
|
66
|
+
}
|
|
67
|
+
// Force-refspec prefix (`git fetch origin +main` / `+refs/heads/main:…`).
|
|
68
|
+
// Rejected here so neither the CLI nor HTTP can pass a force-update refspec
|
|
69
|
+
// through as a "branch" (#3199 review, defense in depth).
|
|
70
|
+
if (trimmed.startsWith('+')) {
|
|
71
|
+
throw new InvalidBranchError(`${source}: branch name must not start with "+".`);
|
|
72
|
+
}
|
|
73
|
+
// The symbolic ref HEAD (case-sensitive). A repo can have a branch named
|
|
74
|
+
// `head`; git itself treats only `HEAD` as the current-commit alias.
|
|
75
|
+
if (trimmed === 'HEAD') {
|
|
76
|
+
throw new InvalidBranchError(`${source}: branch name must not be "HEAD".`);
|
|
77
|
+
}
|
|
78
|
+
if (trimmed.includes('..')) {
|
|
79
|
+
throw new InvalidBranchError(`${source}: branch name must not contain "..".`);
|
|
80
|
+
}
|
|
81
|
+
// The remaining `git check-ref-format` rules. Without these the validator
|
|
82
|
+
// accepted refs git itself refuses (`feature.lock`, `/feature`, `feature/`,
|
|
83
|
+
// `feature//next`, `@`, `.hidden`), so the failure surfaced later from the
|
|
84
|
+
// git subprocess instead of here. No real branch can violate them — git
|
|
85
|
+
// could not have created one — so nothing that works today starts failing.
|
|
86
|
+
if (trimmed.endsWith('.lock') || trimmed.split('/').some((part) => part.endsWith('.lock'))) {
|
|
87
|
+
throw new InvalidBranchError(`${source}: branch name must not end with ".lock".`);
|
|
88
|
+
}
|
|
89
|
+
if (trimmed.startsWith('/') || trimmed.endsWith('/')) {
|
|
90
|
+
throw new InvalidBranchError(`${source}: branch name must not start or end with "/".`);
|
|
91
|
+
}
|
|
92
|
+
if (trimmed.includes('//')) {
|
|
93
|
+
throw new InvalidBranchError(`${source}: branch name must not contain consecutive slashes.`);
|
|
94
|
+
}
|
|
95
|
+
if (trimmed === '@') {
|
|
96
|
+
throw new InvalidBranchError(`${source}: branch name must not be the single character "@".`);
|
|
97
|
+
}
|
|
98
|
+
if (trimmed.includes('@{')) {
|
|
99
|
+
throw new InvalidBranchError(`${source}: branch name must not contain "@{".`);
|
|
100
|
+
}
|
|
101
|
+
if (trimmed.endsWith('.') || trimmed.split('/').some((part) => part.startsWith('.'))) {
|
|
102
|
+
throw new InvalidBranchError(`${source}: branch name must not end with "." or have a path component starting with ".".`);
|
|
103
|
+
}
|
|
104
|
+
// Git permits a backtick in a ref, but the branch is embedded inside a
|
|
105
|
+
// Markdown inline-code span in the generated AGENTS.md/CLAUDE.md regression
|
|
106
|
+
// example, where a backtick would close the span early and let the rest of
|
|
107
|
+
// the template render as instruction text. Reject it at this single
|
|
108
|
+
// chokepoint so all three tiers (CLI flag, .gitnexusrc, auto-detect via
|
|
109
|
+
// sanitizeDetectedBranch) are covered (#1996 tri-review P1).
|
|
110
|
+
if (trimmed.includes('`')) {
|
|
111
|
+
throw new InvalidBranchError(`${source}: branch name must not contain a backtick (it would break the generated Markdown).`);
|
|
112
|
+
}
|
|
113
|
+
return trimmed;
|
|
114
|
+
}
|
|
@@ -8,6 +8,12 @@ import path from 'path';
|
|
|
8
8
|
import { readRegistry } from '../storage/repo-manager.js';
|
|
9
9
|
import { findGitRootByDotGit, getCurrentCommit, getRemoteUrl } from '../storage/git.js';
|
|
10
10
|
const execFileAsync = promisify(execFile);
|
|
11
|
+
/**
|
|
12
|
+
* Ceiling for one `git rev-list` staleness probe. Generous for the local
|
|
13
|
+
* history walk this is, and short enough that an unresponsive working tree
|
|
14
|
+
* degrades to "not stale" quickly rather than holding a request open.
|
|
15
|
+
*/
|
|
16
|
+
const STALENESS_TIMEOUT_MS = 5_000;
|
|
11
17
|
/**
|
|
12
18
|
* Check how many commits the index is behind HEAD (synchronous; uses git CLI).
|
|
13
19
|
*/
|
|
@@ -46,6 +52,14 @@ export async function checkStalenessAsync(repoPath, lastCommit) {
|
|
|
46
52
|
cwd: repoPath,
|
|
47
53
|
encoding: 'utf-8',
|
|
48
54
|
windowsHide: true,
|
|
55
|
+
// The catch below fails closed on every git ERROR, but a hang is not an
|
|
56
|
+
// error — it is silence, and without a bound this await never settles.
|
|
57
|
+
// A working tree on a disconnected network mount or behind a stuck lock
|
|
58
|
+
// does exactly that, and `/api/repos` fans this out once per registered
|
|
59
|
+
// repo, so one unreachable mount could hold the whole listing open
|
|
60
|
+
// (#3232 review). The timeout kills the child and rejects, which routes
|
|
61
|
+
// a hang into the same fail-closed "not stale" answer as a bad SHA.
|
|
62
|
+
timeout: STALENESS_TIMEOUT_MS,
|
|
49
63
|
});
|
|
50
64
|
const commitsBehind = parseInt(stdout.trim(), 10) || 0;
|
|
51
65
|
if (commitsBehind > 0) {
|
|
@@ -5,7 +5,7 @@ type GraphqlOperationKind = 'query' | 'mutation' | 'subscription';
|
|
|
5
5
|
type GeneratedSymbolIndex = Map<string, Parser.SyntaxNode[]>;
|
|
6
6
|
export declare const RESOLVE_METHOD_QUERY = "\nMATCH (n)\nWHERE labels(n) IN ['Method','Function','Property','CodeElement']\n AND n.name = $name AND n.filePath = $filePath AND n.startLine = $startLine AND n.id <> ''\nRETURN n.id AS uid, n.name AS name, n.filePath AS filePath\nORDER BY n.id ASC\nLIMIT 2";
|
|
7
7
|
export declare const RESOLVE_GENERATED_SYMBOL_QUERY = "\nMATCH (n)\nWHERE labels(n) IN ['Const','Variable','Function','Method','CodeElement']\n AND n.name = $name AND n.filePath <> '' AND n.id <> ''\nRETURN n.id AS uid, n.name AS name, n.filePath AS filePath\nORDER BY n.id ASC\nLIMIT 2";
|
|
8
|
-
export declare function hasGeneratedDocumentProof(initializer: Parser.SyntaxNode, operationKind: GraphqlOperationKind, operationName: string, requiredFields: readonly string[]): boolean;
|
|
8
|
+
export declare function hasGeneratedDocumentProof(initializer: Parser.SyntaxNode, operationKind: GraphqlOperationKind, operationName: string, requiredFields: readonly string[], declarators?: GeneratedSymbolIndex): boolean;
|
|
9
9
|
export declare function indexGeneratedDeclarators(root: Parser.SyntaxNode): GeneratedSymbolIndex;
|
|
10
10
|
export declare class GraphqlExtractor implements ContractExtractor {
|
|
11
11
|
type: "graphql";
|
|
@@ -58,6 +58,130 @@ function unquote(text) {
|
|
|
58
58
|
const value = trimmed.slice(1, -1);
|
|
59
59
|
return value.includes('${') ? null : value;
|
|
60
60
|
}
|
|
61
|
+
const SIMPLE_TEMPLATE_ESCAPES = {
|
|
62
|
+
b: '\b',
|
|
63
|
+
f: '\f',
|
|
64
|
+
n: '\n',
|
|
65
|
+
r: '\r',
|
|
66
|
+
t: '\t',
|
|
67
|
+
v: '\v',
|
|
68
|
+
'0': '\0',
|
|
69
|
+
"'": "'",
|
|
70
|
+
'"': '"',
|
|
71
|
+
'\\': '\\',
|
|
72
|
+
'`': '`',
|
|
73
|
+
};
|
|
74
|
+
/** Graph Method `startLine` is the 0-based wrapper row (see line-base.ts). */
|
|
75
|
+
function providerMemberStartLine(member) {
|
|
76
|
+
// Class-field arrows are `@declaration.property`; parse-worker falls back to
|
|
77
|
+
// the `public_field_definition` wrapper (decorator row when it is a child),
|
|
78
|
+
// not the initializer. Do not probe the `value` child.
|
|
79
|
+
return member.startPosition.row;
|
|
80
|
+
}
|
|
81
|
+
/** tree-sitter `escape_sequence.text` is the source spelling (`\\n`), not the JS value. */
|
|
82
|
+
function decodeEscapeSequence(text) {
|
|
83
|
+
if (!text.startsWith('\\') || text.length < 2)
|
|
84
|
+
return null;
|
|
85
|
+
const escaped = text.slice(1);
|
|
86
|
+
// Tagged-template cooked value: LineContinuation is empty; `\8`/`\9` and
|
|
87
|
+
// LegacyOctalEscapeSequence (`\1`–`\7`, `\00`…) make cooked undefined.
|
|
88
|
+
if (/^[\n\r\u2028\u2029]$/.test(escaped) || escaped === '\r\n')
|
|
89
|
+
return '';
|
|
90
|
+
if (/^[1-9]$/.test(escaped) || /^[0-7]{2,3}$/.test(escaped))
|
|
91
|
+
return null;
|
|
92
|
+
if (escaped.length === 1)
|
|
93
|
+
return SIMPLE_TEMPLATE_ESCAPES[escaped] ?? escaped;
|
|
94
|
+
if (escaped[0] === 'x' && /^[0-9A-Fa-f]{2}$/.test(escaped.slice(1))) {
|
|
95
|
+
return String.fromCharCode(Number.parseInt(escaped.slice(1), 16));
|
|
96
|
+
}
|
|
97
|
+
if (escaped.startsWith('u{') && escaped.endsWith('}')) {
|
|
98
|
+
const hex = escaped.slice(2, -1);
|
|
99
|
+
if (!/^[0-9A-Fa-f]{1,6}$/.test(hex))
|
|
100
|
+
return null;
|
|
101
|
+
const codePoint = Number.parseInt(hex, 16);
|
|
102
|
+
if (codePoint > 0x10ffff)
|
|
103
|
+
return null;
|
|
104
|
+
return String.fromCodePoint(codePoint);
|
|
105
|
+
}
|
|
106
|
+
if (escaped[0] === 'u' && /^[0-9A-Fa-f]{4}$/.test(escaped.slice(1))) {
|
|
107
|
+
return String.fromCharCode(Number.parseInt(escaped.slice(1), 16));
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
function substitutionIdentifier(substitution) {
|
|
112
|
+
if (substitution.namedChildren.length !== 1)
|
|
113
|
+
return null;
|
|
114
|
+
const expr = unwrapExpression(substitution.namedChildren[0]);
|
|
115
|
+
return expr.type === 'identifier' ? expr.text : null;
|
|
116
|
+
}
|
|
117
|
+
function isGraphqlTagCall(call) {
|
|
118
|
+
const callee = call.childForFieldName('function');
|
|
119
|
+
return callee?.type === 'identifier' && callee.text === 'gql';
|
|
120
|
+
}
|
|
121
|
+
function pascalCaseGraphqlName(name) {
|
|
122
|
+
return name
|
|
123
|
+
.split(/[^A-Za-z0-9]+/)
|
|
124
|
+
.filter((part) => part.length > 0)
|
|
125
|
+
.map((part) => `${part[0].toUpperCase()}${part.slice(1)}`)
|
|
126
|
+
.join('');
|
|
127
|
+
}
|
|
128
|
+
function uniqueStaticSource(name, declarators, resolving, cache) {
|
|
129
|
+
if (cache.has(name))
|
|
130
|
+
return cache.get(name) ?? null;
|
|
131
|
+
if (resolving.has(name) || resolving.size >= MAX_GRAPHQL_TRAVERSAL_DEPTH)
|
|
132
|
+
return null;
|
|
133
|
+
const values = declarators.get(name) ?? [];
|
|
134
|
+
if (values.length === 0) {
|
|
135
|
+
cache.set(name, null);
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
resolving.add(name);
|
|
139
|
+
const sources = new Set();
|
|
140
|
+
for (const value of values) {
|
|
141
|
+
const source = staticGraphqlSource(value, declarators, resolving, cache);
|
|
142
|
+
// A dynamic or unprovable sibling makes the name ambiguous — do not pick
|
|
143
|
+
// the one static spelling and ignore the rest.
|
|
144
|
+
if (source === null) {
|
|
145
|
+
resolving.delete(name);
|
|
146
|
+
cache.set(name, null);
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
sources.add(source);
|
|
150
|
+
}
|
|
151
|
+
resolving.delete(name);
|
|
152
|
+
const unique = sources.size === 1 ? [...sources][0] : null;
|
|
153
|
+
cache.set(name, unique);
|
|
154
|
+
return unique;
|
|
155
|
+
}
|
|
156
|
+
function interpolatedTemplateSource(template, declarators, resolving, cache) {
|
|
157
|
+
let out = '';
|
|
158
|
+
for (const child of template.namedChildren) {
|
|
159
|
+
if (child.type === 'string_fragment') {
|
|
160
|
+
out += child.text;
|
|
161
|
+
}
|
|
162
|
+
else if (child.type === 'escape_sequence') {
|
|
163
|
+
const decoded = decodeEscapeSequence(child.text);
|
|
164
|
+
if (decoded === null)
|
|
165
|
+
return null;
|
|
166
|
+
out += decoded;
|
|
167
|
+
}
|
|
168
|
+
else if (child.type === 'template_substitution') {
|
|
169
|
+
const name = substitutionIdentifier(child);
|
|
170
|
+
if (!name)
|
|
171
|
+
return null;
|
|
172
|
+
const inlined = uniqueStaticSource(name, declarators, resolving, cache);
|
|
173
|
+
if (inlined === null)
|
|
174
|
+
return null;
|
|
175
|
+
out += inlined;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
if (out.length > MAX_GRAPHQL_TOKENS)
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
61
185
|
function unwrapExpression(node) {
|
|
62
186
|
let current = node;
|
|
63
187
|
while (['as_expression', 'satisfies_expression', 'parenthesized_expression'].includes(current.type) &&
|
|
@@ -183,7 +307,7 @@ function parsedDocumentProof(source, operationKind, operationName, requiredField
|
|
|
183
307
|
}
|
|
184
308
|
return false;
|
|
185
309
|
}
|
|
186
|
-
function staticGraphqlSource(initializer) {
|
|
310
|
+
function staticGraphqlSource(initializer, declarators, resolving = new Set(), cache = new Map()) {
|
|
187
311
|
const value = unwrapExpression(initializer);
|
|
188
312
|
if (value.type === 'string') {
|
|
189
313
|
if (value.text.startsWith('"')) {
|
|
@@ -196,11 +320,22 @@ function staticGraphqlSource(initializer) {
|
|
|
196
320
|
}
|
|
197
321
|
return unquote(value.text);
|
|
198
322
|
}
|
|
199
|
-
if (value.type === 'template_string')
|
|
200
|
-
|
|
323
|
+
if (value.type === 'template_string') {
|
|
324
|
+
const hasSubstitution = value.namedChildren.some((child) => child.type === 'template_substitution');
|
|
325
|
+
if (!hasSubstitution)
|
|
326
|
+
return unquote(value.text);
|
|
327
|
+
return declarators ? interpolatedTemplateSource(value, declarators, resolving, cache) : null;
|
|
328
|
+
}
|
|
201
329
|
if (value.type === 'call_expression') {
|
|
202
330
|
const template = value.namedChildren.find((child) => child.type === 'template_string');
|
|
203
|
-
|
|
331
|
+
if (!template)
|
|
332
|
+
return null;
|
|
333
|
+
const hasSubstitution = template.namedChildren.some((child) => child.type === 'template_substitution');
|
|
334
|
+
// Interpolated reconstruction is the cooked template. Only `gql` is
|
|
335
|
+
// treated as preserving that source; String.raw / unknown tags stay fail-closed.
|
|
336
|
+
if (hasSubstitution && !isGraphqlTagCall(value))
|
|
337
|
+
return null;
|
|
338
|
+
return staticGraphqlSource(template, declarators, resolving, cache);
|
|
204
339
|
}
|
|
205
340
|
if (value.type !== 'new_expression')
|
|
206
341
|
return null;
|
|
@@ -209,12 +344,12 @@ function staticGraphqlSource(initializer) {
|
|
|
209
344
|
return null;
|
|
210
345
|
const args = value.childForFieldName('arguments');
|
|
211
346
|
const first = args?.namedChildren[0];
|
|
212
|
-
return first ? staticGraphqlSource(first) : null;
|
|
347
|
+
return first ? staticGraphqlSource(first, declarators, resolving, cache) : null;
|
|
213
348
|
}
|
|
214
|
-
export function hasGeneratedDocumentProof(initializer, operationKind, operationName, requiredFields) {
|
|
349
|
+
export function hasGeneratedDocumentProof(initializer, operationKind, operationName, requiredFields, declarators) {
|
|
215
350
|
if (!withinGeneratedAstBudget(initializer))
|
|
216
351
|
return false;
|
|
217
|
-
const staticSource = staticGraphqlSource(initializer);
|
|
352
|
+
const staticSource = staticGraphqlSource(initializer, declarators);
|
|
218
353
|
if (staticSource !== null) {
|
|
219
354
|
return parsedDocumentProof(staticSource, operationKind, operationName, requiredFields);
|
|
220
355
|
}
|
|
@@ -406,7 +541,11 @@ function rootFields(selectionSet, fragments) {
|
|
|
406
541
|
}
|
|
407
542
|
function generatedCandidates(operation) {
|
|
408
543
|
const name = operation.name?.value;
|
|
409
|
-
|
|
544
|
+
if (!name)
|
|
545
|
+
return [];
|
|
546
|
+
const exact = `${name}Document`;
|
|
547
|
+
const pascal = `${pascalCaseGraphqlName(name)}Document`;
|
|
548
|
+
return exact === pascal ? [exact] : [exact, pascal];
|
|
410
549
|
}
|
|
411
550
|
async function generatedDocumentMatches(repoPath, symbol, operationKind, operationName, requiredFields, cache) {
|
|
412
551
|
const normalizedPath = symbol.filePath.replace(/\\/g, '/');
|
|
@@ -417,7 +556,7 @@ async function generatedDocumentMatches(repoPath, symbol, operationKind, operati
|
|
|
417
556
|
}
|
|
418
557
|
const index = await pendingIndex;
|
|
419
558
|
const values = index?.get(symbol.name) ?? [];
|
|
420
|
-
return values.some((value) => hasGeneratedDocumentProof(value, operationKind, operationName, requiredFields));
|
|
559
|
+
return values.some((value) => hasGeneratedDocumentProof(value, operationKind, operationName, requiredFields, index ?? undefined));
|
|
421
560
|
}
|
|
422
561
|
async function buildGeneratedSymbolIndex(repoPath, filePath) {
|
|
423
562
|
const source = await readSafeBounded(repoPath, filePath, getMaxFileSizeBytes());
|
|
@@ -551,10 +690,7 @@ export class GraphqlExtractor {
|
|
|
551
690
|
const symbol = uniqueRealSymbol(await dbExecutor(RESOLVE_METHOD_QUERY, {
|
|
552
691
|
name: methodName,
|
|
553
692
|
filePath,
|
|
554
|
-
startLine: member
|
|
555
|
-
? (member.childForFieldName('value')?.startPosition.row ??
|
|
556
|
-
member.startPosition.row) + 1
|
|
557
|
-
: member.startPosition.row + 1,
|
|
693
|
+
startLine: providerMemberStartLine(member),
|
|
558
694
|
}));
|
|
559
695
|
if (!symbol)
|
|
560
696
|
continue;
|
|
@@ -4,7 +4,7 @@ export interface ManifestExtractResult {
|
|
|
4
4
|
contracts: StoredContract[];
|
|
5
5
|
crossLinks: CrossLink[];
|
|
6
6
|
}
|
|
7
|
-
export declare const CUSTOM_CONTRACT_RESOLVE_QUERY = "MATCH (n)\n WHERE labels(n) IN ['Function','Method','Class','Interface','Struct','Enum','Trait','Constructor','TypeAlias','Impl','Macro','Union','Typedef','Property','Record','Delegate','Annotation','Template','Const','Static','CodeElement']\n AND n.name = $symbolName\n RETURN n.id AS uid, n.name AS name, n.filePath AS filePath\n ORDER BY n.filePath ASC, n.id ASC\n LIMIT 1";
|
|
7
|
+
export declare const CUSTOM_CONTRACT_RESOLVE_QUERY = "MATCH (n)\n WHERE labels(n) IN ['Function','Method','Class','Protocol','Category','Interface','Struct','Enum','Trait','Constructor','TypeAlias','Impl','Macro','Union','Typedef','Property','Record','Delegate','Annotation','Template','Const','Static','CodeElement']\n AND n.name = $symbolName\n RETURN n.id AS uid, n.name AS name, n.filePath AS filePath\n ORDER BY n.filePath ASC, n.id ASC\n LIMIT 1";
|
|
8
8
|
/**
|
|
9
9
|
* Stable synthetic symbolUid for a manifest-declared contract whose target
|
|
10
10
|
* symbol could not be resolved against the per-repo graph (resolveSymbol
|
|
@@ -3,7 +3,7 @@ import { logger } from '../../logger.js';
|
|
|
3
3
|
// #2325 integration test can run the EXACT production query against a real
|
|
4
4
|
// LadybugDB — a hand-copied query string in the test would silently drift
|
|
5
5
|
// from this allowlist. Uses the `labels(n) IN [...]` allowlist form rather
|
|
6
|
-
// than a `MATCH (n:A|B)` disjunction: this
|
|
6
|
+
// than a `MATCH (n:A|B)` disjunction: this 23-label list contains the
|
|
7
7
|
// reserved-keyword labels `Macro` and `Union`, and LadybugDB's parser rejects
|
|
8
8
|
// a disjunction that names a reserved keyword (#2325) — which the resolver's
|
|
9
9
|
// try/catch then swallowed. `labels(n) IN` has no such collision.
|
|
@@ -12,7 +12,7 @@ import { logger } from '../../logger.js';
|
|
|
12
12
|
// two would widen which nodes resolve as contract symbols and must update the
|
|
13
13
|
// #2325 test, so they are intentionally kept separate for now.
|
|
14
14
|
export const CUSTOM_CONTRACT_RESOLVE_QUERY = `MATCH (n)
|
|
15
|
-
WHERE labels(n) IN ['Function','Method','Class','Interface','Struct','Enum','Trait','Constructor','TypeAlias','Impl','Macro','Union','Typedef','Property','Record','Delegate','Annotation','Template','Const','Static','CodeElement']
|
|
15
|
+
WHERE labels(n) IN ['Function','Method','Class','Protocol','Category','Interface','Struct','Enum','Trait','Constructor','TypeAlias','Impl','Macro','Union','Typedef','Property','Record','Delegate','Annotation','Template','Const','Static','CodeElement']
|
|
16
16
|
AND n.name = $symbolName
|
|
17
17
|
RETURN n.id AS uid, n.name AS name, n.filePath AS filePath
|
|
18
18
|
ORDER BY n.filePath ASC, n.id ASC
|