gitnexus 1.6.12-rc.19 → 1.6.12-rc.20
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/core/group/extractors/graphql-extractor.d.ts +1 -1
- package/dist/core/group/extractors/graphql-extractor.js +149 -13
- package/dist/core/ingestion/import-resolvers/node-workspace-packages.js +3 -2
- package/dist/core/ingestion/languages/typescript/tsconfig.js +3 -2
- package/package.json +1 -1
|
@@ -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;
|
|
@@ -268,13 +268,14 @@ export async function loadNodeWorkspacePackages(repoRoot) {
|
|
|
268
268
|
const scope = await loadWorkspaceScope(repoRoot);
|
|
269
269
|
const byName = new Map();
|
|
270
270
|
const queue = [{ dir: repoRoot, depth: 0 }];
|
|
271
|
+
let queueHead = 0;
|
|
271
272
|
let dirsScanned = 0;
|
|
272
|
-
while (queue.length
|
|
273
|
+
while (queueHead < queue.length) {
|
|
273
274
|
if (dirsScanned >= SCAN_MAX_DIRS) {
|
|
274
275
|
logger.warn(`[node] package.json scan of ${repoRoot} hit the ${SCAN_MAX_DIRS}-directory cap; workspace packages below it will not resolve`);
|
|
275
276
|
break;
|
|
276
277
|
}
|
|
277
|
-
const { dir, depth } = queue
|
|
278
|
+
const { dir, depth } = queue[queueHead++];
|
|
278
279
|
dirsScanned++;
|
|
279
280
|
let entries;
|
|
280
281
|
try {
|
|
@@ -234,9 +234,10 @@ async function firstReadableConfig(base) {
|
|
|
234
234
|
async function findTsconfigFiles(repoRoot) {
|
|
235
235
|
const found = [];
|
|
236
236
|
const queue = [{ dir: repoRoot, depth: 0 }];
|
|
237
|
+
let queueHead = 0;
|
|
237
238
|
let dirsScanned = 0;
|
|
238
|
-
while (queue.length
|
|
239
|
-
const { dir, depth } = queue
|
|
239
|
+
while (queueHead < queue.length && dirsScanned < SCAN_MAX_DIRS) {
|
|
240
|
+
const { dir, depth } = queue[queueHead++];
|
|
240
241
|
dirsScanned++;
|
|
241
242
|
let entries;
|
|
242
243
|
try {
|
package/package.json
CHANGED