mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-07 08:26:11 +00:00
* feat(route): resolve vendor-derived Spring mapping annotations by suffix Frameworks commonly wrap Spring's built-in annotations with company-specific variants (e.g. Winning Health's @WinPostMapping wraps @PostMapping). The annotation definition lives in a binary JAR — not in source — so the meta-annotation cannot be read statically. Add resolveSpringAnnotationAlias(): resolves custom annotations by naming suffix (WinPostMapping → PostMapping → POST). This matches the universal Java convention of naming derived annotations with the base name as a suffix. Works for any vendor prefix, not just one company. The fix is in springAnnotationHttpMethods() (spring-shared.ts), which both the ingestion extractor (spring.ts) and the group extractor (java.ts) call. A single-function change propagates to both layers automatically. Zero configuration: no .gitnexusrc, no annotation allowlist. If an annotation name ends with a known Spring mapping suffix, it inherits that annotation's HTTP semantics. False-positive risk is negligible. Tests: 16 new unit tests covering resolveSpringAnnotationAlias directly, springAnnotationHttpMethods with aliased annotations, end-to-end extractSpringRoutes with vendor annotations, and ingestion/group parity. Existing route tests (260) continue to pass. * fix(route): address review findings — class-level aliases, registered prefixes P1: class-level @WinRequestMapping now gets the same prefix/constraint semantics as @RequestMapping — all five class-level exact-match sites (spring.ts phase-1 collect, typeRequestMethods, typeClassPrefixes; group http-patterns java.ts typeRequestMethods + type-level branch) route through the new shared isClassLevelMappingAnnotation predicate, and the hard-coded 'RequestMapping' argument in springAnnotationHttpMethods calls is replaced with the actual annotation name so alias resolution applies. P2: suffix-only alias matching accepted unrelated annotations (@AuditPostMapping emitted a phantom POST /audit). Alias resolution now requires a REGISTERED vendor prefix — 'Win' by default, extendable via GITNEXUS_SPRING_VENDOR_PREFIXES=Win,Acme without a rebuild. Tests: negative e2e for the unregistered-suffix phantom route, vendor class-prefix parity e2e, predicate unit matrix, env-registration test. * style: prettier * chore: drop accidental gitnexus-shared/dist worktree symlink from prettier commit Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#2883) Wire vendor Spring mapping aliases into Kotlin ingestion and group extraction, restore GITNEXUS_SPRING_VENDOR_PREFIXES after the env test, and stamp spring.route-bindings so existing indexes rebuild. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(autofix): apply prettier + eslint fixes via /autofix command * fix(route): honor Kotlin vendor aliases and prefix freshness Parse Kotlin RequestMapping method arrays in the shared Spring helper, bump spring.route-bindings, and rebuild when registered vendor prefixes change. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: ChunxueLi <mecoloud@users.noreply.gitee.com> Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
114 lines
4.1 KiB
TypeScript
114 lines
4.1 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
CLASS_FRAMEWORK_ANNOTATIONS_FEATURE,
|
|
findAnalysisFeatureMismatches,
|
|
resolveAnalysisFeatureVersions,
|
|
type AnalysisFeatureDescriptor,
|
|
} from '../../src/core/analysis-features.js';
|
|
import { ANALYSIS_FEATURES } from '../../src/core/analysis-feature-registry.js';
|
|
|
|
describe('analysis feature versions', () => {
|
|
it('separates the global Class schema capability from JVM-only Bean evidence', () => {
|
|
expect(resolveAnalysisFeatureVersions(ANALYSIS_FEATURES, ['src/app.ts'])).toEqual({
|
|
'graph.class-framework-annotations': 1,
|
|
});
|
|
expect(resolveAnalysisFeatureVersions(ANALYSIS_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,
|
|
'spring.route-bindings': 2,
|
|
});
|
|
expect(resolveAnalysisFeatureVersions(ANALYSIS_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,
|
|
'spring.route-bindings': 2,
|
|
});
|
|
expect(resolveAnalysisFeatureVersions(ANALYSIS_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,
|
|
'spring.route-bindings': 2,
|
|
});
|
|
expect(
|
|
resolveAnalysisFeatureVersions(ANALYSIS_FEATURES, [
|
|
'src/main/resources/application-local.yml',
|
|
'README.md',
|
|
]),
|
|
).toEqual({
|
|
'graph.class-framework-annotations': 1,
|
|
'spring.config-bindings': 2,
|
|
});
|
|
expect(
|
|
resolveAnalysisFeatureVersions(ANALYSIS_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');
|
|
});
|
|
});
|