GitNexus/gitnexus/test/unit/scope-resolution/swift/import-decomposer.test.ts
Twisted_Arrow dcb2eb5cb4
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
fix(swift): resolve imports from Package.swift targets, not path segments (#3105)
* fix(swift): match repeated SPM target prefixes

* test(swift): cover repeated SPM target prefixes

* test(swift): cover valid prefix before later partial match

* docs(swift): clarify target grouping parity scope

* docs(swift): clarify target grouping parity scope

* docs(swift): clarify target grouping parity scope

* fix(swift): resolve imports from Package.swift targets, not path segments

Stop fabricating IMPORTS from import Foundation onto a same-named folder.
Declare modules from Package.swift when the manifest is usable; keep
Sources/* for grouping and fail-open folder resolve minus SDK names.

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

* fix(swift): keep empty Package.swift declarations and nested .target() deps external

An inferred Sources/* folder is grouping-only. A dependency .target(name:) is not a module. Treat both as unresolved so import Foundation cannot bind to a decoy folder.

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

* fix(swift): keep implicit IMPORTS intra-group and gate linear Package.swift resolve

@_exported must not paint sibling files as implicit imports. A dedicated
bench pins declaration-only resolve and (t_4n/t_n)/4 linearity.

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

* Address PR review feedback (#3105)

Honor member-only @_exported imports, skip comments while scanning
Package.swift factories, fail-open mixed helper-built target lists, and
block CoreData/CoreGraphics decoy folders on the inferred path.

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

* Address PR review feedback (#3105)

Match path: "." as the package root, skip block-commented Package.swift
factories, read import kind from the clause only, and skip capture tests
when the optional Swift grammar is missing.

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

* fix(ci): scan Swift import-kind without nested regex backtracking

CodeQL js/redos flagged the comment-skipping IMPORT_KIND_RE; a linear walk keeps the same kind tokens.

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

* fix(ci): linear Package.swift factory scan and gate Swift context

parseSwiftPackageManifest re-walked every prefix for comments (O(n²) in factory count). Resume the scan and cover nested factories in one pass. Wire Swift into the import-target context arm now that resolveImportTarget is 5-arg.

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

* Address PR review feedback (#3105)

Tighten Package.swift and import-text scanners: skip comments/strings, reject escapes, treat ident + [ as incomplete, and drop the unused factory-comment wrapper.

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

* fix(ci): prettier the @_exported availability fixture

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

* Address PR review feedback (#3105)

Judge Package.swift completeness from Package(...)'s own targets: argument instead of raw-text regexes, nest block comments when reading an import kind, and strip leading ./ from declared target paths.

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

* Address PR review feedback (#3105)

Collect Package.swift factories only from Package(targets: [...]), fail-open on computed array elements, and treat // after a label colon as a comment.

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

* Address PR review feedback (#3105)

Ignore stray factories when Package() exists but omits targets:.

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

* Address PR review feedback (#3105)

Require the Package-scan seen box so the always-true undefined guard goes away.

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

---------

Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-19 15:50:31 +01:00

154 lines
4.4 KiB
TypeScript

/**
* Swift import capture: import-kind, @_exported, and module path (R5, R6).
*/
import { describe, expect, it } from 'vitest';
import { SupportedLanguages } from '../../../../src/config/supported-languages.js';
import { emitSwiftScopeCaptures } from '../../../../src/core/ingestion/languages/swift/index.js';
import { interpretSwiftImport } from '../../../../src/core/ingestion/languages/swift/interpret.js';
import { isLanguageAvailable } from '../../../../src/core/tree-sitter/parser-loader.js';
function importsOf(src: string) {
return emitSwiftScopeCaptures(src, 'Probe.swift')
.map((match) => interpretSwiftImport(match))
.filter((imp): imp is NonNullable<typeof imp> => imp !== null);
}
const swiftAvailable = isLanguageAvailable(SupportedLanguages.Swift);
describe.skipIf(!swiftAvailable)('interpretSwiftImport via emitSwiftScopeCaptures', () => {
it('import Foundation is a namespace, not exported', () => {
expect(importsOf('import Foundation')).toEqual([
{
kind: 'namespace',
localName: 'Foundation',
importedName: 'Foundation',
targetRaw: 'Foundation',
},
]);
});
it('import struct Models.User is a named binding of User', () => {
expect(importsOf('import struct Models.User')).toEqual([
{
kind: 'named',
localName: 'User',
importedName: 'User',
targetRaw: 'Models',
},
]);
});
it('allows a block comment between import and its kind', () => {
expect(importsOf('import /* selected API */ struct Models.User')).toEqual([
{
kind: 'named',
localName: 'User',
importedName: 'User',
targetRaw: 'Models',
},
]);
});
it('allows a line comment between import and its kind', () => {
expect(importsOf('import // selected API\nstruct Models.User')).toEqual([
{
kind: 'named',
localName: 'User',
importedName: 'User',
targetRaw: 'Models',
},
]);
});
it('reads the kind after a long block comment that repeats star-slash-slash-star', () => {
const noise = '*//*'.repeat(80);
expect(importsOf(`import /* ${noise} */ struct Models.User`)).toEqual([
{
kind: 'named',
localName: 'User',
importedName: 'User',
targetRaw: 'Models',
},
]);
});
it('does not take import kind from an @available message string', () => {
expect(
importsOf('@available(*, deprecated, message: "import struct") import Foo.Bar'),
).toMatchObject([{ kind: 'namespace', targetRaw: 'Foo' }]);
});
it('preserves @testable as the same module', () => {
const [imp] = importsOf('@testable import App');
expect(imp).toMatchObject({
kind: 'namespace',
targetRaw: 'App',
});
});
it('@_exported import Models is a reexport of the module handle', () => {
expect(importsOf('@_exported import Models')).toEqual([
{
kind: 'reexport',
localName: 'Models',
importedName: 'Models',
targetRaw: 'Models',
},
]);
});
it('@_exported import struct Models.User is a reexport of User', () => {
expect(importsOf('@_exported import struct Models.User')).toEqual([
{
kind: 'reexport',
localName: 'User',
importedName: 'User',
targetRaw: 'Models',
},
]);
});
it('public import Models is not a reexport', () => {
expect(importsOf('public import Models')).toEqual([
{
kind: 'namespace',
localName: 'Models',
importedName: 'Models',
targetRaw: 'Models',
},
]);
});
it('does not treat _exported inside an @available message as @_exported', () => {
expect(importsOf('@available(*, deprecated, message: "_exported") import Models')).toEqual([
{
kind: 'namespace',
localName: 'Models',
importedName: 'Models',
targetRaw: 'Models',
},
]);
});
it('reads the kind after a comment that itself contains import', () => {
expect(importsOf('import /* import */ struct Models.User')).toEqual([
{
kind: 'named',
localName: 'User',
importedName: 'User',
targetRaw: 'Models',
},
]);
});
it('reads the kind after nested block comments', () => {
expect(importsOf('import /* outer /* inner */ */ struct Models.User')).toEqual([
{
kind: 'named',
localName: 'User',
importedName: 'User',
targetRaw: 'Models',
},
]);
});
});