From ccc0b4a9f2354672dcd66ab107b4db4cfc3bc4f2 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 17 Aug 2026 11:58:36 -0700 Subject: [PATCH 01/24] Add TypeScript 7 support to dtslint --- .changeset/strong-dragons-check.md | 9 + package.json | 2 +- packages/dtslint-runner/src/index.ts | 1 + packages/dtslint/README.md | 10 + packages/dtslint/package.json | 11 +- packages/dtslint/src/index.ts | 9 +- packages/dtslint/src/lint.ts | 72 ++- packages/dtslint/src/lintTypeScript7.ts | 422 ++++++++++++++++++ .../test/fixtures/typescript7/fail/index.d.ts | 1 + .../test/fixtures/typescript7/fail/test.ts | 6 + .../fixtures/typescript7/fail/tsconfig.json | 9 + .../test/fixtures/typescript7/pass/index.d.ts | 1 + .../test/fixtures/typescript7/pass/test.ts | 4 + .../fixtures/typescript7/pass/tsconfig.json | 9 + packages/dtslint/test/index.test.ts | 37 ++ packages/dtslint/test/tsconfig.json | 2 +- packages/typescript-packages/package.json | 3 +- packages/typescript-packages/src/index.ts | 4 +- .../typescript-packages/test/index.test.ts | 7 +- packages/typescript-versions/src/index.ts | 2 +- .../typescript-versions/test/index.test.ts | 20 +- pnpm-lock.yaml | 87 +++- 22 files changed, 692 insertions(+), 36 deletions(-) create mode 100644 .changeset/strong-dragons-check.md create mode 100644 packages/dtslint/src/lintTypeScript7.ts create mode 100644 packages/dtslint/test/fixtures/typescript7/fail/index.d.ts create mode 100644 packages/dtslint/test/fixtures/typescript7/fail/test.ts create mode 100644 packages/dtslint/test/fixtures/typescript7/fail/tsconfig.json create mode 100644 packages/dtslint/test/fixtures/typescript7/pass/index.d.ts create mode 100644 packages/dtslint/test/fixtures/typescript7/pass/test.ts create mode 100644 packages/dtslint/test/fixtures/typescript7/pass/tsconfig.json diff --git a/.changeset/strong-dragons-check.md b/.changeset/strong-dragons-check.md new file mode 100644 index 0000000000..07092eb024 --- /dev/null +++ b/.changeset/strong-dragons-check.md @@ -0,0 +1,9 @@ +--- +"@definitelytyped/dtslint": patch +"@definitelytyped/dtslint-runner": patch +"@definitelytyped/typescript-packages": patch +"@definitelytyped/typescript-versions": patch +--- + +Add TypeScript 7.1 support using TypeScript's unstable out-of-process API for compiler diagnostics and +`$ExpectType` assertions. diff --git a/package.json b/package.json index b25c424fb1..01ee18650d 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint": "eslint . --report-unused-disable-directives --ext .ts", "format": "prettier --write 'packages/**/*.ts'", "format:check": "prettier --check 'packages/**/*.ts'", - "test": "jest", + "test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js", "build": "tsc -b .", "retag": "node packages/retag/dist/index.js", "ci:publish": "pnpm changeset tag && pnpm publish -r" diff --git a/packages/dtslint-runner/src/index.ts b/packages/dtslint-runner/src/index.ts index b6ec14885e..2cb91e5548 100644 --- a/packages/dtslint-runner/src/index.ts +++ b/packages/dtslint-runner/src/index.ts @@ -59,6 +59,7 @@ if (require.main === module) { group: "dtslint options", description: "Run dtslint only with typescript@next instead of all supported TypeScript versions.", type: "boolean", + alias: "next", conflicts: "localTypeScriptPath", }, expectOnly: { diff --git a/packages/dtslint/README.md b/packages/dtslint/README.md index 4e8aeaa1cb..5586651c8e 100644 --- a/packages/dtslint/README.md +++ b/packages/dtslint/README.md @@ -182,6 +182,16 @@ Use your locally installed version of TypeScript. ```sh dtslint --localTs node_modules/typescript/lib types ``` + +For a locally built TypeScript 7, pass the build output directory containing the `tsserver` or `tsgo` executable. + +- `--next` / `--onlyTestTsNext` + +Test only with `typescript@next`. + +TypeScript 7 is checked through its unstable out-of-process API. Compiler diagnostics and `$ExpectType` assertions +are supported; other ESLint-based dtslint rules are skipped for TypeScript 7. + - `--expectOnly` Disable all the lint rules except the one that checks for type correctness. diff --git a/packages/dtslint/package.json b/packages/dtslint/package.json index d3173cedd7..0123a22e6a 100644 --- a/packages/dtslint/package.json +++ b/packages/dtslint/package.json @@ -19,7 +19,7 @@ }, "scripts": { "build": "tsc -b .", - "test": "../../node_modules/.bin/jest --config ../../jest.config.js packages/dtslint" + "test": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js --config ../../jest.config.js packages/dtslint" }, "dependencies": { "@arethetypeswrong/cli": "0.18.3", @@ -36,15 +36,12 @@ "eslint": "^8.57.1", "eslint-plugin-import": "^2.32.0", "semver": "^7.7.4", - "strip-json-comments": "^3.1.1" - }, - "peerDependencies": { - "typescript": "<7" + "strip-json-comments": "^3.1.1", + "typescript": "^5.9.3" }, "devDependencies": { "@types/eslint": "^8.56.12", - "@types/semver": "^7.7.1", - "typescript": "^5.9.3" + "@types/semver": "^7.7.1" }, "engines": { "node": ">=20.17.0" diff --git a/packages/dtslint/src/index.ts b/packages/dtslint/src/index.ts index 64ca690d68..21ab46f561 100644 --- a/packages/dtslint/src/index.ts +++ b/packages/dtslint/src/index.ts @@ -60,6 +60,7 @@ async function main(): Promise { onlyNpmChecks = true; break; case "--onlyTestTsNext": + case "--next": onlyTestTsNext = true; break; // Only for use by types-publisher. @@ -91,6 +92,9 @@ async function main(): Promise { if (onlyNpmChecks && skipNpmChecks) { throw new Error("Cannot use --onlyNpmChecks and --skipNpmChecks together."); } + if (onlyTestTsNext && tsLocal) { + throw new Error("Cannot use --next/--onlyTestTsNext and --localTs together."); + } if (lookingForTsLocal) { throw new Error("Path for --localTs was not provided."); } @@ -110,12 +114,13 @@ async function main(): Promise { function usage(): void { console.error( - "Usage: dtslint [--version] [--onlyTestTsNext] [--expectOnly] [--localTs path] [--skipNpmChecks] [--onlyNpmChecks]", + "Usage: dtslint [--version] [--next|--onlyTestTsNext] [--expectOnly] [--localTs path] [--skipNpmChecks] [--onlyNpmChecks]", ); console.error("Args:"); console.error(" --version Print version and exit."); console.error(" --expectOnly Run only the ExpectType lint rule."); console.error(" --onlyTestTsNext Only run with `typescript@next`, not with the minimum version."); + console.error(" --next Alias for --onlyTestTsNext."); console.error(" --localTs path Run with *path* as the latest version of TS."); console.error(" --skipNpmChecks Don't query npm - skips name/version checks and @arethetypeswrong/cli."); console.error(" --onlyNpmChecks Only run the npm checks (name/version checks and @arethetypeswrong/cli)."); @@ -299,7 +304,7 @@ async function testTypesVersion( } } - const err = await lint(dirPath, lowVersion, hiVersion, isLatest, expectOnly, tsLocal); + const err = await lint(dirPath, tsconfigs, lowVersion, hiVersion, isLatest, expectOnly, tsLocal); if (err) { errors.push(err); } diff --git a/packages/dtslint/src/lint.ts b/packages/dtslint/src/lint.ts index e560b21290..2438ff0eaa 100644 --- a/packages/dtslint/src/lint.ts +++ b/packages/dtslint/src/lint.ts @@ -1,15 +1,18 @@ import { TypeScriptVersion } from "@definitelytyped/typescript-versions"; import { withoutStart } from "@definitelytyped/utils"; +import fs from "fs"; import assert = require("assert"); import { join as joinPaths, normalize, resolve } from "path"; import { ESLint } from "eslint"; import * as TsType from "typescript"; import { createProgram } from "./createProgram"; +import { lintTypeScript7 } from "./lintTypeScript7"; import { typeScriptPath } from "./typescript-installer"; export async function lint( dirPath: string, + tsconfigs: readonly string[], minVersion: TsVersion, maxVersion: TsVersion, isLatest: boolean, @@ -31,17 +34,38 @@ export async function lint( return files; } - const options = getEslintOptions(expectOnly, minVersion, maxVersion, tsLocal); - const eslint = new ESLint(options); - const formatter = await eslint.loadFormatter("stylish"); - const results = await eslint.lintFiles(files); - const output = formatter.format(results); - for (const estreePath of estrees) { - if (!estreePath) continue; - const estree = require(estreePath) as typeof import("@typescript-eslint/typescript-estree"); - estree.clearCaches(); + const versions = range(minVersion, maxVersion); + const legacyVersions: TsVersion[] = []; + const typeScript7Versions: TsVersion[] = []; + for (const version of versions) { + (isTypeScript7(version, tsLocal) ? typeScript7Versions : legacyVersions).push(version); } - return output; + + const outputs: string[] = []; + if (legacyVersions.length) { + const options = getEslintOptions(expectOnly, legacyVersions, tsLocal); + const eslint = new ESLint(options); + const formatter = await eslint.loadFormatter("stylish"); + const results = await eslint.lintFiles(files); + const output = await formatter.format(results); + if (output) { + outputs.push(output); + } + for (const estreePath of estrees) { + if (!estreePath) continue; + const estree = require(estreePath) as typeof import("@typescript-eslint/typescript-estree"); + estree.clearCaches(); + } + } + + for (const version of typeScript7Versions) { + const output = await lintTypeScript7(dirPath, tsconfigs, version, isLatest, tsLocal); + if (output) { + outputs.push(output); + } + } + + return outputs.join("\n") || undefined; } function getSourceFiles(dirPath: string, isLatest: boolean) { @@ -84,11 +108,10 @@ function tryResolve(path: string, options?: { paths?: string[] | undefined }): s function getEslintOptions( expectOnly: boolean, - minVersion: TsVersion, - maxVersion: TsVersion, + versions: readonly TsVersion[], tsLocal: string | undefined, ): ESLint.Options { - const versionsToTest = range(minVersion, maxVersion).map((versionName) => ({ + const versionsToTest = versions.map((versionName) => ({ versionName, path: typeScriptPath(versionName, tsLocal), })); @@ -196,7 +219,7 @@ function range(minVersion: TsVersion, maxVersion: TsVersion): readonly TsVersion const minIdx = TypeScriptVersion.supported.indexOf(minVersion); assert(minIdx >= 0); if (maxVersion === TypeScriptVersion.latest) { - return [...TypeScriptVersion.supported.slice(minIdx), TypeScriptVersion.latest]; + return TypeScriptVersion.supported.slice(minIdx); } const maxIdx = TypeScriptVersion.supported.indexOf(maxVersion as TypeScriptVersion); assert(maxIdx >= minIdx); @@ -204,3 +227,24 @@ function range(minVersion: TsVersion, maxVersion: TsVersion): readonly TsVersion } export type TsVersion = TypeScriptVersion | "local"; + +function isTypeScript7(version: TsVersion, tsLocal: string | undefined): boolean { + if (version !== "local") { + return parseFloat(version) >= 7; + } + + assert(tsLocal); + if (fs.existsSync(tsLocal) && fs.statSync(tsLocal).isFile()) { + return !tsLocal.endsWith("typescript.js"); + } + if (["tsserver", "tsserver.exe", "tsgo", "tsgo.exe"].some((name) => fs.existsSync(joinPaths(tsLocal, name)))) { + return true; + } + if (fs.existsSync(joinPaths(tsLocal, "typescript.js"))) { + return false; + } + throw new Error( + `Could not detect the TypeScript build at ${tsLocal}. Expected typescript.js for TypeScript 6 or ` + + "a tsserver/tsgo executable for TypeScript 7.", + ); +} diff --git a/packages/dtslint/src/lintTypeScript7.ts b/packages/dtslint/src/lintTypeScript7.ts new file mode 100644 index 0000000000..cdc929a9f6 --- /dev/null +++ b/packages/dtslint/src/lintTypeScript7.ts @@ -0,0 +1,422 @@ +import { TypeScriptVersion } from "@definitelytyped/typescript-versions"; +import * as typeScriptPackages from "@definitelytyped/typescript-packages"; +import fs from "fs"; +import path from "path"; +import semver from "semver"; +import { pathToFileURL } from "url"; +import * as ts from "typescript"; +import type { TsVersion } from "./lint"; + +interface Node { + readonly kind: number; + readonly parent: Node; + forEachChild(visitor: (node: Node) => void): void; + getStart(sourceFile?: SourceFile): number; + getEnd(): number; +} + +interface ExpressionStatement extends Node { + readonly expression: Node; +} + +interface VariableStatement extends Node { + readonly declarationList: { + readonly declarations: readonly { + readonly initializer?: Node; + }[]; + }; +} + +interface SourceFile extends Node { + readonly fileName: string; + readonly text: string; + readonly isDeclarationFile: boolean; + getLineStarts(): readonly number[]; + getLineAndCharacterOfPosition(position: number): { line: number; character: number }; + getPositionOfLineAndCharacter(line: number, character: number): number; +} + +interface Diagnostic { + readonly fileName?: string; + readonly pos: number; + readonly end: number; + readonly code: number; + readonly text: string; + readonly messageChain?: readonly Diagnostic[]; +} + +interface Program { + getSourceFile(fileName: string): SourceFile | undefined; + getSourceFileNames(): readonly string[]; + getSourceFileMetadata(fileName: string): { isDefaultLibrary: boolean; isFromExternalLibrary: boolean } | undefined; + getSyntacticDiagnostics(): readonly Diagnostic[]; + getBindDiagnostics(): readonly Diagnostic[]; + getSemanticDiagnostics(): readonly Diagnostic[]; + getDeclarationDiagnostics(): readonly Diagnostic[]; + getProgramDiagnostics(): readonly Diagnostic[]; + getGlobalDiagnostics(): readonly Diagnostic[]; + getConfigFileParsingDiagnostics(): readonly Diagnostic[]; +} + +interface Project { + readonly program: Program; + readonly checker: { + getTypeAtLocation(node: Node): unknown; + typeToString(type: unknown, enclosingDeclaration: undefined, flags: number): string; + }; +} + +interface Snapshot { + getProject(configFileName: string): Project | undefined; + dispose(): void; +} + +interface TypeScript7Api { + readonly NodeBuilderFlags: { readonly NoTruncation: number }; + readonly API: new (options: { cwd: string; tsserverPath?: string }) => { + updateSnapshot(options: { openProjects: readonly string[] }): Snapshot; + close(): void; + }; +} + +interface TypeScript7Ast { + readonly SyntaxKind: { readonly EndOfFileToken: number }; + isExpressionStatement(node: Node): node is ExpressionStatement; + isVariableStatement(node: Node): node is VariableStatement; +} + +interface Failure { + readonly fileName?: string; + readonly start?: number; + readonly end?: number; + readonly message: string; +} + +const expectTypeToken = "$ExpectType"; +const expectErrorSingleLine = /^\/\/\/?\s*@ts-expect-error\s+(.*)/; +const expectErrorMultiLine = /^(?:\/|\*)*\s*@ts-expect-error\s+(.*)/; + +export async function lintTypeScript7( + dirPath: string, + tsconfigs: readonly string[], + version: TsVersion, + isLatest: boolean, + tsLocal: string | undefined, +): Promise { + const clientVersion = version === "local" ? TypeScriptVersion.latest : version; + const [apiModule, astModule] = await Promise.all([ + importModule(typeScriptPackages.resolve(clientVersion, "unstable/sync")), + importModule(typeScriptPackages.resolve(clientVersion, "unstable/ast")), + ]); + const tsserverPath = version === "local" ? findTypeScript7Server(tsLocal!) : undefined; + const api = new apiModule.API({ cwd: dirPath, tsserverPath }); + const configPaths = tsconfigs.map((config) => path.resolve(dirPath, config)); + const failures: Failure[] = []; + + try { + const snapshot = api.updateSnapshot({ openProjects: configPaths }); + try { + for (const configPath of configPaths) { + const project = snapshot.getProject(configPath); + if (!project) { + failures.push({ + message: `TypeScript@${version} could not open ${configPath}.`, + }); + continue; + } + failures.push(...getDiagnosticFailures(project, version)); + failures.push(...getExpectTypeFailures(project, apiModule, astModule, dirPath, version, isLatest)); + } + } finally { + snapshot.dispose(); + } + } finally { + api.close(); + } + + return formatFailures(failures); +} + +async function importModule(fileName: string): Promise { + return (await import(pathToFileURL(fileName).href)) as T; +} + +function findTypeScript7Server(tsLocal: string): string { + if (fs.statSync(tsLocal).isFile()) { + return tsLocal; + } + for (const name of ["tsserver", "tsserver.exe", "tsgo", "tsgo.exe"]) { + const candidate = path.join(tsLocal, name); + if (fs.existsSync(candidate)) { + return candidate; + } + } + throw new Error(`Could not find a TypeScript 7 tsserver/tsgo executable in ${tsLocal}.`); +} + +function getDiagnosticFailures(project: Project, version: TsVersion): Failure[] { + const diagnostics = [ + ...project.program.getConfigFileParsingDiagnostics(), + ...project.program.getProgramDiagnostics(), + ...project.program.getGlobalDiagnostics(), + ...project.program.getSyntacticDiagnostics(), + ...project.program.getBindDiagnostics(), + ...project.program.getSemanticDiagnostics(), + ...project.program.getDeclarationDiagnostics(), + ]; + const failures = new Map(); + + for (const diagnostic of diagnostics) { + if (diagnostic.code === 2578 && isVersionedExpectErrorOutsideRange(diagnostic, project, version)) { + continue; + } + const failure = { + fileName: diagnostic.fileName, + start: diagnostic.pos >= 0 ? diagnostic.pos : undefined, + end: diagnostic.end >= 0 ? diagnostic.end : undefined, + message: `TypeScript@${version} compile error TS${diagnostic.code}:\n${flattenDiagnostic(diagnostic)}`, + }; + failures.set(JSON.stringify(failure), failure); + } + return [...failures.values()]; +} + +function isVersionedExpectErrorOutsideRange(diagnostic: Diagnostic, project: Project, version: TsVersion): boolean { + if (!diagnostic.fileName || diagnostic.pos < 0 || diagnostic.end < diagnostic.pos) { + return false; + } + const sourceFile = project.program.getSourceFile(diagnostic.fileName); + if (!sourceFile) { + return false; + } + const text = sourceFile.text.slice(diagnostic.pos, diagnostic.end); + const match = text.match(expectErrorSingleLine) || text.match(expectErrorMultiLine); + if (!match) { + return false; + } + try { + return !semver.satisfies(version, match[1].trim(), { loose: true }); + } catch { + return false; + } +} + +function flattenDiagnostic(diagnostic: Diagnostic): string { + if (!diagnostic.messageChain?.length) { + return diagnostic.text; + } + return [diagnostic.text, ...diagnostic.messageChain.map(flattenDiagnostic)].join("\n"); +} + +function getExpectTypeFailures( + project: Project, + apiModule: TypeScript7Api, + astModule: TypeScript7Ast, + dirPath: string, + version: TsVersion, + isLatest: boolean, +): Failure[] { + const failures: Failure[] = []; + for (const fileName of project.program.getSourceFileNames()) { + const sourceFile = project.program.getSourceFile(fileName); + const metadata = project.program.getSourceFileMetadata(fileName); + if ( + !sourceFile || + metadata?.isDefaultLibrary || + metadata?.isFromExternalLibrary || + !startsWithDirectory(fileName, dirPath) || + (isLatest && isTypesVersionPath(fileName, dirPath)) || + sourceFile.isDeclarationFile || + !sourceFile.text.includes(expectTypeToken) + ) { + continue; + } + + const { typeAssertions, duplicates } = parseAssertions(sourceFile); + for (const line of duplicates) { + failures.push(failureAtLine(sourceFile, line, "This line has 2 $ExpectType assertions.")); + } + + sourceFile.forEachChild(function visit(node) { + if (node.kind === astModule.SyntaxKind.EndOfFileToken) { + return; + } + const line = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line; + const expected = typeAssertions.get(line); + if (expected !== undefined) { + const target = getNodeForExpectType(node, astModule); + const type = project.checker.getTypeAtLocation(target); + const actual = project.checker.typeToString(type, undefined, apiModule.NodeBuilderFlags.NoTruncation); + if (!typeStringsMatch(expected, actual)) { + failures.push({ + fileName, + start: node.getStart(sourceFile), + end: node.getEnd(), + message: `TypeScript@${version} expected type to be:\n ${expected}\ngot:\n ${actual}`, + }); + } + typeAssertions.delete(line); + } + node.forEachChild(visit); + }); + + for (const line of typeAssertions.keys()) { + failures.push( + failureAtLine( + sourceFile, + line - 1, + "Cannot match a node to this assertion. If this is a multiline function call, ensure the assertion is on the line above.", + ), + ); + } + } + return failures; +} + +function typeStringsMatch(expected: string, actual: string): boolean { + const candidates = expected.split(/\s*\|\|\s*/).map((candidate) => candidate.trim()); + if (candidates.includes(actual)) { + return true; + } + const actualNormalized = normalizedTypeToString(actual); + return candidates.some((candidate) => normalizedTypeToString(candidate) === actualNormalized); +} + +function normalizedTypeToString(type: string): string { + const sourceFile = ts.createSourceFile("type.ts", `declare var x: ${type};`, ts.ScriptTarget.Latest); + const typeNode = (sourceFile.statements[0] as ts.VariableStatement).declarationList.declarations[0].type!; + const printer = ts.createPrinter({}); + const context = (ts as typeof ts & { nullTransformationContext: ts.TransformationContext }).nullTransformationContext; + + function print(node: ts.Node): string { + return printer.printNode(ts.EmitHint.Unspecified, node, sourceFile); + } + function visit(node: ts.Node): ts.VisitResult { + node = ts.visitEachChild(node, visit, context); + if (ts.isUnionTypeNode(node)) { + const types = node.types + .map((item) => [item, print(item)] as const) + .sort((a, b) => a[1].localeCompare(b[1])) + .map(([item]) => item); + return ts.factory.updateUnionTypeNode(node, ts.factory.createNodeArray(types)); + } + if ( + ts.isTypeOperatorNode(node) && + node.operator === ts.SyntaxKind.ReadonlyKeyword && + ts.isArrayTypeNode(node.type) + ) { + return ts.factory.createTypeReferenceNode("ReadonlyArray", [skipTypeParentheses(node.type.elementType)]); + } + return node; + } + + return print(ts.visitNode(typeNode, visit)); +} + +function skipTypeParentheses(node: ts.TypeNode): ts.TypeNode { + while (ts.isParenthesizedTypeNode(node)) { + node = node.type; + } + return node; +} + +function getNodeForExpectType(node: Node, astModule: TypeScript7Ast): Node { + if (astModule.isExpressionStatement(node)) { + return node.expression; + } + if (astModule.isVariableStatement(node) && node.declarationList.declarations.length === 1) { + return node.declarationList.declarations[0].initializer ?? node; + } + return node; +} + +interface Assertions { + readonly typeAssertions: Map; + readonly duplicates: readonly number[]; +} + +function parseAssertions(sourceFile: SourceFile): Assertions { + const typeAssertions = new Map(); + const duplicates: number[] = []; + const commentRegexp = /^(.*?)\/\/(.*)$/gm; + const lineStarts = sourceFile.getLineStarts(); + let currentLine = 0; + + while (true) { + const commentMatch = commentRegexp.exec(sourceFile.text); + if (!commentMatch) { + break; + } + const comment = commentMatch[2].trim(); + if (!comment.startsWith(expectTypeToken)) { + continue; + } + const commentPosition = commentMatch.index + commentMatch[1].length; + while (lineStarts[currentLine + 1] <= commentPosition) { + currentLine++; + } + const line = isFirstOnLine(sourceFile.text, lineStarts[currentLine], commentPosition) + ? currentLine + 1 + : currentLine; + const expectedType = comment.slice(expectTypeToken.length).trim(); + if (typeAssertions.delete(line)) { + duplicates.push(line); + } else { + typeAssertions.set(line, expectedType); + } + } + return { typeAssertions, duplicates }; +} + +function isFirstOnLine(text: string, lineStart: number, position: number): boolean { + for (let i = lineStart; i < position; i++) { + if (text[i] !== " ") { + return false; + } + } + return true; +} + +function failureAtLine(sourceFile: SourceFile, line: number, message: string): Failure { + const start = sourceFile.getPositionOfLineAndCharacter(line, 0); + let end = start + sourceFile.text.split("\n")[line].length; + if (sourceFile.text[end - 1] === "\r") { + end--; + } + return { fileName: sourceFile.fileName, start, end, message }; +} + +function formatFailures(failures: readonly Failure[]): string | undefined { + if (!failures.length) { + return undefined; + } + return failures + .map((failure) => { + if (!failure.fileName || failure.start === undefined) { + return failure.message; + } + const text = fs.readFileSync(failure.fileName, "utf8"); + const sourceFile = ts.createSourceFile(failure.fileName, text, ts.ScriptTarget.Latest); + const { line, character } = sourceFile.getLineAndCharacterOfPosition(failure.start); + return `${failure.fileName}:${line + 1}:${character + 1}\n${failure.message}`; + }) + .join("\n\n"); +} + +function normalizePath(fileName: string): string { + return path + .normalize(fileName) + .replace(/\\/g, "/") + .replace(/^[a-z](?=:)/, (character) => character.toUpperCase()); +} + +function startsWithDirectory(fileName: string, dirPath: string): boolean { + const normalizedFileName = normalizePath(fileName); + const normalizedDirPath = normalizePath(dirPath).replace(/\/$/, ""); + return normalizedFileName.startsWith(normalizedDirPath + "/"); +} + +function isTypesVersionPath(fileName: string, dirPath: string): boolean { + const subdirPath = normalizePath(fileName).slice(normalizePath(dirPath).length); + return /^\/ts\d+\.\d/.test(subdirPath); +} diff --git a/packages/dtslint/test/fixtures/typescript7/fail/index.d.ts b/packages/dtslint/test/fixtures/typescript7/fail/index.d.ts new file mode 100644 index 0000000000..4c05c227e7 --- /dev/null +++ b/packages/dtslint/test/fixtures/typescript7/fail/index.d.ts @@ -0,0 +1 @@ +export declare function literal(): 1; diff --git a/packages/dtslint/test/fixtures/typescript7/fail/test.ts b/packages/dtslint/test/fixtures/typescript7/fail/test.ts new file mode 100644 index 0000000000..8d87ae8821 --- /dev/null +++ b/packages/dtslint/test/fixtures/typescript7/fail/test.ts @@ -0,0 +1,6 @@ +import { literal } from "./index"; + +// $ExpectType 2 +literal(); + +const bad: string = 1; diff --git a/packages/dtslint/test/fixtures/typescript7/fail/tsconfig.json b/packages/dtslint/test/fixtures/typescript7/fail/tsconfig.json new file mode 100644 index 0000000000..b918e4146a --- /dev/null +++ b/packages/dtslint/test/fixtures/typescript7/fail/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "module": "commonjs", + "noEmit": true, + "strict": true, + "target": "esnext" + }, + "files": ["index.d.ts", "test.ts"] +} diff --git a/packages/dtslint/test/fixtures/typescript7/pass/index.d.ts b/packages/dtslint/test/fixtures/typescript7/pass/index.d.ts new file mode 100644 index 0000000000..4c05c227e7 --- /dev/null +++ b/packages/dtslint/test/fixtures/typescript7/pass/index.d.ts @@ -0,0 +1 @@ +export declare function literal(): 1; diff --git a/packages/dtslint/test/fixtures/typescript7/pass/test.ts b/packages/dtslint/test/fixtures/typescript7/pass/test.ts new file mode 100644 index 0000000000..bff4be719c --- /dev/null +++ b/packages/dtslint/test/fixtures/typescript7/pass/test.ts @@ -0,0 +1,4 @@ +import { literal } from "./index"; + +// $ExpectType 1 +literal(); diff --git a/packages/dtslint/test/fixtures/typescript7/pass/tsconfig.json b/packages/dtslint/test/fixtures/typescript7/pass/tsconfig.json new file mode 100644 index 0000000000..b918e4146a --- /dev/null +++ b/packages/dtslint/test/fixtures/typescript7/pass/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "module": "commonjs", + "noEmit": true, + "strict": true, + "target": "esnext" + }, + "files": ["index.d.ts", "test.ts"] +} diff --git a/packages/dtslint/test/index.test.ts b/packages/dtslint/test/index.test.ts index af07e11ec7..8d7ec4c445 100644 --- a/packages/dtslint/test/index.test.ts +++ b/packages/dtslint/test/index.test.ts @@ -1,6 +1,10 @@ /// import { CompilerOptionsRaw, checkTsconfig } from "../src/checks"; import { assertPackageIsNotDeprecated } from "../src/index"; +import { lint } from "../src/lint"; +import { lintTypeScript7 } from "../src/lintTypeScript7"; +import * as typeScriptPackages from "@definitelytyped/typescript-packages"; +import path from "path"; describe("dtslint", () => { const base: CompilerOptionsRaw = { @@ -139,6 +143,39 @@ describe("dtslint", () => { // `"files" list must include at least one ".ts", ".tsx", ".mts" or ".cts" file for testing.`, ]); }); + + describe("TypeScript 7", () => { + const fixtures = path.join(__dirname, "fixtures", "typescript7"); + + it("checks compiler diagnostics and ExpectType through the IPC API", async () => { + const result = await lintTypeScript7(path.join(fixtures, "fail"), ["tsconfig.json"], "7.1", true, undefined); + + expect(result).toContain("compile error TS2322"); + expect(result).toContain("expected type to be:\n 2\ngot:\n 1"); + }); + + it("passes matching ExpectType assertions without invoking ESLint", async () => { + await expect( + lint(path.join(fixtures, "pass"), ["tsconfig.json"], "7.1", "7.1", true, true, undefined), + ).resolves.toBeUndefined(); + }); + + it("can use a local TypeScript 7 server executable", async () => { + const apiPath = typeScriptPackages.resolve("7.1", "unstable/sync"); + const packageRoot = path.resolve(apiPath, "../../../../"); + const executable = path.join( + path.dirname(packageRoot), + "@typescript", + `typescript-${process.platform}-${process.arch}`, + "lib", + process.platform === "win32" ? "tsc.exe" : "tsc", + ); + + await expect( + lint(path.join(fixtures, "pass"), ["tsconfig.json"], "local", "local", true, true, executable), + ).resolves.toBeUndefined(); + }); + }); }); describe("assertPackageIsNotDeprecated", () => { it("disallows packages that are in notNeededPackages.json", () => { diff --git a/packages/dtslint/test/tsconfig.json b/packages/dtslint/test/tsconfig.json index 85ba8b7b57..4c4cfada5e 100644 --- a/packages/dtslint/test/tsconfig.json +++ b/packages/dtslint/test/tsconfig.json @@ -9,6 +9,6 @@ { "path": ".." }, { "path": "../../utils" } ], - "exclude": ["expect"] + "exclude": ["expect", "fixtures"] } \ No newline at end of file diff --git a/packages/typescript-packages/package.json b/packages/typescript-packages/package.json index 71542c9c41..54cfbffbb2 100644 --- a/packages/typescript-packages/package.json +++ b/packages/typescript-packages/package.json @@ -26,6 +26,7 @@ "typescript-5.7": "npm:typescript@~5.7.0-0", "typescript-5.8": "npm:typescript@~5.8.0-0", "typescript-5.9": "npm:typescript@~5.9.0-0", - "typescript-6.0": "npm:typescript@~6.0.0-0" + "typescript-6.0": "npm:typescript@~6.0.0-0", + "typescript-7.1": "npm:typescript@~7.1.0-0" } } diff --git a/packages/typescript-packages/src/index.ts b/packages/typescript-packages/src/index.ts index e38f0f8e17..f9b85398b8 100644 --- a/packages/typescript-packages/src/index.ts +++ b/packages/typescript-packages/src/index.ts @@ -1,5 +1,5 @@ import { TypeScriptVersion } from "@definitelytyped/typescript-versions"; -export function resolve(version: TypeScriptVersion) { - return require.resolve(`typescript-${version}`); +export function resolve(version: TypeScriptVersion, subpath?: string) { + return require.resolve(`typescript-${version}${subpath ? `/${subpath}` : ""}`); } diff --git a/packages/typescript-packages/test/index.test.ts b/packages/typescript-packages/test/index.test.ts index 9d67f8d195..7398512f5c 100644 --- a/packages/typescript-packages/test/index.test.ts +++ b/packages/typescript-packages/test/index.test.ts @@ -24,7 +24,12 @@ describe("resolve", () => { for (const version of TypeScriptVersion.supported) { const ts = require(resolve(version)); expect(typeof ts.versionMajorMinor).toBe("string"); - expect(ts.versionMajorMinor).toBe(version); + if (version === "7.1") { + expect(ts.version).toMatch(/^7\.1\./); + expect(resolve(version, "unstable/sync")).toMatch(/api[\\/]sync[\\/]api\.js$/); + } else { + expect(ts.versionMajorMinor).toBe(version); + } } }); }); diff --git a/packages/typescript-versions/src/index.ts b/packages/typescript-versions/src/index.ts index aa1f1c7b84..7d57c6bf7a 100644 --- a/packages/typescript-versions/src/index.ts +++ b/packages/typescript-versions/src/index.ts @@ -43,7 +43,7 @@ export namespace TypeScriptVersion { /** Add to this list when a version actually ships. */ export const shipped = ["5.6", "5.7", "5.8", "5.9", "6.0"] as const; /** Add to this list when a version is available as typescript@next */ - export const supported = [...shipped] as const; + export const supported = [...shipped, "7.1"] as const; /** Add to this list when it will no longer be supported on Definitely Typed */ export const unsupported = [ "2.0", diff --git a/packages/typescript-versions/test/index.test.ts b/packages/typescript-versions/test/index.test.ts index 01827a9d1b..c76dc65836 100644 --- a/packages/typescript-versions/test/index.test.ts +++ b/packages/typescript-versions/test/index.test.ts @@ -10,7 +10,13 @@ describe("all", () => { it("doesn't have any holes", () => { let prev = TypeScriptVersion.all[0]; for (const version of TypeScriptVersion.all.slice(1)) { - expect(+version * 10 - +prev * 10).toEqual(1); + const [prevMajor, prevMinor] = prev.split(".").map(Number); + const [major, minor] = version.split(".").map(Number); + expect( + (major === prevMajor && minor === prevMinor + 1) || + (major === prevMajor + 1 && prevMinor === 9 && minor === 0) || + (prev === "6.0" && version === "7.1"), + ).toBeTruthy(); prev = version; } }); @@ -42,7 +48,7 @@ describe("isTypeScriptVersion", () => { describe("range", () => { it("works", () => { - expect(TypeScriptVersion.range("5.6")).toEqual(["5.6", "5.7", "5.8", "5.9", "6.0"]); + expect(TypeScriptVersion.range("5.6")).toEqual(["5.6", "5.7", "5.8", "5.9", "6.0", "7.1"]); }); it("includes 5.6 onwards", () => { expect(TypeScriptVersion.range("5.6")).toEqual(TypeScriptVersion.supported); @@ -51,7 +57,15 @@ describe("range", () => { describe("tagsToUpdate", () => { it("works", () => { - expect(TypeScriptVersion.tagsToUpdate("5.6")).toEqual(["ts5.6", "ts5.7", "ts5.8", "ts5.9", "ts6.0", "latest"]); + expect(TypeScriptVersion.tagsToUpdate("5.6")).toEqual([ + "ts5.6", + "ts5.7", + "ts5.8", + "ts5.9", + "ts6.0", + "ts7.1", + "latest", + ]); }); it("allows 5.6 onwards", () => { expect(TypeScriptVersion.tagsToUpdate("5.6")).toEqual( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b473eb1e40..9afce59d90 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -181,6 +181,9 @@ importers: strip-json-comments: specifier: ^3.1.1 version: 3.1.1 + typescript: + specifier: ^5.9.3 + version: 5.9.3 devDependencies: '@types/eslint': specifier: ^8.56.12 @@ -188,9 +191,6 @@ importers: '@types/semver': specifier: ^7.7.1 version: 7.7.1 - typescript: - specifier: ^5.9.3 - version: 5.9.3 packages/dtslint-runner: dependencies: @@ -455,6 +455,9 @@ importers: typescript-6.0: specifier: npm:typescript@~6.0.0-0 version: typescript@6.0.2 + typescript-7.1: + specifier: npm:typescript@~7.1.0-0 + version: typescript@7.1.0-dev.20260817.1 packages/typescript-versions: {} @@ -2091,6 +2094,48 @@ packages: resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript/typescript-darwin-arm64@7.1.0-dev.20260817.1': + resolution: {integrity: sha512-WtnmAQAdn2nICm0lZOGJSgxSkdz9QaLfETgMKuMpD6Cf5lqzaZ5C2DRxqxl8B2moVqU3kWnK0ijWos1jO/3P0Q==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.1.0-dev.20260817.1': + resolution: {integrity: sha512-wsuj0wwxM0CY8BG+5TQLatleOVe3Dq/DEgwSAm8vhHA8eY0YmgC+iADFQBDcLiwaSj90tKp0B/wRWyxaYySOVQ==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-linux-arm64@7.1.0-dev.20260817.1': + resolution: {integrity: sha512-jY/0sNMQUeIfML6qR12h7/AgdeIdPTyQmQU6p/f2+0+jgDnOan6gpFCfzNa+ZRu6gEJAmKP2GsQTetoeRtOmCQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.1.0-dev.20260817.1': + resolution: {integrity: sha512-0Ggdk5cRMLCZYaKf6BqCT9vH7XXV2iqYv4y6psnt3Ub61AnZjERgaiEfm16wE0DxA/ugEHjsgj6l2KqR+XYXKQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-x64@7.1.0-dev.20260817.1': + resolution: {integrity: sha512-HkYIfyFCjopf/9g7FdABIQUpkAjbVMTkjO9Q+dmNYVvZb89K47ZAPrhVAw5Em3zcCdc3qK9MXVXBfcfqD/YSmg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-win32-arm64@7.1.0-dev.20260817.1': + resolution: {integrity: sha512-jTBf06dI+71vyMeZqnndL9Nf1ViI6MTmPyCvT05ipkKb65CbnQMunCI5pPcuKWLwg4EaJuTSVruvwSJeimkFqw==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.1.0-dev.20260817.1': + resolution: {integrity: sha512-dU3HQ4kuV0c+gGoGruqHbbIPuyxoxQ2MkNXo+yum5rBfmXEdbgtL9BvyuSa+mHo1ZeC6m2qWj05B0BFILMBsDg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + '@typespec/ts-http-runtime@0.3.6': resolution: {integrity: sha512-jIXhD0eWQ1JA6ln/5Dltyx22UxWNrw0hZmhy2rlv6m6KgF7kplHx3g0fzi09lNmTJQRR91OlemYp3xFnvDK9og==} engines: {node: '>=20.0.0'} @@ -4946,6 +4991,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@7.1.0-dev.20260817.1: + resolution: {integrity: sha512-PkDN/j5CrorUpue1dmiFluIYgKv10SeVUTLlzgpRsN2qYUNHi3mD7hDuo9s8mcBS2ZMui19yqvK+0gl00zNrmQ==} + engines: {node: '>=16.20.0'} + hasBin: true + ua-parser-js@1.0.41: resolution: {integrity: sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==} hasBin: true @@ -7274,6 +7324,27 @@ snapshots: '@typescript-eslint/types': 8.56.1 eslint-visitor-keys: 5.0.1 + '@typescript/typescript-darwin-arm64@7.1.0-dev.20260817.1': + optional: true + + '@typescript/typescript-darwin-x64@7.1.0-dev.20260817.1': + optional: true + + '@typescript/typescript-linux-arm64@7.1.0-dev.20260817.1': + optional: true + + '@typescript/typescript-linux-arm@7.1.0-dev.20260817.1': + optional: true + + '@typescript/typescript-linux-x64@7.1.0-dev.20260817.1': + optional: true + + '@typescript/typescript-win32-arm64@7.1.0-dev.20260817.1': + optional: true + + '@typescript/typescript-win32-x64@7.1.0-dev.20260817.1': + optional: true + '@typespec/ts-http-runtime@0.3.6': dependencies: http-proxy-agent: 7.0.2 @@ -10545,6 +10616,16 @@ snapshots: typescript@6.0.2: {} + typescript@7.1.0-dev.20260817.1: + optionalDependencies: + '@typescript/typescript-darwin-arm64': 7.1.0-dev.20260817.1 + '@typescript/typescript-darwin-x64': 7.1.0-dev.20260817.1 + '@typescript/typescript-linux-arm': 7.1.0-dev.20260817.1 + '@typescript/typescript-linux-arm64': 7.1.0-dev.20260817.1 + '@typescript/typescript-linux-x64': 7.1.0-dev.20260817.1 + '@typescript/typescript-win32-arm64': 7.1.0-dev.20260817.1 + '@typescript/typescript-win32-x64': 7.1.0-dev.20260817.1 + ua-parser-js@1.0.41: {} uglify-js@3.19.3: From 80348b1d2339769ec668343615494c7fc538c43c Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 17 Aug 2026 12:04:18 -0700 Subject: [PATCH 02/24] Add TypeScript 7.0 to dtslint --- packages/dtslint/src/lintTypeScript7.ts | 7 +- packages/dtslint/test/index.test.ts | 28 ++- packages/typescript-packages/package.json | 1 + .../typescript-packages/test/index.test.ts | 4 +- packages/typescript-versions/src/index.ts | 2 +- .../typescript-versions/test/index.test.ts | 9 +- pnpm-lock.yaml | 211 ++++++++++++++++++ 7 files changed, 242 insertions(+), 20 deletions(-) diff --git a/packages/dtslint/src/lintTypeScript7.ts b/packages/dtslint/src/lintTypeScript7.ts index cdc929a9f6..46fe12b080 100644 --- a/packages/dtslint/src/lintTypeScript7.ts +++ b/packages/dtslint/src/lintTypeScript7.ts @@ -61,7 +61,7 @@ interface Program { interface Project { readonly program: Program; readonly checker: { - getTypeAtLocation(node: Node): unknown; + getTypeAtLocation(node: Node): unknown | undefined; typeToString(type: unknown, enclosingDeclaration: undefined, flags: number): string; }; } @@ -246,7 +246,10 @@ function getExpectTypeFailures( if (expected !== undefined) { const target = getNodeForExpectType(node, astModule); const type = project.checker.getTypeAtLocation(target); - const actual = project.checker.typeToString(type, undefined, apiModule.NodeBuilderFlags.NoTruncation); + const actual = + type === undefined + ? "" + : project.checker.typeToString(type, undefined, apiModule.NodeBuilderFlags.NoTruncation); if (!typeStringsMatch(expected, actual)) { failures.push({ fileName, diff --git a/packages/dtslint/test/index.test.ts b/packages/dtslint/test/index.test.ts index 8d7ec4c445..5f23014683 100644 --- a/packages/dtslint/test/index.test.ts +++ b/packages/dtslint/test/index.test.ts @@ -147,18 +147,26 @@ describe("dtslint", () => { describe("TypeScript 7", () => { const fixtures = path.join(__dirname, "fixtures", "typescript7"); - it("checks compiler diagnostics and ExpectType through the IPC API", async () => { - const result = await lintTypeScript7(path.join(fixtures, "fail"), ["tsconfig.json"], "7.1", true, undefined); + for (const version of ["7.0", "7.1"] as const) { + it(`checks compiler diagnostics and ExpectType through the TypeScript ${version} IPC API`, async () => { + const result = await lintTypeScript7( + path.join(fixtures, "fail"), + ["tsconfig.json"], + version, + true, + undefined, + ); - expect(result).toContain("compile error TS2322"); - expect(result).toContain("expected type to be:\n 2\ngot:\n 1"); - }); + expect(result).toContain("compile error TS2322"); + expect(result).toContain("expected type to be:\n 2\ngot:\n 1"); + }); - it("passes matching ExpectType assertions without invoking ESLint", async () => { - await expect( - lint(path.join(fixtures, "pass"), ["tsconfig.json"], "7.1", "7.1", true, true, undefined), - ).resolves.toBeUndefined(); - }); + it(`passes matching TypeScript ${version} ExpectType assertions without invoking ESLint`, async () => { + await expect( + lint(path.join(fixtures, "pass"), ["tsconfig.json"], version, version, true, true, undefined), + ).resolves.toBeUndefined(); + }); + } it("can use a local TypeScript 7 server executable", async () => { const apiPath = typeScriptPackages.resolve("7.1", "unstable/sync"); diff --git a/packages/typescript-packages/package.json b/packages/typescript-packages/package.json index 54cfbffbb2..93fb475296 100644 --- a/packages/typescript-packages/package.json +++ b/packages/typescript-packages/package.json @@ -27,6 +27,7 @@ "typescript-5.8": "npm:typescript@~5.8.0-0", "typescript-5.9": "npm:typescript@~5.9.0-0", "typescript-6.0": "npm:typescript@~6.0.0-0", + "typescript-7.0": "npm:typescript@~7.0.0-0", "typescript-7.1": "npm:typescript@~7.1.0-0" } } diff --git a/packages/typescript-packages/test/index.test.ts b/packages/typescript-packages/test/index.test.ts index 7398512f5c..e8f8e7c010 100644 --- a/packages/typescript-packages/test/index.test.ts +++ b/packages/typescript-packages/test/index.test.ts @@ -26,10 +26,12 @@ describe("resolve", () => { expect(typeof ts.versionMajorMinor).toBe("string"); if (version === "7.1") { expect(ts.version).toMatch(/^7\.1\./); - expect(resolve(version, "unstable/sync")).toMatch(/api[\\/]sync[\\/]api\.js$/); } else { expect(ts.versionMajorMinor).toBe(version); } + if (version.startsWith("7.")) { + expect(resolve(version, "unstable/sync")).toMatch(/api[\\/]sync[\\/]api\.js$/); + } } }); }); diff --git a/packages/typescript-versions/src/index.ts b/packages/typescript-versions/src/index.ts index 7d57c6bf7a..2e1c42430a 100644 --- a/packages/typescript-versions/src/index.ts +++ b/packages/typescript-versions/src/index.ts @@ -41,7 +41,7 @@ export type AllTypeScriptVersion = UnsupportedTypeScriptVersion | TypeScriptVers export namespace TypeScriptVersion { /** Add to this list when a version actually ships. */ - export const shipped = ["5.6", "5.7", "5.8", "5.9", "6.0"] as const; + export const shipped = ["5.6", "5.7", "5.8", "5.9", "6.0", "7.0"] as const; /** Add to this list when a version is available as typescript@next */ export const supported = [...shipped, "7.1"] as const; /** Add to this list when it will no longer be supported on Definitely Typed */ diff --git a/packages/typescript-versions/test/index.test.ts b/packages/typescript-versions/test/index.test.ts index c76dc65836..61bc4d4ced 100644 --- a/packages/typescript-versions/test/index.test.ts +++ b/packages/typescript-versions/test/index.test.ts @@ -12,11 +12,7 @@ describe("all", () => { for (const version of TypeScriptVersion.all.slice(1)) { const [prevMajor, prevMinor] = prev.split(".").map(Number); const [major, minor] = version.split(".").map(Number); - expect( - (major === prevMajor && minor === prevMinor + 1) || - (major === prevMajor + 1 && prevMinor === 9 && minor === 0) || - (prev === "6.0" && version === "7.1"), - ).toBeTruthy(); + expect((major === prevMajor && minor === prevMinor + 1) || (major === prevMajor + 1 && minor === 0)).toBeTruthy(); prev = version; } }); @@ -48,7 +44,7 @@ describe("isTypeScriptVersion", () => { describe("range", () => { it("works", () => { - expect(TypeScriptVersion.range("5.6")).toEqual(["5.6", "5.7", "5.8", "5.9", "6.0", "7.1"]); + expect(TypeScriptVersion.range("5.6")).toEqual(["5.6", "5.7", "5.8", "5.9", "6.0", "7.0", "7.1"]); }); it("includes 5.6 onwards", () => { expect(TypeScriptVersion.range("5.6")).toEqual(TypeScriptVersion.supported); @@ -63,6 +59,7 @@ describe("tagsToUpdate", () => { "ts5.8", "ts5.9", "ts6.0", + "ts7.0", "ts7.1", "latest", ]); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9afce59d90..d3a99d52f9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -455,6 +455,9 @@ importers: typescript-6.0: specifier: npm:typescript@~6.0.0-0 version: typescript@6.0.2 + typescript-7.0: + specifier: npm:typescript@~7.0.0-0 + version: typescript@7.0.2 typescript-7.1: specifier: npm:typescript@~7.1.0-0 version: typescript@7.1.0-dev.20260817.1 @@ -2094,42 +2097,162 @@ packages: resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + '@typescript/typescript-darwin-arm64@7.1.0-dev.20260817.1': resolution: {integrity: sha512-WtnmAQAdn2nICm0lZOGJSgxSkdz9QaLfETgMKuMpD6Cf5lqzaZ5C2DRxqxl8B2moVqU3kWnK0ijWos1jO/3P0Q==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [darwin] + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + '@typescript/typescript-darwin-x64@7.1.0-dev.20260817.1': resolution: {integrity: sha512-wsuj0wwxM0CY8BG+5TQLatleOVe3Dq/DEgwSAm8vhHA8eY0YmgC+iADFQBDcLiwaSj90tKp0B/wRWyxaYySOVQ==} engines: {node: '>=16.20.0'} cpu: [x64] os: [darwin] + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + '@typescript/typescript-linux-arm64@7.1.0-dev.20260817.1': resolution: {integrity: sha512-jY/0sNMQUeIfML6qR12h7/AgdeIdPTyQmQU6p/f2+0+jgDnOan6gpFCfzNa+ZRu6gEJAmKP2GsQTetoeRtOmCQ==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [linux] + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + '@typescript/typescript-linux-arm@7.1.0-dev.20260817.1': resolution: {integrity: sha512-0Ggdk5cRMLCZYaKf6BqCT9vH7XXV2iqYv4y6psnt3Ub61AnZjERgaiEfm16wE0DxA/ugEHjsgj6l2KqR+XYXKQ==} engines: {node: '>=16.20.0'} cpu: [arm] os: [linux] + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + '@typescript/typescript-linux-x64@7.1.0-dev.20260817.1': resolution: {integrity: sha512-HkYIfyFCjopf/9g7FdABIQUpkAjbVMTkjO9Q+dmNYVvZb89K47ZAPrhVAw5Em3zcCdc3qK9MXVXBfcfqD/YSmg==} engines: {node: '>=16.20.0'} cpu: [x64] os: [linux] + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + '@typescript/typescript-win32-arm64@7.1.0-dev.20260817.1': resolution: {integrity: sha512-jTBf06dI+71vyMeZqnndL9Nf1ViI6MTmPyCvT05ipkKb65CbnQMunCI5pPcuKWLwg4EaJuTSVruvwSJeimkFqw==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [win32] + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + '@typescript/typescript-win32-x64@7.1.0-dev.20260817.1': resolution: {integrity: sha512-dU3HQ4kuV0c+gGoGruqHbbIPuyxoxQ2MkNXo+yum5rBfmXEdbgtL9BvyuSa+mHo1ZeC6m2qWj05B0BFILMBsDg==} engines: {node: '>=16.20.0'} @@ -4991,6 +5114,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} + hasBin: true + typescript@7.1.0-dev.20260817.1: resolution: {integrity: sha512-PkDN/j5CrorUpue1dmiFluIYgKv10SeVUTLlzgpRsN2qYUNHi3mD7hDuo9s8mcBS2ZMui19yqvK+0gl00zNrmQ==} engines: {node: '>=16.20.0'} @@ -7324,24 +7452,84 @@ snapshots: '@typescript-eslint/types': 8.56.1 eslint-visitor-keys: 5.0.1 + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true + + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true + '@typescript/typescript-darwin-arm64@7.1.0-dev.20260817.1': optional: true + '@typescript/typescript-darwin-x64@7.0.2': + optional: true + '@typescript/typescript-darwin-x64@7.1.0-dev.20260817.1': optional: true + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm64@7.0.2': + optional: true + '@typescript/typescript-linux-arm64@7.1.0-dev.20260817.1': optional: true + '@typescript/typescript-linux-arm@7.0.2': + optional: true + '@typescript/typescript-linux-arm@7.1.0-dev.20260817.1': optional: true + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true + + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + '@typescript/typescript-linux-x64@7.1.0-dev.20260817.1': optional: true + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + '@typescript/typescript-win32-arm64@7.1.0-dev.20260817.1': optional: true + '@typescript/typescript-win32-x64@7.0.2': + optional: true + '@typescript/typescript-win32-x64@7.1.0-dev.20260817.1': optional: true @@ -10616,6 +10804,29 @@ snapshots: typescript@6.0.2: {} + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 + typescript@7.1.0-dev.20260817.1: optionalDependencies: '@typescript/typescript-darwin-arm64': 7.1.0-dev.20260817.1 From a6c9bbe30e09845f2a2a91c20335762e34506be4 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 17 Aug 2026 12:06:01 -0700 Subject: [PATCH 03/24] Update TypeScript 7 changeset --- .changeset/strong-dragons-check.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/strong-dragons-check.md b/.changeset/strong-dragons-check.md index 07092eb024..4a31d78b2c 100644 --- a/.changeset/strong-dragons-check.md +++ b/.changeset/strong-dragons-check.md @@ -5,5 +5,5 @@ "@definitelytyped/typescript-versions": patch --- -Add TypeScript 7.1 support using TypeScript's unstable out-of-process API for compiler diagnostics and -`$ExpectType` assertions. +Add TypeScript 7.0 and 7.1 support using TypeScript's unstable out-of-process API for compiler diagnostics and +`$ExpectType` assertions, including `--next` and local TypeScript 7 builds. From 8813723c9ad08b2abac746422f7e842ae4664fed Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 17 Aug 2026 12:12:21 -0700 Subject: [PATCH 04/24] Run ESLint rules for TypeScript 7 --- packages/dtslint/src/lint.ts | 6 ++--- packages/dtslint/test/index.test.ts | 15 +++++++++++++ .../test/typescript7-eslint/.eslintrc.json | 22 +++++++++++++++++++ .../test/typescript7-eslint/index.d.ts | 1 + .../dtslint/test/typescript7-eslint/test.ts | 4 ++++ .../test/typescript7-eslint/tsconfig.json | 9 ++++++++ 6 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 packages/dtslint/test/typescript7-eslint/.eslintrc.json create mode 100644 packages/dtslint/test/typescript7-eslint/index.d.ts create mode 100644 packages/dtslint/test/typescript7-eslint/test.ts create mode 100644 packages/dtslint/test/typescript7-eslint/tsconfig.json diff --git a/packages/dtslint/src/lint.ts b/packages/dtslint/src/lint.ts index 2438ff0eaa..b670daf945 100644 --- a/packages/dtslint/src/lint.ts +++ b/packages/dtslint/src/lint.ts @@ -42,7 +42,7 @@ export async function lint( } const outputs: string[] = []; - if (legacyVersions.length) { + if (!expectOnly || legacyVersions.length) { const options = getEslintOptions(expectOnly, legacyVersions, tsLocal); const eslint = new ESLint(options); const formatter = await eslint.loadFormatter("stylish"); @@ -128,8 +128,8 @@ function getEslintOptions( { files: allFiles, rules: { - // This prevents anyone from disabling this rule. - "@definitelytyped/expect": ["error"], + // This prevents anyone from disabling this rule when it is responsible for ExpectType. + "@definitelytyped/expect": versions.length ? ["error"] : "off", }, }, ], diff --git a/packages/dtslint/test/index.test.ts b/packages/dtslint/test/index.test.ts index 5f23014683..cc86430273 100644 --- a/packages/dtslint/test/index.test.ts +++ b/packages/dtslint/test/index.test.ts @@ -168,6 +168,21 @@ describe("dtslint", () => { }); } + it("runs ordinary ESLint rules during TypeScript 7-only testing", async () => { + const result = await lint( + path.join(__dirname, "typescript7-eslint"), + ["tsconfig.json"], + "7.0", + "7.0", + true, + false, + undefined, + ); + + expect(result).toContain("no-var"); + expect(result).toContain("@typescript-eslint/naming-convention"); + }); + it("can use a local TypeScript 7 server executable", async () => { const apiPath = typeScriptPackages.resolve("7.1", "unstable/sync"); const packageRoot = path.resolve(apiPath, "../../../../"); diff --git a/packages/dtslint/test/typescript7-eslint/.eslintrc.json b/packages/dtslint/test/typescript7-eslint/.eslintrc.json new file mode 100644 index 0000000000..e3ba04dd16 --- /dev/null +++ b/packages/dtslint/test/typescript7-eslint/.eslintrc.json @@ -0,0 +1,22 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": true + }, + "plugins": ["@typescript-eslint"], + "rules": { + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": "interface", + "format": [], + "custom": { + "regex": "^I[A-Z]", + "match": false + } + } + ], + "no-var": "error" + } +} diff --git a/packages/dtslint/test/typescript7-eslint/index.d.ts b/packages/dtslint/test/typescript7-eslint/index.d.ts new file mode 100644 index 0000000000..cb0ff5c3b5 --- /dev/null +++ b/packages/dtslint/test/typescript7-eslint/index.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/dtslint/test/typescript7-eslint/test.ts b/packages/dtslint/test/typescript7-eslint/test.ts new file mode 100644 index 0000000000..3e4a26fb75 --- /dev/null +++ b/packages/dtslint/test/typescript7-eslint/test.ts @@ -0,0 +1,4 @@ +export interface IFoo {} + +var value = 1; +value; diff --git a/packages/dtslint/test/typescript7-eslint/tsconfig.json b/packages/dtslint/test/typescript7-eslint/tsconfig.json new file mode 100644 index 0000000000..b918e4146a --- /dev/null +++ b/packages/dtslint/test/typescript7-eslint/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "module": "commonjs", + "noEmit": true, + "strict": true, + "target": "esnext" + }, + "files": ["index.d.ts", "test.ts"] +} From 6ec451e22e84c04fcd5e987d0cd81c3ed5b672b9 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 17 Aug 2026 12:13:26 -0700 Subject: [PATCH 05/24] Remove accidental next alias --- .changeset/strong-dragons-check.md | 3 +-- packages/dtslint-runner/src/index.ts | 1 - packages/dtslint/README.md | 10 ---------- packages/dtslint/src/index.ts | 6 ++---- 4 files changed, 3 insertions(+), 17 deletions(-) diff --git a/.changeset/strong-dragons-check.md b/.changeset/strong-dragons-check.md index 4a31d78b2c..f0c0a33e70 100644 --- a/.changeset/strong-dragons-check.md +++ b/.changeset/strong-dragons-check.md @@ -1,9 +1,8 @@ --- "@definitelytyped/dtslint": patch -"@definitelytyped/dtslint-runner": patch "@definitelytyped/typescript-packages": patch "@definitelytyped/typescript-versions": patch --- Add TypeScript 7.0 and 7.1 support using TypeScript's unstable out-of-process API for compiler diagnostics and -`$ExpectType` assertions, including `--next` and local TypeScript 7 builds. +`$ExpectType` assertions, including local TypeScript 7 builds. diff --git a/packages/dtslint-runner/src/index.ts b/packages/dtslint-runner/src/index.ts index 2cb91e5548..b6ec14885e 100644 --- a/packages/dtslint-runner/src/index.ts +++ b/packages/dtslint-runner/src/index.ts @@ -59,7 +59,6 @@ if (require.main === module) { group: "dtslint options", description: "Run dtslint only with typescript@next instead of all supported TypeScript versions.", type: "boolean", - alias: "next", conflicts: "localTypeScriptPath", }, expectOnly: { diff --git a/packages/dtslint/README.md b/packages/dtslint/README.md index 5586651c8e..4e8aeaa1cb 100644 --- a/packages/dtslint/README.md +++ b/packages/dtslint/README.md @@ -182,16 +182,6 @@ Use your locally installed version of TypeScript. ```sh dtslint --localTs node_modules/typescript/lib types ``` - -For a locally built TypeScript 7, pass the build output directory containing the `tsserver` or `tsgo` executable. - -- `--next` / `--onlyTestTsNext` - -Test only with `typescript@next`. - -TypeScript 7 is checked through its unstable out-of-process API. Compiler diagnostics and `$ExpectType` assertions -are supported; other ESLint-based dtslint rules are skipped for TypeScript 7. - - `--expectOnly` Disable all the lint rules except the one that checks for type correctness. diff --git a/packages/dtslint/src/index.ts b/packages/dtslint/src/index.ts index 21ab46f561..2b845e11f6 100644 --- a/packages/dtslint/src/index.ts +++ b/packages/dtslint/src/index.ts @@ -60,7 +60,6 @@ async function main(): Promise { onlyNpmChecks = true; break; case "--onlyTestTsNext": - case "--next": onlyTestTsNext = true; break; // Only for use by types-publisher. @@ -93,7 +92,7 @@ async function main(): Promise { throw new Error("Cannot use --onlyNpmChecks and --skipNpmChecks together."); } if (onlyTestTsNext && tsLocal) { - throw new Error("Cannot use --next/--onlyTestTsNext and --localTs together."); + throw new Error("Cannot use --onlyTestTsNext and --localTs together."); } if (lookingForTsLocal) { throw new Error("Path for --localTs was not provided."); @@ -114,13 +113,12 @@ async function main(): Promise { function usage(): void { console.error( - "Usage: dtslint [--version] [--next|--onlyTestTsNext] [--expectOnly] [--localTs path] [--skipNpmChecks] [--onlyNpmChecks]", + "Usage: dtslint [--version] [--onlyTestTsNext] [--expectOnly] [--localTs path] [--skipNpmChecks] [--onlyNpmChecks]", ); console.error("Args:"); console.error(" --version Print version and exit."); console.error(" --expectOnly Run only the ExpectType lint rule."); console.error(" --onlyTestTsNext Only run with `typescript@next`, not with the minimum version."); - console.error(" --next Alias for --onlyTestTsNext."); console.error(" --localTs path Run with *path* as the latest version of TS."); console.error(" --skipNpmChecks Don't query npm - skips name/version checks and @arethetypeswrong/cli."); console.error(" --onlyNpmChecks Only run the npm checks (name/version checks and @arethetypeswrong/cli)."); From e1750b67976128239e92ca33605cbe9287dcab39 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 17 Aug 2026 12:18:06 -0700 Subject: [PATCH 06/24] Load TypeScript 7 with require --- package.json | 2 +- packages/dtslint/package.json | 2 +- packages/dtslint/src/lintTypeScript7.ts | 11 +---- packages/dtslint/test/index.test.ts | 59 ++++++++++++++++++++----- 4 files changed, 53 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 01ee18650d..b25c424fb1 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint": "eslint . --report-unused-disable-directives --ext .ts", "format": "prettier --write 'packages/**/*.ts'", "format:check": "prettier --check 'packages/**/*.ts'", - "test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js", + "test": "jest", "build": "tsc -b .", "retag": "node packages/retag/dist/index.js", "ci:publish": "pnpm changeset tag && pnpm publish -r" diff --git a/packages/dtslint/package.json b/packages/dtslint/package.json index 0123a22e6a..864867187b 100644 --- a/packages/dtslint/package.json +++ b/packages/dtslint/package.json @@ -19,7 +19,7 @@ }, "scripts": { "build": "tsc -b .", - "test": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js --config ../../jest.config.js packages/dtslint" + "test": "../../node_modules/.bin/jest --config ../../jest.config.js packages/dtslint" }, "dependencies": { "@arethetypeswrong/cli": "0.18.3", diff --git a/packages/dtslint/src/lintTypeScript7.ts b/packages/dtslint/src/lintTypeScript7.ts index 46fe12b080..6eb468eaef 100644 --- a/packages/dtslint/src/lintTypeScript7.ts +++ b/packages/dtslint/src/lintTypeScript7.ts @@ -3,7 +3,6 @@ import * as typeScriptPackages from "@definitelytyped/typescript-packages"; import fs from "fs"; import path from "path"; import semver from "semver"; -import { pathToFileURL } from "url"; import * as ts from "typescript"; import type { TsVersion } from "./lint"; @@ -104,10 +103,8 @@ export async function lintTypeScript7( tsLocal: string | undefined, ): Promise { const clientVersion = version === "local" ? TypeScriptVersion.latest : version; - const [apiModule, astModule] = await Promise.all([ - importModule(typeScriptPackages.resolve(clientVersion, "unstable/sync")), - importModule(typeScriptPackages.resolve(clientVersion, "unstable/ast")), - ]); + const apiModule = require(typeScriptPackages.resolve(clientVersion, "unstable/sync")) as TypeScript7Api; + const astModule = require(typeScriptPackages.resolve(clientVersion, "unstable/ast")) as TypeScript7Ast; const tsserverPath = version === "local" ? findTypeScript7Server(tsLocal!) : undefined; const api = new apiModule.API({ cwd: dirPath, tsserverPath }); const configPaths = tsconfigs.map((config) => path.resolve(dirPath, config)); @@ -137,10 +134,6 @@ export async function lintTypeScript7( return formatFailures(failures); } -async function importModule(fileName: string): Promise { - return (await import(pathToFileURL(fileName).href)) as T; -} - function findTypeScript7Server(tsLocal: string): string { if (fs.statSync(tsLocal).isFile()) { return tsLocal; diff --git a/packages/dtslint/test/index.test.ts b/packages/dtslint/test/index.test.ts index cc86430273..ac93ab50d0 100644 --- a/packages/dtslint/test/index.test.ts +++ b/packages/dtslint/test/index.test.ts @@ -1,10 +1,33 @@ /// import { CompilerOptionsRaw, checkTsconfig } from "../src/checks"; import { assertPackageIsNotDeprecated } from "../src/index"; -import { lint } from "../src/lint"; -import { lintTypeScript7 } from "../src/lintTypeScript7"; import * as typeScriptPackages from "@definitelytyped/typescript-packages"; +import { execFile } from "child_process"; import path from "path"; +import { promisify } from "util"; + +const execFileAsync = promisify(execFile); + +async function runBuilt(moduleName: string, exportName: string, args: readonly unknown[]): Promise { + const modulePath = path.resolve(__dirname, `../dist/${moduleName}.js`); + const script = ` +const fn = require(process.argv[1])[process.argv[2]]; +Promise.resolve(fn(...JSON.parse(process.argv[3]))).then( + result => process.stdout.write(JSON.stringify({ result })), + error => { + console.error(error?.stack ?? error); + process.exitCode = 1; + }, +);`; + const { stdout } = await execFileAsync(process.execPath, [ + "-e", + script, + modulePath, + exportName, + JSON.stringify(args), + ]); + return (JSON.parse(stdout) as { result?: T }).result; +} describe("dtslint", () => { const base: CompilerOptionsRaw = { @@ -149,13 +172,13 @@ describe("dtslint", () => { for (const version of ["7.0", "7.1"] as const) { it(`checks compiler diagnostics and ExpectType through the TypeScript ${version} IPC API`, async () => { - const result = await lintTypeScript7( + const result = await runBuilt("lintTypeScript7", "lintTypeScript7", [ path.join(fixtures, "fail"), ["tsconfig.json"], version, true, - undefined, - ); + null, + ]); expect(result).toContain("compile error TS2322"); expect(result).toContain("expected type to be:\n 2\ngot:\n 1"); @@ -163,21 +186,29 @@ describe("dtslint", () => { it(`passes matching TypeScript ${version} ExpectType assertions without invoking ESLint`, async () => { await expect( - lint(path.join(fixtures, "pass"), ["tsconfig.json"], version, version, true, true, undefined), + runBuilt("lint", "lint", [ + path.join(fixtures, "pass"), + ["tsconfig.json"], + version, + version, + true, + true, + null, + ]), ).resolves.toBeUndefined(); }); } it("runs ordinary ESLint rules during TypeScript 7-only testing", async () => { - const result = await lint( + const result = await runBuilt("lint", "lint", [ path.join(__dirname, "typescript7-eslint"), ["tsconfig.json"], "7.0", "7.0", true, false, - undefined, - ); + null, + ]); expect(result).toContain("no-var"); expect(result).toContain("@typescript-eslint/naming-convention"); @@ -195,7 +226,15 @@ describe("dtslint", () => { ); await expect( - lint(path.join(fixtures, "pass"), ["tsconfig.json"], "local", "local", true, true, executable), + runBuilt("lint", "lint", [ + path.join(fixtures, "pass"), + ["tsconfig.json"], + "local", + "local", + true, + true, + executable, + ]), ).resolves.toBeUndefined(); }); }); From 10e2f111da7dbbf7ba884528875572ecd0c7eef9 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 17 Aug 2026 12:28:06 -0700 Subject: [PATCH 07/24] Upgrade repository to TypeScript 6 --- .changeset/tidy-tigers-compile.md | 8 + package.json | 10 +- packages/definitions-parser/package.json | 2 +- packages/dts-critic/package.json | 3 +- packages/dts-gen/package.json | 2 +- packages/dtslint/package.json | 12 +- packages/eslint-plugin/package.json | 10 +- .../expect-tsconfigs/expect-dom-tests.ts.lint | 9 +- .../types/expect-tsconfigs/index.d.ts.lint | 9 +- packages/mergebot/package.json | 4 +- packages/publisher/package.json | 2 +- pnpm-lock.yaml | 366 ++++++++++-------- 12 files changed, 246 insertions(+), 191 deletions(-) create mode 100644 .changeset/tidy-tigers-compile.md diff --git a/.changeset/tidy-tigers-compile.md b/.changeset/tidy-tigers-compile.md new file mode 100644 index 0000000000..443c817d77 --- /dev/null +++ b/.changeset/tidy-tigers-compile.md @@ -0,0 +1,8 @@ +--- +"@definitelytyped/dts-critic": patch +"@definitelytyped/dtslint": patch +"@definitelytyped/eslint-plugin": patch +"dts-gen": patch +--- + +Upgrade the bundled TypeScript compiler to 6.0 and update TypeScript ESLint dependencies for TypeScript 6 compatibility. diff --git a/package.json b/package.json index b25c424fb1..c419c97bc1 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ }, "scripts": { "preinstall": "npx only-allow pnpm", - "lint": "eslint . --report-unused-disable-directives --ext .ts", + "lint": "eslint . --report-unused-disable-directives --ext .ts --ignore-pattern 'packages/dtslint/test/typescript7-eslint/**'", "format": "prettier --write 'packages/**/*.ts'", "format:check": "prettier --check 'packages/**/*.ts'", "test": "jest", @@ -19,16 +19,16 @@ "@types/jest": "^30.0.0", "@types/node": "^25.3.3", "@types/yargs": "^17.0.35", - "@typescript-eslint/eslint-plugin": "^8.56.1", - "@typescript-eslint/parser": "^8.56.1", + "@typescript-eslint/eslint-plugin": "^8.67.0", + "@typescript-eslint/parser": "^8.67.0", "eslint": "^8.57.1", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsdoc": "^48.11.0", "jest": "^30.2.0", "knip": "^5.85.0", "prettier": "^3.8.1", - "ts-jest": "^29.4.6", - "typescript": "^5.9.3" + "ts-jest": "^29.4.12", + "typescript": "^6.0.3" }, "pnpm": { "overrides": { diff --git a/packages/definitions-parser/package.json b/packages/definitions-parser/package.json index 036f5a9a29..dc584eb7c0 100644 --- a/packages/definitions-parser/package.json +++ b/packages/definitions-parser/package.json @@ -29,7 +29,7 @@ }, "devDependencies": { "@types/pacote": "^11.1.8", - "typescript": "^5.9.3" + "typescript": "^6.0.3" }, "peerDependencies": { "typescript": "<7" diff --git a/packages/dts-critic/package.json b/packages/dts-critic/package.json index 30d7741cef..3826b69959 100644 --- a/packages/dts-critic/package.json +++ b/packages/dts-critic/package.json @@ -8,13 +8,12 @@ }, "dependencies": { "@definitelytyped/header-parser": "workspace:*", - "typescript": "^5.9.3", + "typescript": "^6.0.3", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": "<7" }, - "devDependencies": {}, "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { diff --git a/packages/dts-gen/package.json b/packages/dts-gen/package.json index 1b9c9f3d05..af8cdb91f4 100644 --- a/packages/dts-gen/package.json +++ b/packages/dts-gen/package.json @@ -25,7 +25,7 @@ }, "dependencies": { "dts-dom": "^3.7.0", - "typescript": "^5.9.3", + "typescript": "^6.0.3", "which": "^6.0.1", "yargs": "^17.7.2" }, diff --git a/packages/dtslint/package.json b/packages/dtslint/package.json index 864867187b..6a84c0a305 100644 --- a/packages/dtslint/package.json +++ b/packages/dtslint/package.json @@ -28,16 +28,16 @@ "@definitelytyped/typescript-packages": "workspace:*", "@definitelytyped/typescript-versions": "workspace:*", "@definitelytyped/utils": "workspace:*", - "@typescript-eslint/eslint-plugin": "^8.56.1", - "@typescript-eslint/parser": "^8.56.1", - "@typescript-eslint/types": "^8.56.1", - "@typescript-eslint/typescript-estree": "^8.56.1", - "@typescript-eslint/utils": "^8.56.1", + "@typescript-eslint/eslint-plugin": "^8.67.0", + "@typescript-eslint/parser": "^8.67.0", + "@typescript-eslint/types": "^8.67.0", + "@typescript-eslint/typescript-estree": "^8.67.0", + "@typescript-eslint/utils": "^8.67.0", "eslint": "^8.57.1", "eslint-plugin-import": "^2.32.0", "semver": "^7.7.4", "strip-json-comments": "^3.1.1", - "typescript": "^5.9.3" + "typescript": "^6.0.3" }, "devDependencies": { "@types/eslint": "^8.56.12", diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 2461f105f2..1e7cbf8cb9 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -23,13 +23,13 @@ }, "dependencies": { "@definitelytyped/utils": "workspace:*", - "@typescript-eslint/types": "^8.56.1", - "@typescript-eslint/utils": "^8.56.1", + "@typescript-eslint/types": "^8.67.0", + "@typescript-eslint/utils": "^8.67.0", "semver": "^7.7.4" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^8.54.0", - "@typescript-eslint/parser": "^8.54.0", + "@typescript-eslint/eslint-plugin": "^8.67.0", + "@typescript-eslint/parser": "^8.67.0", "eslint": "^8.57.0", "eslint-plugin-jsdoc": "^44.0.0", "typescript": "<7" @@ -40,7 +40,7 @@ "@types/semver": "^7.7.1", "glob": "^13.0.6", "jest-file-snapshot": "^0.7.0", - "typescript": "^5.9.3", + "typescript": "^6.0.3", "typescript-5.4": "npm:typescript@~5.4.0-0", "typescript-5.5": "npm:typescript@~5.5.0-0" }, diff --git a/packages/eslint-plugin/test/__file_snapshots__/types/expect-tsconfigs/expect-dom-tests.ts.lint b/packages/eslint-plugin/test/__file_snapshots__/types/expect-tsconfigs/expect-dom-tests.ts.lint index 1c6cde52db..4651185e20 100644 --- a/packages/eslint-plugin/test/__file_snapshots__/types/expect-tsconfigs/expect-dom-tests.ts.lint +++ b/packages/eslint-plugin/test/__file_snapshots__/types/expect-tsconfigs/expect-dom-tests.ts.lint @@ -1,8 +1,4 @@ -types/expect-tsconfigs/expect-dom-tests.ts - 6:16 error TypeScript@tsconfig.dom.json compile error: -Cannot find name 'WeakSet'. Do you need to change your target library? Try changing the 'lib' compiler option to 'es2015' or later @definitelytyped/expect - -✖ 1 problem (1 error, 0 warnings) +No errors ==== types/expect-tsconfigs/expect-dom-tests.ts ==== @@ -12,6 +8,3 @@ Cannot find name 'WeakSet'. Do you need to change your target library? Try chang const element: HTMLElement = expect.element; const weakSet: WeakSet<{}> = expect.weakSet; - ~~~~~~~ -!!! @definitelytyped/expect: TypeScript@tsconfig.dom.json compile error: -!!! : Cannot find name 'WeakSet'. Do you need to change your target library? Try changing the 'lib' compiler option to 'es2015' or later. diff --git a/packages/eslint-plugin/test/__file_snapshots__/types/expect-tsconfigs/index.d.ts.lint b/packages/eslint-plugin/test/__file_snapshots__/types/expect-tsconfigs/index.d.ts.lint index 05dfa5e83a..0589f67864 100644 --- a/packages/eslint-plugin/test/__file_snapshots__/types/expect-tsconfigs/index.d.ts.lint +++ b/packages/eslint-plugin/test/__file_snapshots__/types/expect-tsconfigs/index.d.ts.lint @@ -1,10 +1,8 @@ types/expect-tsconfigs/index.d.ts 1:23 error TypeScript@tsconfig.no-dom.json compile error: -Cannot find name 'HTMLElement' @definitelytyped/expect - 3:23 error TypeScript@tsconfig.dom.json compile error: -Cannot find name 'WeakSet'. Do you need to change your target library? Try changing the 'lib' compiler option to 'es2015' or later @definitelytyped/expect +Cannot find name 'HTMLElement' @definitelytyped/expect -✖ 2 problems (2 errors, 0 warnings) +✖ 1 problem (1 error, 0 warnings) ==== types/expect-tsconfigs/index.d.ts ==== @@ -14,6 +12,3 @@ Cannot find name 'WeakSet'. Do you need to change your target library? Try chang !!! : Cannot find name 'HTMLElement'. export const weakSet: WeakSet<{}>; - ~~~~~~~ -!!! @definitelytyped/expect: TypeScript@tsconfig.dom.json compile error: -!!! : Cannot find name 'WeakSet'. Do you need to change your target library? Try changing the 'lib' compiler option to 'es2015' or later. diff --git a/packages/mergebot/package.json b/packages/mergebot/package.json index 9913784ab2..f944533cfe 100644 --- a/packages/mergebot/package.json +++ b/packages/mergebot/package.json @@ -20,9 +20,9 @@ }, "dependencies": { "@apollo/client": "^4.1.6", + "@azure/functions": "^4.11.2", "@azure/identity": "^4.13.0", "@azure/keyvault-keys": "^4.10.0", - "@azure/functions": "^4.11.2", "@definitelytyped/old-header-parser": "npm:@definitelytyped/header-parser@0.0.178", "@definitelytyped/utils": "workspace:*", "@octokit/webhooks-methods": "^4.1.0", @@ -45,7 +45,7 @@ "jest-file-snapshot": "^0.7.0", "rimraf": "^6.1.3", "seroval": "^1.5.0", - "typescript": "^5.9.3" + "typescript": "^6.0.3" }, "scripts": { "prestart": "pnpm run build", diff --git a/packages/publisher/package.json b/packages/publisher/package.json index ae42950e1d..ff63ba3acc 100644 --- a/packages/publisher/package.json +++ b/packages/publisher/package.json @@ -13,7 +13,7 @@ "longjohn": "^0.2.12", "pacote": "^21.4.0", "semver": "^7.7.4", - "typescript": "^5.9.3", + "typescript": "^6.0.3", "yargs": "^17.7.2" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d3a99d52f9..b77ad6e298 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,17 +27,17 @@ importers: specifier: ^17.0.35 version: 17.0.35 '@typescript-eslint/eslint-plugin': - specifier: ^8.56.1 - version: 8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + specifier: ^8.67.0 + version: 8.67.0(@typescript-eslint/parser@8.67.0(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3) '@typescript-eslint/parser': - specifier: ^8.56.1 - version: 8.56.1(eslint@8.57.1)(typescript@5.9.3) + specifier: ^8.67.0 + version: 8.67.0(eslint@8.57.1)(typescript@6.0.3) eslint: specifier: ^8.57.1 version: 8.57.1 eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1) + version: 2.32.0(@typescript-eslint/parser@8.67.0(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1) eslint-plugin-jsdoc: specifier: ^48.11.0 version: 48.11.0(eslint@8.57.1) @@ -46,16 +46,16 @@ importers: version: 30.2.0(@types/node@25.3.3) knip: specifier: ^5.85.0 - version: 5.85.0(@types/node@25.3.3)(typescript@5.9.3) + version: 5.85.0(@types/node@25.3.3)(typescript@6.0.3) prettier: specifier: ^3.8.1 version: 3.8.1 ts-jest: - specifier: ^29.4.6 - version: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@30.2.0(@types/node@25.3.3))(typescript@5.9.3) + specifier: ^29.4.12 + version: 29.4.12(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@30.2.0(@types/node@25.3.3))(typescript@6.0.3) typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^6.0.3 + version: 6.0.3 packages/definitions-parser: dependencies: @@ -85,8 +85,8 @@ importers: specifier: ^11.1.8 version: 11.1.8 typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^6.0.3 + version: 6.0.3 packages/dts-critic: dependencies: @@ -94,8 +94,8 @@ importers: specifier: workspace:* version: link:../header-parser typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^6.0.3 + version: 6.0.3 yargs: specifier: ^17.7.2 version: 17.7.2 @@ -106,8 +106,8 @@ importers: specifier: ^3.7.0 version: 3.7.0 typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^6.0.3 + version: 6.0.3 which: specifier: ^6.0.1 version: 6.0.1 @@ -155,26 +155,26 @@ importers: specifier: workspace:* version: link:../utils '@typescript-eslint/eslint-plugin': - specifier: ^8.56.1 - version: 8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + specifier: ^8.67.0 + version: 8.67.0(@typescript-eslint/parser@8.67.0(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3) '@typescript-eslint/parser': - specifier: ^8.56.1 - version: 8.56.1(eslint@8.57.1)(typescript@5.9.3) + specifier: ^8.67.0 + version: 8.67.0(eslint@8.57.1)(typescript@6.0.3) '@typescript-eslint/types': - specifier: ^8.56.1 - version: 8.56.1 + specifier: ^8.67.0 + version: 8.67.0 '@typescript-eslint/typescript-estree': - specifier: ^8.56.1 - version: 8.56.1(typescript@5.9.3) + specifier: ^8.67.0 + version: 8.67.0(typescript@6.0.3) '@typescript-eslint/utils': - specifier: ^8.56.1 - version: 8.56.1(eslint@8.57.1)(typescript@5.9.3) + specifier: ^8.67.0 + version: 8.67.0(eslint@8.57.1)(typescript@6.0.3) eslint: specifier: ^8.57.1 version: 8.57.1 eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1) + version: 2.32.0(@typescript-eslint/parser@8.67.0(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1) semver: specifier: ^7.7.4 version: 7.7.4 @@ -182,8 +182,8 @@ importers: specifier: ^3.1.1 version: 3.1.1 typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^6.0.3 + version: 6.0.3 devDependencies: '@types/eslint': specifier: ^8.56.12 @@ -220,17 +220,17 @@ importers: specifier: workspace:* version: link:../utils '@typescript-eslint/eslint-plugin': - specifier: ^8.54.0 - version: 8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + specifier: ^8.67.0 + version: 8.67.0(@typescript-eslint/parser@8.67.0(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3) '@typescript-eslint/parser': - specifier: ^8.54.0 - version: 8.56.1(eslint@8.57.1)(typescript@5.9.3) + specifier: ^8.67.0 + version: 8.67.0(eslint@8.57.1)(typescript@6.0.3) '@typescript-eslint/types': - specifier: ^8.56.1 - version: 8.56.1 + specifier: ^8.67.0 + version: 8.67.0 '@typescript-eslint/utils': - specifier: ^8.56.1 - version: 8.56.1(eslint@8.57.1)(typescript@5.9.3) + specifier: ^8.67.0 + version: 8.67.0(eslint@8.57.1)(typescript@6.0.3) eslint: specifier: ^8.57.0 version: 8.57.1 @@ -257,8 +257,8 @@ importers: specifier: ^0.7.0 version: 0.7.0 typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^6.0.3 + version: 6.0.3 typescript-5.4: specifier: npm:typescript@~5.4.0-0 version: typescript@5.4.5 @@ -323,7 +323,7 @@ importers: devDependencies: '@graphql-codegen/cli': specifier: ^6.1.2 - version: 6.1.2(@types/node@25.3.3)(graphql@16.13.0)(typescript@5.9.3) + version: 6.1.2(@types/node@25.3.3)(graphql@16.13.0)(typescript@6.0.3) '@graphql-codegen/client-preset': specifier: ^5.2.3 version: 5.2.3(graphql@16.13.0) @@ -358,8 +358,8 @@ importers: specifier: ^1.5.0 version: 1.5.0 typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^6.0.3 + version: 6.0.3 packages/publisher: dependencies: @@ -385,8 +385,8 @@ importers: specifier: ^7.7.4 version: 7.7.4 typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^6.0.3 + version: 6.0.3 yargs: specifier: ^17.7.2 version: 17.7.2 @@ -1056,6 +1056,12 @@ packages: cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2038,63 +2044,63 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/eslint-plugin@8.56.1': - resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==} + '@typescript-eslint/eslint-plugin@8.67.0': + resolution: {integrity: sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.56.1 + '@typescript-eslint/parser': ^8.67.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.56.1': - resolution: {integrity: sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==} + '@typescript-eslint/parser@8.67.0': + resolution: {integrity: sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.56.1': - resolution: {integrity: sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==} + '@typescript-eslint/project-service@8.67.0': + resolution: {integrity: sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.56.1': - resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==} + '@typescript-eslint/scope-manager@8.67.0': + resolution: {integrity: sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.56.1': - resolution: {integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==} + '@typescript-eslint/tsconfig-utils@8.67.0': + resolution: {integrity: sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.56.1': - resolution: {integrity: sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==} + '@typescript-eslint/type-utils@8.67.0': + resolution: {integrity: sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.56.1': - resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==} + '@typescript-eslint/types@8.67.0': + resolution: {integrity: sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.56.1': - resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==} + '@typescript-eslint/typescript-estree@8.67.0': + resolution: {integrity: sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.56.1': - resolution: {integrity: sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==} + '@typescript-eslint/utils@8.67.0': + resolution: {integrity: sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.56.1': - resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==} + '@typescript-eslint/visitor-keys@8.67.0': + resolution: {integrity: sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript/typescript-aix-ppc64@7.0.2': @@ -2620,6 +2626,10 @@ packages: resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} engines: {node: 18 || 20 || >=22} + brace-expansion@5.0.9: + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} + engines: {node: 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -3433,8 +3443,8 @@ packages: resolution: {integrity: sha512-uSisMYERbaB9bkA9M4/4dnqyktaEkf1kMHNKq/7DHyxVeWqHQ2mBmVqm5u6/FVHwF3iCNalKcg82Zfl+tffWoA==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - handlebars@4.7.8: - resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + handlebars@4.7.9: + resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} engines: {node: '>=0.4.7'} hasBin: true @@ -3509,8 +3519,8 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + ignore@7.0.6: + resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} engines: {node: '>= 4'} immutable@3.7.6: @@ -4157,6 +4167,10 @@ packages: resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} @@ -4503,6 +4517,10 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} @@ -4683,6 +4701,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + sentence-case@3.0.4: resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} @@ -4972,6 +4995,10 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + title-case@3.0.3: resolution: {integrity: sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==} @@ -4989,14 +5016,14 @@ packages: resolution: {integrity: sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==} engines: {node: '>=0.10.0'} - ts-api-utils@2.4.0: - resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' - ts-jest@29.4.6: - resolution: {integrity: sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==} + ts-jest@29.4.12: + resolution: {integrity: sha512-Ov6ClY53Fflh6BGAnY2DlTq1hYDrTycz2PVTXBWFW2CU+9zrEqAp9fWdGXl42EXO5RLSFAcAZ2JFKbP+zBTFfw==} engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -5007,7 +5034,7 @@ packages: esbuild: '*' jest: ^29.0.0 || ^30.0.0 jest-util: ^29.0.0 || ^30.0.0 - typescript: '>=4.3 <6' + typescript: '>=4.3 <7' peerDependenciesMeta: '@babel/core': optional: true @@ -5114,6 +5141,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + typescript@7.0.2: resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} engines: {node: '>=16.20.0'} @@ -6020,6 +6052,11 @@ snapshots: '@esbuild/win32-x64@0.27.3': optional: true + '@eslint-community/eslint-utils@4.10.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': dependencies: eslint: 8.57.1 @@ -6055,7 +6092,7 @@ snapshots: graphql: 16.13.0 tslib: 2.6.3 - '@graphql-codegen/cli@6.1.2(@types/node@25.3.3)(graphql@16.13.0)(typescript@5.9.3)': + '@graphql-codegen/cli@6.1.2(@types/node@25.3.3)(graphql@16.13.0)(typescript@6.0.3)': dependencies: '@babel/generator': 7.29.1 '@babel/template': 7.28.6 @@ -6075,11 +6112,11 @@ snapshots: '@inquirer/prompts': 7.10.1(@types/node@25.3.3) '@whatwg-node/fetch': 0.10.13 chalk: 4.1.2 - cosmiconfig: 9.0.1(typescript@5.9.3) + cosmiconfig: 9.0.1(typescript@6.0.3) debounce: 2.2.0 detect-indent: 6.1.0 graphql: 16.13.0 - graphql-config: 5.1.5(@types/node@25.3.3)(graphql@16.13.0)(typescript@5.9.3) + graphql-config: 5.1.5(@types/node@25.3.3)(graphql@16.13.0)(typescript@6.0.3) is-glob: 4.0.3 jiti: 2.6.1 json-to-pretty-yaml: 1.2.2 @@ -7361,95 +7398,95 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.56.1(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.56.1 - '@typescript-eslint/type-utils': 8.56.1(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/utils': 8.56.1(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.56.1 + '@typescript-eslint/parser': 8.67.0(eslint@8.57.1)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.67.0 + '@typescript-eslint/type-utils': 8.67.0(eslint@8.57.1)(typescript@6.0.3) + '@typescript-eslint/utils': 8.67.0(eslint@8.57.1)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.67.0 eslint: 8.57.1 - ignore: 7.0.5 + ignore: 7.0.6 natural-compare: 1.4.0 - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/parser@8.67.0(eslint@8.57.1)(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.56.1 - '@typescript-eslint/types': 8.56.1 - '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.56.1 + '@typescript-eslint/scope-manager': 8.67.0 + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.67.0 debug: 4.4.3 eslint: 8.57.1 - typescript: 5.9.3 + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.56.1(typescript@5.9.3)': + '@typescript-eslint/project-service@8.67.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) - '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.3) + '@typescript-eslint/types': 8.67.0 debug: 4.4.3 - typescript: 5.9.3 + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.56.1': + '@typescript-eslint/scope-manager@8.67.0': dependencies: - '@typescript-eslint/types': 8.56.1 - '@typescript-eslint/visitor-keys': 8.56.1 + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/visitor-keys': 8.67.0 - '@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.67.0(typescript@6.0.3)': dependencies: - typescript: 5.9.3 + typescript: 6.0.3 - '@typescript-eslint/type-utils@8.56.1(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.67.0(eslint@8.57.1)(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.56.1 - '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.56.1(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.67.0(eslint@8.57.1)(typescript@6.0.3) debug: 4.4.3 eslint: 8.57.1 - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.56.1': {} + '@typescript-eslint/types@8.67.0': {} - '@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.67.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.56.1(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) - '@typescript-eslint/types': 8.56.1 - '@typescript-eslint/visitor-keys': 8.56.1 + '@typescript-eslint/project-service': 8.67.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.3) + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/visitor-keys': 8.67.0 debug: 4.4.3 - minimatch: 10.2.4 + minimatch: 10.2.6 semver: 7.7.4 - tinyglobby: 0.2.15 - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.56.1(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/utils@8.67.0(eslint@8.57.1)(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) - '@typescript-eslint/scope-manager': 8.56.1 - '@typescript-eslint/types': 8.56.1 - '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@eslint-community/eslint-utils': 4.10.1(eslint@8.57.1) + '@typescript-eslint/scope-manager': 8.67.0 + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3) eslint: 8.57.1 - typescript: 5.9.3 + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.56.1': + '@typescript-eslint/visitor-keys@8.67.0': dependencies: - '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/types': 8.67.0 eslint-visitor-keys: 5.0.1 '@typescript/typescript-aix-ppc64@7.0.2': @@ -7871,6 +7908,10 @@ snapshots: dependencies: balanced-match: 4.0.4 + brace-expansion@5.0.9: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -8080,23 +8121,23 @@ snapshots: cookie@0.7.2: {} - cosmiconfig@8.3.6(typescript@5.9.3): + cosmiconfig@8.3.6(typescript@6.0.3): dependencies: import-fresh: 3.3.1 js-yaml: 4.1.1 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: - typescript: 5.9.3 + typescript: 6.0.3 - cosmiconfig@9.0.1(typescript@5.9.3): + cosmiconfig@9.0.1(typescript@6.0.3): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 js-yaml: 4.1.1 parse-json: 5.2.0 optionalDependencies: - typescript: 5.9.3 + typescript: 6.0.3 cross-fetch@3.2.0: dependencies: @@ -8381,17 +8422,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.67.0(eslint@8.57.1)(typescript@6.0.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.56.1(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.67.0(eslint@8.57.1)(typescript@6.0.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.67.0(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -8402,7 +8443,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.67.0(eslint@8.57.1)(typescript@6.0.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -8414,7 +8455,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.56.1(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.67.0(eslint@8.57.1)(typescript@6.0.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -8614,6 +8655,10 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + fetch-blob@3.2.0: dependencies: node-domexception: 1.0.0 @@ -8806,7 +8851,7 @@ snapshots: graphemer@1.4.0: {} - graphql-config@5.1.5(@types/node@25.3.3)(graphql@16.13.0)(typescript@5.9.3): + graphql-config@5.1.5(@types/node@25.3.3)(graphql@16.13.0)(typescript@6.0.3): dependencies: '@graphql-tools/graphql-file-loader': 8.1.9(graphql@16.13.0) '@graphql-tools/json-file-loader': 8.0.26(graphql@16.13.0) @@ -8814,7 +8859,7 @@ snapshots: '@graphql-tools/merge': 9.1.7(graphql@16.13.0) '@graphql-tools/url-loader': 8.0.33(@types/node@25.3.3)(graphql@16.13.0) '@graphql-tools/utils': 10.11.0(graphql@16.13.0) - cosmiconfig: 8.3.6(typescript@5.9.3) + cosmiconfig: 8.3.6(typescript@6.0.3) graphql: 16.13.0 jiti: 2.6.1 minimatch: 9.0.9 @@ -8842,7 +8887,7 @@ snapshots: graphql@16.13.0: {} - handlebars@4.7.8: + handlebars@4.7.9: dependencies: minimist: 1.2.8 neo-async: 2.6.2 @@ -8916,7 +8961,7 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.5: {} + ignore@7.0.6: {} immutable@3.7.6: {} @@ -9568,7 +9613,7 @@ snapshots: dependencies: json-buffer: 3.0.1 - knip@5.85.0(@types/node@25.3.3)(typescript@5.9.3): + knip@5.85.0(@types/node@25.3.3)(typescript@6.0.3): dependencies: '@nodelib/fs.walk': 1.2.8 '@types/node': 25.3.3 @@ -9582,7 +9627,7 @@ snapshots: picomatch: 4.0.3 smol-toml: 1.6.0 strip-json-comments: 5.0.3 - typescript: 5.9.3 + typescript: 6.0.3 zod: 4.3.6 leven@3.1.0: {} @@ -9755,6 +9800,10 @@ snapshots: dependencies: brace-expansion: 5.0.4 + minimatch@10.2.6: + dependencies: + brace-expansion: 5.0.9 + minimatch@3.1.5: dependencies: brace-expansion: 1.1.12 @@ -10156,6 +10205,8 @@ snapshots: picomatch@4.0.3: {} + picomatch@4.0.5: {} + pify@4.0.1: {} pirates@4.0.7: {} @@ -10326,6 +10377,8 @@ snapshots: semver@7.7.4: {} + semver@7.8.5: {} + sentence-case@3.0.4: dependencies: no-case: 3.0.4 @@ -10682,6 +10735,11 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + title-case@3.0.3: dependencies: tslib: 2.6.3 @@ -10698,22 +10756,22 @@ snapshots: dependencies: escape-string-regexp: 1.0.5 - ts-api-utils@2.4.0(typescript@5.9.3): + ts-api-utils@2.5.0(typescript@6.0.3): dependencies: - typescript: 5.9.3 + typescript: 6.0.3 - ts-jest@29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@30.2.0(@types/node@25.3.3))(typescript@5.9.3): + ts-jest@29.4.12(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(jest-util@30.2.0)(jest@30.2.0(@types/node@25.3.3))(typescript@6.0.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - handlebars: 4.7.8 + handlebars: 4.7.9 jest: 30.2.0(@types/node@25.3.3) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.7.4 + semver: 7.8.5 type-fest: 4.41.0 - typescript: 5.9.3 + typescript: 6.0.3 yargs-parser: 21.1.1 optionalDependencies: '@babel/core': 7.29.0 @@ -10804,6 +10862,8 @@ snapshots: typescript@6.0.2: {} + typescript@6.0.3: {} + typescript@7.0.2: optionalDependencies: '@typescript/typescript-aix-ppc64': 7.0.2 From d17c89078bebe9c2baa2cc156673e33fc519d623 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:33:55 -0700 Subject: [PATCH 08/24] Match legacy diagnostic scoping for TypeScript 7 --- packages/dtslint/src/lintTypeScript7.ts | 41 ++++++++++++++----- .../fixtures/typescript7/external/index.d.ts | 3 ++ .../test/fixtures/typescript7/pass/test.ts | 4 ++ 3 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 packages/dtslint/test/fixtures/typescript7/external/index.d.ts diff --git a/packages/dtslint/src/lintTypeScript7.ts b/packages/dtslint/src/lintTypeScript7.ts index 6eb468eaef..5c6ed7f440 100644 --- a/packages/dtslint/src/lintTypeScript7.ts +++ b/packages/dtslint/src/lintTypeScript7.ts @@ -45,13 +45,13 @@ interface Diagnostic { } interface Program { + getCompilerOptions(): { readonly declaration?: boolean; readonly composite?: boolean }; getSourceFile(fileName: string): SourceFile | undefined; getSourceFileNames(): readonly string[]; getSourceFileMetadata(fileName: string): { isDefaultLibrary: boolean; isFromExternalLibrary: boolean } | undefined; - getSyntacticDiagnostics(): readonly Diagnostic[]; - getBindDiagnostics(): readonly Diagnostic[]; - getSemanticDiagnostics(): readonly Diagnostic[]; - getDeclarationDiagnostics(): readonly Diagnostic[]; + getSyntacticDiagnostics(fileName?: string): readonly Diagnostic[]; + getSemanticDiagnostics(fileName?: string): readonly Diagnostic[]; + getDeclarationDiagnostics(fileName?: string): readonly Diagnostic[]; getProgramDiagnostics(): readonly Diagnostic[]; getGlobalDiagnostics(): readonly Diagnostic[]; getConfigFileParsingDiagnostics(): readonly Diagnostic[]; @@ -121,7 +121,7 @@ export async function lintTypeScript7( }); continue; } - failures.push(...getDiagnosticFailures(project, version)); + failures.push(...getDiagnosticFailures(project, dirPath, version, isLatest)); failures.push(...getExpectTypeFailures(project, apiModule, astModule, dirPath, version, isLatest)); } } finally { @@ -147,16 +147,37 @@ function findTypeScript7Server(tsLocal: string): string { throw new Error(`Could not find a TypeScript 7 tsserver/tsgo executable in ${tsLocal}.`); } -function getDiagnosticFailures(project: Project, version: TsVersion): Failure[] { +function getDiagnosticFailures(project: Project, dirPath: string, version: TsVersion, isLatest: boolean): Failure[] { const diagnostics = [ ...project.program.getConfigFileParsingDiagnostics(), ...project.program.getProgramDiagnostics(), ...project.program.getGlobalDiagnostics(), - ...project.program.getSyntacticDiagnostics(), - ...project.program.getBindDiagnostics(), - ...project.program.getSemanticDiagnostics(), - ...project.program.getDeclarationDiagnostics(), ]; + const compilerOptions = project.program.getCompilerOptions(); + const checkDeclarationDiagnostics = compilerOptions.declaration || compilerOptions.composite; + + // TODO: Keep this aligned with the legacy expect rule's getPreEmitDiagnostics(program, sourceFile) calls. + // dtslint historically reports diagnostics only for files it lints, suppressing errors inside dependencies. + for (const fileName of project.program.getSourceFileNames()) { + const sourceFile = project.program.getSourceFile(fileName); + const metadata = project.program.getSourceFileMetadata(fileName); + if ( + !sourceFile || + metadata?.isDefaultLibrary || + metadata?.isFromExternalLibrary || + !startsWithDirectory(fileName, dirPath) || + (isLatest && isTypesVersionPath(fileName, dirPath)) + ) { + continue; + } + diagnostics.push( + ...project.program.getSyntacticDiagnostics(fileName), + ...project.program.getSemanticDiagnostics(fileName), + ); + if (checkDeclarationDiagnostics) { + diagnostics.push(...project.program.getDeclarationDiagnostics(fileName)); + } + } const failures = new Map(); for (const diagnostic of diagnostics) { diff --git a/packages/dtslint/test/fixtures/typescript7/external/index.d.ts b/packages/dtslint/test/fixtures/typescript7/external/index.d.ts new file mode 100644 index 0000000000..5be6dc8f0d --- /dev/null +++ b/packages/dtslint/test/fixtures/typescript7/external/index.d.ts @@ -0,0 +1,3 @@ +export interface ExternalType { + broken: MissingType; +} diff --git a/packages/dtslint/test/fixtures/typescript7/pass/test.ts b/packages/dtslint/test/fixtures/typescript7/pass/test.ts index bff4be719c..6e6ab64c73 100644 --- a/packages/dtslint/test/fixtures/typescript7/pass/test.ts +++ b/packages/dtslint/test/fixtures/typescript7/pass/test.ts @@ -1,4 +1,8 @@ import { literal } from "./index"; +import type { ExternalType } from "../external"; // $ExpectType 1 literal(); + +declare const external: ExternalType; +external; From bb9960ea9a6de8e094af73012dc4865ef04a6737 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:35:04 -0700 Subject: [PATCH 09/24] Ignore intentional dtslint fixture in knip --- .knip.jsonc | 1 + 1 file changed, 1 insertion(+) diff --git a/.knip.jsonc b/.knip.jsonc index 93d122a07d..c7d0a6349f 100644 --- a/.knip.jsonc +++ b/.knip.jsonc @@ -16,6 +16,7 @@ }, "packages/dtslint": { "project": "**/*.ts", + "ignore": ["test/typescript7-eslint/**"], "ignoreDependencies": [ "@typescript-eslint/eslint-plugin", "@typescript-eslint/parser", From be306d444bb4b9487be135c3b231e417aa517e7a Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:40:11 -0700 Subject: [PATCH 10/24] Make TypeScript 7 ESLint fixture CI-safe --- .knip.jsonc | 1 - package.json | 2 +- packages/dtslint/test/typescript7-eslint/{test.ts => test.cts} | 0 packages/dtslint/test/typescript7-eslint/tsconfig.json | 2 +- 4 files changed, 2 insertions(+), 3 deletions(-) rename packages/dtslint/test/typescript7-eslint/{test.ts => test.cts} (100%) diff --git a/.knip.jsonc b/.knip.jsonc index c7d0a6349f..93d122a07d 100644 --- a/.knip.jsonc +++ b/.knip.jsonc @@ -16,7 +16,6 @@ }, "packages/dtslint": { "project": "**/*.ts", - "ignore": ["test/typescript7-eslint/**"], "ignoreDependencies": [ "@typescript-eslint/eslint-plugin", "@typescript-eslint/parser", diff --git a/package.json b/package.json index c419c97bc1..3f083e45d6 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ }, "scripts": { "preinstall": "npx only-allow pnpm", - "lint": "eslint . --report-unused-disable-directives --ext .ts --ignore-pattern 'packages/dtslint/test/typescript7-eslint/**'", + "lint": "eslint . --report-unused-disable-directives --ext .ts", "format": "prettier --write 'packages/**/*.ts'", "format:check": "prettier --check 'packages/**/*.ts'", "test": "jest", diff --git a/packages/dtslint/test/typescript7-eslint/test.ts b/packages/dtslint/test/typescript7-eslint/test.cts similarity index 100% rename from packages/dtslint/test/typescript7-eslint/test.ts rename to packages/dtslint/test/typescript7-eslint/test.cts diff --git a/packages/dtslint/test/typescript7-eslint/tsconfig.json b/packages/dtslint/test/typescript7-eslint/tsconfig.json index b918e4146a..ea4774bacb 100644 --- a/packages/dtslint/test/typescript7-eslint/tsconfig.json +++ b/packages/dtslint/test/typescript7-eslint/tsconfig.json @@ -5,5 +5,5 @@ "strict": true, "target": "esnext" }, - "files": ["index.d.ts", "test.ts"] + "files": ["index.d.ts", "test.cts"] } From 261b593d43d11069cdc75100292f7da4d6dd986f Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 17 Aug 2026 15:32:20 -0700 Subject: [PATCH 11/24] Normalize TypeScript 7 type member order --- packages/dtslint/src/lintTypeScript7.ts | 4 ++++ packages/dtslint/test/fixtures/typescript7/pass/test.ts | 3 +++ 2 files changed, 7 insertions(+) diff --git a/packages/dtslint/src/lintTypeScript7.ts b/packages/dtslint/src/lintTypeScript7.ts index 5c6ed7f440..42557e18bc 100644 --- a/packages/dtslint/src/lintTypeScript7.ts +++ b/packages/dtslint/src/lintTypeScript7.ts @@ -317,6 +317,10 @@ function normalizedTypeToString(type: string): string { .map(([item]) => item); return ts.factory.updateUnionTypeNode(node, ts.factory.createNodeArray(types)); } + if (ts.isTypeLiteralNode(node)) { + const members = [...node.members].sort((a, b) => print(a).localeCompare(print(b))); + return ts.factory.updateTypeLiteralNode(node, ts.factory.createNodeArray(members)); + } if ( ts.isTypeOperatorNode(node) && node.operator === ts.SyntaxKind.ReadonlyKeyword && diff --git a/packages/dtslint/test/fixtures/typescript7/pass/test.ts b/packages/dtslint/test/fixtures/typescript7/pass/test.ts index 6e6ab64c73..bce9174ea3 100644 --- a/packages/dtslint/test/fixtures/typescript7/pass/test.ts +++ b/packages/dtslint/test/fixtures/typescript7/pass/test.ts @@ -6,3 +6,6 @@ literal(); declare const external: ExternalType; external; + +declare const object: { b: string; a: number }; +object; // $ExpectType { a: number; b: string; } From e2fc7a1e98f14453f42241ca6b0afc77a1f8791c Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 17 Aug 2026 15:49:05 -0700 Subject: [PATCH 12/24] Test TypeScript 7 expect-error ranges --- packages/dtslint/test/fixtures/typescript7/pass/test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/dtslint/test/fixtures/typescript7/pass/test.ts b/packages/dtslint/test/fixtures/typescript7/pass/test.ts index bce9174ea3..8bc4273501 100644 --- a/packages/dtslint/test/fixtures/typescript7/pass/test.ts +++ b/packages/dtslint/test/fixtures/typescript7/pass/test.ts @@ -9,3 +9,9 @@ external; declare const object: { b: string; a: number }; object; // $ExpectType { a: number; b: string; } + +// @ts-expect-error >=7.0 +const expectedError: string = 1; + +// @ts-expect-error <7.0 +const noErrorOutsideRange: string = ""; From ff76178ce9fb87acb9bdbf43100d411ed734802e Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 18 Aug 2026 05:27:02 -0700 Subject: [PATCH 13/24] Address TypeScript 7 review feedback --- packages/dtslint/src/lintTypeScript7.ts | 47 ++++++++++++++----- .../test/fixtures/typescript7/fail/test.ts | 14 ++++++ .../typescript7/fail/tsconfig.alternate.json | 14 ++++++ .../test/fixtures/typescript7/pass/test.ts | 7 +++ packages/dtslint/test/index.test.ts | 23 +++++++++ 5 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 packages/dtslint/test/fixtures/typescript7/fail/tsconfig.alternate.json diff --git a/packages/dtslint/src/lintTypeScript7.ts b/packages/dtslint/src/lintTypeScript7.ts index 42557e18bc..3df93f33b9 100644 --- a/packages/dtslint/src/lintTypeScript7.ts +++ b/packages/dtslint/src/lintTypeScript7.ts @@ -108,21 +108,22 @@ export async function lintTypeScript7( const tsserverPath = version === "local" ? findTypeScript7Server(tsLocal!) : undefined; const api = new apiModule.API({ cwd: dirPath, tsserverPath }); const configPaths = tsconfigs.map((config) => path.resolve(dirPath, config)); - const failures: Failure[] = []; + const reportTsconfigName = tsconfigs.length !== 1 || tsconfigs[0] !== "tsconfig.json"; + const failures = new Map }>(); try { const snapshot = api.updateSnapshot({ openProjects: configPaths }); try { - for (const configPath of configPaths) { + for (let i = 0; i < configPaths.length; i++) { + const configPath = configPaths[i]; + const run = `${version} ${tsconfigs[i]}`; const project = snapshot.getProject(configPath); if (!project) { - failures.push({ - message: `TypeScript@${version} could not open ${configPath}.`, - }); + addFailures([{ message: `could not open ${configPath}.` }], run); continue; } - failures.push(...getDiagnosticFailures(project, dirPath, version, isLatest)); - failures.push(...getExpectTypeFailures(project, apiModule, astModule, dirPath, version, isLatest)); + addFailures(getDiagnosticFailures(project, dirPath, version, isLatest), run); + addFailures(getExpectTypeFailures(project, apiModule, astModule, dirPath, isLatest), run); } } finally { snapshot.dispose(); @@ -131,7 +132,30 @@ export async function lintTypeScript7( api.close(); } - return formatFailures(failures); + return formatFailures( + [...failures.values()].map(({ failure, runs }) => ({ + ...failure, + message: `TypeScript@${formatRuns(runs, reportTsconfigName)} ${failure.message}`, + })), + ); + + function addFailures(newFailures: readonly Failure[], run: string): void { + for (const failure of newFailures) { + const key = JSON.stringify(failure); + let existing = failures.get(key); + if (!existing) { + failures.set(key, (existing = { failure, runs: new Set() })); + } + existing.runs.add(run); + } + } +} + +function formatRuns(runs: ReadonlySet, reportTsconfigName: boolean): string { + return [...runs] + .sort() + .map((run) => (reportTsconfigName ? run : run.split(" ")[0])) + .join(", "); } function findTypeScript7Server(tsLocal: string): string { @@ -188,7 +212,7 @@ function getDiagnosticFailures(project: Project, dirPath: string, version: TsVer fileName: diagnostic.fileName, start: diagnostic.pos >= 0 ? diagnostic.pos : undefined, end: diagnostic.end >= 0 ? diagnostic.end : undefined, - message: `TypeScript@${version} compile error TS${diagnostic.code}:\n${flattenDiagnostic(diagnostic)}`, + message: `compile error TS${diagnostic.code}:\n${flattenDiagnostic(diagnostic)}`, }; failures.set(JSON.stringify(failure), failure); } @@ -227,7 +251,6 @@ function getExpectTypeFailures( apiModule: TypeScript7Api, astModule: TypeScript7Ast, dirPath: string, - version: TsVersion, isLatest: boolean, ): Failure[] { const failures: Failure[] = []; @@ -269,7 +292,7 @@ function getExpectTypeFailures( fileName, start: node.getStart(sourceFile), end: node.getEnd(), - message: `TypeScript@${version} expected type to be:\n ${expected}\ngot:\n ${actual}`, + message: `expected type to be:\n ${expected}\ngot:\n ${actual}`, }); } typeAssertions.delete(line); @@ -317,7 +340,7 @@ function normalizedTypeToString(type: string): string { .map(([item]) => item); return ts.factory.updateUnionTypeNode(node, ts.factory.createNodeArray(types)); } - if (ts.isTypeLiteralNode(node)) { + if (ts.isTypeLiteralNode(node) && node.members.every(ts.isPropertySignature)) { const members = [...node.members].sort((a, b) => print(a).localeCompare(print(b))); return ts.factory.updateTypeLiteralNode(node, ts.factory.createNodeArray(members)); } diff --git a/packages/dtslint/test/fixtures/typescript7/fail/test.ts b/packages/dtslint/test/fixtures/typescript7/fail/test.ts index 8d87ae8821..01ec3cd3e1 100644 --- a/packages/dtslint/test/fixtures/typescript7/fail/test.ts +++ b/packages/dtslint/test/fixtures/typescript7/fail/test.ts @@ -4,3 +4,17 @@ import { literal } from "./index"; literal(); const bad: string = 1; + +declare const overloaded: { + (value: string): string; + (value: number): number; +}; +// $ExpectType { (value: number): number; (value: string): string; } +overloaded; + +declare const overloadedMethod: { + method(value: string): string; + method(value: number): number; +}; +// $ExpectType { method(value: number): number; method(value: string): string; } +overloadedMethod; diff --git a/packages/dtslint/test/fixtures/typescript7/fail/tsconfig.alternate.json b/packages/dtslint/test/fixtures/typescript7/fail/tsconfig.alternate.json new file mode 100644 index 0000000000..7f9cd80f95 --- /dev/null +++ b/packages/dtslint/test/fixtures/typescript7/fail/tsconfig.alternate.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "module": "node16", + "lib": ["es2024"], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": ["index.d.ts", "test.ts"] +} diff --git a/packages/dtslint/test/fixtures/typescript7/pass/test.ts b/packages/dtslint/test/fixtures/typescript7/pass/test.ts index 8bc4273501..d2c538f68f 100644 --- a/packages/dtslint/test/fixtures/typescript7/pass/test.ts +++ b/packages/dtslint/test/fixtures/typescript7/pass/test.ts @@ -10,6 +10,13 @@ external; declare const object: { b: string; a: number }; object; // $ExpectType { a: number; b: string; } +declare const overloaded: { + (value: string): string; + (value: number): number; +}; +// $ExpectType { (value: string): string; (value: number): number; } || { (value: number): number; (value: string): string; } +overloaded; + // @ts-expect-error >=7.0 const expectedError: string = 1; diff --git a/packages/dtslint/test/index.test.ts b/packages/dtslint/test/index.test.ts index ac93ab50d0..115eb873f0 100644 --- a/packages/dtslint/test/index.test.ts +++ b/packages/dtslint/test/index.test.ts @@ -182,6 +182,12 @@ describe("dtslint", () => { expect(result).toContain("compile error TS2322"); expect(result).toContain("expected type to be:\n 2\ngot:\n 1"); + expect(result).toContain( + "expected type to be:\n { (value: number): number; (value: string): string; }\ngot:", + ); + expect(result).toContain( + "expected type to be:\n { method(value: number): number; method(value: string): string; }\ngot:", + ); }); it(`passes matching TypeScript ${version} ExpectType assertions without invoking ESLint`, async () => { @@ -199,6 +205,23 @@ describe("dtslint", () => { }); } + it("reports and deduplicates TypeScript 7 failures across tsconfigs", async () => { + const result = await runBuilt("lintTypeScript7", "lintTypeScript7", [ + path.join(fixtures, "fail"), + ["tsconfig.json", "tsconfig.alternate.json"], + "7.0", + true, + null, + ]); + + expect(result).toContain("TypeScript@7.0 tsconfig.alternate.json, 7.0 tsconfig.json compile error TS2322"); + expect(result).toContain( + "TypeScript@7.0 tsconfig.alternate.json, 7.0 tsconfig.json expected type to be:\n 2\ngot:\n 1", + ); + expect(result?.match(/compile error TS2322/g)).toHaveLength(1); + expect(result?.match(/expected type to be:\n 2\ngot:\n 1/g)).toHaveLength(1); + }); + it("runs ordinary ESLint rules during TypeScript 7-only testing", async () => { const result = await runBuilt("lint", "lint", [ path.join(__dirname, "typescript7-eslint"), From da0fd666099e5b26cc12de9ab10b50fd220a927d Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 18 Aug 2026 11:16:37 -0700 Subject: [PATCH 14/24] Share expect-error range evaluation --- .changeset/calm-ranges-agree.md | 8 ++ packages/dtslint/src/lint.ts | 6 +- packages/dtslint/src/lintTypeScript7.ts | 89 +++++++++++-------- .../test/fixtures/typescript7/fail/test.ts | 6 ++ .../fixtures/typescript7/partial/excluded.ts | 1 + .../fixtures/typescript7/partial/included.ts | 3 + .../fixtures/typescript7/partial/index.d.ts | 1 + .../partial/tsconfig.alternate.json | 9 ++ .../typescript7/partial/tsconfig.json | 9 ++ packages/dtslint/test/index.test.ts | 49 +++++++++- packages/eslint-plugin/package.json | 4 +- packages/eslint-plugin/src/rules/expect.ts | 23 +---- .../expect-error-range-tests.ts | 1 - packages/utils/package.json | 2 + packages/utils/src/expect-error-range.ts | 20 +++++ packages/utils/src/index.ts | 1 + .../utils/test/expect-error-range.test.ts | 12 +++ pnpm-lock.yaml | 12 +-- 18 files changed, 182 insertions(+), 74 deletions(-) create mode 100644 .changeset/calm-ranges-agree.md create mode 100644 packages/dtslint/test/fixtures/typescript7/partial/excluded.ts create mode 100644 packages/dtslint/test/fixtures/typescript7/partial/included.ts create mode 100644 packages/dtslint/test/fixtures/typescript7/partial/index.d.ts create mode 100644 packages/dtslint/test/fixtures/typescript7/partial/tsconfig.alternate.json create mode 100644 packages/dtslint/test/fixtures/typescript7/partial/tsconfig.json create mode 100644 packages/utils/src/expect-error-range.ts create mode 100644 packages/utils/test/expect-error-range.test.ts diff --git a/.changeset/calm-ranges-agree.md b/.changeset/calm-ranges-agree.md new file mode 100644 index 0000000000..3db58da437 --- /dev/null +++ b/.changeset/calm-ranges-agree.md @@ -0,0 +1,8 @@ +--- +"@definitelytyped/dtslint": patch +"@definitelytyped/eslint-plugin": patch +"@definitelytyped/utils": patch +--- + +Share versioned `@ts-expect-error` range handling, correctly evaluate shorthand and local TypeScript versions, aggregate +TypeScript 7 failures across versions, and report files excluded from every configured project. diff --git a/packages/dtslint/src/lint.ts b/packages/dtslint/src/lint.ts index b670daf945..fe464b4b7b 100644 --- a/packages/dtslint/src/lint.ts +++ b/packages/dtslint/src/lint.ts @@ -7,7 +7,7 @@ import { ESLint } from "eslint"; import * as TsType from "typescript"; import { createProgram } from "./createProgram"; -import { lintTypeScript7 } from "./lintTypeScript7"; +import { lintTypeScript7Versions } from "./lintTypeScript7"; import { typeScriptPath } from "./typescript-installer"; export async function lint( @@ -58,8 +58,8 @@ export async function lint( } } - for (const version of typeScript7Versions) { - const output = await lintTypeScript7(dirPath, tsconfigs, version, isLatest, tsLocal); + if (typeScript7Versions.length) { + const output = await lintTypeScript7Versions(dirPath, tsconfigs, typeScript7Versions, isLatest, tsLocal, files); if (output) { outputs.push(output); } diff --git a/packages/dtslint/src/lintTypeScript7.ts b/packages/dtslint/src/lintTypeScript7.ts index 3df93f33b9..fff3b25464 100644 --- a/packages/dtslint/src/lintTypeScript7.ts +++ b/packages/dtslint/src/lintTypeScript7.ts @@ -1,8 +1,9 @@ import { TypeScriptVersion } from "@definitelytyped/typescript-versions"; import * as typeScriptPackages from "@definitelytyped/typescript-packages"; +import { isVersionedExpectErrorOutsideRange } from "@definitelytyped/utils"; +import { execFileSync } from "child_process"; import fs from "fs"; import path from "path"; -import semver from "semver"; import * as ts from "typescript"; import type { TsVersion } from "./lint"; @@ -92,44 +93,57 @@ interface Failure { } const expectTypeToken = "$ExpectType"; -const expectErrorSingleLine = /^\/\/\/?\s*@ts-expect-error\s+(.*)/; -const expectErrorMultiLine = /^(?:\/|\*)*\s*@ts-expect-error\s+(.*)/; -export async function lintTypeScript7( +export async function lintTypeScript7Versions( dirPath: string, tsconfigs: readonly string[], - version: TsVersion, + versions: readonly TsVersion[], isLatest: boolean, tsLocal: string | undefined, + sourceFiles?: readonly string[], ): Promise { - const clientVersion = version === "local" ? TypeScriptVersion.latest : version; - const apiModule = require(typeScriptPackages.resolve(clientVersion, "unstable/sync")) as TypeScript7Api; - const astModule = require(typeScriptPackages.resolve(clientVersion, "unstable/ast")) as TypeScript7Ast; - const tsserverPath = version === "local" ? findTypeScript7Server(tsLocal!) : undefined; - const api = new apiModule.API({ cwd: dirPath, tsserverPath }); - const configPaths = tsconfigs.map((config) => path.resolve(dirPath, config)); const reportTsconfigName = tsconfigs.length !== 1 || tsconfigs[0] !== "tsconfig.json"; const failures = new Map }>(); - try { - const snapshot = api.updateSnapshot({ openProjects: configPaths }); + for (const version of versions) { + const clientVersion = version === "local" ? TypeScriptVersion.latest : version; + const apiModule = require(typeScriptPackages.resolve(clientVersion, "unstable/sync")) as TypeScript7Api; + const astModule = require(typeScriptPackages.resolve(clientVersion, "unstable/ast")) as TypeScript7Ast; + const tsserverPath = version === "local" ? findTypeScript7Server(tsLocal!) : undefined; + const rangeVersion = tsserverPath ? getTypeScript7ServerVersion(tsserverPath) : version; + const api = new apiModule.API({ cwd: dirPath, tsserverPath }); + const configPaths = tsconfigs.map((config) => path.resolve(dirPath, config)); + const matchedFiles = new Set(); + try { - for (let i = 0; i < configPaths.length; i++) { - const configPath = configPaths[i]; - const run = `${version} ${tsconfigs[i]}`; - const project = snapshot.getProject(configPath); - if (!project) { - addFailures([{ message: `could not open ${configPath}.` }], run); - continue; + const snapshot = api.updateSnapshot({ openProjects: configPaths }); + try { + for (let i = 0; i < configPaths.length; i++) { + const configPath = configPaths[i]; + const run = `${version} ${tsconfigs[i]}`; + const project = snapshot.getProject(configPath); + if (!project) { + addFailures([{ message: `could not open ${configPath}.` }], run); + continue; + } + for (const fileName of project.program.getSourceFileNames()) { + matchedFiles.add(normalizePath(fileName)); + } + addFailures(getDiagnosticFailures(project, dirPath, rangeVersion, isLatest), run); + addFailures(getExpectTypeFailures(project, apiModule, astModule, dirPath, isLatest), run); } - addFailures(getDiagnosticFailures(project, dirPath, version, isLatest), run); - addFailures(getExpectTypeFailures(project, apiModule, astModule, dirPath, isLatest), run); + } finally { + snapshot.dispose(); } } finally { - snapshot.dispose(); + api.close(); + } + + for (const fileName of sourceFiles ?? []) { + if (!matchedFiles.has(normalizePath(fileName))) { + addFailures([{ fileName, start: 0, message: "could not find a tsconfig that includes this file." }], version); + } } - } finally { - api.close(); } return formatFailures( @@ -171,7 +185,16 @@ function findTypeScript7Server(tsLocal: string): string { throw new Error(`Could not find a TypeScript 7 tsserver/tsgo executable in ${tsLocal}.`); } -function getDiagnosticFailures(project: Project, dirPath: string, version: TsVersion, isLatest: boolean): Failure[] { +function getTypeScript7ServerVersion(tsserverPath: string): string { + const output = execFileSync(tsserverPath, ["--version"], { encoding: "utf8" }); + const match = /^Version\s+(\d+\.\d+)/m.exec(output); + if (!match) { + throw new Error(`Could not determine the TypeScript version from ${tsserverPath}.`); + } + return match[1]; +} + +function getDiagnosticFailures(project: Project, dirPath: string, version: string, isLatest: boolean): Failure[] { const diagnostics = [ ...project.program.getConfigFileParsingDiagnostics(), ...project.program.getProgramDiagnostics(), @@ -205,7 +228,7 @@ function getDiagnosticFailures(project: Project, dirPath: string, version: TsVer const failures = new Map(); for (const diagnostic of diagnostics) { - if (diagnostic.code === 2578 && isVersionedExpectErrorOutsideRange(diagnostic, project, version)) { + if (diagnostic.code === 2578 && isDiagnosticOutsideExpectErrorRange(diagnostic, project, version)) { continue; } const failure = { @@ -219,7 +242,7 @@ function getDiagnosticFailures(project: Project, dirPath: string, version: TsVer return [...failures.values()]; } -function isVersionedExpectErrorOutsideRange(diagnostic: Diagnostic, project: Project, version: TsVersion): boolean { +function isDiagnosticOutsideExpectErrorRange(diagnostic: Diagnostic, project: Project, version: string): boolean { if (!diagnostic.fileName || diagnostic.pos < 0 || diagnostic.end < diagnostic.pos) { return false; } @@ -228,15 +251,7 @@ function isVersionedExpectErrorOutsideRange(diagnostic: Diagnostic, project: Pro return false; } const text = sourceFile.text.slice(diagnostic.pos, diagnostic.end); - const match = text.match(expectErrorSingleLine) || text.match(expectErrorMultiLine); - if (!match) { - return false; - } - try { - return !semver.satisfies(version, match[1].trim(), { loose: true }); - } catch { - return false; - } + return isVersionedExpectErrorOutsideRange(text, version); } function flattenDiagnostic(diagnostic: Diagnostic): string { diff --git a/packages/dtslint/test/fixtures/typescript7/fail/test.ts b/packages/dtslint/test/fixtures/typescript7/fail/test.ts index 01ec3cd3e1..916e74ee39 100644 --- a/packages/dtslint/test/fixtures/typescript7/fail/test.ts +++ b/packages/dtslint/test/fixtures/typescript7/fail/test.ts @@ -5,6 +5,12 @@ literal(); const bad: string = 1; +// @ts-expect-error >=7.0 +const unusedInTypeScript7: string = ""; + +// @ts-expect-error >=7.1 +const unusedInTypeScript71: string = ""; + declare const overloaded: { (value: string): string; (value: number): number; diff --git a/packages/dtslint/test/fixtures/typescript7/partial/excluded.ts b/packages/dtslint/test/fixtures/typescript7/partial/excluded.ts new file mode 100644 index 0000000000..eb6593d5d9 --- /dev/null +++ b/packages/dtslint/test/fixtures/typescript7/partial/excluded.ts @@ -0,0 +1 @@ +const excluded: string = 1; diff --git a/packages/dtslint/test/fixtures/typescript7/partial/included.ts b/packages/dtslint/test/fixtures/typescript7/partial/included.ts new file mode 100644 index 0000000000..7d3fb8a2a0 --- /dev/null +++ b/packages/dtslint/test/fixtures/typescript7/partial/included.ts @@ -0,0 +1,3 @@ +import { value } from "./index"; + +value; diff --git a/packages/dtslint/test/fixtures/typescript7/partial/index.d.ts b/packages/dtslint/test/fixtures/typescript7/partial/index.d.ts new file mode 100644 index 0000000000..a5987fbd29 --- /dev/null +++ b/packages/dtslint/test/fixtures/typescript7/partial/index.d.ts @@ -0,0 +1 @@ +export declare const value: string; diff --git a/packages/dtslint/test/fixtures/typescript7/partial/tsconfig.alternate.json b/packages/dtslint/test/fixtures/typescript7/partial/tsconfig.alternate.json new file mode 100644 index 0000000000..f479797400 --- /dev/null +++ b/packages/dtslint/test/fixtures/typescript7/partial/tsconfig.alternate.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "module": "commonjs", + "noEmit": true, + "strict": true, + "target": "esnext" + }, + "files": ["index.d.ts", "included.ts"] +} diff --git a/packages/dtslint/test/fixtures/typescript7/partial/tsconfig.json b/packages/dtslint/test/fixtures/typescript7/partial/tsconfig.json new file mode 100644 index 0000000000..32f00dd8e7 --- /dev/null +++ b/packages/dtslint/test/fixtures/typescript7/partial/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "module": "commonjs", + "noEmit": true, + "strict": true, + "target": "esnext" + }, + "files": ["index.d.ts", "included.ts", "excluded.ts"] +} diff --git a/packages/dtslint/test/index.test.ts b/packages/dtslint/test/index.test.ts index 115eb873f0..a0a206fdd5 100644 --- a/packages/dtslint/test/index.test.ts +++ b/packages/dtslint/test/index.test.ts @@ -172,15 +172,17 @@ describe("dtslint", () => { for (const version of ["7.0", "7.1"] as const) { it(`checks compiler diagnostics and ExpectType through the TypeScript ${version} IPC API`, async () => { - const result = await runBuilt("lintTypeScript7", "lintTypeScript7", [ + const result = await runBuilt("lintTypeScript7", "lintTypeScript7Versions", [ path.join(fixtures, "fail"), ["tsconfig.json"], - version, + [version], true, null, ]); expect(result).toContain("compile error TS2322"); + expect(result).toContain("compile error TS2578"); + expect(result?.match(/compile error TS2578/g)).toHaveLength(version === "7.0" ? 1 : 2); expect(result).toContain("expected type to be:\n 2\ngot:\n 1"); expect(result).toContain( "expected type to be:\n { (value: number): number; (value: string): string; }\ngot:", @@ -206,10 +208,10 @@ describe("dtslint", () => { } it("reports and deduplicates TypeScript 7 failures across tsconfigs", async () => { - const result = await runBuilt("lintTypeScript7", "lintTypeScript7", [ + const result = await runBuilt("lintTypeScript7", "lintTypeScript7Versions", [ path.join(fixtures, "fail"), ["tsconfig.json", "tsconfig.alternate.json"], - "7.0", + ["7.0"], true, null, ]); @@ -222,6 +224,36 @@ describe("dtslint", () => { expect(result?.match(/expected type to be:\n 2\ngot:\n 1/g)).toHaveLength(1); }); + it("reports and deduplicates TypeScript 7 failures across versions", async () => { + const result = await runBuilt("lint", "lint", [ + path.join(fixtures, "fail"), + ["tsconfig.json"], + "7.0", + "7.1", + true, + true, + null, + ]); + + expect(result).toContain("TypeScript@7.0, 7.1 compile error TS2322"); + expect(result?.match(/compile error TS2322/g)).toHaveLength(1); + }); + + it("reports files excluded from every alternate tsconfig", async () => { + const result = await runBuilt("lint", "lint", [ + path.join(fixtures, "partial"), + ["tsconfig.alternate.json"], + "7.0", + "7.0", + true, + true, + null, + ]); + + expect(result).toContain("excluded.ts:1:1"); + expect(result).toContain("TypeScript@7.0 could not find a tsconfig that includes this file."); + }); + it("runs ordinary ESLint rules during TypeScript 7-only testing", async () => { const result = await runBuilt("lint", "lint", [ path.join(__dirname, "typescript7-eslint"), @@ -259,6 +291,15 @@ describe("dtslint", () => { executable, ]), ).resolves.toBeUndefined(); + + const result = await runBuilt("lintTypeScript7", "lintTypeScript7Versions", [ + path.join(fixtures, "fail"), + ["tsconfig.json"], + ["local"], + true, + executable, + ]); + expect(result).toContain("TypeScript@local compile error TS2578"); }); }); }); diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 1e7cbf8cb9..738aeb531f 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -24,8 +24,7 @@ "dependencies": { "@definitelytyped/utils": "workspace:*", "@typescript-eslint/types": "^8.67.0", - "@typescript-eslint/utils": "^8.67.0", - "semver": "^7.7.4" + "@typescript-eslint/utils": "^8.67.0" }, "peerDependencies": { "@typescript-eslint/eslint-plugin": "^8.67.0", @@ -37,7 +36,6 @@ "devDependencies": { "@definitelytyped/eslint-plugin": "link:", "@types/eslint": "^8.56.12", - "@types/semver": "^7.7.1", "glob": "^13.0.6", "jest-file-snapshot": "^0.7.0", "typescript": "^6.0.3", diff --git a/packages/eslint-plugin/src/rules/expect.ts b/packages/eslint-plugin/src/rules/expect.ts index bb323beb13..45dd824e90 100644 --- a/packages/eslint-plugin/src/rules/expect.ts +++ b/packages/eslint-plugin/src/rules/expect.ts @@ -1,9 +1,9 @@ +import { isVersionedExpectErrorOutsideRange } from "@definitelytyped/utils"; import { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; import { ReportDescriptorMessageData } from "@typescript-eslint/utils/ts-eslint"; import fs from "fs"; import v8 from "node:v8"; import path from "path"; -import * as semver from "semver"; import type * as ts from "typescript"; import { createRule, findTypesPackage, findUp } from "../util"; @@ -250,10 +250,6 @@ const zeroSourceLocation: Readonly = { const expectTypeToken = "$ExpectType"; -// Based on TypeScript's scanner.ts -const expectErrorSingleLine = /^\/\/\/?\s*@ts-expect-error\s+(.*)/; -const expectErrorMultiLine = /^(?:\/|\*)*\s*@ts-expect-error\s+(.*)/; - function walk( getLocFromIndex: (index: number) => Readonly, report: Reporter, @@ -273,22 +269,9 @@ function walk( const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile); for (const diagnostic of diagnostics) { if (diagnostic.code === 2578) { - // This is a ts-expect-error; parse "// @ts-expect-error " and ignore when the current version is outside that range. const text = sourceFile.text.slice(diagnostic.start!, diagnostic.start! + diagnostic.length!); - const match = text.match(expectErrorSingleLine) || text.match(expectErrorMultiLine); - if (match) { - let range: semver.Range | undefined; - try { - range = new semver.Range(match[1].trim()); - } catch { - // Ignore any parsing errors. - } - - if (range) { - if (!semver.satisfies(versionName, range, { loose: true })) { - continue; - } - } + if (isVersionedExpectErrorOutsideRange(text, ts.versionMajorMinor)) { + continue; } } diff --git a/packages/eslint-plugin/test/fixtures/types/expect-error-range/expect-error-range-tests.ts b/packages/eslint-plugin/test/fixtures/types/expect-error-range/expect-error-range-tests.ts index 28f07a2d2c..086cd2fc91 100644 --- a/packages/eslint-plugin/test/fixtures/types/expect-error-range/expect-error-range-tests.ts +++ b/packages/eslint-plugin/test/fixtures/types/expect-error-range/expect-error-range-tests.ts @@ -40,4 +40,3 @@ const elem = ["value", undefined].filter(x => x != null)[0]; // @ts-expect-error >=5.5 const test2: undefined extends typeof elem ? typeof elem : never = elem; } - diff --git a/packages/utils/package.json b/packages/utils/package.json index a193802d76..e94e2e81ad 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -25,6 +25,7 @@ "libnpmpublish": "^11.1.3", "minimatch": "^10.2.4", "npm-registry-fetch": "^19.1.1", + "semver": "^7.7.4", "tar": "^7.5.9", "tar-stream": "^3.1.8", "which": "^6.0.1" @@ -33,6 +34,7 @@ "@types/charm": "^1.0.6", "@types/libnpmpublish": "^9.0.1", "@types/npm-registry-fetch": "^8.0.9", + "@types/semver": "^7.7.1", "@types/tar-stream": "^3.1.4", "@types/which": "^3.0.4" }, diff --git a/packages/utils/src/expect-error-range.ts b/packages/utils/src/expect-error-range.ts new file mode 100644 index 0000000000..da8bfb7271 --- /dev/null +++ b/packages/utils/src/expect-error-range.ts @@ -0,0 +1,20 @@ +import * as semver from "semver"; + +// Based on TypeScript's scanner.ts. +const expectErrorSingleLine = /^\/\/\/?\s*@ts-expect-error\s+(.*)/; +const expectErrorMultiLine = /^(?:\/|\*)*\s*@ts-expect-error\s+(.*)/; + +export function isVersionedExpectErrorOutsideRange(text: string, version: string): boolean { + const match = text.match(expectErrorSingleLine) || text.match(expectErrorMultiLine); + if (!match) { + return false; + } + + try { + const normalizedVersion = semver.coerce(version); + const range = semver.validRange(match[1].trim()); + return normalizedVersion && range ? !semver.satisfies(normalizedVersion.version, range) : false; + } catch { + return false; + } +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index e81d162131..13be91a323 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,6 +1,7 @@ export * from "./assertions"; export * from "./async"; export * from "./collections"; +export * from "./expect-error-range"; export * from "./fs"; export * from "./io"; export * from "./logging"; diff --git a/packages/utils/test/expect-error-range.test.ts b/packages/utils/test/expect-error-range.test.ts new file mode 100644 index 0000000000..330f2385e0 --- /dev/null +++ b/packages/utils/test/expect-error-range.test.ts @@ -0,0 +1,12 @@ +import { isVersionedExpectErrorOutsideRange } from "../src"; + +describe(isVersionedExpectErrorOutsideRange.name, () => { + it("normalizes TypeScript major-minor versions before evaluating ranges", () => { + expect(isVersionedExpectErrorOutsideRange("// @ts-expect-error >=7.0", "7.1")).toBe(false); + expect(isVersionedExpectErrorOutsideRange("// @ts-expect-error >=7.1", "7.0")).toBe(true); + }); + + it("does not suppress diagnostics for invalid ranges", () => { + expect(isVersionedExpectErrorOutsideRange("// @ts-expect-error not a range", "7.1")).toBe(false); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b77ad6e298..d32c4ea208 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -237,9 +237,6 @@ importers: eslint-plugin-jsdoc: specifier: ^44.0.0 version: 44.2.7(eslint@8.57.1) - semver: - specifier: ^7.7.4 - version: 7.7.4 devDependencies: '@definitelytyped/eslint-plugin': specifier: 'link:' @@ -247,9 +244,6 @@ importers: '@types/eslint': specifier: ^8.56.12 version: 8.56.12 - '@types/semver': - specifier: ^7.7.1 - version: 7.7.1 glob: specifier: ^13.0.6 version: 13.0.6 @@ -484,6 +478,9 @@ importers: npm-registry-fetch: specifier: ^19.1.1 version: 19.1.1 + semver: + specifier: ^7.7.4 + version: 7.7.4 tar: specifier: ^7.5.9 version: 7.5.9 @@ -503,6 +500,9 @@ importers: '@types/npm-registry-fetch': specifier: ^8.0.9 version: 8.0.9 + '@types/semver': + specifier: ^7.7.1 + version: 7.7.1 '@types/tar-stream': specifier: ^3.1.4 version: 3.1.4 From b314c3cf08097e20d8e03b2d155e89f1168172ed Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 18 Aug 2026 11:29:06 -0700 Subject: [PATCH 15/24] Allow TS7 ESLint test more time on Windows --- packages/dtslint/test/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dtslint/test/index.test.ts b/packages/dtslint/test/index.test.ts index a0a206fdd5..0b83eb4235 100644 --- a/packages/dtslint/test/index.test.ts +++ b/packages/dtslint/test/index.test.ts @@ -267,7 +267,7 @@ describe("dtslint", () => { expect(result).toContain("no-var"); expect(result).toContain("@typescript-eslint/naming-convention"); - }); + }, 30_000); it("can use a local TypeScript 7 server executable", async () => { const apiPath = typeScriptPackages.resolve("7.1", "unstable/sync"); From af0b1d8ccbbff3c52e19e060d4836af7ac2e7370 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:33:25 -0700 Subject: [PATCH 16/24] Discover local TypeScript executables --- packages/dtslint/src/lint.ts | 17 +--- packages/dtslint/src/lintTypeScript7.ts | 32 ++------ packages/dtslint/src/typescript-installer.ts | 83 +++++++++++++++++++- packages/dtslint/test/index.test.ts | 25 +++--- 4 files changed, 106 insertions(+), 51 deletions(-) diff --git a/packages/dtslint/src/lint.ts b/packages/dtslint/src/lint.ts index fe464b4b7b..e0eebaa69f 100644 --- a/packages/dtslint/src/lint.ts +++ b/packages/dtslint/src/lint.ts @@ -1,6 +1,5 @@ import { TypeScriptVersion } from "@definitelytyped/typescript-versions"; import { withoutStart } from "@definitelytyped/utils"; -import fs from "fs"; import assert = require("assert"); import { join as joinPaths, normalize, resolve } from "path"; import { ESLint } from "eslint"; @@ -8,7 +7,7 @@ import * as TsType from "typescript"; import { createProgram } from "./createProgram"; import { lintTypeScript7Versions } from "./lintTypeScript7"; -import { typeScriptPath } from "./typescript-installer"; +import { resolveLocalTypeScript, typeScriptPath } from "./typescript-installer"; export async function lint( dirPath: string, @@ -234,17 +233,5 @@ function isTypeScript7(version: TsVersion, tsLocal: string | undefined): boolean } assert(tsLocal); - if (fs.existsSync(tsLocal) && fs.statSync(tsLocal).isFile()) { - return !tsLocal.endsWith("typescript.js"); - } - if (["tsserver", "tsserver.exe", "tsgo", "tsgo.exe"].some((name) => fs.existsSync(joinPaths(tsLocal, name)))) { - return true; - } - if (fs.existsSync(joinPaths(tsLocal, "typescript.js"))) { - return false; - } - throw new Error( - `Could not detect the TypeScript build at ${tsLocal}. Expected typescript.js for TypeScript 6 or ` + - "a tsserver/tsgo executable for TypeScript 7.", - ); + return resolveLocalTypeScript(tsLocal).kind === "native"; } diff --git a/packages/dtslint/src/lintTypeScript7.ts b/packages/dtslint/src/lintTypeScript7.ts index fff3b25464..50192a1cd5 100644 --- a/packages/dtslint/src/lintTypeScript7.ts +++ b/packages/dtslint/src/lintTypeScript7.ts @@ -1,11 +1,11 @@ import { TypeScriptVersion } from "@definitelytyped/typescript-versions"; import * as typeScriptPackages from "@definitelytyped/typescript-packages"; import { isVersionedExpectErrorOutsideRange } from "@definitelytyped/utils"; -import { execFileSync } from "child_process"; import fs from "fs"; import path from "path"; import * as ts from "typescript"; import type { TsVersion } from "./lint"; +import { resolveLocalTypeScript } from "./typescript-installer"; interface Node { readonly kind: number; @@ -109,8 +109,12 @@ export async function lintTypeScript7Versions( const clientVersion = version === "local" ? TypeScriptVersion.latest : version; const apiModule = require(typeScriptPackages.resolve(clientVersion, "unstable/sync")) as TypeScript7Api; const astModule = require(typeScriptPackages.resolve(clientVersion, "unstable/ast")) as TypeScript7Ast; - const tsserverPath = version === "local" ? findTypeScript7Server(tsLocal!) : undefined; - const rangeVersion = tsserverPath ? getTypeScript7ServerVersion(tsserverPath) : version; + const localTypeScript = version === "local" ? resolveLocalTypeScript(tsLocal!) : undefined; + if (localTypeScript?.kind === "legacy") { + throw new Error(`Expected a TypeScript 7 native executable at ${tsLocal}.`); + } + const tsserverPath = localTypeScript?.executablePath; + const rangeVersion = localTypeScript?.version ?? version; const api = new apiModule.API({ cwd: dirPath, tsserverPath }); const configPaths = tsconfigs.map((config) => path.resolve(dirPath, config)); const matchedFiles = new Set(); @@ -172,28 +176,6 @@ function formatRuns(runs: ReadonlySet, reportTsconfigName: boolean): str .join(", "); } -function findTypeScript7Server(tsLocal: string): string { - if (fs.statSync(tsLocal).isFile()) { - return tsLocal; - } - for (const name of ["tsserver", "tsserver.exe", "tsgo", "tsgo.exe"]) { - const candidate = path.join(tsLocal, name); - if (fs.existsSync(candidate)) { - return candidate; - } - } - throw new Error(`Could not find a TypeScript 7 tsserver/tsgo executable in ${tsLocal}.`); -} - -function getTypeScript7ServerVersion(tsserverPath: string): string { - const output = execFileSync(tsserverPath, ["--version"], { encoding: "utf8" }); - const match = /^Version\s+(\d+\.\d+)/m.exec(output); - if (!match) { - throw new Error(`Could not determine the TypeScript version from ${tsserverPath}.`); - } - return match[1]; -} - function getDiagnosticFailures(project: Project, dirPath: string, version: string, isLatest: boolean): Failure[] { const diagnostics = [ ...project.program.getConfigFileParsingDiagnostics(), diff --git a/packages/dtslint/src/typescript-installer.ts b/packages/dtslint/src/typescript-installer.ts index bdcfe4c78a..5612226fac 100644 --- a/packages/dtslint/src/typescript-installer.ts +++ b/packages/dtslint/src/typescript-installer.ts @@ -1,11 +1,92 @@ import { TypeScriptVersion } from "@definitelytyped/typescript-versions"; import * as typeScriptPackages from "@definitelytyped/typescript-packages"; +import { execFileSync } from "child_process"; +import fs from "fs"; +import path from "path"; export type TsVersion = TypeScriptVersion | "local"; +export type LocalTypeScript = + | { readonly kind: "legacy"; readonly compilerPath: string } + | { readonly kind: "native"; readonly executablePath: string; readonly version: string }; + export function typeScriptPath(version: TsVersion, tsLocal: string | undefined): string { if (version === "local") { - return tsLocal! + "/typescript.js"; + const localTypeScript = resolveLocalTypeScript(tsLocal!); + if (localTypeScript.kind !== "legacy") { + throw new Error(`Expected a TypeScript compiler library at ${tsLocal}.`); + } + return localTypeScript.compilerPath; } return typeScriptPackages.resolve(version); } + +export function resolveLocalTypeScript(tsLocal: string): LocalTypeScript { + if (!fs.existsSync(tsLocal)) { + throw new Error(`Could not find the TypeScript build at ${tsLocal}.`); + } + + const stat = fs.statSync(tsLocal); + if (stat.isFile()) { + if (/\.[cm]?js$/i.test(tsLocal)) { + return { kind: "legacy", compilerPath: tsLocal }; + } + const version = tryGetNativeTypeScriptVersion(tsLocal); + if (version) { + return { kind: "native", executablePath: tsLocal, version }; + } + throw new Error(`The file at ${tsLocal} is not a TypeScript compiler library or native executable.`); + } + if (!stat.isDirectory()) { + throw new Error(`The TypeScript build path ${tsLocal} is not a file or directory.`); + } + + const compilerPath = path.join(tsLocal, "typescript.js"); + if (fs.existsSync(compilerPath) && fs.statSync(compilerPath).isFile()) { + return { kind: "legacy", compilerPath }; + } + + const nativeExecutables = fs + .readdirSync(tsLocal) + .map((name) => path.join(tsLocal, name)) + .filter(isExecutableFile) + .flatMap((executablePath) => { + const version = tryGetNativeTypeScriptVersion(executablePath); + return version ? [{ kind: "native" as const, executablePath, version }] : []; + }); + + if (nativeExecutables.length === 1) { + return nativeExecutables[0]; + } + if (nativeExecutables.length > 1) { + throw new Error(`Found multiple TypeScript native executables in ${tsLocal}. Pass the intended executable path.`); + } + throw new Error(`Could not find a TypeScript compiler library or native executable in ${tsLocal}.`); +} + +function isExecutableFile(filePath: string): boolean { + let stat: fs.Stats; + try { + stat = fs.statSync(filePath); + } catch { + return false; + } + if (!stat.isFile()) { + return false; + } + return process.platform === "win32" ? path.extname(filePath).toLowerCase() === ".exe" : (stat.mode & 0o111) !== 0; +} + +function tryGetNativeTypeScriptVersion(executablePath: string): string | undefined { + try { + const output = execFileSync(executablePath, ["--version"], { + encoding: "utf8", + timeout: 5_000, + windowsHide: true, + }); + const version = /^Version\s+(\d+\.\d+(?:\.\d+)?(?:-[^\s]+)?)/m.exec(output)?.[1]; + return version && Number.parseInt(version, 10) >= 7 ? version : undefined; + } catch { + return undefined; + } +} diff --git a/packages/dtslint/test/index.test.ts b/packages/dtslint/test/index.test.ts index 0b83eb4235..22320e4018 100644 --- a/packages/dtslint/test/index.test.ts +++ b/packages/dtslint/test/index.test.ts @@ -3,7 +3,9 @@ import { CompilerOptionsRaw, checkTsconfig } from "../src/checks"; import { assertPackageIsNotDeprecated } from "../src/index"; import * as typeScriptPackages from "@definitelytyped/typescript-packages"; import { execFile } from "child_process"; +import fs from "fs"; import path from "path"; +import { pathToFileURL } from "url"; import { promisify } from "util"; const execFileAsync = promisify(execFile); @@ -269,16 +271,19 @@ describe("dtslint", () => { expect(result).toContain("@typescript-eslint/naming-convention"); }, 30_000); - it("can use a local TypeScript 7 server executable", async () => { + it("can discover a local TypeScript 7 executable from its directory", async () => { const apiPath = typeScriptPackages.resolve("7.1", "unstable/sync"); const packageRoot = path.resolve(apiPath, "../../../../"); - const executable = path.join( - path.dirname(packageRoot), - "@typescript", - `typescript-${process.platform}-${process.arch}`, - "lib", - process.platform === "win32" ? "tsc.exe" : "tsc", - ); + const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8")) as { + imports: { "#getExePath": string }; + }; + const resolverUrl = pathToFileURL(path.resolve(packageRoot, packageJson.imports["#getExePath"])).href; + const { stdout: executable } = await execFileAsync(process.execPath, [ + "--input-type=module", + "--eval", + `import getExePath from ${JSON.stringify(resolverUrl)}; process.stdout.write(getExePath());`, + ]); + const executableDirectory = path.dirname(executable); await expect( runBuilt("lint", "lint", [ @@ -288,7 +293,7 @@ describe("dtslint", () => { "local", true, true, - executable, + executableDirectory, ]), ).resolves.toBeUndefined(); @@ -297,7 +302,7 @@ describe("dtslint", () => { ["tsconfig.json"], ["local"], true, - executable, + executableDirectory, ]); expect(result).toContain("TypeScript@local compile error TS2578"); }); From c4b10a3a338b23aca1bf066ac07d198ef1733c31 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:33:25 -0700 Subject: [PATCH 17/24] Parse expect-error block comment ranges --- packages/utils/src/expect-error-range.ts | 2 +- packages/utils/test/expect-error-range.test.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/utils/src/expect-error-range.ts b/packages/utils/src/expect-error-range.ts index da8bfb7271..9bfe885afb 100644 --- a/packages/utils/src/expect-error-range.ts +++ b/packages/utils/src/expect-error-range.ts @@ -2,7 +2,7 @@ import * as semver from "semver"; // Based on TypeScript's scanner.ts. const expectErrorSingleLine = /^\/\/\/?\s*@ts-expect-error\s+(.*)/; -const expectErrorMultiLine = /^(?:\/|\*)*\s*@ts-expect-error\s+(.*)/; +const expectErrorMultiLine = /^(?:\/|\*)*\s*@ts-expect-error\s+(.*?)\s*(?:\*\/)?$/; export function isVersionedExpectErrorOutsideRange(text: string, version: string): boolean { const match = text.match(expectErrorSingleLine) || text.match(expectErrorMultiLine); diff --git a/packages/utils/test/expect-error-range.test.ts b/packages/utils/test/expect-error-range.test.ts index 330f2385e0..f3432faac2 100644 --- a/packages/utils/test/expect-error-range.test.ts +++ b/packages/utils/test/expect-error-range.test.ts @@ -9,4 +9,9 @@ describe(isVersionedExpectErrorOutsideRange.name, () => { it("does not suppress diagnostics for invalid ranges", () => { expect(isVersionedExpectErrorOutsideRange("// @ts-expect-error not a range", "7.1")).toBe(false); }); + + it("excludes multiline comment delimiters from the range", () => { + expect(isVersionedExpectErrorOutsideRange("/* @ts-expect-error <7.0 */", "7.0")).toBe(true); + expect(isVersionedExpectErrorOutsideRange("/* @ts-expect-error <7.0 */", "6.0")).toBe(false); + }); }); From 0ea8a8e81577c4a2095f40fb30e884b985a556ed Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:37:44 -0700 Subject: [PATCH 18/24] Require an explicit local TypeScript 7 executable --- packages/dtslint/src/typescript-installer.ts | 34 +++----------------- packages/dtslint/test/index.test.ts | 7 ++-- 2 files changed, 7 insertions(+), 34 deletions(-) diff --git a/packages/dtslint/src/typescript-installer.ts b/packages/dtslint/src/typescript-installer.ts index 5612226fac..081dde02e8 100644 --- a/packages/dtslint/src/typescript-installer.ts +++ b/packages/dtslint/src/typescript-installer.ts @@ -45,36 +45,10 @@ export function resolveLocalTypeScript(tsLocal: string): LocalTypeScript { if (fs.existsSync(compilerPath) && fs.statSync(compilerPath).isFile()) { return { kind: "legacy", compilerPath }; } - - const nativeExecutables = fs - .readdirSync(tsLocal) - .map((name) => path.join(tsLocal, name)) - .filter(isExecutableFile) - .flatMap((executablePath) => { - const version = tryGetNativeTypeScriptVersion(executablePath); - return version ? [{ kind: "native" as const, executablePath, version }] : []; - }); - - if (nativeExecutables.length === 1) { - return nativeExecutables[0]; - } - if (nativeExecutables.length > 1) { - throw new Error(`Found multiple TypeScript native executables in ${tsLocal}. Pass the intended executable path.`); - } - throw new Error(`Could not find a TypeScript compiler library or native executable in ${tsLocal}.`); -} - -function isExecutableFile(filePath: string): boolean { - let stat: fs.Stats; - try { - stat = fs.statSync(filePath); - } catch { - return false; - } - if (!stat.isFile()) { - return false; - } - return process.platform === "win32" ? path.extname(filePath).toLowerCase() === ".exe" : (stat.mode & 0o111) !== 0; + throw new Error( + `The directory at ${tsLocal} does not contain typescript.js. ` + + "Pass the TypeScript 7 native executable path directly.", + ); } function tryGetNativeTypeScriptVersion(executablePath: string): string | undefined { diff --git a/packages/dtslint/test/index.test.ts b/packages/dtslint/test/index.test.ts index 22320e4018..333aad427a 100644 --- a/packages/dtslint/test/index.test.ts +++ b/packages/dtslint/test/index.test.ts @@ -271,7 +271,7 @@ describe("dtslint", () => { expect(result).toContain("@typescript-eslint/naming-convention"); }, 30_000); - it("can discover a local TypeScript 7 executable from its directory", async () => { + it("can use a local TypeScript 7 executable", async () => { const apiPath = typeScriptPackages.resolve("7.1", "unstable/sync"); const packageRoot = path.resolve(apiPath, "../../../../"); const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8")) as { @@ -283,7 +283,6 @@ describe("dtslint", () => { "--eval", `import getExePath from ${JSON.stringify(resolverUrl)}; process.stdout.write(getExePath());`, ]); - const executableDirectory = path.dirname(executable); await expect( runBuilt("lint", "lint", [ @@ -293,7 +292,7 @@ describe("dtslint", () => { "local", true, true, - executableDirectory, + executable, ]), ).resolves.toBeUndefined(); @@ -302,7 +301,7 @@ describe("dtslint", () => { ["tsconfig.json"], ["local"], true, - executableDirectory, + executable, ]); expect(result).toContain("TypeScript@local compile error TS2578"); }); From 7c1344f0cf28f8bdafc52721df50c311d208713d Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:41:17 -0700 Subject: [PATCH 19/24] Parse expect-error ranges without backtracking --- packages/utils/src/expect-error-range.ts | 32 +++++++++++++++---- .../utils/test/expect-error-range.test.ts | 1 + 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/packages/utils/src/expect-error-range.ts b/packages/utils/src/expect-error-range.ts index 9bfe885afb..4e8454614f 100644 --- a/packages/utils/src/expect-error-range.ts +++ b/packages/utils/src/expect-error-range.ts @@ -1,20 +1,40 @@ import * as semver from "semver"; -// Based on TypeScript's scanner.ts. -const expectErrorSingleLine = /^\/\/\/?\s*@ts-expect-error\s+(.*)/; -const expectErrorMultiLine = /^(?:\/|\*)*\s*@ts-expect-error\s+(.*?)\s*(?:\*\/)?$/; +const expectErrorToken = "@ts-expect-error"; export function isVersionedExpectErrorOutsideRange(text: string, version: string): boolean { - const match = text.match(expectErrorSingleLine) || text.match(expectErrorMultiLine); - if (!match) { + const rangeText = getExpectErrorRange(text); + if (!rangeText) { return false; } try { const normalizedVersion = semver.coerce(version); - const range = semver.validRange(match[1].trim()); + const range = semver.validRange(rangeText); return normalizedVersion && range ? !semver.satisfies(normalizedVersion.version, range) : false; } catch { return false; } } + +function getExpectErrorRange(text: string): string | undefined { + let comment = text.trim(); + let contentStart = 0; + while (comment[contentStart] === "/" || comment[contentStart] === "*") { + contentStart++; + } + comment = comment.slice(contentStart).trimStart(); + if (!comment.startsWith(expectErrorToken)) { + return undefined; + } + + let range = comment.slice(expectErrorToken.length); + if (range.length === range.trimStart().length) { + return undefined; + } + range = range.trim(); + if (range.endsWith("*/")) { + range = range.slice(0, -2).trimEnd(); + } + return range || undefined; +} diff --git a/packages/utils/test/expect-error-range.test.ts b/packages/utils/test/expect-error-range.test.ts index f3432faac2..f641486051 100644 --- a/packages/utils/test/expect-error-range.test.ts +++ b/packages/utils/test/expect-error-range.test.ts @@ -13,5 +13,6 @@ describe(isVersionedExpectErrorOutsideRange.name, () => { it("excludes multiline comment delimiters from the range", () => { expect(isVersionedExpectErrorOutsideRange("/* @ts-expect-error <7.0 */", "7.0")).toBe(true); expect(isVersionedExpectErrorOutsideRange("/* @ts-expect-error <7.0 */", "6.0")).toBe(false); + expect(isVersionedExpectErrorOutsideRange("/** @ts-expect-error <7.0 */", "7.0")).toBe(true); }); }); From 80f396c3d953368f4efeb7746c5b093ab2f4ba66 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:49:44 -0700 Subject: [PATCH 20/24] Load local TypeScript 7 packages coherently --- packages/dtslint/README.md | 7 ++ packages/dtslint/src/lint.ts | 2 +- packages/dtslint/src/lintTypeScript7.ts | 15 ++-- packages/dtslint/src/typescript-installer.ts | 72 ++++++++++++++------ packages/dtslint/test/index.test.ts | 21 ++---- 5 files changed, 71 insertions(+), 46 deletions(-) diff --git a/packages/dtslint/README.md b/packages/dtslint/README.md index 4e8aeaa1cb..2d2b4c8a69 100644 --- a/packages/dtslint/README.md +++ b/packages/dtslint/README.md @@ -182,6 +182,13 @@ Use your locally installed version of TypeScript. ```sh dtslint --localTs node_modules/typescript/lib types ``` + +For TypeScript 7, pass the TypeScript package directory after building both the native compiler and JavaScript API: + +```sh +dtslint --localTs ../TypeScript/packages/typescript types +``` + - `--expectOnly` Disable all the lint rules except the one that checks for type correctness. diff --git a/packages/dtslint/src/lint.ts b/packages/dtslint/src/lint.ts index e0eebaa69f..3c6a0d6923 100644 --- a/packages/dtslint/src/lint.ts +++ b/packages/dtslint/src/lint.ts @@ -233,5 +233,5 @@ function isTypeScript7(version: TsVersion, tsLocal: string | undefined): boolean } assert(tsLocal); - return resolveLocalTypeScript(tsLocal).kind === "native"; + return resolveLocalTypeScript(tsLocal).kind === "typescript7"; } diff --git a/packages/dtslint/src/lintTypeScript7.ts b/packages/dtslint/src/lintTypeScript7.ts index 50192a1cd5..32c344ace7 100644 --- a/packages/dtslint/src/lintTypeScript7.ts +++ b/packages/dtslint/src/lintTypeScript7.ts @@ -73,7 +73,7 @@ interface Snapshot { interface TypeScript7Api { readonly NodeBuilderFlags: { readonly NoTruncation: number }; - readonly API: new (options: { cwd: string; tsserverPath?: string }) => { + readonly API: new (options: { cwd: string }) => { updateSnapshot(options: { openProjects: readonly string[] }): Snapshot; close(): void; }; @@ -106,16 +106,17 @@ export async function lintTypeScript7Versions( const failures = new Map }>(); for (const version of versions) { - const clientVersion = version === "local" ? TypeScriptVersion.latest : version; - const apiModule = require(typeScriptPackages.resolve(clientVersion, "unstable/sync")) as TypeScript7Api; - const astModule = require(typeScriptPackages.resolve(clientVersion, "unstable/ast")) as TypeScript7Ast; const localTypeScript = version === "local" ? resolveLocalTypeScript(tsLocal!) : undefined; if (localTypeScript?.kind === "legacy") { - throw new Error(`Expected a TypeScript 7 native executable at ${tsLocal}.`); + throw new Error(`Expected a TypeScript 7 package at ${tsLocal}.`); } - const tsserverPath = localTypeScript?.executablePath; + const clientVersion = version === "local" ? TypeScriptVersion.latest : version; + const apiPath = localTypeScript?.apiPath ?? typeScriptPackages.resolve(clientVersion, "unstable/sync"); + const astPath = localTypeScript?.astPath ?? typeScriptPackages.resolve(clientVersion, "unstable/ast"); + const apiModule = require(apiPath) as TypeScript7Api; + const astModule = require(astPath) as TypeScript7Ast; const rangeVersion = localTypeScript?.version ?? version; - const api = new apiModule.API({ cwd: dirPath, tsserverPath }); + const api = new apiModule.API({ cwd: dirPath }); const configPaths = tsconfigs.map((config) => path.resolve(dirPath, config)); const matchedFiles = new Set(); diff --git a/packages/dtslint/src/typescript-installer.ts b/packages/dtslint/src/typescript-installer.ts index 081dde02e8..02a4144387 100644 --- a/packages/dtslint/src/typescript-installer.ts +++ b/packages/dtslint/src/typescript-installer.ts @@ -1,14 +1,19 @@ import { TypeScriptVersion } from "@definitelytyped/typescript-versions"; import * as typeScriptPackages from "@definitelytyped/typescript-packages"; -import { execFileSync } from "child_process"; import fs from "fs"; +import { createRequire } from "module"; import path from "path"; export type TsVersion = TypeScriptVersion | "local"; export type LocalTypeScript = | { readonly kind: "legacy"; readonly compilerPath: string } - | { readonly kind: "native"; readonly executablePath: string; readonly version: string }; + | { + readonly kind: "typescript7"; + readonly apiPath: string; + readonly astPath: string; + readonly version: string; + }; export function typeScriptPath(version: TsVersion, tsLocal: string | undefined): string { if (version === "local") { @@ -31,11 +36,7 @@ export function resolveLocalTypeScript(tsLocal: string): LocalTypeScript { if (/\.[cm]?js$/i.test(tsLocal)) { return { kind: "legacy", compilerPath: tsLocal }; } - const version = tryGetNativeTypeScriptVersion(tsLocal); - if (version) { - return { kind: "native", executablePath: tsLocal, version }; - } - throw new Error(`The file at ${tsLocal} is not a TypeScript compiler library or native executable.`); + throw new Error(`The file at ${tsLocal} is not a TypeScript compiler library.`); } if (!stat.isDirectory()) { throw new Error(`The TypeScript build path ${tsLocal} is not a file or directory.`); @@ -45,22 +46,49 @@ export function resolveLocalTypeScript(tsLocal: string): LocalTypeScript { if (fs.existsSync(compilerPath) && fs.statSync(compilerPath).isFile()) { return { kind: "legacy", compilerPath }; } - throw new Error( - `The directory at ${tsLocal} does not contain typescript.js. ` + - "Pass the TypeScript 7 native executable path directly.", - ); -} -function tryGetNativeTypeScriptVersion(executablePath: string): string | undefined { + const packageJsonPath = path.join(tsLocal, "package.json"); + if (!fs.existsSync(packageJsonPath) || !fs.statSync(packageJsonPath).isFile()) { + throw new Error(`The directory at ${tsLocal} does not contain typescript.js or package.json.`); + } + + let packageJson: { readonly name?: unknown }; + try { + packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as { readonly name?: unknown }; + } catch (error) { + throw new Error(`Could not read the TypeScript package at ${tsLocal}.`, { cause: error }); + } + if (typeof packageJson.name !== "string") { + throw new Error(`The package at ${tsLocal} does not have a valid name.`); + } + + const localRequire = createRequire(packageJsonPath); try { - const output = execFileSync(executablePath, ["--version"], { - encoding: "utf8", - timeout: 5_000, - windowsHide: true, - }); - const version = /^Version\s+(\d+\.\d+(?:\.\d+)?(?:-[^\s]+)?)/m.exec(output)?.[1]; - return version && Number.parseInt(version, 10) >= 7 ? version : undefined; - } catch { - return undefined; + const versionModule = localRequire(packageJson.name) as { + readonly version?: unknown; + readonly versionMajorMinor?: unknown; + }; + const version = isTypeScript7Version(versionModule.version) + ? versionModule.version + : versionModule.versionMajorMinor; + if (!isTypeScript7Version(version)) { + throw new Error(`The package reports an unsupported TypeScript version: ${String(version)}.`); + } + return { + kind: "typescript7", + apiPath: localRequire.resolve(`${packageJson.name}/unstable/sync`), + astPath: localRequire.resolve(`${packageJson.name}/unstable/ast`), + version, + }; + } catch (error) { + throw new Error(`The package at ${tsLocal} is not a usable TypeScript 7 package.`, { cause: error }); + } +} + +function isTypeScript7Version(version: unknown): version is string { + if (typeof version !== "string") { + return false; } + const major = /^(\d+)(?:\.|$)/.exec(version)?.[1]; + return major !== undefined && Number.parseInt(major, 10) >= 7; } diff --git a/packages/dtslint/test/index.test.ts b/packages/dtslint/test/index.test.ts index 333aad427a..c6e5fdbd8c 100644 --- a/packages/dtslint/test/index.test.ts +++ b/packages/dtslint/test/index.test.ts @@ -3,9 +3,7 @@ import { CompilerOptionsRaw, checkTsconfig } from "../src/checks"; import { assertPackageIsNotDeprecated } from "../src/index"; import * as typeScriptPackages from "@definitelytyped/typescript-packages"; import { execFile } from "child_process"; -import fs from "fs"; import path from "path"; -import { pathToFileURL } from "url"; import { promisify } from "util"; const execFileAsync = promisify(execFile); @@ -271,18 +269,8 @@ describe("dtslint", () => { expect(result).toContain("@typescript-eslint/naming-convention"); }, 30_000); - it("can use a local TypeScript 7 executable", async () => { - const apiPath = typeScriptPackages.resolve("7.1", "unstable/sync"); - const packageRoot = path.resolve(apiPath, "../../../../"); - const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8")) as { - imports: { "#getExePath": string }; - }; - const resolverUrl = pathToFileURL(path.resolve(packageRoot, packageJson.imports["#getExePath"])).href; - const { stdout: executable } = await execFileAsync(process.execPath, [ - "--input-type=module", - "--eval", - `import getExePath from ${JSON.stringify(resolverUrl)}; process.stdout.write(getExePath());`, - ]); + it("can use a local TypeScript 7 package", async () => { + const packageRoot = path.dirname(typeScriptPackages.resolve("7.0", "package.json")); await expect( runBuilt("lint", "lint", [ @@ -292,7 +280,7 @@ describe("dtslint", () => { "local", true, true, - executable, + packageRoot, ]), ).resolves.toBeUndefined(); @@ -301,9 +289,10 @@ describe("dtslint", () => { ["tsconfig.json"], ["local"], true, - executable, + packageRoot, ]); expect(result).toContain("TypeScript@local compile error TS2578"); + expect(result?.match(/compile error TS2578/g)).toHaveLength(1); }); }); }); From a3b0bb56243e57342816153a8664e8c14a5f77e7 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 20 Aug 2026 20:13:51 -0700 Subject: [PATCH 21/24] Use Corsa terminology for TypeScript 7 --- packages/dtslint/src/lint.ts | 14 ++++++------- .../src/{lintTypeScript7.ts => lintCorsa.ts} | 18 ++++++++--------- packages/dtslint/src/typescript-installer.ts | 16 +++++++-------- .../.eslintrc.json | 0 .../index.d.ts | 0 .../test.cts | 0 .../tsconfig.json | 0 .../external/index.d.ts | 0 .../{typescript7 => corsa}/fail/index.d.ts | 0 .../{typescript7 => corsa}/fail/test.ts | 0 .../fail/tsconfig.alternate.json | 0 .../{typescript7 => corsa}/fail/tsconfig.json | 0 .../partial/excluded.ts | 0 .../partial/included.ts | 0 .../{typescript7 => corsa}/partial/index.d.ts | 0 .../partial/tsconfig.alternate.json | 0 .../partial/tsconfig.json | 0 .../{typescript7 => corsa}/pass/index.d.ts | 0 .../{typescript7 => corsa}/pass/test.ts | 0 .../{typescript7 => corsa}/pass/tsconfig.json | 0 packages/dtslint/test/index.test.ts | 20 +++++++++---------- 21 files changed, 34 insertions(+), 34 deletions(-) rename packages/dtslint/src/{lintTypeScript7.ts => lintCorsa.ts} (97%) rename packages/dtslint/test/{typescript7-eslint => corsa-eslint}/.eslintrc.json (100%) rename packages/dtslint/test/{typescript7-eslint => corsa-eslint}/index.d.ts (100%) rename packages/dtslint/test/{typescript7-eslint => corsa-eslint}/test.cts (100%) rename packages/dtslint/test/{typescript7-eslint => corsa-eslint}/tsconfig.json (100%) rename packages/dtslint/test/fixtures/{typescript7 => corsa}/external/index.d.ts (100%) rename packages/dtslint/test/fixtures/{typescript7 => corsa}/fail/index.d.ts (100%) rename packages/dtslint/test/fixtures/{typescript7 => corsa}/fail/test.ts (100%) rename packages/dtslint/test/fixtures/{typescript7 => corsa}/fail/tsconfig.alternate.json (100%) rename packages/dtslint/test/fixtures/{typescript7 => corsa}/fail/tsconfig.json (100%) rename packages/dtslint/test/fixtures/{typescript7 => corsa}/partial/excluded.ts (100%) rename packages/dtslint/test/fixtures/{typescript7 => corsa}/partial/included.ts (100%) rename packages/dtslint/test/fixtures/{typescript7 => corsa}/partial/index.d.ts (100%) rename packages/dtslint/test/fixtures/{typescript7 => corsa}/partial/tsconfig.alternate.json (100%) rename packages/dtslint/test/fixtures/{typescript7 => corsa}/partial/tsconfig.json (100%) rename packages/dtslint/test/fixtures/{typescript7 => corsa}/pass/index.d.ts (100%) rename packages/dtslint/test/fixtures/{typescript7 => corsa}/pass/test.ts (100%) rename packages/dtslint/test/fixtures/{typescript7 => corsa}/pass/tsconfig.json (100%) diff --git a/packages/dtslint/src/lint.ts b/packages/dtslint/src/lint.ts index 3c6a0d6923..2c62d2a374 100644 --- a/packages/dtslint/src/lint.ts +++ b/packages/dtslint/src/lint.ts @@ -6,7 +6,7 @@ import { ESLint } from "eslint"; import * as TsType from "typescript"; import { createProgram } from "./createProgram"; -import { lintTypeScript7Versions } from "./lintTypeScript7"; +import { lintCorsaVersions } from "./lintCorsa"; import { resolveLocalTypeScript, typeScriptPath } from "./typescript-installer"; export async function lint( @@ -35,9 +35,9 @@ export async function lint( const versions = range(minVersion, maxVersion); const legacyVersions: TsVersion[] = []; - const typeScript7Versions: TsVersion[] = []; + const corsaVersions: TsVersion[] = []; for (const version of versions) { - (isTypeScript7(version, tsLocal) ? typeScript7Versions : legacyVersions).push(version); + (usesCorsa(version, tsLocal) ? corsaVersions : legacyVersions).push(version); } const outputs: string[] = []; @@ -57,8 +57,8 @@ export async function lint( } } - if (typeScript7Versions.length) { - const output = await lintTypeScript7Versions(dirPath, tsconfigs, typeScript7Versions, isLatest, tsLocal, files); + if (corsaVersions.length) { + const output = await lintCorsaVersions(dirPath, tsconfigs, corsaVersions, isLatest, tsLocal, files); if (output) { outputs.push(output); } @@ -227,11 +227,11 @@ function range(minVersion: TsVersion, maxVersion: TsVersion): readonly TsVersion export type TsVersion = TypeScriptVersion | "local"; -function isTypeScript7(version: TsVersion, tsLocal: string | undefined): boolean { +function usesCorsa(version: TsVersion, tsLocal: string | undefined): boolean { if (version !== "local") { return parseFloat(version) >= 7; } assert(tsLocal); - return resolveLocalTypeScript(tsLocal).kind === "typescript7"; + return resolveLocalTypeScript(tsLocal).kind === "corsa"; } diff --git a/packages/dtslint/src/lintTypeScript7.ts b/packages/dtslint/src/lintCorsa.ts similarity index 97% rename from packages/dtslint/src/lintTypeScript7.ts rename to packages/dtslint/src/lintCorsa.ts index 32c344ace7..1e3ced7069 100644 --- a/packages/dtslint/src/lintTypeScript7.ts +++ b/packages/dtslint/src/lintCorsa.ts @@ -71,7 +71,7 @@ interface Snapshot { dispose(): void; } -interface TypeScript7Api { +interface CorsaApi { readonly NodeBuilderFlags: { readonly NoTruncation: number }; readonly API: new (options: { cwd: string }) => { updateSnapshot(options: { openProjects: readonly string[] }): Snapshot; @@ -79,7 +79,7 @@ interface TypeScript7Api { }; } -interface TypeScript7Ast { +interface CorsaAst { readonly SyntaxKind: { readonly EndOfFileToken: number }; isExpressionStatement(node: Node): node is ExpressionStatement; isVariableStatement(node: Node): node is VariableStatement; @@ -94,7 +94,7 @@ interface Failure { const expectTypeToken = "$ExpectType"; -export async function lintTypeScript7Versions( +export async function lintCorsaVersions( dirPath: string, tsconfigs: readonly string[], versions: readonly TsVersion[], @@ -108,13 +108,13 @@ export async function lintTypeScript7Versions( for (const version of versions) { const localTypeScript = version === "local" ? resolveLocalTypeScript(tsLocal!) : undefined; if (localTypeScript?.kind === "legacy") { - throw new Error(`Expected a TypeScript 7 package at ${tsLocal}.`); + throw new Error(`Expected a TypeScript package exposing unstable/sync and unstable/ast at ${tsLocal}.`); } const clientVersion = version === "local" ? TypeScriptVersion.latest : version; const apiPath = localTypeScript?.apiPath ?? typeScriptPackages.resolve(clientVersion, "unstable/sync"); const astPath = localTypeScript?.astPath ?? typeScriptPackages.resolve(clientVersion, "unstable/ast"); - const apiModule = require(apiPath) as TypeScript7Api; - const astModule = require(astPath) as TypeScript7Ast; + const apiModule = require(apiPath) as CorsaApi; + const astModule = require(astPath) as CorsaAst; const rangeVersion = localTypeScript?.version ?? version; const api = new apiModule.API({ cwd: dirPath }); const configPaths = tsconfigs.map((config) => path.resolve(dirPath, config)); @@ -246,8 +246,8 @@ function flattenDiagnostic(diagnostic: Diagnostic): string { function getExpectTypeFailures( project: Project, - apiModule: TypeScript7Api, - astModule: TypeScript7Ast, + apiModule: CorsaApi, + astModule: CorsaAst, dirPath: string, isLatest: boolean, ): Failure[] { @@ -362,7 +362,7 @@ function skipTypeParentheses(node: ts.TypeNode): ts.TypeNode { return node; } -function getNodeForExpectType(node: Node, astModule: TypeScript7Ast): Node { +function getNodeForExpectType(node: Node, astModule: CorsaAst): Node { if (astModule.isExpressionStatement(node)) { return node.expression; } diff --git a/packages/dtslint/src/typescript-installer.ts b/packages/dtslint/src/typescript-installer.ts index 02a4144387..a4abae0086 100644 --- a/packages/dtslint/src/typescript-installer.ts +++ b/packages/dtslint/src/typescript-installer.ts @@ -9,7 +9,7 @@ export type TsVersion = TypeScriptVersion | "local"; export type LocalTypeScript = | { readonly kind: "legacy"; readonly compilerPath: string } | { - readonly kind: "typescript7"; + readonly kind: "corsa"; readonly apiPath: string; readonly astPath: string; readonly version: string; @@ -68,24 +68,24 @@ export function resolveLocalTypeScript(tsLocal: string): LocalTypeScript { readonly version?: unknown; readonly versionMajorMinor?: unknown; }; - const version = isTypeScript7Version(versionModule.version) - ? versionModule.version - : versionModule.versionMajorMinor; - if (!isTypeScript7Version(version)) { + const version = isCorsaVersion(versionModule.version) ? versionModule.version : versionModule.versionMajorMinor; + if (!isCorsaVersion(version)) { throw new Error(`The package reports an unsupported TypeScript version: ${String(version)}.`); } return { - kind: "typescript7", + kind: "corsa", apiPath: localRequire.resolve(`${packageJson.name}/unstable/sync`), astPath: localRequire.resolve(`${packageJson.name}/unstable/ast`), version, }; } catch (error) { - throw new Error(`The package at ${tsLocal} is not a usable TypeScript 7 package.`, { cause: error }); + throw new Error(`The package at ${tsLocal} does not expose usable unstable/sync and unstable/ast entrypoints.`, { + cause: error, + }); } } -function isTypeScript7Version(version: unknown): version is string { +function isCorsaVersion(version: unknown): version is string { if (typeof version !== "string") { return false; } diff --git a/packages/dtslint/test/typescript7-eslint/.eslintrc.json b/packages/dtslint/test/corsa-eslint/.eslintrc.json similarity index 100% rename from packages/dtslint/test/typescript7-eslint/.eslintrc.json rename to packages/dtslint/test/corsa-eslint/.eslintrc.json diff --git a/packages/dtslint/test/typescript7-eslint/index.d.ts b/packages/dtslint/test/corsa-eslint/index.d.ts similarity index 100% rename from packages/dtslint/test/typescript7-eslint/index.d.ts rename to packages/dtslint/test/corsa-eslint/index.d.ts diff --git a/packages/dtslint/test/typescript7-eslint/test.cts b/packages/dtslint/test/corsa-eslint/test.cts similarity index 100% rename from packages/dtslint/test/typescript7-eslint/test.cts rename to packages/dtslint/test/corsa-eslint/test.cts diff --git a/packages/dtslint/test/typescript7-eslint/tsconfig.json b/packages/dtslint/test/corsa-eslint/tsconfig.json similarity index 100% rename from packages/dtslint/test/typescript7-eslint/tsconfig.json rename to packages/dtslint/test/corsa-eslint/tsconfig.json diff --git a/packages/dtslint/test/fixtures/typescript7/external/index.d.ts b/packages/dtslint/test/fixtures/corsa/external/index.d.ts similarity index 100% rename from packages/dtslint/test/fixtures/typescript7/external/index.d.ts rename to packages/dtslint/test/fixtures/corsa/external/index.d.ts diff --git a/packages/dtslint/test/fixtures/typescript7/fail/index.d.ts b/packages/dtslint/test/fixtures/corsa/fail/index.d.ts similarity index 100% rename from packages/dtslint/test/fixtures/typescript7/fail/index.d.ts rename to packages/dtslint/test/fixtures/corsa/fail/index.d.ts diff --git a/packages/dtslint/test/fixtures/typescript7/fail/test.ts b/packages/dtslint/test/fixtures/corsa/fail/test.ts similarity index 100% rename from packages/dtslint/test/fixtures/typescript7/fail/test.ts rename to packages/dtslint/test/fixtures/corsa/fail/test.ts diff --git a/packages/dtslint/test/fixtures/typescript7/fail/tsconfig.alternate.json b/packages/dtslint/test/fixtures/corsa/fail/tsconfig.alternate.json similarity index 100% rename from packages/dtslint/test/fixtures/typescript7/fail/tsconfig.alternate.json rename to packages/dtslint/test/fixtures/corsa/fail/tsconfig.alternate.json diff --git a/packages/dtslint/test/fixtures/typescript7/fail/tsconfig.json b/packages/dtslint/test/fixtures/corsa/fail/tsconfig.json similarity index 100% rename from packages/dtslint/test/fixtures/typescript7/fail/tsconfig.json rename to packages/dtslint/test/fixtures/corsa/fail/tsconfig.json diff --git a/packages/dtslint/test/fixtures/typescript7/partial/excluded.ts b/packages/dtslint/test/fixtures/corsa/partial/excluded.ts similarity index 100% rename from packages/dtslint/test/fixtures/typescript7/partial/excluded.ts rename to packages/dtslint/test/fixtures/corsa/partial/excluded.ts diff --git a/packages/dtslint/test/fixtures/typescript7/partial/included.ts b/packages/dtslint/test/fixtures/corsa/partial/included.ts similarity index 100% rename from packages/dtslint/test/fixtures/typescript7/partial/included.ts rename to packages/dtslint/test/fixtures/corsa/partial/included.ts diff --git a/packages/dtslint/test/fixtures/typescript7/partial/index.d.ts b/packages/dtslint/test/fixtures/corsa/partial/index.d.ts similarity index 100% rename from packages/dtslint/test/fixtures/typescript7/partial/index.d.ts rename to packages/dtslint/test/fixtures/corsa/partial/index.d.ts diff --git a/packages/dtslint/test/fixtures/typescript7/partial/tsconfig.alternate.json b/packages/dtslint/test/fixtures/corsa/partial/tsconfig.alternate.json similarity index 100% rename from packages/dtslint/test/fixtures/typescript7/partial/tsconfig.alternate.json rename to packages/dtslint/test/fixtures/corsa/partial/tsconfig.alternate.json diff --git a/packages/dtslint/test/fixtures/typescript7/partial/tsconfig.json b/packages/dtslint/test/fixtures/corsa/partial/tsconfig.json similarity index 100% rename from packages/dtslint/test/fixtures/typescript7/partial/tsconfig.json rename to packages/dtslint/test/fixtures/corsa/partial/tsconfig.json diff --git a/packages/dtslint/test/fixtures/typescript7/pass/index.d.ts b/packages/dtslint/test/fixtures/corsa/pass/index.d.ts similarity index 100% rename from packages/dtslint/test/fixtures/typescript7/pass/index.d.ts rename to packages/dtslint/test/fixtures/corsa/pass/index.d.ts diff --git a/packages/dtslint/test/fixtures/typescript7/pass/test.ts b/packages/dtslint/test/fixtures/corsa/pass/test.ts similarity index 100% rename from packages/dtslint/test/fixtures/typescript7/pass/test.ts rename to packages/dtslint/test/fixtures/corsa/pass/test.ts diff --git a/packages/dtslint/test/fixtures/typescript7/pass/tsconfig.json b/packages/dtslint/test/fixtures/corsa/pass/tsconfig.json similarity index 100% rename from packages/dtslint/test/fixtures/typescript7/pass/tsconfig.json rename to packages/dtslint/test/fixtures/corsa/pass/tsconfig.json diff --git a/packages/dtslint/test/index.test.ts b/packages/dtslint/test/index.test.ts index c6e5fdbd8c..4e2feed7ee 100644 --- a/packages/dtslint/test/index.test.ts +++ b/packages/dtslint/test/index.test.ts @@ -167,12 +167,12 @@ describe("dtslint", () => { ]); }); - describe("TypeScript 7", () => { - const fixtures = path.join(__dirname, "fixtures", "typescript7"); + describe("Corsa", () => { + const fixtures = path.join(__dirname, "fixtures", "corsa"); for (const version of ["7.0", "7.1"] as const) { it(`checks compiler diagnostics and ExpectType through the TypeScript ${version} IPC API`, async () => { - const result = await runBuilt("lintTypeScript7", "lintTypeScript7Versions", [ + const result = await runBuilt("lintCorsa", "lintCorsaVersions", [ path.join(fixtures, "fail"), ["tsconfig.json"], [version], @@ -207,8 +207,8 @@ describe("dtslint", () => { }); } - it("reports and deduplicates TypeScript 7 failures across tsconfigs", async () => { - const result = await runBuilt("lintTypeScript7", "lintTypeScript7Versions", [ + it("reports and deduplicates Corsa failures across tsconfigs", async () => { + const result = await runBuilt("lintCorsa", "lintCorsaVersions", [ path.join(fixtures, "fail"), ["tsconfig.json", "tsconfig.alternate.json"], ["7.0"], @@ -224,7 +224,7 @@ describe("dtslint", () => { expect(result?.match(/expected type to be:\n 2\ngot:\n 1/g)).toHaveLength(1); }); - it("reports and deduplicates TypeScript 7 failures across versions", async () => { + it("reports and deduplicates Corsa failures across versions", async () => { const result = await runBuilt("lint", "lint", [ path.join(fixtures, "fail"), ["tsconfig.json"], @@ -254,9 +254,9 @@ describe("dtslint", () => { expect(result).toContain("TypeScript@7.0 could not find a tsconfig that includes this file."); }); - it("runs ordinary ESLint rules during TypeScript 7-only testing", async () => { + it("runs ordinary ESLint rules during Corsa-only testing", async () => { const result = await runBuilt("lint", "lint", [ - path.join(__dirname, "typescript7-eslint"), + path.join(__dirname, "corsa-eslint"), ["tsconfig.json"], "7.0", "7.0", @@ -269,7 +269,7 @@ describe("dtslint", () => { expect(result).toContain("@typescript-eslint/naming-convention"); }, 30_000); - it("can use a local TypeScript 7 package", async () => { + it("can use a local Corsa package", async () => { const packageRoot = path.dirname(typeScriptPackages.resolve("7.0", "package.json")); await expect( @@ -284,7 +284,7 @@ describe("dtslint", () => { ]), ).resolves.toBeUndefined(); - const result = await runBuilt("lintTypeScript7", "lintTypeScript7Versions", [ + const result = await runBuilt("lintCorsa", "lintCorsaVersions", [ path.join(fixtures, "fail"), ["tsconfig.json"], ["local"], From acbccf1c4abb6377ebbd735a25ac66592fe23ff8 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 20 Aug 2026 21:13:08 -0700 Subject: [PATCH 22/24] Clean up TypeScript changesets --- .changeset/calm-ranges-agree.md | 4 ++-- .changeset/strong-dragons-check.md | 4 ++-- .changeset/tidy-tigers-compile.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.changeset/calm-ranges-agree.md b/.changeset/calm-ranges-agree.md index 3db58da437..3f8428ddf3 100644 --- a/.changeset/calm-ranges-agree.md +++ b/.changeset/calm-ranges-agree.md @@ -4,5 +4,5 @@ "@definitelytyped/utils": patch --- -Share versioned `@ts-expect-error` range handling, correctly evaluate shorthand and local TypeScript versions, aggregate -TypeScript 7 failures across versions, and report files excluded from every configured project. +Support version ranges on `@ts-expect-error` directives consistently across dtslint and its ESLint plugin, including +shorthand and local TypeScript versions. diff --git a/.changeset/strong-dragons-check.md b/.changeset/strong-dragons-check.md index f0c0a33e70..9ae95ed362 100644 --- a/.changeset/strong-dragons-check.md +++ b/.changeset/strong-dragons-check.md @@ -4,5 +4,5 @@ "@definitelytyped/typescript-versions": patch --- -Add TypeScript 7.0 and 7.1 support using TypeScript's unstable out-of-process API for compiler diagnostics and -`$ExpectType` assertions, including local TypeScript 7 builds. +Add TypeScript 7.0 and 7.1 support through the Corsa compiler API, including compiler diagnostics, `$ExpectType` +assertions, multi-project failure reporting, and local TypeScript packages. diff --git a/.changeset/tidy-tigers-compile.md b/.changeset/tidy-tigers-compile.md index 443c817d77..85e1c05568 100644 --- a/.changeset/tidy-tigers-compile.md +++ b/.changeset/tidy-tigers-compile.md @@ -5,4 +5,4 @@ "dts-gen": patch --- -Upgrade the bundled TypeScript compiler to 6.0 and update TypeScript ESLint dependencies for TypeScript 6 compatibility. +Upgrade bundled TypeScript dependencies to 6.0 and update TypeScript ESLint dependencies for compatibility. From 68bee58abe937e2a7ea5281b2f4a0a735bff3249 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 20 Aug 2026 21:15:51 -0700 Subject: [PATCH 23/24] Fold version ranges into Corsa changeset --- .changeset/calm-ranges-agree.md | 8 -------- .changeset/strong-dragons-check.md | 4 +++- 2 files changed, 3 insertions(+), 9 deletions(-) delete mode 100644 .changeset/calm-ranges-agree.md diff --git a/.changeset/calm-ranges-agree.md b/.changeset/calm-ranges-agree.md deleted file mode 100644 index 3f8428ddf3..0000000000 --- a/.changeset/calm-ranges-agree.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@definitelytyped/dtslint": patch -"@definitelytyped/eslint-plugin": patch -"@definitelytyped/utils": patch ---- - -Support version ranges on `@ts-expect-error` directives consistently across dtslint and its ESLint plugin, including -shorthand and local TypeScript versions. diff --git a/.changeset/strong-dragons-check.md b/.changeset/strong-dragons-check.md index 9ae95ed362..285a9a670e 100644 --- a/.changeset/strong-dragons-check.md +++ b/.changeset/strong-dragons-check.md @@ -1,8 +1,10 @@ --- "@definitelytyped/dtslint": patch +"@definitelytyped/eslint-plugin": patch "@definitelytyped/typescript-packages": patch "@definitelytyped/typescript-versions": patch +"@definitelytyped/utils": patch --- Add TypeScript 7.0 and 7.1 support through the Corsa compiler API, including compiler diagnostics, `$ExpectType` -assertions, multi-project failure reporting, and local TypeScript packages. +assertions, version-ranged `@ts-expect-error` directives, multi-project failure reporting, and local TypeScript packages. From dc0fa47b1af4ba9a2fbd838724e14bb435feed7b Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Fri, 21 Aug 2026 11:52:41 -0700 Subject: [PATCH 24/24] Use shipped Corsa API types --- packages/dtslint/package.json | 3 +- packages/dtslint/src/lintCorsa.ts | 83 ++--------------------------- packages/dtslint/test/index.test.ts | 2 +- pnpm-lock.yaml | 3 ++ 4 files changed, 11 insertions(+), 80 deletions(-) diff --git a/packages/dtslint/package.json b/packages/dtslint/package.json index 6a84c0a305..f67f512653 100644 --- a/packages/dtslint/package.json +++ b/packages/dtslint/package.json @@ -41,7 +41,8 @@ }, "devDependencies": { "@types/eslint": "^8.56.12", - "@types/semver": "^7.7.1" + "@types/semver": "^7.7.1", + "typescript-7.1": "npm:typescript@~7.1.0-0" }, "engines": { "node": ">=20.17.0" diff --git a/packages/dtslint/src/lintCorsa.ts b/packages/dtslint/src/lintCorsa.ts index 1e3ced7069..c48563db07 100644 --- a/packages/dtslint/src/lintCorsa.ts +++ b/packages/dtslint/src/lintCorsa.ts @@ -4,86 +4,13 @@ import { isVersionedExpectErrorOutsideRange } from "@definitelytyped/utils"; import fs from "fs"; import path from "path"; import * as ts from "typescript"; +import type { Diagnostic, Project } from "typescript-7.1/unstable/sync"; +import type { Node, SourceFile } from "typescript-7.1/unstable/ast"; import type { TsVersion } from "./lint"; import { resolveLocalTypeScript } from "./typescript-installer"; -interface Node { - readonly kind: number; - readonly parent: Node; - forEachChild(visitor: (node: Node) => void): void; - getStart(sourceFile?: SourceFile): number; - getEnd(): number; -} - -interface ExpressionStatement extends Node { - readonly expression: Node; -} - -interface VariableStatement extends Node { - readonly declarationList: { - readonly declarations: readonly { - readonly initializer?: Node; - }[]; - }; -} - -interface SourceFile extends Node { - readonly fileName: string; - readonly text: string; - readonly isDeclarationFile: boolean; - getLineStarts(): readonly number[]; - getLineAndCharacterOfPosition(position: number): { line: number; character: number }; - getPositionOfLineAndCharacter(line: number, character: number): number; -} - -interface Diagnostic { - readonly fileName?: string; - readonly pos: number; - readonly end: number; - readonly code: number; - readonly text: string; - readonly messageChain?: readonly Diagnostic[]; -} - -interface Program { - getCompilerOptions(): { readonly declaration?: boolean; readonly composite?: boolean }; - getSourceFile(fileName: string): SourceFile | undefined; - getSourceFileNames(): readonly string[]; - getSourceFileMetadata(fileName: string): { isDefaultLibrary: boolean; isFromExternalLibrary: boolean } | undefined; - getSyntacticDiagnostics(fileName?: string): readonly Diagnostic[]; - getSemanticDiagnostics(fileName?: string): readonly Diagnostic[]; - getDeclarationDiagnostics(fileName?: string): readonly Diagnostic[]; - getProgramDiagnostics(): readonly Diagnostic[]; - getGlobalDiagnostics(): readonly Diagnostic[]; - getConfigFileParsingDiagnostics(): readonly Diagnostic[]; -} - -interface Project { - readonly program: Program; - readonly checker: { - getTypeAtLocation(node: Node): unknown | undefined; - typeToString(type: unknown, enclosingDeclaration: undefined, flags: number): string; - }; -} - -interface Snapshot { - getProject(configFileName: string): Project | undefined; - dispose(): void; -} - -interface CorsaApi { - readonly NodeBuilderFlags: { readonly NoTruncation: number }; - readonly API: new (options: { cwd: string }) => { - updateSnapshot(options: { openProjects: readonly string[] }): Snapshot; - close(): void; - }; -} - -interface CorsaAst { - readonly SyntaxKind: { readonly EndOfFileToken: number }; - isExpressionStatement(node: Node): node is ExpressionStatement; - isVariableStatement(node: Node): node is VariableStatement; -} +type CorsaApi = typeof import("typescript-7.1/unstable/sync"); +type CorsaAst = typeof import("typescript-7.1/unstable/ast"); interface Failure { readonly fileName?: string; @@ -273,7 +200,7 @@ function getExpectTypeFailures( } sourceFile.forEachChild(function visit(node) { - if (node.kind === astModule.SyntaxKind.EndOfFileToken) { + if (node.kind === astModule.SyntaxKind.EndOfFile) { return; } const line = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line; diff --git a/packages/dtslint/test/index.test.ts b/packages/dtslint/test/index.test.ts index 4e2feed7ee..56b552031e 100644 --- a/packages/dtslint/test/index.test.ts +++ b/packages/dtslint/test/index.test.ts @@ -293,7 +293,7 @@ describe("dtslint", () => { ]); expect(result).toContain("TypeScript@local compile error TS2578"); expect(result?.match(/compile error TS2578/g)).toHaveLength(1); - }); + }, 30_000); }); }); describe("assertPackageIsNotDeprecated", () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d32c4ea208..c751c1babd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -191,6 +191,9 @@ importers: '@types/semver': specifier: ^7.7.1 version: 7.7.1 + typescript-7.1: + specifier: npm:typescript@~7.1.0-0 + version: typescript@7.1.0-dev.20260817.1 packages/dtslint-runner: dependencies: