gitnexus 1.6.11-rc.32 → 1.6.11-rc.33
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/ingestion/call-processor.js +3 -1
- package/dist/core/ingestion/frameworks/spring/annotation-arguments.js +8 -1
- package/dist/core/ingestion/languages/kotlin.js +6 -0
- package/dist/core/ingestion/route-extractors/kotlin-const-resolver.d.ts +5 -10
- package/dist/core/ingestion/route-extractors/kotlin-const-resolver.js +5 -10
- package/dist/core/ingestion/route-extractors/kotlin-spring.d.ts +13 -0
- package/dist/core/ingestion/route-extractors/kotlin-spring.js +266 -0
- package/dist/core/ingestion/workers/parse-worker.js +5 -1
- package/dist/storage/parse-cache.js +5 -1
- package/package.json +1 -1
|
@@ -324,7 +324,9 @@ export function resolveRouteHandlerSymbols(model, extractedRoutes, decoratorRout
|
|
|
324
324
|
: uniqueById(model.methods.lookupAllByOwner(owner.nodeId, member))?.nodeId;
|
|
325
325
|
};
|
|
326
326
|
const claim = (routePath, prefix, httpMethod, symbolId) => {
|
|
327
|
-
|
|
327
|
+
// An empty path is a valid, pathless mapping and normalizes to either `/`
|
|
328
|
+
// or its class/router prefix. Only null means the extractor had no route.
|
|
329
|
+
if (routePath === null)
|
|
328
330
|
return;
|
|
329
331
|
const url = normalizeExtractedRoutePath(routePath, prefix);
|
|
330
332
|
const key = routeNodeKey(normalizeRouteMethod(httpMethod), url);
|
|
@@ -123,7 +123,14 @@ export function parseSpringAnnotationArguments(annotationText) {
|
|
|
123
123
|
if (body.length === 0)
|
|
124
124
|
return [];
|
|
125
125
|
const rawArguments = splitTopLevel(body, ',');
|
|
126
|
-
if (rawArguments === null
|
|
126
|
+
if (rawArguments === null)
|
|
127
|
+
return null;
|
|
128
|
+
// Kotlin (and some formatters) allow a trailing comma. An empty *middle*
|
|
129
|
+
// argument is still invalid and fail-closed.
|
|
130
|
+
while (rawArguments.at(-1)?.length === 0) {
|
|
131
|
+
rawArguments.pop();
|
|
132
|
+
}
|
|
133
|
+
if (rawArguments.some((argument) => argument.length === 0))
|
|
127
134
|
return null;
|
|
128
135
|
const parsed = [];
|
|
129
136
|
for (const raw of rawArguments) {
|
|
@@ -30,6 +30,8 @@ import { kotlinVariableConfig } from '../variable-extractors/configs/jvm.js';
|
|
|
30
30
|
import { collectKotlinCaptureSideChannel, emitKotlinScopeCaptures, interpretKotlinImport, interpretKotlinTypeBinding, kotlinArityCompatibility, kotlinBindingScopeFor, kotlinImportOwningScope, kotlinMergeBindings, kotlinReceiverBinding, } from './kotlin/index.js';
|
|
31
31
|
import { synthesizeLombokAccessors } from './kotlin/lombok-synthesizer.js';
|
|
32
32
|
import { extractKotlinRuntimeSymbolProperties, kotlinRuntimeSymbolStrategy, } from './kotlin/spring-actuator.js';
|
|
33
|
+
import { extractKotlinSpringRoutes } from '../route-extractors/kotlin-spring.js';
|
|
34
|
+
import { extractKotlinModuleConstants, foldKotlinOperands, } from '../route-extractors/kotlin-const-resolver.js';
|
|
33
35
|
/** Check if a Kotlin function_declaration capture is inside a class_body (i.e., a method).
|
|
34
36
|
* Kotlin grammar uses function_declaration for both top-level functions and class methods.
|
|
35
37
|
* Returns true when the captured definition node has a class_body ancestor. */
|
|
@@ -188,4 +190,8 @@ export const kotlinProvider = defineLanguage({
|
|
|
188
190
|
receiverBinding: kotlinReceiverBinding,
|
|
189
191
|
arityCompatibility: kotlinArityCompatibility,
|
|
190
192
|
synthesizeStructureMembers: synthesizeLombokAccessors,
|
|
193
|
+
// ── Spring decorator routes + composed path constants (#3130) ──
|
|
194
|
+
extractDecoratorRoutes: extractKotlinSpringRoutes,
|
|
195
|
+
extractModuleConstants: extractKotlinModuleConstants,
|
|
196
|
+
foldRoutePathOperands: foldKotlinOperands,
|
|
191
197
|
});
|
|
@@ -123,16 +123,11 @@
|
|
|
123
123
|
*
|
|
124
124
|
* WHERE THIS IS WIRED. Java reaches its binding from BOTH layers: the group
|
|
125
125
|
* extractor (`group/extractors/http-patterns/java.ts`) and the ingestion
|
|
126
|
-
* provider (`languages/java.ts
|
|
127
|
-
* `
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* is bound to `tree-sitter-java` and its node types. Declaring the constant
|
|
132
|
-
* hooks on the Kotlin provider today would harvest a map on every Kotlin file
|
|
133
|
-
* that nothing consumes. An ingestion-side Kotlin route extractor is the
|
|
134
|
-
* prerequisite; when it lands, this binding is what its provider hooks should
|
|
135
|
-
* point at, and no change here is needed.
|
|
126
|
+
* provider (`languages/java.ts`). Kotlin now does the same: the group extractor
|
|
127
|
+
* (`group/extractors/http-patterns/kotlin.ts`) plus `languages/kotlin.ts`
|
|
128
|
+
* (`extractDecoratorRoutes`, `extractModuleConstants`, `foldRoutePathOperands`).
|
|
129
|
+
* The dedicated ingestion walker is `route-extractors/kotlin-spring.ts`; it
|
|
130
|
+
* does not reuse Java `spring.ts`.
|
|
136
131
|
*/
|
|
137
132
|
import type Parser from 'tree-sitter';
|
|
138
133
|
import { type ModuleConstants, type Operand, type RepoConstants } from './constant-resolver.js';
|
|
@@ -123,16 +123,11 @@
|
|
|
123
123
|
*
|
|
124
124
|
* WHERE THIS IS WIRED. Java reaches its binding from BOTH layers: the group
|
|
125
125
|
* extractor (`group/extractors/http-patterns/java.ts`) and the ingestion
|
|
126
|
-
* provider (`languages/java.ts
|
|
127
|
-
* `
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* is bound to `tree-sitter-java` and its node types. Declaring the constant
|
|
132
|
-
* hooks on the Kotlin provider today would harvest a map on every Kotlin file
|
|
133
|
-
* that nothing consumes. An ingestion-side Kotlin route extractor is the
|
|
134
|
-
* prerequisite; when it lands, this binding is what its provider hooks should
|
|
135
|
-
* point at, and no change here is needed.
|
|
126
|
+
* provider (`languages/java.ts`). Kotlin now does the same: the group extractor
|
|
127
|
+
* (`group/extractors/http-patterns/kotlin.ts`) plus `languages/kotlin.ts`
|
|
128
|
+
* (`extractDecoratorRoutes`, `extractModuleConstants`, `foldRoutePathOperands`).
|
|
129
|
+
* The dedicated ingestion walker is `route-extractors/kotlin-spring.ts`; it
|
|
130
|
+
* does not reuse Java `spring.ts`.
|
|
136
131
|
*/
|
|
137
132
|
import { unquoteSpringLiteral } from './spring-shared.js';
|
|
138
133
|
import { MAX_FOLD_LENGTH, unfoldableDeclarationsOf, } from './constant-resolver.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kotlin Spring MVC route annotations for the ingestion pipeline (#3130).
|
|
3
|
+
*
|
|
4
|
+
* This module only walks a caller-provided tree. In particular, it does not
|
|
5
|
+
* import or load tree-sitter-kotlin at module initialization, so platforms
|
|
6
|
+
* without the optional grammar can still load the language-provider registry.
|
|
7
|
+
*/
|
|
8
|
+
import type Parser from 'tree-sitter';
|
|
9
|
+
import type { ExtractedDecoratorRoute } from '../workers/parse-worker.js';
|
|
10
|
+
/**
|
|
11
|
+
* Extract direct Spring handler methods from concrete Kotlin RestControllers.
|
|
12
|
+
*/
|
|
13
|
+
export declare function extractKotlinSpringRoutes(tree: Parser.Tree, filePath: string, lineOffset?: number): ExtractedDecoratorRoute[];
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { intersectSpringHttpMethods, springAnnotationHttpMethods, unquoteSpringLiteral, } from './spring-shared.js';
|
|
2
|
+
import { extractKotlinModuleConstants, parseKotlinConstOperands, unfoldableDeclarationsOf, unquoteKotlinIdentifier, } from './kotlin-const-resolver.js';
|
|
3
|
+
/** Direct declaration annotations in source order. */
|
|
4
|
+
function declarationAnnotations(node) {
|
|
5
|
+
const modifiers = node.namedChildren.find((child) => child.type === 'modifiers');
|
|
6
|
+
return modifiers?.namedChildren.filter((child) => child.type === 'annotation') ?? [];
|
|
7
|
+
}
|
|
8
|
+
/** `@Foo`, `@Foo(...)`, or `@a.b.Foo(...)` → `Foo`. */
|
|
9
|
+
function annotationName(annotation) {
|
|
10
|
+
const constructor = annotation.namedChildren.find((child) => child.type === 'constructor_invocation');
|
|
11
|
+
const userType = annotation.namedChildren.find((child) => child.type === 'user_type') ??
|
|
12
|
+
constructor?.namedChildren.find((child) => child.type === 'user_type');
|
|
13
|
+
const identifiers = userType?.namedChildren.filter((child) => child.type === 'type_identifier') ?? [];
|
|
14
|
+
const identifier = identifiers.at(-1);
|
|
15
|
+
return identifier ? unquoteKotlinIdentifier(identifier.text) : null;
|
|
16
|
+
}
|
|
17
|
+
function annotationArguments(annotation) {
|
|
18
|
+
const constructor = annotation.namedChildren.find((child) => child.type === 'constructor_invocation');
|
|
19
|
+
const values = constructor?.namedChildren.find((child) => child.type === 'value_arguments');
|
|
20
|
+
return values?.namedChildren.filter((child) => child.type === 'value_argument') ?? [];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Kotlin represents positional and named annotation arguments with the same
|
|
24
|
+
* `value_argument` node. A direct `=` token distinguishes the named form.
|
|
25
|
+
*/
|
|
26
|
+
function readAnnotationArgument(argument) {
|
|
27
|
+
const named = argument.children.some((child) => child.type === '=');
|
|
28
|
+
if (!named) {
|
|
29
|
+
const expression = argument.namedChild(0);
|
|
30
|
+
return expression ? { expression } : null;
|
|
31
|
+
}
|
|
32
|
+
const key = argument.namedChild(0);
|
|
33
|
+
const expression = argument.namedChild(1);
|
|
34
|
+
if (key?.type !== 'simple_identifier' || !expression)
|
|
35
|
+
return null;
|
|
36
|
+
return { name: unquoteKotlinIdentifier(key.text), expression };
|
|
37
|
+
}
|
|
38
|
+
function routeArguments(annotation) {
|
|
39
|
+
const out = [];
|
|
40
|
+
for (const argument of annotationArguments(annotation)) {
|
|
41
|
+
const parsed = readAnnotationArgument(argument);
|
|
42
|
+
if (!parsed)
|
|
43
|
+
return null;
|
|
44
|
+
if (parsed.name === undefined || parsed.name === 'path' || parsed.name === 'value') {
|
|
45
|
+
out.push(parsed);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/** A fully static Kotlin string literal: no `$name` or `${expr}` children. */
|
|
51
|
+
function isPlainStringLiteral(node) {
|
|
52
|
+
return (node.type === 'string_literal' &&
|
|
53
|
+
node.namedChildren.every((child) => child.type === 'string_content'));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Empty `[]` / `arrayOf()` is Spring "no path", not an unresolvable prefix.
|
|
57
|
+
* tree-sitter-kotlin may put a zero-width recovery child inside `[]`.
|
|
58
|
+
*/
|
|
59
|
+
function isEmptyKotlinPathCollection(node) {
|
|
60
|
+
if (node.type === 'collection_literal') {
|
|
61
|
+
return node.namedChildren.every((child) => child.text.length === 0);
|
|
62
|
+
}
|
|
63
|
+
if (node.type !== 'call_expression')
|
|
64
|
+
return false;
|
|
65
|
+
const callee = node.namedChild(0);
|
|
66
|
+
if (callee?.type !== 'simple_identifier' || unquoteKotlinIdentifier(callee.text) !== 'arrayOf') {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
const suffix = node.namedChildren.find((child) => child.type === 'call_suffix');
|
|
70
|
+
const args = suffix?.namedChildren.find((child) => child.type === 'value_arguments');
|
|
71
|
+
if (!args)
|
|
72
|
+
return true;
|
|
73
|
+
return args.namedChildren.every((child) => child.type !== 'value_argument');
|
|
74
|
+
}
|
|
75
|
+
function isKotlinInterface(node) {
|
|
76
|
+
return node.children.some((child) => child.type === 'interface');
|
|
77
|
+
}
|
|
78
|
+
function isAbstractOrSealedClass(node) {
|
|
79
|
+
const modifiers = node.namedChildren.find((child) => child.type === 'modifiers');
|
|
80
|
+
return (modifiers?.namedChildren.some((child) => {
|
|
81
|
+
if (child.type !== 'inheritance_modifier' && child.type !== 'class_modifier') {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
const text = child.text.trim();
|
|
85
|
+
return text === 'abstract' || text === 'sealed';
|
|
86
|
+
}) === true);
|
|
87
|
+
}
|
|
88
|
+
function directFunctions(node) {
|
|
89
|
+
const body = node.namedChildren.find((child) => child.type === 'class_body');
|
|
90
|
+
return body?.namedChildren.filter((child) => child.type === 'function_declaration') ?? [];
|
|
91
|
+
}
|
|
92
|
+
function functionName(node) {
|
|
93
|
+
const field = node.childForFieldName('name');
|
|
94
|
+
const identifier = field?.type === 'simple_identifier'
|
|
95
|
+
? field
|
|
96
|
+
: node.namedChildren.find((child) => child.type === 'simple_identifier');
|
|
97
|
+
return identifier ? unquoteKotlinIdentifier(identifier.text) : null;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* `springAnnotationHttpMethods` parses Java `{A, B}` collections.
|
|
101
|
+
* Translate only Kotlin `method = [A, B]` before delegating.
|
|
102
|
+
*/
|
|
103
|
+
function kotlinSpringHttpMethods(name, annotation) {
|
|
104
|
+
if (name !== 'RequestMapping')
|
|
105
|
+
return springAnnotationHttpMethods(name, annotation.text);
|
|
106
|
+
const normalized = annotation.text.replace(/(\bmethod\s*=\s*)\[([^\]]*)\]/gs, (_match, assignment, values) => `${assignment}{${values}}`);
|
|
107
|
+
return springAnnotationHttpMethods(name, normalized);
|
|
108
|
+
}
|
|
109
|
+
function typeName(node) {
|
|
110
|
+
const identifier = node.children.find((child) => child.type === 'type_identifier');
|
|
111
|
+
return identifier ? unquoteKotlinIdentifier(identifier.text) : null;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Qualified enclosing type paths, innermost first. Kotlin companion members
|
|
115
|
+
* are keyed through their enclosing class, so companion_object itself adds no
|
|
116
|
+
* segment.
|
|
117
|
+
*/
|
|
118
|
+
function enclosingTypeNames(node) {
|
|
119
|
+
const simpleNames = [];
|
|
120
|
+
for (let current = node.parent; current; current = current.parent) {
|
|
121
|
+
if (current.type !== 'class_declaration' && current.type !== 'object_declaration')
|
|
122
|
+
continue;
|
|
123
|
+
const name = typeName(current);
|
|
124
|
+
if (name)
|
|
125
|
+
simpleNames.push(name);
|
|
126
|
+
}
|
|
127
|
+
return simpleNames.map((_, index) => simpleNames.slice(index).reverse().join('.'));
|
|
128
|
+
}
|
|
129
|
+
function declarationExists(constants, name) {
|
|
130
|
+
return (constants.literals.has(name) ||
|
|
131
|
+
constants.exprs.has(name) ||
|
|
132
|
+
unfoldableDeclarationsOf(constants).has(name));
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The provider fold hook has a language-neutral three-argument signature and
|
|
136
|
+
* cannot receive a Kotlin reference site's enclosing type chain. Qualify only
|
|
137
|
+
* names whose owner is proven by this same tree; unresolved/imported names are
|
|
138
|
+
* left untouched for the repo-wide resolver.
|
|
139
|
+
*/
|
|
140
|
+
function qualifySameFileOperands(operands, functionNode, constants) {
|
|
141
|
+
const enclosingTypes = enclosingTypeNames(functionNode);
|
|
142
|
+
return operands.map((operand) => {
|
|
143
|
+
if (operand.kind === 'literal')
|
|
144
|
+
return operand;
|
|
145
|
+
for (const owner of enclosingTypes) {
|
|
146
|
+
const qualified = `${owner}.${operand.name}`;
|
|
147
|
+
if (declarationExists(constants, qualified)) {
|
|
148
|
+
return { kind: 'ref', name: qualified };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return operand;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Read the one optional class-level RequestMapping. A present route member must
|
|
156
|
+
* be exactly one plain string literal, an empty `[]`/`arrayOf()` (no prefix),
|
|
157
|
+
* or absent; constants, interpolation, non-empty collections, duplicate
|
|
158
|
+
* mappings, and dynamic expressions fail closed for the whole class.
|
|
159
|
+
*/
|
|
160
|
+
function classMapping(annotations) {
|
|
161
|
+
const mappings = annotations.filter((annotation) => annotationName(annotation) === 'RequestMapping');
|
|
162
|
+
if (mappings.length === 0)
|
|
163
|
+
return { prefix: '', methods: ['*'] };
|
|
164
|
+
if (mappings.length !== 1)
|
|
165
|
+
return null;
|
|
166
|
+
const mapping = mappings[0];
|
|
167
|
+
const paths = routeArguments(mapping);
|
|
168
|
+
if (paths === null || paths.length > 1)
|
|
169
|
+
return null;
|
|
170
|
+
let prefix = '';
|
|
171
|
+
if (paths.length === 1) {
|
|
172
|
+
const path = paths[0].expression;
|
|
173
|
+
if (isEmptyKotlinPathCollection(path)) {
|
|
174
|
+
prefix = '';
|
|
175
|
+
}
|
|
176
|
+
else if (isPlainStringLiteral(path)) {
|
|
177
|
+
const literal = unquoteSpringLiteral(path.text);
|
|
178
|
+
if (literal === null)
|
|
179
|
+
return null;
|
|
180
|
+
prefix = literal;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const methods = kotlinSpringHttpMethods('RequestMapping', mapping);
|
|
187
|
+
return methods.length === 0 ? null : { prefix, methods };
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Extract direct Spring handler methods from concrete Kotlin RestControllers.
|
|
191
|
+
*/
|
|
192
|
+
export function extractKotlinSpringRoutes(tree, filePath, lineOffset = 0) {
|
|
193
|
+
const routes = [];
|
|
194
|
+
let moduleConstants;
|
|
195
|
+
for (const classNode of tree.rootNode.descendantsOfType('class_declaration')) {
|
|
196
|
+
if (isKotlinInterface(classNode) || isAbstractOrSealedClass(classNode))
|
|
197
|
+
continue;
|
|
198
|
+
const annotations = declarationAnnotations(classNode);
|
|
199
|
+
const annotationNames = annotations.map(annotationName);
|
|
200
|
+
if (!annotationNames.includes('RestController'))
|
|
201
|
+
continue;
|
|
202
|
+
if (annotationNames.includes('FeignClient'))
|
|
203
|
+
continue;
|
|
204
|
+
const ownerMapping = classMapping(annotations);
|
|
205
|
+
if (ownerMapping === null)
|
|
206
|
+
continue;
|
|
207
|
+
for (const functionNode of directFunctions(classNode)) {
|
|
208
|
+
const handlerName = functionName(functionNode);
|
|
209
|
+
if (!handlerName)
|
|
210
|
+
continue;
|
|
211
|
+
for (const annotation of declarationAnnotations(functionNode)) {
|
|
212
|
+
const decoratorName = annotationName(annotation);
|
|
213
|
+
if (!decoratorName)
|
|
214
|
+
continue;
|
|
215
|
+
const methodMethods = kotlinSpringHttpMethods(decoratorName, annotation);
|
|
216
|
+
const methods = intersectSpringHttpMethods(ownerMapping.methods, methodMethods);
|
|
217
|
+
if (methods.length === 0)
|
|
218
|
+
continue;
|
|
219
|
+
const paths = routeArguments(annotation);
|
|
220
|
+
if (paths === null || paths.length > 1)
|
|
221
|
+
continue;
|
|
222
|
+
let routePath = '';
|
|
223
|
+
let routePathExpr;
|
|
224
|
+
let routePathOperands;
|
|
225
|
+
if (paths.length === 1) {
|
|
226
|
+
const expression = paths[0].expression;
|
|
227
|
+
if (isEmptyKotlinPathCollection(expression)) {
|
|
228
|
+
routePath = '';
|
|
229
|
+
}
|
|
230
|
+
else if (isPlainStringLiteral(expression)) {
|
|
231
|
+
const literal = unquoteSpringLiteral(expression.text);
|
|
232
|
+
if (literal === null)
|
|
233
|
+
continue;
|
|
234
|
+
routePath = literal;
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
const operands = parseKotlinConstOperands(expression);
|
|
238
|
+
if (operands === null)
|
|
239
|
+
continue;
|
|
240
|
+
moduleConstants ??= extractKotlinModuleConstants(tree);
|
|
241
|
+
routePathExpr = expression.text;
|
|
242
|
+
routePathOperands = qualifySameFileOperands(operands, functionNode, moduleConstants);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
for (const httpMethod of methods) {
|
|
246
|
+
routes.push({
|
|
247
|
+
filePath,
|
|
248
|
+
routePath,
|
|
249
|
+
httpMethod,
|
|
250
|
+
decoratorName,
|
|
251
|
+
lineNumber: annotation.startPosition.row + lineOffset,
|
|
252
|
+
...(ownerMapping.prefix ? { prefix: ownerMapping.prefix } : {}),
|
|
253
|
+
handlerName,
|
|
254
|
+
...(routePathExpr === undefined
|
|
255
|
+
? {}
|
|
256
|
+
: {
|
|
257
|
+
routePathExpr,
|
|
258
|
+
routePathOperands,
|
|
259
|
+
}),
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return routes;
|
|
266
|
+
}
|
|
@@ -946,6 +946,7 @@ export function extractORMQueries(filePath, content, out) {
|
|
|
946
946
|
// import the function and its types directly from `route-extractors/`.
|
|
947
947
|
import { extractFastAPIRouterBindings } from '../route-extractors/fastapi-router-bindings.js';
|
|
948
948
|
import { parseConstOperands, } from '../route-extractors/python-const-resolver.js';
|
|
949
|
+
import { unfoldableDeclarationsOf } from '../route-extractors/constant-resolver.js';
|
|
949
950
|
/**
|
|
950
951
|
* Report a non-fatal worker issue to the pool over IPC so a caught error is not
|
|
951
952
|
* invisible to the operator (#2264). The pool logs it on the main thread AND
|
|
@@ -2331,10 +2332,13 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
2331
2332
|
// without booting a worker.
|
|
2332
2333
|
if (provider.extractModuleConstants && shouldHarvestModuleConstants(provider, parseContent)) {
|
|
2333
2334
|
const constants = provider.extractModuleConstants(tree);
|
|
2335
|
+
const topLevelDeclarations = constants.topLevelDeclarations;
|
|
2334
2336
|
if (constants.literals.size > 0 ||
|
|
2335
2337
|
constants.exprs.size > 0 ||
|
|
2336
2338
|
constants.imports.size > 0 ||
|
|
2337
|
-
(constants.wildcardImports?.length ?? 0) > 0
|
|
2339
|
+
(constants.wildcardImports?.length ?? 0) > 0 ||
|
|
2340
|
+
unfoldableDeclarationsOf(constants).size > 0 ||
|
|
2341
|
+
(topLevelDeclarations instanceof Set && topLevelDeclarations.size > 0)) {
|
|
2338
2342
|
(result.moduleConstants ??= []).push({ filePath: file.path, constants });
|
|
2339
2343
|
}
|
|
2340
2344
|
}
|
|
@@ -711,7 +711,11 @@ import { copyV8CacheIfPresent, tryLoadV8Cache, writeV8CacheFile } from './v8-sid
|
|
|
711
711
|
// (89) and above every in-flight claim found by scanning open PRs'
|
|
712
712
|
// parse-cache.ts at their exact head SHAs (highest other open claim was still
|
|
713
713
|
// ≤88). RE-CHECK AGAINST origin/main AND OPEN PRs IMMEDIATELY BEFORE MERGING.
|
|
714
|
-
|
|
714
|
+
// 90 -> 91 (#3130): Kotlin providers now emit Spring decoratorRoutes plus
|
|
715
|
+
// ModuleConstants shadow metadata. A warm v90 cache would replay unchanged
|
|
716
|
+
// Kotlin files with neither route candidates nor the constant declarations
|
|
717
|
+
// needed to fold them, leaving the new ingestion path silently inert.
|
|
718
|
+
const SCHEMA_BUMP = 91;
|
|
715
719
|
const GITNEXUS_PKG_VERSION = (() => {
|
|
716
720
|
try {
|
|
717
721
|
// package.json sits at gitnexus/package.json — two levels up from
|
package/package.json
CHANGED