Files
Sebastian Poxhofer 00043d193e test(linters): add codeblock linter rule for spec fixtures (#44551)
* ci(linter): add renovate/codeblock-in-spec-fixtures rule

Add a custom oxlint rule that requires the codeBlock (or stripIndent)
template helper from common-tags for multi-line string fixtures in spec
files (enabled for **/*.spec.ts and test/** only).

The rule only flags shapes whose value can round-trip through the
common-tags helpers unchanged, since both codeBlock and stripIndent trim
the end result and strip the shortest line indentation:

- only literals spanning multiple source lines are flagged (escaped \n
  one-liners are left alone: tagging them is not value-preserving in
  general and gains nothing)
- values with leading/trailing whitespace (e.g. fixtures ending in a
  newline) are not flagged
- tagged templates, describe/it titles, and inline snapshot arguments
  are skipped

All 55 pre-existing violations (22 files) were fixed with a one-off
codemod that re-emits each fixture indented to the statement depth and
verifies value preservation by evaluating codeBlock on the rewritten
template (substitutions kept as opaque placeholders). One site
(pip_requirements/artifacts.spec.ts) mixes line continuations with
escaped newlines and was tagged verbatim instead of re-indented.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(linter): address review findings

Make getSimpleRequirementsFile in pip-compile/extract.spec.ts
value-preserving again: tagging the whole template with codeBlock
trimmed the trailing blank line when deps is empty (the headerOnly.txt
case), silently changing the fixture from ending in '#\n\n' to '#'.
Tag only the static header and append the deps section via a
single-line template, which reproduces the original string exactly
for both empty and non-empty deps (verified by executing old vs new
in node).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:00:02 +00:00

140 lines
4.1 KiB
TypeScript

import { codeBlock } from 'common-tags';
import * as httpMock from '~test/http-mock.ts';
import { logger } from '~test/util.ts';
import { getPkgReleases } from '../index.ts';
import { RustVersionDatasource } from './index.ts';
const datasource = RustVersionDatasource.id;
describe('modules/datasource/rust-version/index', () => {
describe('getReleases', () => {
it('fetches and parses manifest data', async () => {
const manifestsContent = codeBlock`
static.rust-lang.org/dist/2025-11-24/channel-rust-nightly.toml
static.rust-lang.org/dist/2024-10-17/channel-rust-1.82.0.toml
static.rust-lang.org/dist/2025-01-15/channel-rust-1.83.0-beta.5.toml
static.rust-lang.org/dist/2025-11-24/channel-rust-stable.toml
static.rust-lang.org/dist/2025-11-24/channel-rust-beta.toml
`;
httpMock
.scope('https://static.rust-lang.org')
.get('/manifests.txt')
.reply(200, manifestsContent);
const res = await getPkgReleases({
datasource,
packageName: 'rust',
});
expect(res).toMatchObject({
releases: expect.arrayContaining([
{
version: 'nightly-2025-11-24',
releaseTimestamp: '2025-11-24T00:00:00.000Z',
},
{
version: '1.82.0',
releaseTimestamp: '2024-10-17T00:00:00.000Z',
},
{
version: '1.83.0-beta.5',
releaseTimestamp: '2025-01-15T00:00:00.000Z',
},
]),
sourceUrl: 'https://github.com/rust-lang/rust',
});
expect(res?.releases).toHaveLength(3);
});
it('deduplicates versions with latest date', async () => {
const manifestsContent = codeBlock`
static.rust-lang.org/dist/2024-10-17/channel-rust-1.82.0.toml
static.rust-lang.org/dist/2024-10-18/channel-rust-1.82.0.toml
static.rust-lang.org/dist/2024-10-19/channel-rust-1.82.0.toml
`;
httpMock
.scope('https://static.rust-lang.org')
.get('/manifests.txt')
.reply(200, manifestsContent);
const res = await getPkgReleases({
datasource,
packageName: 'rust',
});
expect(res?.releases).toEqual([
{
version: '1.82.0',
releaseTimestamp: '2024-10-19T00:00:00.000Z',
},
]);
});
it('ignores unexpected URLs', async () => {
const manifestsContent = codeBlock`
static.rust-lang.org/dist/invalid.toml
static.rust-lang.org/dist/2024-10-17/channel-rust-1.82.0.toml
`;
httpMock
.scope('https://static.rust-lang.org')
.get('/manifests.txt')
.reply(200, manifestsContent);
const res = await getPkgReleases({
datasource,
packageName: 'rust',
});
expect(res?.releases).toEqual([
{
version: '1.82.0',
releaseTimestamp: '2024-10-17T00:00:00.000Z',
},
]);
});
it('ignores blank lines silently (no spurious warning)', async () => {
// `manifests.txt` ends with a trailing newline upstream, so the
// body's `split('\n')` always yields an empty final element. Also
// exercise a whitespace-only line in case upstream introduces one.
const manifestsContent =
'static.rust-lang.org/dist/2024-10-17/channel-rust-1.82.0.toml\n\n \n';
httpMock
.scope('https://static.rust-lang.org')
.get('/manifests.txt')
.reply(200, manifestsContent);
const res = await getPkgReleases({
datasource,
packageName: 'rust',
});
expect(res?.releases).toEqual([
{
version: '1.82.0',
releaseTimestamp: '2024-10-17T00:00:00.000Z',
},
]);
expect(logger.logger.warn).not.toHaveBeenCalled();
});
it('throws for network error', async () => {
httpMock
.scope('https://static.rust-lang.org')
.get('/manifests.txt')
.reply(500);
await expect(
getPkgReleases({
datasource,
packageName: 'rust',
}),
).rejects.toThrow('external-host-error');
});
});
});