Skip to content

Support TypeScript 6.0 so tsoa projects can upgrade #1877

Description

@gabsong

Sorting

  • I'm submitting a ...

    • bug report
    • feature request
    • support request
  • I confirm that I

    • used the search to make sure that a similar issue hasn't already been submit

Expected Behavior

A project that has upgraded to TypeScript 6 can install tsoa and generate its spec and routes, the same way it can on TypeScript 5.

// tsoa should install alongside typescript@6 and generate this model as it does on TypeScript 5
interface CustomError extends Error {
  status: number;
}

type KeysMember<T = any> = {
  keys: keyof T;
};

class DefaultsClass {
  boolValue?: boolean;
}

Current Behavior

@tsoa/cli declares typescript: ^5.7.2 as a runtime dependency, so TypeScript 6 is not an allowed install. Beyond the version range, generation itself breaks on TypeScript 6 in three separate places:

  1. keyof T on a generic fails. TypeResolver masks a symbol's flags with ts.TypeFlags.TypeParameter. That only ever matched because TypeFlags.TypeParameter and SymbolFlags.TypeParameter both happened to be 262144. TypeScript 6 renumbers TypeFlags.TypeParameter to 524288, so the branch is never taken:
GenerateMetadataError: Could not determine the keys on T
This was caused by 'keys: keyof T;'
    at TypeResolver.resolveTypeOperatorNode (packages/cli/src/metadataGeneration/typeResolver.ts:444:15)
  1. Types declared only by TypeScript's libs fail to resolve. When a name has several declarations, getModelTypeDeclarations drops the ones under node_modules/typescript so a user model shadowing a lib name wins. TypeScript 6 ships more lib declarations, so Error now has several lib declarations and no user declaration — the filter removes all of them:
GenerateMetadataError: Could not find declarations for type 'Error'.
    at calcReferenceType (packages/cli/src/metadataGeneration/typeResolver.ts:865:17)
  1. Generated specs silently change shape. MetadataGenerator builds the program with compilerOptions || {}. Through TypeScript 5 an empty option bag meant strictNullChecks: false. TypeScript 6 enables strict null checking by default, so optional properties resolve as T | undefined and conditional types take the other branch. Optional boolean properties are emitted as "type": "object", and mapped/conditional aliases resolve differently. This is the quietest of the three — no error is thrown, the spec is just wrong.

The repo's own configuration also no longer compiles on TypeScript 6: downlevelIteration, baseUrl, esModuleInterop: false and moduleResolution: "node" are now error-level deprecations (TS5101/TS5107), and ignoreDeprecations: "6.0" cannot be used to silence them while still supporting TypeScript 5, because TypeScript 5 rejects that value (TS5103). Separately, @typescript-eslint 6.16.0 only supports TypeScript <5.4; on TypeScript 6 its type resolution collapses and it reports ~1100 spurious errors, so the repo cannot be linted on TS 6 at all.

Possible Solution

  • Mask with ts.SymbolFlags.TypeParameter in the keyof branch — correct on every supported TypeScript version.
  • Only apply the lib-declaration filter when it leaves at least one declaration behind.
  • Default explicitly to { strictNullChecks: false } when the user configures no compilerOptions, so a project that does not set them gets the same spec on TypeScript 5 and 6. Projects that do pass compilerOptions keep full control.
  • Widen the typescript dependency to ^5.7.2 || ^6.0.0, migrate the repo's tsconfigs/fixtures off the removed options, upgrade to typescript-eslint v8 (supports >=4.8.4 <6.1.0), and add ^6.0.0 to the CI matrix so both majors are covered.

Steps to Reproduce

  1. Install typescript@6.0.3 in a project using tsoa (the @tsoa/cli dependency range has to be overridden to allow it).
  2. Add a model with keyof T on a generic, or an interface extending Error.
  3. Run tsoa spec-and-routes.
  4. Generation fails with the errors above. Removing those models and re-running instead produces a spec where optional boolean properties are typed as object.

Context (Environment)

Version of the library: master (7.0.0-alpha.0)
Version of NodeJS: 22, 24 and 25 (the versions in the CI matrix)

  • Confirm you were using yarn not npm: [x]

Detailed Description

TypeScript 6.0 is the last release of the JavaScript-based compiler; npm's latest tag is now the native TypeScript 7, which does not expose the JS compiler API that @tsoa/cli uses to read a consumer's project. That makes TypeScript 6 the version tsoa users will be on for the foreseeable future, and today they cannot upgrade without dropping tsoa.

TypeScript 7 support is deliberately out of scope here — it is a different architecture and a much larger piece of work.

Happy to send a PR; I have the four points above implemented, with build, lint and the full test suite green on both TypeScript 5.9.3 and 6.0.3.

Breaking change?

Not for consumers. The dependency range only widens, and the strictNullChecks default is chosen specifically so that projects which do not configure compilerOptions keep the spec they get today rather than silently changing output on a TypeScript 6 upgrade.

One judgement call worth maintainer input: projects that do pass compilerOptions with strict/strictNullChecks enabled will see optional properties resolve as T | undefined under TypeScript 6, because that is what their own configuration now means. The alternative — letting the TypeScript 6 defaults through and updating the expected specs — would make generated output differ between the TypeScript 5 and 6 CI legs, so I did not take it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions