gitnexus 1.6.11-rc.2 → 1.6.11-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/README.md +43 -1
- package/dist/_shared/impact-risk.d.ts +37 -0
- package/dist/_shared/impact-risk.d.ts.map +1 -0
- package/dist/_shared/impact-risk.js +92 -0
- package/dist/_shared/impact-risk.js.map +1 -0
- package/dist/_shared/index.d.ts +2 -0
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js +2 -0
- package/dist/_shared/index.js.map +1 -1
- package/dist/cli/ai-context.js +4 -4
- package/dist/cli/analyze-config.d.ts +2 -0
- package/dist/cli/analyze-config.js +16 -0
- package/dist/cli/analyze-options.d.ts +4 -0
- package/dist/cli/analyze.d.ts +17 -0
- package/dist/cli/analyze.js +43 -7
- package/dist/cli/eval-server.js +22 -5
- package/dist/cli/group.js +7 -1
- package/dist/cli/help-i18n.js +2 -0
- package/dist/cli/i18n/en.d.ts +12 -2
- package/dist/cli/i18n/en.js +12 -2
- package/dist/cli/i18n/resources.d.ts +22 -2
- package/dist/cli/i18n/zh-CN.d.ts +10 -0
- package/dist/cli/i18n/zh-CN.js +12 -2
- package/dist/cli/index.js +11 -4
- package/dist/cli/status.js +91 -6
- package/dist/cli/watch-queue.d.ts +41 -0
- package/dist/cli/watch-queue.js +184 -0
- package/dist/cli/watch.d.ts +20 -0
- package/dist/cli/watch.js +372 -0
- package/dist/cli/wiki.js +15 -2
- package/dist/config/ignore-service.d.ts +11 -0
- package/dist/config/ignore-service.js +41 -4
- package/dist/config/repo-control-file.d.ts +3 -0
- package/dist/config/repo-control-file.js +115 -0
- package/dist/core/group/config-parser.js +20 -2
- package/dist/core/group/cross-impact.d.ts +2 -1
- package/dist/core/group/cross-impact.js +33 -2
- package/dist/core/group/extractors/fs-utils.d.ts +2 -0
- package/dist/core/group/extractors/fs-utils.js +86 -0
- package/dist/core/group/extractors/graphql-extractor.d.ts +17 -0
- package/dist/core/group/extractors/graphql-extractor.js +652 -0
- package/dist/core/group/extractors/java-workspace-extractor.js +244 -31
- package/dist/core/group/extractors/manifest-extractor.d.ts +1 -1
- package/dist/core/group/extractors/manifest-extractor.js +1 -1
- package/dist/core/group/matching.js +8 -1
- package/dist/core/group/service.js +5 -1
- package/dist/core/group/storage.js +1 -0
- package/dist/core/group/sync.d.ts +3 -1
- package/dist/core/group/sync.js +43 -5
- package/dist/core/group/types.d.ts +19 -5
- package/dist/core/incremental/derived-writeback.d.ts +36 -0
- package/dist/core/incremental/derived-writeback.js +68 -0
- package/dist/core/incremental/subgraph-extract.d.ts +6 -4
- package/dist/core/incremental/subgraph-extract.js +7 -5
- package/dist/core/index-content-drift.d.ts +54 -0
- package/dist/core/index-content-drift.js +127 -0
- package/dist/core/ingestion/filesystem-walker.d.ts +15 -5
- package/dist/core/ingestion/filesystem-walker.js +20 -3
- package/dist/core/ingestion/frameworks/spring/dynamic-lookups.d.ts +22 -0
- package/dist/core/ingestion/frameworks/spring/dynamic-lookups.js +124 -0
- package/dist/core/ingestion/language-provider.d.ts +43 -0
- package/dist/core/ingestion/languages/csharp/razor-view-components.d.ts +62 -0
- package/dist/core/ingestion/languages/csharp/razor-view-components.js +954 -0
- package/dist/core/ingestion/languages/csharp/resolution-config.d.ts +3 -0
- package/dist/core/ingestion/languages/csharp/resolution-config.js +6 -1
- package/dist/core/ingestion/languages/csharp/scope-resolver.js +8 -0
- package/dist/core/ingestion/languages/java/capture-side-channel.d.ts +4 -0
- package/dist/core/ingestion/languages/java/capture-side-channel.js +16 -0
- package/dist/core/ingestion/languages/java/captures.js +14 -1
- package/dist/core/ingestion/languages/java/lombok-synthesizer.d.ts +42 -0
- package/dist/core/ingestion/languages/java/lombok-synthesizer.js +439 -0
- package/dist/core/ingestion/languages/java/scope-resolver.js +2 -0
- package/dist/core/ingestion/languages/java/spring-dynamic-lookup.d.ts +8 -0
- package/dist/core/ingestion/languages/java/spring-dynamic-lookup.js +62 -0
- package/dist/core/ingestion/languages/java.js +2 -0
- package/dist/core/ingestion/languages/jvm/accessor-synthesis.d.ts +99 -0
- package/dist/core/ingestion/languages/jvm/accessor-synthesis.js +173 -0
- package/dist/core/ingestion/languages/jvm/beanspec.d.ts +17 -0
- package/dist/core/ingestion/languages/jvm/beanspec.js +42 -0
- package/dist/core/ingestion/languages/kotlin/capture-side-channel.d.ts +5 -0
- package/dist/core/ingestion/languages/kotlin/capture-side-channel.js +16 -0
- package/dist/core/ingestion/languages/kotlin/captures.js +14 -1
- package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.d.ts +26 -0
- package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.js +417 -0
- package/dist/core/ingestion/languages/kotlin/scope-resolver.js +2 -0
- package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.d.ts +8 -0
- package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.js +77 -0
- package/dist/core/ingestion/languages/kotlin.js +2 -0
- package/dist/core/ingestion/pipeline-phases/di.js +47 -14
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +3 -1
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +57 -86
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +2 -0
- package/dist/core/ingestion/pipeline-phases/runner.d.ts +4 -1
- package/dist/core/ingestion/pipeline-phases/runner.js +34 -14
- package/dist/core/ingestion/pipeline-phases/scan.js +25 -13
- package/dist/core/ingestion/pipeline.d.ts +6 -0
- package/dist/core/ingestion/pipeline.js +44 -12
- package/dist/core/ingestion/scope-extractor.js +1 -0
- package/dist/core/ingestion/utils/symbol-labels.d.ts +2 -2
- package/dist/core/ingestion/utils/symbol-labels.js +2 -2
- package/dist/core/ingestion/workers/parse-worker.js +28 -2
- package/dist/core/lbug/lbug-adapter.d.ts +59 -0
- package/dist/core/lbug/lbug-adapter.js +154 -1
- package/dist/core/run-analyze.d.ts +17 -0
- package/dist/core/run-analyze.js +231 -23
- package/dist/core/search/fts-indexes.d.ts +19 -1
- package/dist/core/search/fts-indexes.js +28 -1
- package/dist/core/wiki/generator.js +8 -0
- package/dist/core/wiki/grok-client.d.ts +21 -0
- package/dist/core/wiki/grok-client.js +287 -0
- package/dist/core/wiki/llm-client.d.ts +1 -1
- package/dist/core/wiki/llm-client.js +5 -2
- package/dist/core/wiki/local-cli-client.d.ts +11 -0
- package/dist/core/wiki/local-cli-client.js +22 -9
- package/dist/mcp/local/local-backend.d.ts +24 -7
- package/dist/mcp/local/local-backend.js +190 -81
- package/dist/mcp/local/pdg-impact.d.ts +8 -4
- package/dist/mcp/local/pdg-impact.js +7 -2
- package/dist/mcp/repository-policy.d.ts +5 -1
- package/dist/mcp/repository-policy.js +48 -4
- package/dist/mcp/resources.js +2 -1
- package/dist/mcp/server.js +6 -5
- package/dist/mcp/tools.js +31 -19
- package/dist/server/api.js +23 -64
- package/dist/server/grep-params.d.ts +18 -0
- package/dist/server/grep-params.js +83 -0
- package/dist/server/grep-scan.d.ts +23 -0
- package/dist/server/grep-scan.js +107 -0
- package/dist/server/grep-worker.d.ts +1 -0
- package/dist/server/grep-worker.js +12 -0
- package/dist/server/mcp-http.d.ts +8 -0
- package/dist/server/mcp-http.js +16 -1
- package/dist/storage/file-hash.d.ts +5 -0
- package/dist/storage/file-hash.js +16 -6
- package/dist/storage/fs-atomic.d.ts +24 -0
- package/dist/storage/fs-atomic.js +86 -2
- package/dist/storage/git.d.ts +19 -8
- package/dist/storage/git.js +83 -24
- package/dist/storage/gitnexus-managed-paths.d.ts +36 -0
- package/dist/storage/gitnexus-managed-paths.js +46 -0
- package/dist/storage/parse-cache.d.ts +22 -4
- package/dist/storage/parse-cache.js +106 -29
- package/dist/storage/parsedfile-store.d.ts +34 -60
- package/dist/storage/parsedfile-store.js +177 -171
- package/dist/storage/repo-manager.d.ts +11 -1
- package/dist/storage/repo-manager.js +23 -2
- package/dist/storage/repo-meta.d.ts +12 -0
- package/dist/storage/v8-sidecar.d.ts +48 -0
- package/dist/storage/v8-sidecar.js +347 -0
- package/dist/types/pipeline.d.ts +14 -0
- package/package.json +4 -1
- package/scripts/cross-platform-shard.ts +4 -2
- package/scripts/cross-platform-tests.ts +7 -1
- package/skills/gitnexus-cli.md +11 -3
- package/skills/gitnexus-impact-analysis.md +9 -0
- package/web/assets/{agent-Dr4l5EOp.js → agent-CFqT4hjR.js} +108 -104
- package/web/assets/{index-2zdvEdzg.js → index-BMIniRtX.js} +3 -3
- package/web/index.html +1 -1
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { toZeroBasedLine } from '../../utils/line-base.js';
|
|
2
|
+
export function createExistingMethodIndex(matching) {
|
|
3
|
+
return { matching, aritiesByName: new Map(), arityRangesByName: new Map() };
|
|
4
|
+
}
|
|
5
|
+
function methodKey(index, name) {
|
|
6
|
+
return index.matching === 'case-folded' ? name.toLowerCase() : name;
|
|
7
|
+
}
|
|
8
|
+
export function rememberExistingMethod(index, name, arity) {
|
|
9
|
+
const key = methodKey(index, name);
|
|
10
|
+
let arities = index.aritiesByName.get(key);
|
|
11
|
+
if (!arities) {
|
|
12
|
+
arities = new Set();
|
|
13
|
+
index.aritiesByName.set(key, arities);
|
|
14
|
+
}
|
|
15
|
+
arities.add(arity);
|
|
16
|
+
}
|
|
17
|
+
export function rememberExistingMethodRange(index, name, min, max) {
|
|
18
|
+
if (min === max) {
|
|
19
|
+
rememberExistingMethod(index, name, min);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const key = methodKey(index, name);
|
|
23
|
+
const ranges = index.arityRangesByName.get(key) ?? [];
|
|
24
|
+
ranges.push({ min, max });
|
|
25
|
+
index.arityRangesByName.set(key, ranges);
|
|
26
|
+
}
|
|
27
|
+
export function hasExistingMethod(index, name, arity) {
|
|
28
|
+
const key = methodKey(index, name);
|
|
29
|
+
if (index.aritiesByName.get(key)?.has(arity) === true)
|
|
30
|
+
return true;
|
|
31
|
+
return (index.arityRangesByName.get(key)?.some((range) => range.min <= arity && arity <= range.max) ===
|
|
32
|
+
true);
|
|
33
|
+
}
|
|
34
|
+
export function createJvmAccessorSynthesis(config) {
|
|
35
|
+
return {
|
|
36
|
+
synthesize(tree, filePath, classOwnersById) {
|
|
37
|
+
const result = emptySyntheticAccessorResult();
|
|
38
|
+
for (const owner of config.planOwners(tree.rootNode)) {
|
|
39
|
+
const ownerId = classOwnersById.get(owner.node.id);
|
|
40
|
+
if (!ownerId)
|
|
41
|
+
continue;
|
|
42
|
+
emitPlannedAccessors({
|
|
43
|
+
planned: owner.accessors,
|
|
44
|
+
filePath,
|
|
45
|
+
ownerId,
|
|
46
|
+
idPrefix: ownerIdNamePrefix(ownerId, filePath, owner.name),
|
|
47
|
+
language: config.language,
|
|
48
|
+
synthetic: config.synthetic,
|
|
49
|
+
result,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
},
|
|
54
|
+
captures(rootNode) {
|
|
55
|
+
return capturesForPlannedAccessors(config.planOwners(rootNode));
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function emptySyntheticAccessorResult() {
|
|
60
|
+
return { symbols: [], nodes: [], relationships: [] };
|
|
61
|
+
}
|
|
62
|
+
function ownerIdNamePrefix(ownerId, filePath, fallback) {
|
|
63
|
+
const needle = `Class:${filePath}:`;
|
|
64
|
+
if (ownerId.startsWith(needle))
|
|
65
|
+
return ownerId.slice(needle.length);
|
|
66
|
+
const enumNeedle = `Enum:${filePath}:`;
|
|
67
|
+
if (ownerId.startsWith(enumNeedle))
|
|
68
|
+
return ownerId.slice(enumNeedle.length);
|
|
69
|
+
const ifaceNeedle = `Interface:${filePath}:`;
|
|
70
|
+
if (ownerId.startsWith(ifaceNeedle))
|
|
71
|
+
return ownerId.slice(ifaceNeedle.length);
|
|
72
|
+
return fallback;
|
|
73
|
+
}
|
|
74
|
+
export function jvmTypeSimpleName(node) {
|
|
75
|
+
const named = node.childForFieldName('name')?.text;
|
|
76
|
+
if (named)
|
|
77
|
+
return named;
|
|
78
|
+
for (const child of node.namedChildren) {
|
|
79
|
+
if (child.type === 'type_identifier' || child.type === 'simple_identifier')
|
|
80
|
+
return child.text;
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
function emitPlannedAccessors(args) {
|
|
85
|
+
const emittedIds = new Set();
|
|
86
|
+
for (const acc of args.planned) {
|
|
87
|
+
const arity = acc.parameterTypes.length;
|
|
88
|
+
const qualifiedName = `${args.idPrefix}.${acc.name}`;
|
|
89
|
+
const nodeId = `Method:${args.filePath}:${qualifiedName}#${arity}`;
|
|
90
|
+
if (emittedIds.has(nodeId))
|
|
91
|
+
continue;
|
|
92
|
+
emittedIds.add(nodeId);
|
|
93
|
+
args.result.nodes.push({
|
|
94
|
+
id: nodeId,
|
|
95
|
+
label: 'Method',
|
|
96
|
+
properties: {
|
|
97
|
+
name: acc.name,
|
|
98
|
+
filePath: args.filePath,
|
|
99
|
+
startLine: toZeroBasedLine(acc.startLine),
|
|
100
|
+
endLine: toZeroBasedLine(acc.endLine),
|
|
101
|
+
language: args.language,
|
|
102
|
+
isExported: false,
|
|
103
|
+
synthetic: args.synthetic,
|
|
104
|
+
visibility: acc.visibility,
|
|
105
|
+
isStatic: acc.isStatic,
|
|
106
|
+
returnType: acc.returnType,
|
|
107
|
+
parameterTypes: acc.parameterTypes,
|
|
108
|
+
parameterCount: arity,
|
|
109
|
+
qualifiedName,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
args.result.symbols.push({
|
|
113
|
+
filePath: args.filePath,
|
|
114
|
+
name: acc.name,
|
|
115
|
+
nodeId,
|
|
116
|
+
type: 'Method',
|
|
117
|
+
ownerId: args.ownerId,
|
|
118
|
+
qualifiedName,
|
|
119
|
+
parameterCount: arity,
|
|
120
|
+
requiredParameterCount: arity,
|
|
121
|
+
parameterTypes: acc.parameterTypes,
|
|
122
|
+
returnType: acc.returnType,
|
|
123
|
+
visibility: acc.visibility,
|
|
124
|
+
isStatic: acc.isStatic,
|
|
125
|
+
isAbstract: acc.isAbstract,
|
|
126
|
+
isFinal: false,
|
|
127
|
+
});
|
|
128
|
+
args.result.relationships.push({
|
|
129
|
+
id: `HAS_METHOD:${args.ownerId}->${nodeId}`,
|
|
130
|
+
sourceId: args.ownerId,
|
|
131
|
+
targetId: nodeId,
|
|
132
|
+
type: 'HAS_METHOD',
|
|
133
|
+
confidence: 1.0,
|
|
134
|
+
reason: acc.kind === 'getter' ? `${args.synthetic}-getter` : `${args.synthetic}-setter`,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function accessorCapture(name, acc, text) {
|
|
139
|
+
const node = acc.declaratorNode;
|
|
140
|
+
const startLine = node.startPosition.row + 1;
|
|
141
|
+
const startCol = node.startPosition.column;
|
|
142
|
+
const endLine = node.endPosition.row + 1;
|
|
143
|
+
const endCol = acc.kind === 'getter' ? node.endPosition.column : startCol;
|
|
144
|
+
return { name, range: { startLine, startCol, endLine, endCol }, text };
|
|
145
|
+
}
|
|
146
|
+
function capturesForPlannedAccessors(owners) {
|
|
147
|
+
const captures = [];
|
|
148
|
+
for (const owner of owners) {
|
|
149
|
+
const enclosing = owner.name;
|
|
150
|
+
const emitted = new Set();
|
|
151
|
+
for (const acc of owner.accessors) {
|
|
152
|
+
const arity = String(acc.parameterTypes.length);
|
|
153
|
+
const qualifiedName = `${enclosing}.${acc.name}`;
|
|
154
|
+
const identity = `${qualifiedName}#${arity}`;
|
|
155
|
+
if (emitted.has(identity))
|
|
156
|
+
continue;
|
|
157
|
+
emitted.add(identity);
|
|
158
|
+
captures.push({
|
|
159
|
+
'@scope.function': accessorCapture('@scope.function', acc, acc.name),
|
|
160
|
+
});
|
|
161
|
+
captures.push({
|
|
162
|
+
'@declaration.method': accessorCapture('@declaration.method', acc, acc.name),
|
|
163
|
+
'@declaration.name': accessorCapture('@declaration.name', acc, acc.name),
|
|
164
|
+
'@declaration.qualified_name': accessorCapture('@declaration.qualified_name', acc, qualifiedName),
|
|
165
|
+
'@declaration.parameter-count': accessorCapture('@declaration.parameter-count', acc, arity),
|
|
166
|
+
'@declaration.required-parameter-count': accessorCapture('@declaration.required-parameter-count', acc, arity),
|
|
167
|
+
'@declaration.return-type': accessorCapture('@declaration.return-type', acc, acc.returnType),
|
|
168
|
+
'@declaration.is-synthetic': accessorCapture('@declaration.is-synthetic', acc, 'true'),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return captures;
|
|
173
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language-neutral JVM JavaBeans naming primitives.
|
|
3
|
+
*
|
|
4
|
+
* Language adapters choose whether to invent/preserve an `is` prefix and
|
|
5
|
+
* which single-character capitalization policy their compiler uses.
|
|
6
|
+
*/
|
|
7
|
+
export declare function capitalizeBeanName(s: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Primitive-boolean / Kotlin `is`-prefix fields whose name already starts with
|
|
10
|
+
* `is` plus a non-lowercase character keep that name for the getter and drop
|
|
11
|
+
* the `is` prefix for the setter base (`isEnabled` → `isEnabled()` /
|
|
12
|
+
* `setEnabled(...)`, `is1` → `is1()` / `set1(...)`). Digits and punctuation
|
|
13
|
+
* count as non-lowercase, matching Lombok `!Character.isLowerCase` and kotlinc.
|
|
14
|
+
*/
|
|
15
|
+
export declare function booleanIsPrefixBase(fieldName: string, useIsPrefix: boolean): string | null;
|
|
16
|
+
export declare function jvmGetterName(fieldName: string, useIsPrefix: boolean, capitalize?: (name: string) => string): string;
|
|
17
|
+
export declare function jvmSetterName(fieldName: string, useIsPrefix: boolean, capitalize?: (name: string) => string): string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language-neutral JVM JavaBeans naming primitives.
|
|
3
|
+
*
|
|
4
|
+
* Language adapters choose whether to invent/preserve an `is` prefix and
|
|
5
|
+
* which single-character capitalization policy their compiler uses.
|
|
6
|
+
*/
|
|
7
|
+
export function capitalizeBeanName(s) {
|
|
8
|
+
if (s.length === 0)
|
|
9
|
+
return s;
|
|
10
|
+
const first = s.charAt(0);
|
|
11
|
+
const upper = first.toUpperCase();
|
|
12
|
+
// Java Character case conversion is one UTF-16 code unit. JavaScript
|
|
13
|
+
// full-case conversion may expand one unit (`ß` → `SS`), which would invent
|
|
14
|
+
// a method name no JVM compiler emits.
|
|
15
|
+
return (upper.length === 1 ? upper : first) + s.slice(1);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Primitive-boolean / Kotlin `is`-prefix fields whose name already starts with
|
|
19
|
+
* `is` plus a non-lowercase character keep that name for the getter and drop
|
|
20
|
+
* the `is` prefix for the setter base (`isEnabled` → `isEnabled()` /
|
|
21
|
+
* `setEnabled(...)`, `is1` → `is1()` / `set1(...)`). Digits and punctuation
|
|
22
|
+
* count as non-lowercase, matching Lombok `!Character.isLowerCase` and kotlinc.
|
|
23
|
+
*/
|
|
24
|
+
export function booleanIsPrefixBase(fieldName, useIsPrefix) {
|
|
25
|
+
if (!useIsPrefix || !fieldName.startsWith('is') || fieldName.length < 3)
|
|
26
|
+
return null;
|
|
27
|
+
const third = fieldName.charAt(2);
|
|
28
|
+
return third === third.toUpperCase() ? fieldName.slice(2) : null;
|
|
29
|
+
}
|
|
30
|
+
export function jvmGetterName(fieldName, useIsPrefix, capitalize = capitalizeBeanName) {
|
|
31
|
+
if (booleanIsPrefixBase(fieldName, useIsPrefix) !== null)
|
|
32
|
+
return fieldName;
|
|
33
|
+
if (useIsPrefix)
|
|
34
|
+
return `is${capitalize(fieldName)}`;
|
|
35
|
+
return `get${capitalize(fieldName)}`;
|
|
36
|
+
}
|
|
37
|
+
export function jvmSetterName(fieldName, useIsPrefix, capitalize = capitalizeBeanName) {
|
|
38
|
+
const stripped = booleanIsPrefixBase(fieldName, useIsPrefix);
|
|
39
|
+
if (stripped !== null)
|
|
40
|
+
return `set${stripped}`;
|
|
41
|
+
return `set${capitalize(fieldName)}`;
|
|
42
|
+
}
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
import type { ParsedFile, ScopeId } from '../../../../_shared/index.js';
|
|
40
40
|
import { type ClassAnnotationFact } from '../../frameworks/spring/bean-candidates.js';
|
|
41
41
|
import { type JvmPackageFact } from '../jvm/package-facts.js';
|
|
42
|
+
import type { SpringDynamicLookupFact } from '../../frameworks/spring/dynamic-lookups.js';
|
|
42
43
|
import type { KotlinSpringAopFact } from './spring-aop.js';
|
|
43
44
|
import type { KotlinSpringConditionalFact } from './spring-conditionals.js';
|
|
44
45
|
import type { KotlinSpringDiClassFact } from './spring-di.js';
|
|
@@ -63,6 +64,8 @@ export interface KotlinCaptureSideChannel {
|
|
|
63
64
|
readonly springConditionalFacts?: readonly KotlinSpringConditionalFact[];
|
|
64
65
|
/** Constructor, property, and method injection syntax captured per class. */
|
|
65
66
|
readonly springDiFacts?: readonly KotlinSpringDiClassFact[];
|
|
67
|
+
/** Programmatic Spring bean lookups captured per callable. */
|
|
68
|
+
readonly springDynamicLookupFacts?: readonly SpringDynamicLookupFact[];
|
|
66
69
|
/** Scheduled, event, messaging, and managed-job handler syntax captured per callable. */
|
|
67
70
|
readonly springNonHttpHandlerFacts?: readonly KotlinSpringNonHttpHandlerFact[];
|
|
68
71
|
}
|
|
@@ -75,6 +78,8 @@ export declare function setKotlinSpringConditionalFacts(filePath: string, facts:
|
|
|
75
78
|
export declare function getKotlinSpringConditionalFacts(filePath: string): readonly KotlinSpringConditionalFact[];
|
|
76
79
|
export declare function setKotlinSpringDiFacts(filePath: string, facts: readonly KotlinSpringDiClassFact[]): void;
|
|
77
80
|
export declare function getKotlinSpringDiFacts(filePath: string): readonly KotlinSpringDiClassFact[];
|
|
81
|
+
export declare function setKotlinSpringDynamicLookupFacts(filePath: string, facts: readonly SpringDynamicLookupFact[]): void;
|
|
82
|
+
export declare function getKotlinSpringDynamicLookupFacts(filePath: string): readonly SpringDynamicLookupFact[];
|
|
78
83
|
export declare function setKotlinSpringNonHttpHandlerFacts(filePath: string, facts: readonly KotlinSpringNonHttpHandlerFact[]): void;
|
|
79
84
|
export declare function getKotlinSpringNonHttpHandlerFacts(filePath: string): readonly KotlinSpringNonHttpHandlerFact[];
|
|
80
85
|
/**
|
|
@@ -44,12 +44,14 @@ const classAnnotations = createClassAnnotationFactStore();
|
|
|
44
44
|
const springAopFacts = new Map();
|
|
45
45
|
const springConditionalFacts = new Map();
|
|
46
46
|
const springDiFacts = new Map();
|
|
47
|
+
const springDynamicLookupFacts = new Map();
|
|
47
48
|
const springNonHttpHandlerFacts = new Map();
|
|
48
49
|
export function clearKotlinClassAnnotationFacts() {
|
|
49
50
|
classAnnotations.clear();
|
|
50
51
|
springAopFacts.clear();
|
|
51
52
|
springConditionalFacts.clear();
|
|
52
53
|
springDiFacts.clear();
|
|
54
|
+
springDynamicLookupFacts.clear();
|
|
53
55
|
springNonHttpHandlerFacts.clear();
|
|
54
56
|
}
|
|
55
57
|
export function setKotlinSpringAopFacts(filePath, facts) {
|
|
@@ -85,6 +87,15 @@ export function setKotlinSpringDiFacts(filePath, facts) {
|
|
|
85
87
|
export function getKotlinSpringDiFacts(filePath) {
|
|
86
88
|
return springDiFacts.get(filePath) ?? [];
|
|
87
89
|
}
|
|
90
|
+
export function setKotlinSpringDynamicLookupFacts(filePath, facts) {
|
|
91
|
+
if (facts.length === 0)
|
|
92
|
+
springDynamicLookupFacts.delete(filePath);
|
|
93
|
+
else
|
|
94
|
+
springDynamicLookupFacts.set(filePath, facts);
|
|
95
|
+
}
|
|
96
|
+
export function getKotlinSpringDynamicLookupFacts(filePath) {
|
|
97
|
+
return springDynamicLookupFacts.get(filePath) ?? [];
|
|
98
|
+
}
|
|
88
99
|
export function setKotlinSpringNonHttpHandlerFacts(filePath, facts) {
|
|
89
100
|
if (facts.length === 0)
|
|
90
101
|
springNonHttpHandlerFacts.delete(filePath);
|
|
@@ -105,6 +116,7 @@ export function collectKotlinCaptureSideChannel(filePath) {
|
|
|
105
116
|
const aopFacts = springAopFacts.get(filePath) ?? [];
|
|
106
117
|
const conditionFacts = springConditionalFacts.get(filePath) ?? [];
|
|
107
118
|
const diFacts = springDiFacts.get(filePath) ?? [];
|
|
119
|
+
const dynamicLookupFacts = springDynamicLookupFacts.get(filePath) ?? [];
|
|
108
120
|
const nonHttpHandlerFacts = springNonHttpHandlerFacts.get(filePath) ?? [];
|
|
109
121
|
const packageFact = getKotlinPackageFact(filePath);
|
|
110
122
|
if (companionScopes.length === 0 &&
|
|
@@ -112,6 +124,7 @@ export function collectKotlinCaptureSideChannel(filePath) {
|
|
|
112
124
|
aopFacts.length === 0 &&
|
|
113
125
|
conditionFacts.length === 0 &&
|
|
114
126
|
diFacts.length === 0 &&
|
|
127
|
+
dynamicLookupFacts.length === 0 &&
|
|
115
128
|
nonHttpHandlerFacts.length === 0 &&
|
|
116
129
|
packageFact === undefined) {
|
|
117
130
|
return undefined;
|
|
@@ -124,6 +137,7 @@ export function collectKotlinCaptureSideChannel(filePath) {
|
|
|
124
137
|
...(aopFacts.length > 0 ? { springAopFacts: aopFacts } : {}),
|
|
125
138
|
...(conditionFacts.length > 0 ? { springConditionalFacts: conditionFacts } : {}),
|
|
126
139
|
...(diFacts.length > 0 ? { springDiFacts: diFacts } : {}),
|
|
140
|
+
...(dynamicLookupFacts.length > 0 ? { springDynamicLookupFacts: dynamicLookupFacts } : {}),
|
|
127
141
|
...(nonHttpHandlerFacts.length > 0 ? { springNonHttpHandlerFacts: nonHttpHandlerFacts } : {}),
|
|
128
142
|
};
|
|
129
143
|
}
|
|
@@ -147,6 +161,7 @@ export function applyKotlinCaptureSideChannel(parsed) {
|
|
|
147
161
|
setKotlinSpringAopFacts(parsed.filePath, []);
|
|
148
162
|
setKotlinSpringConditionalFacts(parsed.filePath, []);
|
|
149
163
|
setKotlinSpringDiFacts(parsed.filePath, []);
|
|
164
|
+
setKotlinSpringDynamicLookupFacts(parsed.filePath, []);
|
|
150
165
|
setKotlinSpringNonHttpHandlerFacts(parsed.filePath, []);
|
|
151
166
|
setKotlinPackageFact(parsed.filePath, UNKNOWN_JVM_PACKAGE_FACT);
|
|
152
167
|
return;
|
|
@@ -158,6 +173,7 @@ export function applyKotlinCaptureSideChannel(parsed) {
|
|
|
158
173
|
setKotlinSpringAopFacts(parsed.filePath, Array.isArray(data.springAopFacts) ? data.springAopFacts : []);
|
|
159
174
|
setKotlinSpringConditionalFacts(parsed.filePath, Array.isArray(data.springConditionalFacts) ? data.springConditionalFacts : []);
|
|
160
175
|
setKotlinSpringDiFacts(parsed.filePath, Array.isArray(data.springDiFacts) ? data.springDiFacts : []);
|
|
176
|
+
setKotlinSpringDynamicLookupFacts(parsed.filePath, Array.isArray(data.springDynamicLookupFacts) ? data.springDynamicLookupFacts : []);
|
|
161
177
|
setKotlinSpringNonHttpHandlerFacts(parsed.filePath, Array.isArray(data.springNonHttpHandlerFacts) ? data.springNonHttpHandlerFacts : []);
|
|
162
178
|
setKotlinPackageFact(parsed.filePath, isJvmPackageFact(data.packageFact) ? data.packageFact : UNKNOWN_JVM_PACKAGE_FACT);
|
|
163
179
|
}
|
|
@@ -10,10 +10,12 @@ import { normalizeKotlinType } from './interpret.js';
|
|
|
10
10
|
import { synthesizeKotlinReceiverBinding } from './receiver-binding.js';
|
|
11
11
|
import { getKotlinParser, getKotlinScopeQuery } from './query.js';
|
|
12
12
|
import { markCompanionScope } from './companion-scopes.js';
|
|
13
|
-
import { setKotlinClassAnnotationFacts, setKotlinSpringAopFacts, setKotlinSpringConditionalFacts, setKotlinSpringDiFacts, setKotlinSpringNonHttpHandlerFacts, } from './capture-side-channel.js';
|
|
13
|
+
import { setKotlinClassAnnotationFacts, setKotlinSpringAopFacts, setKotlinSpringConditionalFacts, setKotlinSpringDiFacts, setKotlinSpringDynamicLookupFacts, setKotlinSpringNonHttpHandlerFacts, } from './capture-side-channel.js';
|
|
14
14
|
import { captureKotlinPackageFact } from './package-facts.js';
|
|
15
15
|
import { synthesizeCallableFlowCaptures } from '../../utils/callable-flow-captures.js';
|
|
16
|
+
import { synthesizeLombokAccessorCaptures } from './lombok-synthesizer.js';
|
|
16
17
|
import { captureKotlinSpringDiClassFact } from './spring-di.js';
|
|
18
|
+
import { captureKotlinSpringDynamicLookupFact } from './spring-dynamic-lookup.js';
|
|
17
19
|
import { synthesizeReceiverChainCapture } from '../../utils/receiver-chain-captures.js';
|
|
18
20
|
import { captureKotlinSpringAopFacts } from './spring-aop.js';
|
|
19
21
|
import { captureKotlinSpringConditionalFacts, } from './spring-conditionals.js';
|
|
@@ -75,6 +77,8 @@ export function emitKotlinScopeCaptures(sourceText, filePath, cachedTree) {
|
|
|
75
77
|
const springNonHttpHandlerFacts = [];
|
|
76
78
|
const springNonHttpHandlerTypeNodeIds = new Set();
|
|
77
79
|
const springDiClassNodeIds = new Set();
|
|
80
|
+
const springDynamicLookupFacts = [];
|
|
81
|
+
const springDynamicLookupNodeIds = new Set();
|
|
78
82
|
const returnTypes = collectKotlinReturnTypeTexts(tree.rootNode);
|
|
79
83
|
out.push(...synthesizeKotlinLocalAssignmentBindings(tree.rootNode, returnTypes));
|
|
80
84
|
out.push(...synthesizeKotlinLoopBindings(tree.rootNode, returnTypes));
|
|
@@ -97,6 +101,13 @@ export function emitKotlinScopeCaptures(sourceText, filePath, cachedTree) {
|
|
|
97
101
|
}
|
|
98
102
|
if (Object.keys(grouped).length === 0)
|
|
99
103
|
continue;
|
|
104
|
+
const dynamicLookupNode = nodeIfType(groupedNodes['@reference.call.member'], 'call_expression');
|
|
105
|
+
if (dynamicLookupNode !== null && !springDynamicLookupNodeIds.has(dynamicLookupNode.id)) {
|
|
106
|
+
springDynamicLookupNodeIds.add(dynamicLookupNode.id);
|
|
107
|
+
const fact = captureKotlinSpringDynamicLookupFact(dynamicLookupNode, filePath);
|
|
108
|
+
if (fact !== null)
|
|
109
|
+
springDynamicLookupFacts.push(fact);
|
|
110
|
+
}
|
|
100
111
|
// tree-sitter-kotlin represents both classes and interfaces with
|
|
101
112
|
// `class_declaration`; `object_declaration` is the separate object form.
|
|
102
113
|
const springAopTypeNode = [
|
|
@@ -279,7 +290,9 @@ export function emitKotlinScopeCaptures(sourceText, filePath, cachedTree) {
|
|
|
279
290
|
setKotlinSpringAopFacts(filePath, springAopFacts);
|
|
280
291
|
setKotlinSpringConditionalFacts(filePath, springConditionalFacts);
|
|
281
292
|
setKotlinSpringDiFacts(filePath, springDiFacts);
|
|
293
|
+
setKotlinSpringDynamicLookupFacts(filePath, springDynamicLookupFacts);
|
|
282
294
|
setKotlinSpringNonHttpHandlerFacts(filePath, springNonHttpHandlerFacts);
|
|
295
|
+
out.push(...synthesizeLombokAccessorCaptures(tree.rootNode));
|
|
283
296
|
out.push(...synthesizeCallableFlowCaptures(tree.rootNode, KOTLIN_CALLABLE_CAPTURE_OPTIONS));
|
|
284
297
|
return out;
|
|
285
298
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kotlin accessor synthesizer (same provider-hook role as Java Lombok).
|
|
3
|
+
*
|
|
4
|
+
* kotlinc emits JavaBeans getters/setters for `val`/`var` properties. Those
|
|
5
|
+
* methods are absent from the tree-sitter AST, so Java (and Kotlin) calls
|
|
6
|
+
* like `user.getName()` miss CALLS edges. Planning is Kotlin-specific;
|
|
7
|
+
* naming and Method emission share `jvm/beanspec` + `jvm/accessor-synthesis`.
|
|
8
|
+
*
|
|
9
|
+
* ## Supported subset (v1)
|
|
10
|
+
* - Class / data class / object / companion / interface `val`/`var` properties
|
|
11
|
+
* (interface accessors without a custom body are abstract JVM methods).
|
|
12
|
+
* - Primary-constructor `val`/`var` class parameters.
|
|
13
|
+
* - Names beginning with `is` + a non-lowercase character keep that getter name; all other
|
|
14
|
+
* properties, including `Boolean`, use `get`.
|
|
15
|
+
* - Custom `get()`/`set()` bodies still emit their JVM accessor Methods.
|
|
16
|
+
* - Explicit `fun getX` / `@JvmField` / `const` skip synthesis.
|
|
17
|
+
* - `@JvmName`-renamed accessors are suppressed until custom-name emission lands.
|
|
18
|
+
* Unsupported: `@JvmStatic` renaming, file-facade top-level properties.
|
|
19
|
+
*/
|
|
20
|
+
import type Parser from 'tree-sitter';
|
|
21
|
+
import type { CaptureMatch } from '../../../../_shared/index.js';
|
|
22
|
+
import { type SyntheticAccessorResult } from '../jvm/accessor-synthesis.js';
|
|
23
|
+
export declare function kotlinGetterName(propertyName: string): string;
|
|
24
|
+
export declare function kotlinSetterName(propertyName: string): string;
|
|
25
|
+
export declare function synthesizeLombokAccessors(tree: Parser.Tree, filePath: string, classOwnersById: ReadonlyMap<number, string>): SyntheticAccessorResult;
|
|
26
|
+
export declare function synthesizeLombokAccessorCaptures(rootNode: Parser.SyntaxNode): CaptureMatch[];
|