build: tighten oxlint and repair type checks

This commit is contained in:
Peter Steinberger 2026-05-01 08:17:38 +01:00
parent 6bce49c0e8
commit 07e17b9853
No known key found for this signature in database
13 changed files with 113 additions and 36 deletions

View File

@ -52,9 +52,12 @@
"test:coverage": "pnpm run build:all && node --test --experimental-test-coverage --test-coverage-include='dist/**/*.js' --test-coverage-exclude='dist/repair/*.test.js' --test-coverage-lines=49 --test-coverage-branches=66 --test-coverage-functions=57 test/*.test.ts test/repair/*.test.ts dist/repair/*.test.js",
"test:coverage:changed": "pnpm run build:all && node --test --experimental-test-coverage --test-coverage-include='dist/repair/fix-prompt-builder.js' --test-coverage-lines=85 --test-coverage-branches=85 --test-coverage-functions=85 test/repair/*.test.ts dist/repair/*.test.js",
"check:active-surface": "node scripts/check-active-surface.ts",
"lint": "oxlint src --tsconfig tsconfig.json && oxlint src/repair --tsconfig tsconfig.repair.json",
"format": "oxfmt --write src scripts test package.json tsconfig.json tsconfig.repair.json .oxfmtrc.json schema .github/workflows",
"format:check": "oxfmt --check src scripts test package.json tsconfig.json tsconfig.repair.json .oxfmtrc.json schema .github/workflows",
"lint": "pnpm run lint:src && pnpm run lint:repair && pnpm run lint:scripts",
"lint:src": "oxlint src/*.ts --tsconfig tsconfig.json --type-aware --deny-warnings --report-unused-disable-directives -D correctness",
"lint:repair": "oxlint src/repair --tsconfig tsconfig.repair.json --deny-warnings --report-unused-disable-directives -D correctness",
"lint:scripts": "oxlint scripts test --deny-warnings --report-unused-disable-directives -D correctness",
"format": "oxfmt --write src scripts test package.json tsconfig.json tsconfig.repair.json .oxfmtrc.json schema .github/actions .github/workflows",
"format:check": "oxfmt --check src scripts test package.json tsconfig.json tsconfig.repair.json .oxfmtrc.json schema .github/actions .github/workflows",
"oxformat": "pnpm run format",
"oxformat:check": "pnpm run format:check",
"check": "pnpm run check:active-surface && pnpm run build:all && pnpm run lint && pnpm run test:unit && pnpm run test:repair && pnpm run test:coverage:changed && pnpm run test:coverage && pnpm run format:check"
@ -63,7 +66,8 @@
"@types/node": "^25.6.0",
"@typescript/native-preview": "7.0.0-dev.20260423.1",
"oxfmt": "^0.46.0",
"oxlint": "^1.61.0"
"oxlint": "^1.61.0",
"oxlint-tsgolint": "0.22.1"
},
"engines": {
"node": ">=24"

69
pnpm-lock.yaml generated
View File

@ -19,7 +19,10 @@ importers:
version: 0.46.0
oxlint:
specifier: ^1.61.0
version: 1.61.0
version: 1.61.0(oxlint-tsgolint@0.22.1)
oxlint-tsgolint:
specifier: 0.22.1
version: 0.22.1
packages:
@ -145,6 +148,36 @@ packages:
cpu: [x64]
os: [win32]
'@oxlint-tsgolint/darwin-arm64@0.22.1':
resolution: {integrity: sha512-4150Lpgc1YM09GcjA6GSrra1JoPjC7aOpfywLjWEY4vW0Sd1qKzqHF1WRaiw0/qUZ40OATYdv3aRd7ipPkWQbw==}
cpu: [arm64]
os: [darwin]
'@oxlint-tsgolint/darwin-x64@0.22.1':
resolution: {integrity: sha512-vFWcPWYOgZs4HWcgS1EjUZg33NLcNfEYU49KGImmCfZWkflENrmBYV4HN/C0YeAPum6ZZ/goPSvQrB/cOD+NfA==}
cpu: [x64]
os: [darwin]
'@oxlint-tsgolint/linux-arm64@0.22.1':
resolution: {integrity: sha512-6LiUpP0Zir3+29FvBm7Y28q/dBjSHqTZ5MhG1Ckw4fGhI4cAvbcwXaKvbjx1TP7rRmBNOoq/M5xdpHjTb+GAew==}
cpu: [arm64]
os: [linux]
'@oxlint-tsgolint/linux-x64@0.22.1':
resolution: {integrity: sha512-fuX1hEQfpHauUbXADsfqVhRzrUrGabzGXbj5wsp2vKhV5uk/Rze8Mba9GdjFGECzvXudMGqHqxB4r6jGRdhxVA==}
cpu: [x64]
os: [linux]
'@oxlint-tsgolint/win32-arm64@0.22.1':
resolution: {integrity: sha512-8SZidAj+jrbZf9ZjBEYW0tiNZ+KasqB2zgW26qdiPpQSF/DzURnPmXz651IeA9YsmbVdHGIooEHUmev6QJdquA==}
cpu: [arm64]
os: [win32]
'@oxlint-tsgolint/win32-x64@0.22.1':
resolution: {integrity: sha512-QweSk9H5lFh5Y+WUf2Kq/OAN88V6+62ZwGhP38gqdRotI90luXSMkruFTj7Q2rYrzH4ZVNaSqx7NY8JpSfIzqg==}
cpu: [x64]
os: [win32]
'@oxlint/binding-android-arm-eabi@1.61.0':
resolution: {integrity: sha512-6eZBPgiigK5txqoVgRqxbaxiom4lM8AP8CyKPPvpzKnQ3iFRFOIDc+0AapF+qsUSwjOzr5SGk4SxQDpQhkSJMQ==}
engines: {node: ^20.19.0 || >=22.12.0}
@ -322,6 +355,10 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
oxlint-tsgolint@0.22.1:
resolution: {integrity: sha512-YUSGSLUnoolsu8gxISEDio3q1rtsCozwfOzASUn3DT2mR2EeQ93uEEnen7s+6LpF+lyTQFln1pQfqwBh/fsVEg==}
hasBin: true
oxlint@1.61.0:
resolution: {integrity: sha512-ZC0ALuhDZ6ivOFG+sy0D0pEDN49EvsId98zVlmYdkcXHsEM14m/qTNUEsUpiFiCVbpIxYtVBmmLE87nsbUHohQ==}
engines: {node: ^20.19.0 || >=22.12.0}
@ -398,6 +435,24 @@ snapshots:
'@oxfmt/binding-win32-x64-msvc@0.46.0':
optional: true
'@oxlint-tsgolint/darwin-arm64@0.22.1':
optional: true
'@oxlint-tsgolint/darwin-x64@0.22.1':
optional: true
'@oxlint-tsgolint/linux-arm64@0.22.1':
optional: true
'@oxlint-tsgolint/linux-x64@0.22.1':
optional: true
'@oxlint-tsgolint/win32-arm64@0.22.1':
optional: true
'@oxlint-tsgolint/win32-x64@0.22.1':
optional: true
'@oxlint/binding-android-arm-eabi@1.61.0':
optional: true
@ -514,7 +569,16 @@ snapshots:
'@oxfmt/binding-win32-ia32-msvc': 0.46.0
'@oxfmt/binding-win32-x64-msvc': 0.46.0
oxlint@1.61.0:
oxlint-tsgolint@0.22.1:
optionalDependencies:
'@oxlint-tsgolint/darwin-arm64': 0.22.1
'@oxlint-tsgolint/darwin-x64': 0.22.1
'@oxlint-tsgolint/linux-arm64': 0.22.1
'@oxlint-tsgolint/linux-x64': 0.22.1
'@oxlint-tsgolint/win32-arm64': 0.22.1
'@oxlint-tsgolint/win32-x64': 0.22.1
oxlint@1.61.0(oxlint-tsgolint@0.22.1):
optionalDependencies:
'@oxlint/binding-android-arm-eabi': 1.61.0
'@oxlint/binding-android-arm64': 1.61.0
@ -535,6 +599,7 @@ snapshots:
'@oxlint/binding-win32-arm64-msvc': 1.61.0
'@oxlint/binding-win32-ia32-msvc': 1.61.0
'@oxlint/binding-win32-x64-msvc': 1.61.0
oxlint-tsgolint: 0.22.1
tinypool@2.1.0: {}

View File

@ -1436,7 +1436,7 @@ function closingPullRequestsForIssue(number: number): unknown[] {
export function openClosingPullRequestApplyReason(pullRequests: readonly unknown[]): string | null {
const openPulls = pullRequests
.map(asRecord)
.filter((pull) => String(pull.state ?? "").toLowerCase() === "open")
.filter((pull) => typeof pull.state === "string" && pull.state.toLowerCase() === "open")
.map((pull) => ({
number: typeof pull.number === "number" ? pull.number : null,
title: typeof pull.title === "string" ? pull.title : "",
@ -1502,7 +1502,7 @@ function collectRelatedMentions(options: {
return mentions;
}
function compactRelatedItem(number: number, mentionedIn: string[]): unknown | null {
function compactRelatedItem(number: number, mentionedIn: string[]): Record<string, unknown> | null {
try {
const issue = ghJson<unknown>(["api", `repos/${targetRepo()}/issues/${number}`]);
const issueRecord = asRecord(issue);
@ -1748,11 +1748,12 @@ function relatedCounterpartInfo(value: unknown): {
const issue = asRecord(record.issue);
const pullRequest = asRecord(record.pullRequest);
const isPullRequest = Object.keys(pullRequest).length > 0;
const state = isPullRequest ? pullRequest.state : issue.state;
return {
number: typeof issue.number === "number" ? issue.number : null,
kind: isPullRequest ? "pull_request" : "issue",
author: normalizeAuthorLogin(isPullRequest ? pullRequest.author : issue.author),
state: String((isPullRequest ? pullRequest.state : issue.state) ?? "").toLowerCase(),
state: typeof state === "string" ? state.toLowerCase() : "",
title: typeof issue.title === "string" ? issue.title : "",
};
}
@ -2714,8 +2715,8 @@ function collectItemContext(item: Item): ItemContext {
timeline: timeline.length,
},
};
let pullRequest: unknown | undefined;
let pullReviewComments: unknown[] | undefined;
let pullRequest: unknown = null;
let pullReviewComments: unknown[] | null = null;
if (item.kind === "issue") {
const closingPullRequests = closingPullRequestsForIssue(item.number);
if (closingPullRequests.length > 0) {

View File

@ -33,7 +33,7 @@ function existingTimelineRows(value: JsonValue): string[] {
new RegExp(`${escapeRegExp(TIMELINE_START)}([\\s\\S]*?)${escapeRegExp(TIMELINE_END)}`),
);
if (!match) return [];
return match[1]
return match[1]!
.split(/\r?\n/)
.map((line) => line.trimEnd())
.filter((line) => line.includes(EVENT_PREFIX));

View File

@ -2411,7 +2411,7 @@ async function mapLimit<T, R>(
while (next < items.length) {
const index = next;
next += 1;
results[index] = await mapper(items[index]);
results[index] = await mapper(items[index] as T);
}
});
await Promise.all(workers);

View File

@ -441,9 +441,9 @@ function findingKinds(markdown: string) {
function likelyFilesFromReport(markdown: string) {
const out: JsonValue[] = [];
for (const match of markdown.matchAll(/^- File:\s*`?([^`\n]+?)`?\s*$/gim))
out.push(match[1].trim());
out.push(match[1]!.trim());
const changed = markdown.match(/^- Changed files:\s*(.+)$/im)?.[1] ?? "";
for (const match of changed.matchAll(/`([^`]+)`/g)) out.push(match[1].trim());
for (const match of changed.matchAll(/`([^`]+)`/g)) out.push(match[1]!.trim());
return unique(
out.filter(
(file: JsonValue) =>

View File

@ -155,7 +155,7 @@ function focusedFileExcerpt(content: string, tokens: string[]) {
.map((token) => token.toLowerCase())
.filter((token) => token.length >= 4);
for (let index = 0; index < lines.length; index += 1) {
const lower = lines[index].toLowerCase();
const lower = lines[index]!.toLowerCase();
if (lowerTokens.some((token) => lower.includes(token))) {
for (
let line = Math.max(0, index - 8);

View File

@ -12,11 +12,11 @@ export type GitPublishOptions = {
message: string;
paths: readonly string[];
restorePaths?: readonly string[];
maxAttempts?: number;
pushAttempts?: number;
maxAttempts?: number | undefined;
pushAttempts?: number | undefined;
remote?: string;
branch?: string;
rebaseStrategy?: RebaseStrategy;
rebaseStrategy?: RebaseStrategy | undefined;
};
export type RebaseStrategy = "normal" | "theirs" | "apply-records";

View File

@ -60,8 +60,8 @@ export function remoteBranchSha({ targetDir, branch }: TargetBranch): string {
encoding: "utf8",
});
if (child.status !== 0) return "";
const [sha] = child.stdout.trim().split(/\s+/);
return /^[0-9a-f]{40}$/.test(sha ?? "") ? sha : "";
const sha = child.stdout.trim().split(/\s+/)[0] ?? "";
return /^[0-9a-f]{40}$/.test(sha) ? sha : "";
}
export function branchHasBaseDiff({ targetDir, baseBranch }: TargetBaseBranch): boolean {

View File

@ -9,10 +9,12 @@ export function parsePullRequestUrl(value: unknown): GitHubRef | null {
.trim()
.match(/^https:\/\/github\.com\/([^/\s]+\/[^/\s]+)\/pull\/(\d+)(?:[/?#].*)?$/i);
if (!match) return null;
const repo = match[1]!;
const numberText = match[2]!;
return {
repo: match[1],
number: Number(match[2]),
url: `https://github.com/${match[1]}/pull/${match[2]}`,
repo,
number: Number(numberText),
url: `https://github.com/${repo}/pull/${numberText}`,
};
}
@ -26,19 +28,22 @@ export function parseIssueOrPullRef(value: unknown, defaultRepo = ""): GitHubRef
/^https:\/\/github\.com\/([^/]+\/[^/]+)\/(?:issues|pull)\/(\d+)(?:[/?#].*)?$/i,
);
if (urlMatch) {
const repo = urlMatch[1]!;
const numberText = urlMatch[2]!;
return {
repo: urlMatch[1],
number: Number(urlMatch[2]),
url: `https://github.com/${urlMatch[1]}/issues/${urlMatch[2]}`,
repo,
number: Number(numberText),
url: `https://github.com/${repo}/issues/${numberText}`,
};
}
const shorthand = text.match(/^#?(\d+)$/);
if (!shorthand || !defaultRepo) return null;
const numberText = shorthand[1]!;
return {
repo: defaultRepo,
number: Number(shorthand[1]),
url: `https://github.com/${defaultRepo}/issues/${shorthand[1]}`,
number: Number(numberText),
url: `https://github.com/${defaultRepo}/issues/${numberText}`,
};
}
@ -46,7 +51,7 @@ export function issueNumberFromRef(value: unknown, expectedRepo = ""): number {
const shorthand = String(value ?? "")
.trim()
.match(/^#?(\d+)$/);
if (shorthand) return Number(shorthand[1]);
if (shorthand) return Number(shorthand[1]!);
const parsed = parseIssueOrPullRef(value);
if (!parsed) return 0;

View File

@ -77,10 +77,8 @@ function repairStem(surface: unknown, issue: unknown) {
}
function firstSentence(value: unknown) {
return normalizeTitleText(value)
.split(/(?<=[.!?])\s+/)[0]
.replace(/[.!?]+$/, "")
.trim();
const first = normalizeTitleText(value).split(/(?<=[.!?])\s+/)[0]!;
return first.replace(/[.!?]+$/, "").trim();
}
function compactTitle(value: unknown, maxLength: number) {

View File

@ -97,11 +97,12 @@ export function runAllowedValidationCommands(
for (const command of requiredValidationCommands(commands, cwd, options)) {
const resolvedCommands = resolveAllowedValidationCommands(command, cwd, baseBranch, options);
for (const parts of resolvedCommands) {
const executable = parts[0]!;
const rendered = parts.join(" ");
if (executed.includes(rendered)) continue;
while (true) {
try {
run(parts[0], parts.slice(1), { cwd, env: validationEnv });
run(executable, parts.slice(1), { cwd, env: validationEnv });
executed.push(rendered);
break;
} catch (error) {
@ -114,9 +115,10 @@ export function runAllowedValidationCommands(
});
if (fallbackCommands.length > 0) {
for (const fallbackParts of fallbackCommands) {
const fallbackExecutable = fallbackParts[0]!;
const fallbackRendered = fallbackParts.join(" ");
if (executed.includes(fallbackRendered)) continue;
run(fallbackParts[0], fallbackParts.slice(1), { cwd, env: validationEnv });
run(fallbackExecutable, fallbackParts.slice(1), { cwd, env: validationEnv });
executed.push(fallbackRendered);
}
break;

View File

@ -8,6 +8,8 @@
"rootDir": "src/repair",
"outDir": "dist/repair",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"useUnknownInCatchVariables": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,