Files
renovate/lib/modules/manager/swift/extract.spec.ts
Sebastian Poxhofer 97327c3d28 refactor(linters): add prefer-coerce-array rule (#45492)
* refactor(linters): add prefer-coerce-array rule

Refs #45217

`x ?? []` and `coerceArray(x)` say the same thing, but only the second
one is searchable and consistent. The rule flags a `??` whose default is
a bare `[]` literal; a non-empty default or an asserted `([] as Foo[])`
still passes.

Applying it across `lib/` surfaced three spots the helper cannot cover,
which keep `?? []` with a disable comment or a narrower fix:

- `package-files.ts` destructures a tuple, which `coerceArray()` widens
- `generate.spec.ts` defaults `matchAll()`, an iterator rather than an array
- `mise/upgradeable-tooling.ts` declares a `Record` return, so its
  `?? []` fallback was wrong to begin with and becomes `coerceObject()`

* refactor(platform/github): hoist the GHE version header lookup

`Object.entries(...).find()` returns a tuple, and defaulting it with
`?? []` only to destructure the second element reads as an array
default when it is not one. Name the found entry instead, which drops
the `?? []` rather than routing a tuple through `coerceArray()`.
2026-08-28 09:27:58 +00:00

413 lines
12 KiB
TypeScript

import { codeBlock } from 'common-tags';
import { Fixtures } from '~test/fixtures.ts';
import { coerceArray } from '../../../util/array.ts';
import { extractPackageFile } from './extract.ts';
describe('modules/manager/swift/extract', () => {
describe('extractPackageFile()', () => {
it('returns null for empty content', () => {
expect(extractPackageFile('')).toBeNull();
});
it('returns null for content without dependencies', () => {
const content = `
let package = Package(
name: "MyPackage",
products: [
.library(
name: "MyLibrary",
targets: ["MyLibrary"]
)
],
targets: [
.target(
name: "MyLibrary"
)
]
)
`;
expect(extractPackageFile(content)).toBeNull();
});
it('extracts GitHub dependencies with github-tags datasource', () => {
const content = `
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "https://github.com/example/repo", from: "1.0.0")
]
)
`;
const result = extractPackageFile(content);
expect(result).toMatchObject({
deps: [
{
datasource: 'github-tags',
depName: 'example/repo',
currentValue: 'from: "1.0.0"',
},
],
});
});
it('extracts GitLab dependencies with gitlab-tags datasource', () => {
const content = `
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "https://gitlab.com/example/repo", from: "2.0.0")
]
)
`;
const result = extractPackageFile(content);
expect(result).toMatchObject({
deps: [
{
datasource: 'gitlab-tags',
depName: 'example/repo',
currentValue: 'from: "2.0.0"',
},
],
});
});
it('extracts self-hosted GitHub dependencies with registryUrls', () => {
const content = `
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "https://github.example.com/org/repo", from: "1.0.0")
]
)
`;
const result = extractPackageFile(content);
expect(result).toMatchObject({
deps: [
{
datasource: 'github-tags',
depName: 'org/repo',
currentValue: 'from: "1.0.0"',
registryUrls: ['https://github.example.com'],
},
],
});
});
it('extracts self-hosted GitLab dependencies with registryUrls', () => {
const content = `
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "https://gitlab.mycompany.com/group/project.git", from: "2.5.0")
]
)
`;
const result = extractPackageFile(content);
expect(result).toMatchObject({
deps: [
{
datasource: 'gitlab-tags',
depName: 'group/project',
currentValue: 'from: "2.5.0"',
registryUrls: ['https://gitlab.mycompany.com'],
},
],
});
});
it('extracts GitHub dependencies from SCP-style SSH URL', () => {
const content = `
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "git@github.com:example/repo.git", from: "1.0.0")
]
)
`;
const result = extractPackageFile(content);
expect(result).toMatchObject({
deps: [
{
datasource: 'github-tags',
depName: 'example/repo',
currentValue: 'from: "1.0.0"',
},
],
});
});
it('extracts GitLab dependencies from SCP-style SSH URL', () => {
const content = `
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "git@gitlab.com:group/project.git", from: "2.0.0")
]
)
`;
const result = extractPackageFile(content);
expect(result).toMatchObject({
deps: [
{
datasource: 'gitlab-tags',
depName: 'group/project',
currentValue: 'from: "2.0.0"',
},
],
});
});
it('extracts dependencies from ssh:// URL', () => {
const content = `
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "ssh://git@github.com/example/repo.git", from: "1.0.0")
]
)
`;
const result = extractPackageFile(content);
expect(result).toMatchObject({
deps: [
{
datasource: 'github-tags',
depName: 'example/repo',
currentValue: 'from: "1.0.0"',
},
],
});
});
it('returns null for unparseable SSH URL', () => {
const content = `
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "ssh://", from: "1.0.0")
]
)
`;
expect(extractPackageFile(content)).toBeNull();
});
it('extracts other dependencies with git-tags datasource', () => {
const content = `
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "https://example.com/repo.git", from: "3.0.0")
]
)
`;
const result = extractPackageFile(content);
expect(result).toMatchObject({
deps: [
{
datasource: 'git-tags',
depName: 'https://example.com/repo.git',
currentValue: 'from: "3.0.0"',
},
],
});
});
it('extracts exact version dependencies', () => {
const content = `
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "https://github.com/example/repo", .exact("1.2.3"))
]
)
`;
const result = extractPackageFile(content);
expect(result).toMatchObject({
deps: [
{
datasource: 'github-tags',
depName: 'example/repo',
currentValue: '1.2.3',
},
],
});
});
it('extracts exact version with label syntax', () => {
const content = `
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "https://github.com/example/repo", exact: "1.2.1")
]
)
`;
const result = extractPackageFile(content);
expect(result).toMatchObject({
deps: [
{
datasource: 'github-tags',
depName: 'example/repo',
currentValue: '1.2.1',
},
],
});
});
it('extracts range version dependencies', () => {
const content = `
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "https://github.com/example/repo", "1.0.0"..."2.0.0")
]
)
`;
const result = extractPackageFile(content);
expect(result).toMatchObject({
deps: [
{
datasource: 'github-tags',
depName: 'example/repo',
currentValue: '"1.0.0"..."2.0.0"',
},
],
});
});
it('extracts dependencies from sample package file', () => {
const result = extractPackageFile(Fixtures.get('SamplePackage.swift'));
expect(result?.deps).toHaveLength(10);
const githubDeps = coerceArray(
result?.deps.filter((dep) => dep.datasource === 'github-tags'),
);
expect(githubDeps).toHaveLength(10);
expect(result?.deps).toContainEqual({
datasource: 'github-tags',
depName: '0x7fs/CountedSet',
currentValue: '"master"\n ',
});
expect(result?.deps).toContainEqual({
datasource: 'github-tags',
depName: 'avito-tech/GraphiteClient',
currentValue: '0.1.0',
});
expect(result?.deps).toContainEqual({
datasource: 'github-tags',
depName: 'apple/swift-argument-parser',
currentValue: '1.2.1',
});
// Check that ZIPFoundation was extracted from the multi-line declaration
expect(result?.deps).toContainEqual({
datasource: 'github-tags',
depName: 'weichsel/ZIPFoundation',
currentValue: 'from : "0.9.6"',
});
});
it('handles malformed URLs gracefully', () => {
const content = `
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "not-a-valid-url", from: "1.0.0")
]
)
`;
const result = extractPackageFile(content);
expect(result).toBeNull();
});
it('handles dependencies without version', () => {
const content = `
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "https://github.com/example/repo")
]
)
`;
const result = extractPackageFile(content);
expect(result).toBeNull();
});
it('handles dependencies with local package', () => {
const content = codeBlock`
let package = Package(
name: "MyPackage",
dependencies: [
.package(path: "../LocalPackage")
]
)
`;
const result = extractPackageFile(content);
expect(result).toBeNull();
});
it('handles dependencies with name (deprecated args)', () => {
const content = codeBlock`
let package = Package(
name: "MyPackage",
dependencies: [
.package(name: "repo", url: "https://github.com/example/repo", from: "1.0.0")
]
)
`;
const result = extractPackageFile(content);
expect(result?.deps).toMatchObject([
{ depName: 'example/repo', currentValue: 'from: "1.0.0"' },
]);
});
it('extracts multiple dependencies with different datasources', () => {
const content = `
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "https://github.com/example/github-repo", from: "1.0.0"),
.package(url: "https://gitlab.com/example/gitlab-repo", from: "2.0.0"),
.package(url: "https://example.com/other-repo.git", from: "3.0.0")
]
)
`;
const result = extractPackageFile(content);
expect(result?.deps).toHaveLength(3);
expect(result?.deps[0].datasource).toBe('github-tags');
expect(result?.deps[1].datasource).toBe('gitlab-tags');
expect(result?.deps[2].datasource).toBe('git-tags');
});
it('extracts multiple dependencies with traits arguments', () => {
const content = codeBlock`
let package = Package(
name: "MyPackage",
dependencies: [
.package(url: "https://github.com/example/repo1", from: "1.0.0", traits: []),
.package(url: "https://github.com/example/repo2", "2.0.0"..<"3.0.0", traits: [.defaults]),
.package(
url: "https://github.com/example/repo3",
.exact("4.0.0"),
traits: [
.trait(name: "CUSTOM_TRAIT"),
.trait(name: "ANOTHER_TRAIT")
]
)
]
)
`;
const result = extractPackageFile(content);
expect(result?.deps).toHaveLength(3);
expect(result?.deps).toMatchObject([
{ depName: 'example/repo1', currentValue: 'from: "1.0.0"' },
{ depName: 'example/repo2', currentValue: '"2.0.0"..<"3.0.0"' },
{ depName: 'example/repo3', currentValue: '4.0.0' },
]);
});
});
});