mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-06 02:49:56 +00:00
* feat(kotlin): bind Spring @Value and ConfigurationProperties consumers Kotlin sources were skipping the Java-only config-binding attach path, so mixed JVM apps under-reported blast radius for Kotlin placeholders. Capture from the live AST, serialize on the existing side channel, and reuse the shared binder. (U1-U4) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(review): require exact Spring annotation FQNs and skip raw-string escapes Reject similarly named third-party imports and leave Kotlin triple-quoted bodies undecoded so capture stays fail-closed. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(kotlin): use only grammar-valid Kotlin string and class-name nodes The coverage shard failed the #1920 literal gate on invented string node types and a Java-style name field. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(kotlin): fail closed on non-literal prefixes and persist unresolved config markers Reject constant and boolean @ConfigurationProperties arguments, scope nested Value shadows to their owner, and rewrite drifted consumer files when a config key is deleted. Co-authored-by: Cursor <cursoragent@cursor.com> * test(kotlin): add config-consumer capture benchmark and fix JVM feature stamps The Kotlin capture path had no performance or behavior guard: a file-wide lexical shadow regression silently dropped two of every three facts. The new bench arm fingerprints @Value / @ConfigurationProperties facts from an explicit-import control against a wildcard-import corpus whose files each declare a sibling nested `Value` type, so parity between the arms is the regression gate, and CI runs it with scaling + widening budgets. Broadening spring.config-bindings to .kt also means Kotlin-only JVM repos now stamp the feature, which the two exact-map orchestration expectations still denied. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(kotlin): let an explicit import shadow the Spring wildcard import importedAs accepted a star import of the Spring package even when the same simple name was explicitly bound to another type, so a file importing com.example.Value alongside the Spring annotation package emitted a false @Value consumer fact. Kotlin resolves the explicit import first, so the wildcard branch now only applies when the name is otherwise unbound. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
132 lines
4.6 KiB
TypeScript
132 lines
4.6 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
CLASS_FRAMEWORK_ANNOTATIONS_FEATURE,
|
|
findAnalysisFeatureMismatches,
|
|
resolveAnalysisFeatureVersions,
|
|
type AnalysisFeatureDescriptor,
|
|
} from '../../src/core/analysis-features.js';
|
|
import {
|
|
SPRING_AOP_FEATURE,
|
|
SPRING_BEAN_INVENTORY_FEATURE,
|
|
SPRING_CONDITIONALS_FEATURE,
|
|
SPRING_NON_HTTP_HANDLERS_FEATURE,
|
|
} from '../../src/core/ingestion/frameworks/spring/analysis-features.js';
|
|
import {
|
|
JAVA_ENUM_INTERFACE_HERITAGE_FEATURE,
|
|
JAVA_RECORD_COMPONENT_ACCESSORS_FEATURE,
|
|
SPRING_CONFIG_BINDINGS_FEATURE,
|
|
} from '../../src/core/ingestion/languages/java/analysis-features.js';
|
|
|
|
const FEATURES = [
|
|
CLASS_FRAMEWORK_ANNOTATIONS_FEATURE,
|
|
SPRING_AOP_FEATURE,
|
|
SPRING_BEAN_INVENTORY_FEATURE,
|
|
SPRING_CONDITIONALS_FEATURE,
|
|
SPRING_NON_HTTP_HANDLERS_FEATURE,
|
|
SPRING_CONFIG_BINDINGS_FEATURE,
|
|
JAVA_ENUM_INTERFACE_HERITAGE_FEATURE,
|
|
JAVA_RECORD_COMPONENT_ACCESSORS_FEATURE,
|
|
] as const;
|
|
|
|
describe('analysis feature versions', () => {
|
|
it('separates the global Class schema capability from JVM-only Bean evidence', () => {
|
|
expect(resolveAnalysisFeatureVersions(FEATURES, ['src/app.ts'])).toEqual({
|
|
'graph.class-framework-annotations': 1,
|
|
});
|
|
expect(resolveAnalysisFeatureVersions(FEATURES, ['src/App.java'])).toEqual({
|
|
'graph.class-framework-annotations': 1,
|
|
'java.heritage-captures': 1,
|
|
'java.record-component-accessors': 1,
|
|
'spring.aop-advice': 1,
|
|
'spring.bean-inventory': 2,
|
|
'spring.conditionals-auto-configuration': 1,
|
|
'spring.config-bindings': 2,
|
|
'spring.non-http-handlers': 1,
|
|
});
|
|
expect(resolveAnalysisFeatureVersions(FEATURES, ['src/App.kt'])).toEqual({
|
|
'graph.class-framework-annotations': 1,
|
|
'spring.aop-advice': 1,
|
|
'spring.bean-inventory': 2,
|
|
'spring.conditionals-auto-configuration': 1,
|
|
'spring.config-bindings': 2,
|
|
'spring.non-http-handlers': 1,
|
|
});
|
|
expect(resolveAnalysisFeatureVersions(FEATURES, ['BUILD.GRADLE.KTS'])).toEqual({
|
|
'graph.class-framework-annotations': 1,
|
|
'spring.aop-advice': 1,
|
|
'spring.bean-inventory': 2,
|
|
'spring.conditionals-auto-configuration': 1,
|
|
'spring.non-http-handlers': 1,
|
|
});
|
|
expect(
|
|
resolveAnalysisFeatureVersions(FEATURES, [
|
|
'src/main/resources/application-local.yml',
|
|
'README.md',
|
|
]),
|
|
).toEqual({
|
|
'graph.class-framework-annotations': 1,
|
|
'spring.config-bindings': 2,
|
|
});
|
|
expect(
|
|
resolveAnalysisFeatureVersions(FEATURES, [
|
|
'src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports',
|
|
]),
|
|
).toEqual({
|
|
'graph.class-framework-annotations': 1,
|
|
'spring.conditionals-auto-configuration': 1,
|
|
});
|
|
});
|
|
|
|
it('requires an exact, well-formed feature set', () => {
|
|
const expected = {
|
|
'graph.class-framework-annotations': 1,
|
|
'spring.bean-inventory': 2,
|
|
};
|
|
|
|
expect(findAnalysisFeatureMismatches(expected, expected)).toEqual([]);
|
|
expect(findAnalysisFeatureMismatches(undefined, expected)).toEqual([
|
|
'missing:graph.class-framework-annotations',
|
|
'missing:spring.bean-inventory',
|
|
]);
|
|
expect(
|
|
findAnalysisFeatureMismatches(
|
|
{ 'graph.class-framework-annotations': 1, 'spring.bean-inventory': 1 },
|
|
expected,
|
|
),
|
|
).toEqual(['version:spring.bean-inventory']);
|
|
expect(findAnalysisFeatureMismatches({ feature: 1 }, { feature: 2 })).toEqual([
|
|
'version:feature',
|
|
]);
|
|
expect(
|
|
findAnalysisFeatureMismatches({ ...expected, 'spring.future-feature': 1 }, expected),
|
|
).toEqual(['unexpected:spring.future-feature']);
|
|
expect(findAnalysisFeatureMismatches([], expected)).toEqual(['invalid:analysisFeatures']);
|
|
expect(findAnalysisFeatureMismatches({ ...expected, toString: 1 }, expected)).toEqual([
|
|
'unexpected:toString',
|
|
]);
|
|
});
|
|
|
|
it('rejects invalid or duplicate descriptors', () => {
|
|
const invalid: AnalysisFeatureDescriptor = {
|
|
id: 'invalid',
|
|
version: 0,
|
|
appliesTo: () => true,
|
|
};
|
|
expect(() => resolveAnalysisFeatureVersions([invalid], [])).toThrow('invalid version');
|
|
expect(() =>
|
|
resolveAnalysisFeatureVersions(
|
|
[CLASS_FRAMEWORK_ANNOTATIONS_FEATURE, CLASS_FRAMEWORK_ANNOTATIONS_FEATURE],
|
|
[],
|
|
),
|
|
).toThrow('Duplicate analysis feature descriptor');
|
|
expect(() =>
|
|
resolveAnalysisFeatureVersions(
|
|
[
|
|
CLASS_FRAMEWORK_ANNOTATIONS_FEATURE,
|
|
{ ...CLASS_FRAMEWORK_ANNOTATIONS_FEATURE, appliesTo: () => false },
|
|
],
|
|
[],
|
|
),
|
|
).toThrow('Duplicate analysis feature descriptor');
|
|
});
|
|
});
|