gitnexus 1.6.11-rc.21 → 1.6.11-rc.23
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/README.md +24 -21
- package/dist/_shared/graph/types.d.ts +6 -0
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/cli/ai-context.d.ts +4 -0
- package/dist/cli/ai-context.js +5 -2
- package/dist/cli/analyze-config.js +14 -0
- package/dist/cli/analyze-options.d.ts +5 -0
- package/dist/cli/analyze.js +2 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/watch.js +2 -0
- package/dist/core/group/cross-impact.d.ts +3 -0
- package/dist/core/group/cross-impact.js +26 -4
- package/dist/core/group/extractors/http-patterns/java.js +18 -4
- package/dist/core/group/extractors/http-patterns/kotlin.js +2 -0
- package/dist/core/group/normalization.d.ts +38 -1
- package/dist/core/group/normalization.js +59 -3
- package/dist/core/group/service.d.ts +11 -0
- package/dist/core/group/service.js +8 -0
- package/dist/core/group/sync.d.ts +21 -0
- package/dist/core/group/sync.js +15 -3
- package/dist/core/group/types.d.ts +13 -0
- package/dist/core/ingestion/frameworks/spring/actuator-runtime.d.ts +26 -0
- package/dist/core/ingestion/frameworks/spring/actuator-runtime.js +843 -0
- package/dist/core/ingestion/language-provider.d.ts +38 -1
- package/dist/core/ingestion/language-provider.js +22 -0
- package/dist/core/ingestion/languages/java/spring-actuator.d.ts +2 -0
- package/dist/core/ingestion/languages/java/spring-actuator.js +62 -0
- package/dist/core/ingestion/languages/java.js +8 -7
- package/dist/core/ingestion/languages/kotlin/spring-actuator.d.ts +4 -0
- package/dist/core/ingestion/languages/kotlin/spring-actuator.js +172 -0
- package/dist/core/ingestion/languages/kotlin.js +3 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +6 -1
- package/dist/core/ingestion/pipeline-phases/processes.d.ts +2 -1
- package/dist/core/ingestion/pipeline-phases/processes.js +39 -4
- package/dist/core/ingestion/pipeline-phases/scan.js +82 -1
- package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.d.ts +2 -0
- package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.js +8 -0
- package/dist/core/ingestion/pipeline.d.ts +8 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.d.ts +16 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.js +10 -0
- package/dist/core/ingestion/route-extractors/java-const-resolver.d.ts +27 -1
- package/dist/core/ingestion/route-extractors/java-const-resolver.js +161 -13
- package/dist/core/ingestion/route-extractors/kotlin-const-resolver.d.ts +7 -8
- package/dist/core/ingestion/route-extractors/kotlin-const-resolver.js +209 -23
- package/dist/core/ingestion/workers/parse-worker.js +5 -1
- package/dist/core/lbug/csv-generator.js +4 -1
- package/dist/core/lbug/graph-emit-sink.d.ts +3 -2
- package/dist/core/lbug/graph-emit-sink.js +4 -2
- package/dist/core/lbug/lbug-adapter.js +1 -1
- package/dist/core/lbug/schema.d.ts +1 -1
- package/dist/core/lbug/schema.js +3 -0
- package/dist/core/run-analyze.d.ts +5 -0
- package/dist/core/run-analyze.js +56 -0
- package/dist/mcp/local/local-backend.js +46 -3
- package/dist/mcp/resources.js +1 -0
- package/dist/mcp/tools.js +4 -4
- package/dist/server/analyze-launch.d.ts +1 -0
- package/dist/server/analyze-launch.js +1 -0
- package/dist/server/api.d.ts +4 -0
- package/dist/server/api.js +46 -4
- package/dist/storage/parse-cache.js +8 -1
- package/dist/storage/repo-meta.d.ts +12 -0
- package/package.json +1 -1
- package/skills/gitnexus-cli.md +3 -0
|
@@ -15,6 +15,7 @@ import path from 'node:path';
|
|
|
15
15
|
import { generateId } from '../../../lib/utils.js';
|
|
16
16
|
import { logger } from '../../logger.js';
|
|
17
17
|
import { SPRING_AUTO_CONFIGURATION_FACTORY_REASON, SPRING_AUTO_CONFIGURATION_IMPORT_REASON, SPRING_AUTO_CONFIGURATION_SYNTHETIC_DESCRIPTION, } from '../frameworks/spring/auto-configuration.js';
|
|
18
|
+
import { importSpringActuatorRuntime, MAX_RUNTIME_RECORDS, } from '../frameworks/spring/actuator-runtime.js';
|
|
18
19
|
import { isDev } from '../utils/env.js';
|
|
19
20
|
import { getPhaseOutput } from './types.js';
|
|
20
21
|
const MAX_SPRING_METADATA_BYTES = 2 * 1024 * 1024;
|
|
@@ -246,10 +247,17 @@ export const springAutoConfigurationPhase = {
|
|
|
246
247
|
logger.debug(`Spring auto-configuration: skipped ${ambiguousQualifiedNames.size} ambiguous FQN(s) ` +
|
|
247
248
|
`because classpath precedence is unknown: ${[...ambiguousQualifiedNames].sort().join(', ')}`);
|
|
248
249
|
}
|
|
250
|
+
const actuatorRuntime = ctx.options?.springActuatorPath === undefined
|
|
251
|
+
? undefined
|
|
252
|
+
: await importSpringActuatorRuntime(ctx.graph, ctx.repoPath, ctx.options.springActuatorPath);
|
|
253
|
+
if (actuatorRuntime !== undefined && actuatorRuntime.truncatedEndpoints.length > 0) {
|
|
254
|
+
logger.warn(`Spring Actuator runtime import reached the ${MAX_RUNTIME_RECORDS.toLocaleString('en-US')}-record limit for: ${actuatorRuntime.truncatedEndpoints.join(', ')}. Runtime evidence is incomplete.`);
|
|
255
|
+
}
|
|
249
256
|
return {
|
|
250
257
|
metadataFiles,
|
|
251
258
|
autoConfigurations,
|
|
252
259
|
ambiguousAutoConfigurations: ambiguousQualifiedNames.size,
|
|
260
|
+
...(actuatorRuntime === undefined ? {} : { actuatorRuntime }),
|
|
253
261
|
};
|
|
254
262
|
},
|
|
255
263
|
};
|
|
@@ -31,6 +31,14 @@ export interface PipelineOptions {
|
|
|
31
31
|
* Community/Process rows can be kept instead of wipe+rewrite.
|
|
32
32
|
*/
|
|
33
33
|
skipDerivedGraphPhases?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Explicit local Spring Boot Actuator snapshot input. Accepts a directory
|
|
36
|
+
* containing endpoint-named JSON files or a JSON bundle keyed by endpoint.
|
|
37
|
+
* Undefined keeps runtime enrichment completely disabled.
|
|
38
|
+
*/
|
|
39
|
+
springActuatorPath?: string;
|
|
40
|
+
/** Repo-relative Actuator inputs retained only for a cleanup scan. */
|
|
41
|
+
springActuatorScanExclusions?: readonly string[];
|
|
34
42
|
/** Per-advice Spring AOP candidate inspection cap. `0` disables this cap. */
|
|
35
43
|
springAopMaxCandidateInspectionsPerAdvice?: number;
|
|
36
44
|
/** Aggregate Spring AOP candidate inspection cap for one analysis. `0` disables this cap. */
|
|
@@ -62,7 +62,23 @@ export interface ModuleConstants {
|
|
|
62
62
|
readonly literals: Map<string, string>;
|
|
63
63
|
readonly exprs: Map<string, readonly Operand[]>;
|
|
64
64
|
readonly imports: Map<string, ImportBinding>;
|
|
65
|
+
/**
|
|
66
|
+
* On-demand (wildcard) import specifiers whose bound member names could not
|
|
67
|
+
* be enumerated at extract time — Java `import static a.b.C.*;`, Python
|
|
68
|
+
* `from m import *`. The agnostic fold never reads this (it has no way to
|
|
69
|
+
* enumerate a target module's exports); a language binding materializes the
|
|
70
|
+
* promised bindings from a repo-wide map after extraction — see
|
|
71
|
+
* `expandJavaWildcardStaticImports` in the Java binding — so they resolve
|
|
72
|
+
* through the plain `imports` path with no special cases in the fold.
|
|
73
|
+
*/
|
|
74
|
+
readonly wildcardImports?: readonly string[];
|
|
65
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Declaration keys a language extractor found but could not fold. Java and
|
|
78
|
+
* Kotlin both use this metadata to keep lower-priority imports from replacing
|
|
79
|
+
* a real local declaration; other producers simply return the empty set.
|
|
80
|
+
*/
|
|
81
|
+
export declare function unfoldableDeclarationsOf(mc: ModuleConstants | undefined): ReadonlySet<string>;
|
|
66
82
|
/** Repo-wide map: unique file key (e.g. `app/constants.py`) → that file's
|
|
67
83
|
* {@link ModuleConstants}. */
|
|
68
84
|
export type RepoConstants = ReadonlyMap<string, ModuleConstants>;
|
|
@@ -32,6 +32,16 @@ const MAX_RESOLVE_DEPTH = 8;
|
|
|
32
32
|
* so we floor to `null` (skip) instead (#2393). The depth cap bounds recursion but
|
|
33
33
|
* NOT output size, which grows multiplicatively; this bounds the output. */
|
|
34
34
|
export const MAX_FOLD_LENGTH = 8192;
|
|
35
|
+
const NO_UNFOLDABLE_DECLARATIONS = new Set();
|
|
36
|
+
/**
|
|
37
|
+
* Declaration keys a language extractor found but could not fold. Java and
|
|
38
|
+
* Kotlin both use this metadata to keep lower-priority imports from replacing
|
|
39
|
+
* a real local declaration; other producers simply return the empty set.
|
|
40
|
+
*/
|
|
41
|
+
export function unfoldableDeclarationsOf(mc) {
|
|
42
|
+
const declarations = mc?.unfoldableDeclarations;
|
|
43
|
+
return declarations instanceof Set ? declarations : NO_UNFOLDABLE_DECLARATIONS;
|
|
44
|
+
}
|
|
35
45
|
/**
|
|
36
46
|
* Fold an operand list to its concatenated literal, or `null` if any operand is
|
|
37
47
|
* unresolvable (an unknown name, a non-string term, a cycle, or a depth overrun).
|
|
@@ -42,6 +42,10 @@
|
|
|
42
42
|
import type Parser from 'tree-sitter';
|
|
43
43
|
import { type ImportResolver, type ModuleConstants, type Operand, type RepoConstants } from './constant-resolver.js';
|
|
44
44
|
export type { ImportBinding, ModuleConstants, Operand, RepoConstants, } from './constant-resolver.js';
|
|
45
|
+
export interface JavaModuleConstants extends ModuleConstants {
|
|
46
|
+
/** Declaration keys whose initializer exists but cannot be folded. */
|
|
47
|
+
readonly unfoldableDeclarations: ReadonlySet<string>;
|
|
48
|
+
}
|
|
45
49
|
export declare function isJavaConstantFile(source: string): boolean;
|
|
46
50
|
/**
|
|
47
51
|
* The Java {@link ImportResolver}: map a fully-qualified import specifier to
|
|
@@ -96,7 +100,7 @@ export declare function parseJavaConstOperands(node: Parser.SyntaxNode | null |
|
|
|
96
100
|
* Last-wins in source order; a non-foldable rebind (`X = compute()`) drops X
|
|
97
101
|
* to unresolvable rather than keeping a stale literal.
|
|
98
102
|
*/
|
|
99
|
-
export declare function extractJavaModuleConstants(tree: Parser.Tree):
|
|
103
|
+
export declare function extractJavaModuleConstants(tree: Parser.Tree): JavaModuleConstants;
|
|
100
104
|
/**
|
|
101
105
|
* Resolve a single Java constant referenced in `fileKey` to its literal string
|
|
102
106
|
* value, folding `+` concatenation and following import chains via
|
|
@@ -112,3 +116,25 @@ export declare function resolveJavaConstant(fileKey: string, name: string, repo:
|
|
|
112
116
|
* `fileKey`, or null when any piece is unresolvable (skip floor).
|
|
113
117
|
*/
|
|
114
118
|
export declare function foldJavaOperands(fileKey: string, operands: readonly Operand[], repo: RepoConstants): string | null;
|
|
119
|
+
/**
|
|
120
|
+
* Constant-defining file keys used by Java import resolution.
|
|
121
|
+
*
|
|
122
|
+
* Build once per repo pass. Recomputing this set for every wildcard-importing
|
|
123
|
+
* controller makes expansion quadratic in controller count.
|
|
124
|
+
*/
|
|
125
|
+
export declare function buildJavaConstantKeys(repo: RepoConstants): ReadonlySet<string>;
|
|
126
|
+
export interface JavaConstantIndex {
|
|
127
|
+
readonly keys: ReadonlySet<string>;
|
|
128
|
+
/** Every resolvable path suffix as a dotted module name; null means ambiguous. */
|
|
129
|
+
readonly byModule: ReadonlyMap<string, string | null>;
|
|
130
|
+
/** Direct members by constant-defining file, built once for all importers. */
|
|
131
|
+
readonly membersByFile: ReadonlyMap<string, ReadonlySet<string>>;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Build all Java import suffixes once, turning repeated wildcard target lookup
|
|
135
|
+
* from O(importers × constant files) into O(path segments + importers).
|
|
136
|
+
*/
|
|
137
|
+
export declare function buildJavaConstantIndex(repo: RepoConstants): JavaConstantIndex;
|
|
138
|
+
export declare function expandJavaWildcardStaticImports(mc: ModuleConstants, _fileKey: string, repo: RepoConstants, index?: JavaConstantIndex): ModuleConstants;
|
|
139
|
+
/** Prepare every Java constants entry with one shared suffix index. */
|
|
140
|
+
export declare function prepareJavaRouteConstants(repo: RepoConstants): JavaConstantIndex;
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
* the import is ambiguous and returns null (skip floor), never a wrong path.
|
|
41
41
|
*/
|
|
42
42
|
import { unquoteSpringLiteral } from './spring-shared.js';
|
|
43
|
-
import { MAX_FOLD_LENGTH, } from './constant-resolver.js';
|
|
43
|
+
import { MAX_FOLD_LENGTH, unfoldableDeclarationsOf, } from './constant-resolver.js';
|
|
44
44
|
/**
|
|
45
45
|
* Cheap content gate: can this Java file DEFINE a string constant that a route
|
|
46
46
|
* annotation might reference?
|
|
@@ -121,10 +121,15 @@ export const resolveJavaImport = (_importingFileKey, moduleSpec, repoKeys) => {
|
|
|
121
121
|
// trimming the last segment when the direct hit fails covers both shapes.
|
|
122
122
|
const asPath = moduleSpec.replace(/\./g, '/');
|
|
123
123
|
const classFile = `${asPath}.java`;
|
|
124
|
+
// Compare in POSIX space: on Windows the repo keys can carry backslash
|
|
125
|
+
// separators, which would otherwise never match a '/'-joined class file
|
|
126
|
+
// (observed as 675 calls with zero hits on a backslash-keyed repo).
|
|
127
|
+
const toPosix = (p) => p.replace(/\\/g, '/');
|
|
124
128
|
// Exact package-path suffix match, unique or nothing.
|
|
125
129
|
let hit = null;
|
|
126
130
|
for (const key of repoKeys) {
|
|
127
|
-
|
|
131
|
+
const posixKey = toPosix(key);
|
|
132
|
+
if (posixKey === classFile || posixKey.endsWith(`/${classFile}`)) {
|
|
128
133
|
if (hit !== null)
|
|
129
134
|
return null; // 2+ modules carry this FQN — unresolvable
|
|
130
135
|
hit = key;
|
|
@@ -251,25 +256,48 @@ export function extractJavaModuleConstants(tree) {
|
|
|
251
256
|
const literals = new Map();
|
|
252
257
|
const exprs = new Map();
|
|
253
258
|
const imports = new Map();
|
|
259
|
+
const unfoldableDeclarations = new Set();
|
|
260
|
+
// On-demand static imports (`import static a.b.C.*`) — expanded post-map
|
|
261
|
+
// by expandJavaWildcardStaticImports below.
|
|
262
|
+
const wildcardImports = [];
|
|
254
263
|
// Pass 1: imports (both shapes).
|
|
255
264
|
const walkImports = (node) => {
|
|
256
265
|
if (node.type === 'import_declaration') {
|
|
257
266
|
// import a.b.C; | import static a.b.C; | import static a.b.C.F;
|
|
267
|
+
// import static a.b.C.*; — asterisk is a sibling of scoped_identifier
|
|
268
|
+
// (tree-sitter-java), not the last path segment. Same detection as
|
|
269
|
+
// import-decomposer.ts (`static-wildcard`).
|
|
258
270
|
const isStatic = node.children.some((c) => c.type === 'static' && c.text === 'static');
|
|
259
|
-
const
|
|
271
|
+
const isWildcard = node.children.some((c) => c.type === 'asterisk');
|
|
272
|
+
const scoped = node.children.find((c) => c.type === 'scoped_identifier') ??
|
|
273
|
+
node.children.find((c) => c.type === 'identifier');
|
|
260
274
|
if (scoped) {
|
|
261
275
|
const text = scoped.text;
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
imports.set(name, { module: fqn, originalName: name });
|
|
276
|
+
if (isStatic && isWildcard) {
|
|
277
|
+
// Class FQN only — members are materialized post-map.
|
|
278
|
+
if (text.length > 0 && !wildcardImports.includes(text)) {
|
|
279
|
+
wildcardImports.push(text);
|
|
280
|
+
}
|
|
268
281
|
}
|
|
269
282
|
else {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
283
|
+
const lastDot = text.lastIndexOf('.');
|
|
284
|
+
const fqn = text.slice(0, lastDot);
|
|
285
|
+
const name = text.slice(lastDot + 1);
|
|
286
|
+
if (isStatic) {
|
|
287
|
+
// Preserve the declaring type in the target lookup. Constants from
|
|
288
|
+
// multiple types share one file-level map, so a bare `F` could
|
|
289
|
+
// otherwise resolve to a sibling type's flattened field.
|
|
290
|
+
const declaringClass = fqn.slice(fqn.lastIndexOf('.') + 1);
|
|
291
|
+
imports.set(name, {
|
|
292
|
+
module: fqn,
|
|
293
|
+
originalName: `${declaringClass}.${name}`,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
// import a.b.C → module IS the class FQN; originalName is the class
|
|
298
|
+
// simple name. resolveJavaImport maps `a.b.C` → `a/b/C.java`.
|
|
299
|
+
imports.set(name, { module: text, originalName: name });
|
|
300
|
+
}
|
|
273
301
|
}
|
|
274
302
|
}
|
|
275
303
|
}
|
|
@@ -328,6 +356,7 @@ export function extractJavaModuleConstants(tree) {
|
|
|
328
356
|
if (operands === null) {
|
|
329
357
|
literals.delete(name);
|
|
330
358
|
exprs.delete(name);
|
|
359
|
+
unfoldableDeclarations.add(name);
|
|
331
360
|
// …and the static IMPORT of the same simple name. A local
|
|
332
361
|
// `static final String` shadows `import static a.b.C.PATH` inside
|
|
333
362
|
// that class (JLS 6.4.1), so the correct answer for a non-foldable
|
|
@@ -348,9 +377,13 @@ export function extractJavaModuleConstants(tree) {
|
|
|
348
377
|
if (qname) {
|
|
349
378
|
literals.delete(qname);
|
|
350
379
|
exprs.delete(qname);
|
|
380
|
+
unfoldableDeclarations.add(qname);
|
|
351
381
|
}
|
|
352
382
|
continue;
|
|
353
383
|
}
|
|
384
|
+
unfoldableDeclarations.delete(name);
|
|
385
|
+
if (qname)
|
|
386
|
+
unfoldableDeclarations.delete(qname);
|
|
354
387
|
const literalValue = operands.length === 1 && operands[0].kind === 'literal'
|
|
355
388
|
? operands[0].value
|
|
356
389
|
: null;
|
|
@@ -419,7 +452,13 @@ export function extractJavaModuleConstants(tree) {
|
|
|
419
452
|
}
|
|
420
453
|
};
|
|
421
454
|
walkTypes(tree.rootNode, false);
|
|
422
|
-
return {
|
|
455
|
+
return {
|
|
456
|
+
literals,
|
|
457
|
+
exprs,
|
|
458
|
+
imports: imports,
|
|
459
|
+
wildcardImports,
|
|
460
|
+
unfoldableDeclarations,
|
|
461
|
+
};
|
|
423
462
|
}
|
|
424
463
|
function newFoldState(repo) {
|
|
425
464
|
const constantKeys = new Set();
|
|
@@ -524,6 +563,8 @@ function computeJavaFold(fileKey, name, state, depth) {
|
|
|
524
563
|
const expr = mc.exprs.get(name);
|
|
525
564
|
if (expr !== undefined)
|
|
526
565
|
return foldOperands(fileKey, expr, state, depth + 1);
|
|
566
|
+
if (unfoldableDeclarationsOf(mc).has(name))
|
|
567
|
+
return null;
|
|
527
568
|
const imp = mc.imports.get(name);
|
|
528
569
|
if (imp !== undefined) {
|
|
529
570
|
const targetFile = resolveJavaImport(fileKey, imp.module, constantKeys);
|
|
@@ -567,3 +608,110 @@ export function foldJavaOperands(fileKey, operands, repo) {
|
|
|
567
608
|
const out = foldOperands(fileKey, operands, newFoldState(repo), 0);
|
|
568
609
|
return out === '' ? null : out;
|
|
569
610
|
}
|
|
611
|
+
/**
|
|
612
|
+
* Constant-defining file keys used by Java import resolution.
|
|
613
|
+
*
|
|
614
|
+
* Build once per repo pass. Recomputing this set for every wildcard-importing
|
|
615
|
+
* controller makes expansion quadratic in controller count.
|
|
616
|
+
*/
|
|
617
|
+
export function buildJavaConstantKeys(repo) {
|
|
618
|
+
const repoKeys = new Set();
|
|
619
|
+
for (const [key, target] of repo) {
|
|
620
|
+
if (target.literals.size > 0 || target.exprs.size > 0)
|
|
621
|
+
repoKeys.add(key);
|
|
622
|
+
}
|
|
623
|
+
return repoKeys;
|
|
624
|
+
}
|
|
625
|
+
/** Direct static members owned by `classSimple`, excluding nested-type members. */
|
|
626
|
+
function directJavaMembers(target, classSimple) {
|
|
627
|
+
const members = new Set();
|
|
628
|
+
const prefix = `${classSimple}.`;
|
|
629
|
+
for (const map of [target.literals, target.exprs]) {
|
|
630
|
+
for (const key of map.keys()) {
|
|
631
|
+
if (!key.startsWith(prefix))
|
|
632
|
+
continue;
|
|
633
|
+
const member = key.slice(prefix.length);
|
|
634
|
+
if (member.length > 0 && !member.includes('.'))
|
|
635
|
+
members.add(member);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return members;
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Build all Java import suffixes once, turning repeated wildcard target lookup
|
|
642
|
+
* from O(importers × constant files) into O(path segments + importers).
|
|
643
|
+
*/
|
|
644
|
+
export function buildJavaConstantIndex(repo) {
|
|
645
|
+
const keys = buildJavaConstantKeys(repo);
|
|
646
|
+
const byModule = new Map();
|
|
647
|
+
const membersByFile = new Map();
|
|
648
|
+
for (const key of keys) {
|
|
649
|
+
const normalized = key.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
650
|
+
if (!normalized.endsWith('.java'))
|
|
651
|
+
continue;
|
|
652
|
+
const segments = normalized.slice(0, -'.java'.length).split('/');
|
|
653
|
+
const classSimple = segments[segments.length - 1];
|
|
654
|
+
const constants = repo.get(key);
|
|
655
|
+
if (constants)
|
|
656
|
+
membersByFile.set(key, directJavaMembers(constants, classSimple));
|
|
657
|
+
for (let start = 0; start < segments.length; start++) {
|
|
658
|
+
const moduleName = segments.slice(start).join('.');
|
|
659
|
+
const existing = byModule.get(moduleName);
|
|
660
|
+
if (existing === undefined)
|
|
661
|
+
byModule.set(moduleName, key);
|
|
662
|
+
else if (existing !== key)
|
|
663
|
+
byModule.set(moduleName, null);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return { keys, byModule, membersByFile };
|
|
667
|
+
}
|
|
668
|
+
export function expandJavaWildcardStaticImports(mc, _fileKey, repo, index = buildJavaConstantIndex(repo)) {
|
|
669
|
+
const wildcards = mc.wildcardImports;
|
|
670
|
+
if (!wildcards || wildcards.length === 0)
|
|
671
|
+
return mc;
|
|
672
|
+
// Resolve targets against constant-DEFINING files only. Ingestion's harvest
|
|
673
|
+
// also admits import-only files; measuring uniqueness over every key made
|
|
674
|
+
// a duplicate empty FQN floor ingestion to skip while group still folded
|
|
675
|
+
// (#2980 R4).
|
|
676
|
+
const explicitImports = new Set(mc.imports.keys());
|
|
677
|
+
const pending = new Map();
|
|
678
|
+
for (const fqn of wildcards) {
|
|
679
|
+
const targetKey = index.byModule.get(fqn) ?? null;
|
|
680
|
+
if (targetKey === null)
|
|
681
|
+
continue;
|
|
682
|
+
const classSimple = fqn.slice(fqn.lastIndexOf('.') + 1);
|
|
683
|
+
const members = index.membersByFile.get(targetKey);
|
|
684
|
+
if (!members)
|
|
685
|
+
continue;
|
|
686
|
+
for (const name of members) {
|
|
687
|
+
// Same-file declarations and explicit imports have higher precedence
|
|
688
|
+
// than on-demand imports. An unfoldable declaration must remain a skip,
|
|
689
|
+
// not be resurrected from a wildcard target.
|
|
690
|
+
if (mc.literals.has(name) ||
|
|
691
|
+
mc.exprs.has(name) ||
|
|
692
|
+
unfoldableDeclarationsOf(mc).has(name) ||
|
|
693
|
+
explicitImports.has(name)) {
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
const binding = { module: fqn, originalName: `${classSimple}.${name}` };
|
|
697
|
+
const previous = pending.get(name);
|
|
698
|
+
if (previous === undefined)
|
|
699
|
+
pending.set(name, binding);
|
|
700
|
+
else if (previous !== null && previous.module !== fqn)
|
|
701
|
+
pending.set(name, null);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
for (const [name, binding] of pending) {
|
|
705
|
+
if (binding !== null)
|
|
706
|
+
mc.imports.set(name, binding);
|
|
707
|
+
}
|
|
708
|
+
return mc;
|
|
709
|
+
}
|
|
710
|
+
/** Prepare every Java constants entry with one shared suffix index. */
|
|
711
|
+
export function prepareJavaRouteConstants(repo) {
|
|
712
|
+
const index = buildJavaConstantIndex(repo);
|
|
713
|
+
for (const [fileKey, mc] of repo) {
|
|
714
|
+
expandJavaWildcardStaticImports(mc, fileKey, repo, index);
|
|
715
|
+
}
|
|
716
|
+
return index;
|
|
717
|
+
}
|
|
@@ -95,6 +95,7 @@
|
|
|
95
95
|
* @PostMapping(ApiPaths.ORDERS) // qualified
|
|
96
96
|
* @PostMapping(com.example.app.api.ApiPaths.ORDERS) // FQN-qualified
|
|
97
97
|
* @PostMapping(ORDERS) // single-name import
|
|
98
|
+
* @PostMapping(ORDERS) after import com.example.api.* // package-star import
|
|
98
99
|
* @PostMapping(ApiPaths.BASE + "/orders") // inline concat
|
|
99
100
|
*
|
|
100
101
|
* Which ANNOTATIONS count as routes is a separate question this module has no
|
|
@@ -136,6 +137,7 @@
|
|
|
136
137
|
import type Parser from 'tree-sitter';
|
|
137
138
|
import { type ModuleConstants, type Operand, type RepoConstants } from './constant-resolver.js';
|
|
138
139
|
export type { ImportBinding, ModuleConstants, Operand, RepoConstants, } from './constant-resolver.js';
|
|
140
|
+
export { unfoldableDeclarationsOf } from './constant-resolver.js';
|
|
139
141
|
/**
|
|
140
142
|
* What {@link extractKotlinModuleConstants} returns: the agnostic
|
|
141
143
|
* {@link ModuleConstants} plus the one piece of per-file metadata JVM import
|
|
@@ -162,12 +164,9 @@ export interface KotlinModuleConstants extends ModuleConstants {
|
|
|
162
164
|
readonly packageName: string;
|
|
163
165
|
/** Declaration keys whose initializer cannot be folded. */
|
|
164
166
|
readonly unfoldableDeclarations: ReadonlySet<string>;
|
|
167
|
+
/** Top-level properties and types that shadow lower-priority star imports. */
|
|
168
|
+
readonly topLevelDeclarations: ReadonlySet<string>;
|
|
165
169
|
}
|
|
166
|
-
/**
|
|
167
|
-
* Kotlin declaration keys known to exist but not fold, or an empty set when
|
|
168
|
-
* `mc` came from another language binding.
|
|
169
|
-
*/
|
|
170
|
-
export declare function unfoldableDeclarationsOf(mc: ModuleConstants | undefined): ReadonlySet<string>;
|
|
171
170
|
/**
|
|
172
171
|
* The name a backtick-quoted Kotlin identifier denotes: `` `api` `` → `api`.
|
|
173
172
|
*
|
|
@@ -294,9 +293,9 @@ export declare function resolveKotlinImportWithIndex(rawModuleSpec: string, inde
|
|
|
294
293
|
*/
|
|
295
294
|
export declare function parseKotlinConstOperands(node: Parser.SyntaxNode | null | undefined, depth?: number): Operand[] | null;
|
|
296
295
|
/**
|
|
297
|
-
* Extract the declared package, file-level string constants
|
|
298
|
-
* of one parsed Kotlin file into the
|
|
299
|
-
* resolver consumes.
|
|
296
|
+
* Extract the declared package, file-level string constants, named imports and
|
|
297
|
+
* package-star import scopes of one parsed Kotlin file into the
|
|
298
|
+
* {@link KotlinModuleConstants} shape the resolver consumes.
|
|
300
299
|
*
|
|
301
300
|
* Constants come from the three carriers Kotlin allows a caller to reach without
|
|
302
301
|
* an instance: file top level, `object` members, and `companion object` members.
|