GitNexus/gitnexus/test/unit/analysis-features.test.ts
mengkaka 4154b63131
feat(indexing): add Objective-C semantic indexing support (#3179)
* docs: add Objective-C fork provider notes

* feat(objective-c): add deterministic provider and grammar

* feat(objective-c): finalize provider MVP

* fix(objective-c): harden provider integration

* fix(objective-c): normalize bare macro markers

* docs(objective-c): integrate provider documentation

* fix(objective-c): harden resolution and header classification

* fix(objective-c): complete provider follow-ups

* fix: address Objective-C review follow-ups

* chore: format Objective-C grammar sources

* fix(objective-c): harden review follow-ups

* Address PR review feedback (#3179)

Keep Objective-C chunking and macro recovery aligned with the grammar, and stop Community MEMBER_OF edges from leaking into symbol context.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address follow-up review on ObjC chunking and language fallback.

Keep preprocessor directive text from changing file-scope brace depth, group real ivar nodes, skip header modifiers, and restore Rakefile/Gemfile detection through getLanguageFromFilename.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Parse Objective-C headers with the objc grammar in embeddings.

ensureAndParse and structural extraction now use the same content classifier as ingest, including method snippets from .h files, so Protocol/Category/Class chunks are not re-parsed as C++.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#3179)

Keep file-scope macro elision off C line splices and @interface/@protocol/@implementation bodies, and attach ivar attributes to the following instance variable when chunking.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(bench): rebaseline Objective-C CSV emit

* feat(objective-c): add workspace resolution and linear emit benches

Plain .h files are classified as C++, so the ObjC pass could not
resolve #import of those headers. Load a C/C#-style workspace once
per pass, and keep protocol-candidate USES linear.

Refs #3179

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#3179)

- Compare LadybugDB labels() as a scalar when excluding Community MEMBER_OF edges.
- Walk superclass members, skip file-static C sibling defs, and ignore comments in ObjC header/macro scans.

Note: pre-existing failure in objective-c-provider integration (worker-pool ready timeout) not addressed by this PR.
Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#3179)

Emit Objective-C declaration captures so compilation-unit siblings can share
header/implementation bindings, and keep class vs protocol visibility groups
distinct.

Note: pre-existing failure in worker-pool startup (GITNEXUS_WORKER_READY_TIMEOUT_MS) not addressed by this PR.
Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#3179)

Emit every comma-separated property/ivar declarator, and count @interface
after a multiline block comment closes so in-declaration macros stay intact.

Note: pre-existing failure in worker-pool startup (GITNEXUS_WORKER_READY_TIMEOUT_MS) not addressed by this PR.
Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: ximengkai <ximengkai@soyoung.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 09:40:21 +00:00

146 lines
5.5 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';
import { OBJECTIVE_C_PROVIDER_FEATURE } from '../../src/core/ingestion/languages/objective-c/analysis-features.js';
import {
OBJECTIVE_C_GRAMMAR_PACKAGE,
OBJECTIVE_C_GRAMMAR_VERSION,
OBJECTIVE_C_PROVIDER_VERSION,
} from '../../src/core/ingestion/languages/objective-c/facts.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('stamps Objective-C provider and grammar versions for semantic rebuilds', () => {
const expectedId =
`objective-c.provider-${OBJECTIVE_C_PROVIDER_VERSION}.` +
`${OBJECTIVE_C_GRAMMAR_PACKAGE}-${OBJECTIVE_C_GRAMMAR_VERSION}`;
expect(OBJECTIVE_C_PROVIDER_FEATURE.id).toBe(expectedId);
const objcFeatures = resolveAnalysisFeatureVersions(ANALYSIS_FEATURES, [
'Sources/SYModuleCaller.m',
'Sources/SYModuleCaller.mm',
'Headers/SYModuleCaller.h',
]);
expect(objcFeatures).toMatchObject({
[OBJECTIVE_C_PROVIDER_FEATURE.id]: OBJECTIVE_C_PROVIDER_FEATURE.version,
});
expect(
resolveAnalysisFeatureVersions(ANALYSIS_FEATURES, ['include/plain.hpp']),
).not.toHaveProperty(OBJECTIVE_C_PROVIDER_FEATURE.id);
expect(
findAnalysisFeatureMismatches(
{ [OBJECTIVE_C_PROVIDER_FEATURE.id]: OBJECTIVE_C_PROVIDER_FEATURE.version - 1 },
{ [OBJECTIVE_C_PROVIDER_FEATURE.id]: OBJECTIVE_C_PROVIDER_FEATURE.version },
),
).toEqual([`version:${OBJECTIVE_C_PROVIDER_FEATURE.id}`]);
});
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');
});
});