diff --git a/.changeset/bundle-typescript6.md b/.changeset/bundle-typescript6.md new file mode 100644 index 00000000000..4d1c1be8990 --- /dev/null +++ b/.changeset/bundle-typescript6.md @@ -0,0 +1,7 @@ +--- +"@ui5/ts-interface-generator": minor +--- + +The `typescript` peer dependency has been removed. The tool now bundles its own TypeScript compiler internally (pinned to TypeScript 6.0.3), so it works regardless of which TypeScript version your project uses — including TypeScript 7. + +Also fixes path resolution for projects that use `paths` without `baseUrl` and suppresses spurious diagnostics for unrecognized compiler options. diff --git a/docs/TS7.md b/docs/TS7.md new file mode 100644 index 00000000000..9df8978864a --- /dev/null +++ b/docs/TS7.md @@ -0,0 +1,205 @@ +# TypeScript 7 Migration + +This document covers the migration of `@ui5/ts-interface-generator` to work in a TypeScript 7 world. It explains the background, the approach taken, risks, and the state of the emerging TS7 compiler API. + +## Background + +### What happened with TypeScript 7 + +TypeScript 7.0 (released July 2026) is a ground-up rewrite of the TypeScript compiler in Go. The primary goal was performance — 10× faster compilation for large projects. However, this rewrite came with a major consequence for tooling authors: **TypeScript 7.0 ships no programmatic JavaScript API**. + +Where `require("typescript")` previously gave you the full compiler API (parser, type checker, AST factory, printer, watch mode, etc.), TS 7.0's main entry point exports only two things: + +```js +import ts from "typescript"; // TS 7.x +ts.version; // "7.0.2" +ts.versionMajorMinor; // "7.0" +// That's it. No createProgram, no TypeChecker, no factory, nothing. +``` + +The entire compiler now runs as a native Go binary (`tsgo`), and the npm package is essentially a thin wrapper that invokes it. + +### The compatibility story + +Microsoft provides two paths forward: + +1. **The `typescript` npm package still publishes 6.x versions** (6.0.3 as of this writing). Pinning `"typescript": "6.0.3"` in `dependencies` gives you the full TS6 compiler API. There is also a separate `@typescript/typescript6` package that re-publishes the same code under a different name (useful when you need both TS6 and TS7 in the same scope), but for bundled dependencies where pnpm isolates versions, the regular package works fine. + +2. **`typescript/unstable/*`** — New API subpaths shipped inside the `typescript` 7.x package (starting with 7.0.2). These provide a _different_, Go-backed API surface. More on this below. + +### What this means for `@ui5/ts-interface-generator` + +The ts-interface-generator deeply uses the TS compiler API to: + +- **Parse** user TypeScript projects via `ts.createWatchCompilerHost` / `ts.createWatchProgram` +- **Analyze types** via `ts.TypeChecker` (walking class hierarchies, resolving symbols, inspecting metadata) +- **Construct AST nodes** via `ts.factory.*` (40+ call sites building declaration file content) +- **Print** generated declarations via `ts.createPrinter` +- **Inspect JSDoc** via `ts.getJSDocCommentsAndTags` + +All of these APIs are gone in TS 7.0. The tool was previously configured with `typescript` as a **peer dependency** (`>=5.2.0 <7.0.0`), meaning any user who upgraded to TS7 would break it. + +## Migration approach + +### No version detection needed + +An important observation: the ts-interface-generator uses the TS compiler API exclusively to **read and analyze** user code. The generated `.gen.d.ts` output is standard TypeScript declaration syntax that works with any TS version. There is no version-dependent branching in the generator — the only "version check" in the codebase tests the **UI5 type definition version** (whether `sap/ui/base/Event` has generics), not the TypeScript version. + +This means: + +- We don't need to detect the user's TypeScript version +- We don't need dual-API dispatch (TS6 path vs TS7 path) +- Bundling exactly one TS version internally is sufficient +- A future migration to the TS7 API would be a clean cutover, not a fork + +### Phase 1: Bundle TS6 (current implementation) + +The immediate solution: change `typescript` from a peer dependency to a **direct dependency** (pinned to 6.0.3), so the tool bundles its own compiler internally. This is the same pattern that `@ui5/dts-generator` already uses. + +**What this means for users:** + +- Users can have **any** TypeScript version in their project (5.x, 6.x, 7.x) +- The ts-interface-generator uses its own bundled TS6 to analyze their code +- npm/pnpm properly isolate the two installations — no conflicts +- The generated `.gen.d.ts` files work with any TS version + +**Fixes applied alongside:** + +- **`pathsBasePath` fallback for projects without `baseUrl`**: Projects that use `paths` without `baseUrl` (a pattern that has become more common since TypeScript relaxed the requirement) need special handling. The tool now falls back to the internal `pathsBasePath` property that TS6 populates when `paths` is specified without `baseUrl`. +- **Diagnostic filtering for unknown compiler options**: When the user's tsconfig contains options that the bundled TS6 doesn't recognize (e.g. future TS7-specific options), those warnings are now silently filtered. They're noise — the user's own TS validates the tsconfig. The program still creates correctly. + +### Phase 2: Future migration to TS7 API (planned) + +Once the TS7 compiler API stabilizes (expected with TS 7.1), the plan is to migrate from the bundled TS6 to the native TS7 API. Since we always bundle exactly one TypeScript version, this is a straightforward cutover: update the bundled dependency, adapt the call sites to the new API surface, and ship a new version. + +No abstraction layer or runtime API switching is needed — we don't support multiple TS backends simultaneously. + +The TS compiler API is used in these modules: + +- `typeScriptEnvironment.ts` — watch mode setup (`createWatchCompilerHost`, `createWatchProgram`, `createSemanticDiagnosticsBuilderProgram`) +- `interfaceGenerationHelper.ts` — heavy TypeChecker usage (symbol resolution, type inspection, class hierarchy walking, JSDoc extraction) +- `astGenerationHelper.ts` — 40+ `ts.factory.*` call sites for AST construction +- `astToString.ts` — `createSourceFile`, `createPrinter`, `printer.printNode` +- `addSourceExports.ts` — `typeChecker.getSymbolAtLocation`, `typeChecker.getExportsOfModule` +- `generateTSInterfacesAPI.ts` — `program.getCompilerOptions`, `program.getSourceFiles`, `program.isSourceFileFromExternalLibrary` + +The TS7 API is structured differently from TS6 (class-based instead of function-based, different import paths, some APIs renamed or missing — see the mapping table below). When the time comes, the migration will touch all of these modules, but the changes are mechanical (import path + call pattern). The mapping table below documents the correspondence. + +#### Current status + +This is **not yet actionable** because the TS7 compiler API is incomplete — the `Program`, `Checker`, and `Emitter` classes exist in `typescript/unstable/sync` but their methods fail at runtime in TS 7.0.2 (the Go IPC mechanism is not connected). The AST layer (factory, type guards, enums) works standalone already, but without a functioning TypeChecker the tool cannot operate. Phase 2 is on hold until the API matures. + +## Risks and limitations + +### Language-level ceiling + +The bundled TS6 compiler can only parse syntax it knows. If a future TypeScript 7.x release introduces **new syntax** (not just new compiler options or type system features), the bundled TS6 won't be able to parse files using that syntax. + +In practice, this is low risk for now: + +- TS 7.0 itself adds no new syntax — it's a runtime rewrite +- New syntax tends to arrive in minor releases (7.1, 7.2, ...) and adoption is gradual +- When it happens, we upgrade the bundled compiler (to a newer TS6 point release, or to TS7 if its API is ready) + +### SyntaxKind enum stability + +The `SyntaxKind` enum values are **stable across TS versions** (verified identical across TS 5.7 through 6.0). The TS7 unstable API also exports the same `SyntaxKind`. This is not a risk. + +### Unknown compiler options + +When the user's `tsconfig.json` contains options that the bundled TS6 doesn't recognize, TS6 emits diagnostic codes 5023/5025 ("Unknown compiler option"). These are now filtered. The program still creates and the TypeChecker still works — these diagnostics are informational, not fatal. + +### Two TypeScript installations + +With the bundled approach, a user's project may have two TypeScript installations: their own (e.g. TS 7.x for `tsc`) and the ts-interface-generator's bundled TS6. Package managers (npm, pnpm) resolve them independently, so they don't interfere with each other. The bundled TS is visible under `@ui5/ts-interface-generator` in `node_modules` but does not affect the user's own compilation. + +### Watch mode + +The TS7 unstable API does not yet expose any equivalent to `createWatchCompilerHost` or `createWatchProgram`. The ts-interface-generator's watch mode relies entirely on these TS6 APIs. A future TS7 migration will need to either: + +- Wait for watch-mode support in the TS7 API +- Implement file watching externally (e.g. via `chokidar`) and re-create the Program on each change +- Use the TS7 `Project` class if it turns out to support incremental updates + +## TS7 unstable API surface (as of 7.0.2) + +TypeScript 7.0.2 ships `./unstable/*` subpaths inside the `typescript` package. These are ESM-only imports. The API is structured into layers: + +### AST layer (works standalone — no Go backend needed) + +| Subpath | Exports | Notes | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | +| `typescript/unstable/ast` | `SyntaxKind`, `ScriptTarget`, `ScriptKind`, `NodeFlags`, `ModifierFlags`, JSDoc utilities (`getJSDocTags`, `getAllJSDocTags`, `getTextOfJSDocComment`), comment range utilities, scanner | 409 exports total | +| `typescript/unstable/ast/factory` | `createIdentifier`, `createTypeReferenceNode`, `createSourceFile`, `createNodeArray`, `createClassDeclaration`, ... | 370 factory functions — covers most of `ts.factory.*` | +| `typescript/unstable/ast/is` | `isClassDeclaration`, `isIdentifier`, `isPropertyDeclaration`, `isTypeReferenceNode`, ... | 347 type guard functions — covers `ts.is*` | +| `typescript/unstable/ast/visitor` | `visitEachChild`, `visitNode`, `visitNodes` | 8 exports | +| `typescript/unstable/ast/utils` | `escapeLeadingUnderscores`, `formatSyntaxKind`, `cast`, `tryCast` | 6 exports | +| `typescript/unstable/ast/scanner` | Scanner for tokenizing | | +| `typescript/unstable/ast/clone` | Node cloning utilities | | + +These are pure JavaScript functions that work without the Go backend. They can be used today for AST construction and inspection. + +### Compiler layer (requires Go backend — not functional in 7.0.2) + +| Subpath | Exports | Notes | +| --------------------------- | -------------------------------------------------------------------------------------------------------------------- | ---------- | +| `typescript/unstable/sync` | `API`, `Program`, `Checker`, `Emitter`, `Project`, `Snapshot`, `Symbol`, `Signature`, `NodeHandle` + type flag enums | 44 exports | +| `typescript/unstable/async` | Async versions of the above | | + +The sync API follows a class-based pattern: + +```js +const api = new API(); +api.ensureInitialized(); +const config = api.parseConfigFile("tsconfig.json"); +const program = new Program(api, config); +const checker = new Checker(program); +const emitter = new Emitter(program); +``` + +**Current status (7.0.2):** The classes can be instantiated, but any method that touches the compiler (e.g. `program.getSourceFileNames()`, `checker.getSymbolAtLocation()`, `emitter.printNode()`) fails with `Cannot read properties of undefined (reading 'apiRequest')`. The Go IPC mechanism is not connected. This is expected to be resolved in TS 7.1. + +### API mapping: TS6 → TS7 unstable + +| TS6 API | TS7 Unstable equivalent | Status in 7.0.2 | +| -------------------------------------------- | ----------------------------------------------------------------- | ------------------------ | +| `ts.createWatchCompilerHost()` | _None found_ | ❌ Not available | +| `ts.createWatchProgram()` | _None found_ | ❌ Not available | +| `ts.createSemanticDiagnosticsBuilderProgram` | _None found_ | ❌ Not available | +| `ts.createProgram()` / `new ts.Program()` | `new Program(api, config)` | ⚠️ Exists, Go IPC broken | +| `program.getTypeChecker()` | `new Checker(program)` | ⚠️ Exists, Go IPC broken | +| `program.getSourceFiles()` | `program.getSourceFileNames()` + `program.getSourceFile(name)` | ⚠️ Exists, Go IPC broken | +| `program.isSourceFileFromExternalLibrary()` | `program.isSourceFileFromExternalLibrary()` | ⚠️ Exists, Go IPC broken | +| `ts.createPrinter()` / `printer.printNode()` | `new Emitter(program)` / `emitter.printNode()` | ⚠️ Exists, Go IPC broken | +| `ts.factory.createIdentifier()` etc. | `typescript/unstable/ast/factory` | ✅ Works | +| `ts.isClassDeclaration()` etc. | `typescript/unstable/ast/is` | ✅ Works | +| `ts.SyntaxKind.*` | `typescript/unstable/ast` → `SyntaxKind` | ✅ Works | +| `ts.ScriptTarget.*` | `typescript/unstable/ast` → `ScriptTarget` | ✅ Works | +| `ts.getJSDocCommentsAndTags()` | `typescript/unstable/ast` → `getJSDocTags()`, `getAllJSDocTags()` | ✅ Works | +| `ts.addSyntheticLeadingComment()` | _Not found in unstable exports_ | ❌ Not available | +| `typeChecker.getSymbolAtLocation()` | `checker.getSymbolAtLocation()` | ⚠️ Exists, Go IPC broken | +| `typeChecker.getExportsOfModule()` | `checker.getExportsOfModule()` | ⚠️ Exists, Go IPC broken | +| `typeChecker.getTypeOfSymbol()` | `checker.getTypeOfSymbol()` | ⚠️ Exists, Go IPC broken | +| `typeChecker.getAmbientModules()` | _Not found_ | ❌ Not available | +| `typeChecker.typeToString()` | `checker.typeToString()` | ⚠️ Exists, Go IPC broken | + +### Key observations for future migration + +1. **The AST layer is ready.** Factory functions, type guards, and enums work today. The 40+ `ts.factory.*` call sites in `astGenerationHelper.ts` could theoretically be migrated to `typescript/unstable/ast/factory` imports already. + +2. **The compiler layer is not ready.** Program creation, type checking, and emission all depend on Go IPC that doesn't function in 7.0.2. + +3. **Watch mode has no equivalent.** The TS7 API has `Project` (which might handle incremental updates) but no `createWatchCompilerHost`. External file watching may be needed. + +4. **Some APIs are missing entirely.** `addSyntheticLeadingComment` (used for adding comments to generated AST nodes) and `getAmbientModules` (used to enumerate declared modules) have no equivalent in the unstable API surface. + +5. **The API is class-based, not function-based.** Instead of `program.getTypeChecker()` returning a TypeChecker, you construct `new Checker(program)`. This changes the instantiation pattern but the methods on `Checker` closely mirror the old `TypeChecker`. + +6. **ESM only.** The unstable subpaths are ES modules. If the ts-interface-generator is compiled to CommonJS, it would need to use dynamic `import()` or be converted to ESM. + +## References + +- [TypeScript 7 announcement](https://devblogs.microsoft.com/typescript/typescript-7/) +- [TS 7.1 milestone (API work)](https://github.com/microsoft/TypeScript/milestone/224) +- [`@typescript/typescript6` package](https://www.npmjs.com/package/@typescript/typescript6) +- [Blog post: TypeScript 6 and 7 — What UI5 TypeScript Developers Need to Know](https://community.sap.com/t5/technology-blog-posts-by-sap/typescript-6-and-7-what-ui5-typescript-developers-need-to-know-in-2026/ba-p/14393526) diff --git a/packages/ts-interface-generator/package.json b/packages/ts-interface-generator/package.json index 4afbbf8129a..9a53e751d2d 100644 --- a/packages/ts-interface-generator/package.json +++ b/packages/ts-interface-generator/package.json @@ -47,17 +47,14 @@ "eslint": "10.11.0", "jest": "30.5.2", "npm-run-all2": "9.0.3", - "ts-jest": "29.4.13", - "typescript": "6.0.3" + "ts-jest": "29.4.13" }, "dependencies": { "hjson": "3.2.2", "loglevel": "1.9.2", + "typescript": "6.0.3", "yargs": "18.2.0" }, - "peerDependencies": { - "typescript": ">=5.2.0 <7.0.0" - }, "files": [ "dist" ] diff --git a/packages/ts-interface-generator/src/generateTSInterfacesAPI.ts b/packages/ts-interface-generator/src/generateTSInterfacesAPI.ts index 6c6e9aa59cf..1584f4ec741 100644 --- a/packages/ts-interface-generator/src/generateTSInterfacesAPI.ts +++ b/packages/ts-interface-generator/src/generateTSInterfacesAPI.ts @@ -102,6 +102,12 @@ export function getProgramInfo( const options = program.getCompilerOptions(); if (options.baseUrl) { basePath = path.normalize(options.baseUrl); + } else if ((options as Record).pathsBasePath) { + // When "paths" is specified without "baseUrl", TS6 internally populates + // the "pathsBasePath" property to the tsconfig directory. + basePath = path.normalize( + (options as Record).pathsBasePath as string, + ); } // loop all files, filter for relevant ones, and extract knowledge about all their module exports diff --git a/packages/ts-interface-generator/src/test/testcases/testcaseRunner.test.ts b/packages/ts-interface-generator/src/test/testcases/testcaseRunner.test.ts index 5d277dfa27c..db7febe80af 100644 --- a/packages/ts-interface-generator/src/test/testcases/testcaseRunner.test.ts +++ b/packages/ts-interface-generator/src/test/testcases/testcaseRunner.test.ts @@ -44,7 +44,12 @@ describe("Single Testcases", () => { } else { config = {...standardTsConfig}; } - config.baseUrl = testCaseDir; + if (!config.paths) { + // Only set baseUrl when there are no "paths" mappings — otherwise let + // the compiler populate pathsBasePath internally, which is the mechanism + // used when paths is configured without baseUrl. + config.baseUrl = testCaseDir; + } return config; } diff --git a/packages/ts-interface-generator/src/test/testcases/tsconfig-path-no-baseurl/MyControl.gen.d.ts b/packages/ts-interface-generator/src/test/testcases/tsconfig-path-no-baseurl/MyControl.gen.d.ts new file mode 100644 index 00000000000..d6d2b48c724 --- /dev/null +++ b/packages/ts-interface-generator/src/test/testcases/tsconfig-path-no-baseurl/MyControl.gen.d.ts @@ -0,0 +1,81 @@ +import { MyJSEnum } from "my/library"; +import { MyTSEnum } from "my/library"; +import MyDependency from "my/MyDependency"; +import { PropertyBindingInfo } from "sap/ui/base/ManagedObject"; +import { $ControlSettings } from "sap/ui/core/Control"; + +declare module "./MyControl" { + + /** + * Interface defining the settings object used in constructor calls + */ + interface $MyControlSettings extends $ControlSettings { + myJSEnumVal?: MyJSEnum | PropertyBindingInfo | `{${string}}`; + myTSEnumVal?: MyTSEnum | PropertyBindingInfo | `{${string}}`; + myDependency?: MyDependency | PropertyBindingInfo | `{${string}}`; + } + + export default interface MyControl { + + // property: myJSEnumVal + + /** + * Gets current value of property "myJSEnumVal". + * + * Default value is: "MyJSEnum.Foo," + * @returns Value of property "myJSEnumVal" + */ + getMyJSEnumVal(): MyJSEnum; + + /** + * Sets a new value for property "myJSEnumVal". + * + * When called with a value of "null" or "undefined", the default value of the property will be restored. + * + * Default value is: "MyJSEnum.Foo," + * @param [myJSEnumVal="MyJSEnum.Foo,"] New value for property "myJSEnumVal" + * @returns Reference to "this" in order to allow method chaining + */ + setMyJSEnumVal(myJSEnumVal: MyJSEnum): this; + + // property: myTSEnumVal + + /** + * Gets current value of property "myTSEnumVal". + * + * Default value is: "MyTSEnum.Foo," + * @returns Value of property "myTSEnumVal" + */ + getMyTSEnumVal(): MyTSEnum; + + /** + * Sets a new value for property "myTSEnumVal". + * + * When called with a value of "null" or "undefined", the default value of the property will be restored. + * + * Default value is: "MyTSEnum.Foo," + * @param [myTSEnumVal="MyTSEnum.Foo,"] New value for property "myTSEnumVal" + * @returns Reference to "this" in order to allow method chaining + */ + setMyTSEnumVal(myTSEnumVal: MyTSEnum): this; + + // property: myDependency + + /** + * Gets current value of property "myDependency". + * + * @returns Value of property "myDependency" + */ + getMyDependency(): MyDependency; + + /** + * Sets a new value for property "myDependency". + * + * When called with a value of "null" or "undefined", the default value of the property will be restored. + * + * @param myDependency New value for property "myDependency" + * @returns Reference to "this" in order to allow method chaining + */ + setMyDependency(myDependency: MyDependency): this; + } +} diff --git a/packages/ts-interface-generator/src/test/testcases/tsconfig-path-no-baseurl/MyControl.ts b/packages/ts-interface-generator/src/test/testcases/tsconfig-path-no-baseurl/MyControl.ts new file mode 100644 index 00000000000..4bb06a2515f --- /dev/null +++ b/packages/ts-interface-generator/src/test/testcases/tsconfig-path-no-baseurl/MyControl.ts @@ -0,0 +1,24 @@ +import Control from "sap/ui/core/Control"; +import type { MetadataOptions } from "sap/ui/core/Element"; +import { MyJSEnum, MyTSEnum } from "./library"; + +export default class MyControl extends Control { + static readonly metadata: MetadataOptions = { + properties: { + myJSEnumVal: { + type: "my.MyJSEnum", + defaultValue: MyJSEnum.Foo, + }, + myTSEnumVal: { + type: "my.MyTSEnum", + defaultValue: MyTSEnum.Foo, + }, + myDependency: { + type: "my.MyDependency", + }, + }, + }; + constructor(idOrSettings?: string | $MyControlSettings); + constructor(id?: string, settings?: $MyControlSettings); + constructor(id?: string, settings?: $MyControlSettings) { super(id, settings); } +} diff --git a/packages/ts-interface-generator/src/test/testcases/tsconfig-path-no-baseurl/MyDependency.gen.d.ts b/packages/ts-interface-generator/src/test/testcases/tsconfig-path-no-baseurl/MyDependency.gen.d.ts new file mode 100644 index 00000000000..e54dfae9c71 --- /dev/null +++ b/packages/ts-interface-generator/src/test/testcases/tsconfig-path-no-baseurl/MyDependency.gen.d.ts @@ -0,0 +1,36 @@ +import { PropertyBindingInfo } from "sap/ui/base/ManagedObject"; +import { $ControlSettings } from "sap/ui/core/Control"; + +declare module "./MyDependency" { + + /** + * Interface defining the settings object used in constructor calls + */ + interface $MyDependencySettings extends $ControlSettings { + foo?: string | PropertyBindingInfo; + } + + export default interface MyDependency { + + // property: foo + + /** + * Gets current value of property "foo". + * + * Default value is: "bar" + * @returns Value of property "foo" + */ + getFoo(): string; + + /** + * Sets a new value for property "foo". + * + * When called with a value of "null" or "undefined", the default value of the property will be restored. + * + * Default value is: "bar" + * @param [foo="bar"] New value for property "foo" + * @returns Reference to "this" in order to allow method chaining + */ + setFoo(foo: string): this; + } +} diff --git a/packages/ts-interface-generator/src/test/testcases/tsconfig-path-no-baseurl/MyDependency.ts b/packages/ts-interface-generator/src/test/testcases/tsconfig-path-no-baseurl/MyDependency.ts new file mode 100644 index 00000000000..8ce2921d57c --- /dev/null +++ b/packages/ts-interface-generator/src/test/testcases/tsconfig-path-no-baseurl/MyDependency.ts @@ -0,0 +1,17 @@ +import Control from "sap/ui/core/Control"; +import type { MetadataOptions } from "sap/ui/core/Element"; + +export default class MyDependency extends Control { + static readonly metadata: MetadataOptions = { + properties: { + foo: { + type: "string", + defaultValue: "bar", + }, + }, + }; + + constructor(idOrSettings?: string | $MyDependencySettings); + constructor(id?: string, settings?: $MyDependencySettings); + constructor(id?: string, settings?: $MyDependencySettings) { super(id, settings); } +} diff --git a/packages/ts-interface-generator/src/test/testcases/tsconfig-path-no-baseurl/library.ts b/packages/ts-interface-generator/src/test/testcases/tsconfig-path-no-baseurl/library.ts new file mode 100644 index 00000000000..b8b5aea1f44 --- /dev/null +++ b/packages/ts-interface-generator/src/test/testcases/tsconfig-path-no-baseurl/library.ts @@ -0,0 +1,29 @@ +import DataType from "sap/ui/base/DataType"; +import Lib from "sap/ui/core/Lib"; + +const thisLib: object&{MyJSEnum?: object; MyTSEnum?: object} = Lib.init({ + name: "my", + version: "${version}", + dependencies: ["sap.ui.core"], + types: [], + interfaces: [], + controls: [], + elements: [], + noLibraryCSS: false, +}); + +export const MyJSEnum = { + Foo: "foo", + Bar: "bar", +} as const; +thisLib.MyJSEnum = MyJSEnum; +DataType.registerEnum("mylib.MyJSEnum", thisLib.MyJSEnum); + +export enum MyTSEnum { + Foo = "foo", + Bar = "bar", +} +thisLib.MyTSEnum = MyTSEnum; +DataType.registerEnum("mylib.MyTSEnum", thisLib.MyTSEnum); + +export default thisLib; diff --git a/packages/ts-interface-generator/src/test/testcases/tsconfig-path-no-baseurl/tsconfig.json b/packages/ts-interface-generator/src/test/testcases/tsconfig-path-no-baseurl/tsconfig.json new file mode 100644 index 00000000000..726db8c6a2f --- /dev/null +++ b/packages/ts-interface-generator/src/test/testcases/tsconfig-path-no-baseurl/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "strict": true, + "moduleResolution": "Node16", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "types": ["openui5"], + "noEmit": true, + "paths": { + "my/*": ["./*"] + } + } +} diff --git a/packages/ts-interface-generator/src/typeScriptEnvironment.ts b/packages/ts-interface-generator/src/typeScriptEnvironment.ts index d4174beb105..8cbecd5c799 100644 --- a/packages/ts-interface-generator/src/typeScriptEnvironment.ts +++ b/packages/ts-interface-generator/src/typeScriptEnvironment.ts @@ -69,7 +69,7 @@ function initialize( { noEmit: true }, ts.sys, ts.createSemanticDiagnosticsBuilderProgram, - options.watchMode ? reportDiagnostic : undefined, + reportDiagnostic, options.watchMode ? reportWatchStatusChanged : undefined, ); @@ -133,9 +133,27 @@ function reportDiagnostic(diagnostic: ts.Diagnostic) { } } - // the remaining errors MAY be real - some may still be caused by access to API methods when the interface is not yet generated - // but also the real errors do not need to be brought to the developer's attention - they will appear in the editor anyway. - // log.error("[reportDiagnostic] ", diagnostic.code, ":: [in ", formatHost.getCanonicalFileName(diagnostic.file.fileName), "] ", ts.flattenDiagnosticMessageText( diagnostic.messageText, formatHost.getNewLine())); + // Filter out unknown compiler option warnings. + // When the user's tsconfig contains options the bundled TypeScript version doesn't recognize + // (e.g. options introduced in newer TS versions), these diagnostics are noise — + // the user's own TS installation validates the tsconfig. + if (diagnostic.code === 5023 || diagnostic.code === 5025) { + return; + } + + // The remaining errors MAY be real — some may still be caused by access to API + // methods when the interface is not yet generated. + // In watch mode they can be ignored (the editor shows them). In run-once / CI + // mode we log them so they're visible in the build output. + if (!options.watchMode) { + log.warn( + ts.formatDiagnosticsWithColorAndContext([diagnostic], { + getCurrentDirectory: () => ts.sys.getCurrentDirectory(), + getCanonicalFileName: (f) => f, + getNewLine: () => ts.sys.newLine, + }), + ); + } } /** @@ -178,9 +196,9 @@ function reportWatchStatusChanged(diagnostic: ts.Diagnostic) { ); } } else { - // should not happen - throw new Error( - `reportWatchStatusChanged: diagnostic.code !== 6031 or 6032 or 6193 or 6194, it is: ${diagnostic.code}`, + // other diagnostic codes (e.g. unknown compiler option warnings) can be ignored + log.debug( + `reportWatchStatusChanged: ignoring diagnostic code ${diagnostic.code}`, ); } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7fa04f1bcdc..c2364837686 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -287,6 +287,9 @@ importers: loglevel: specifier: 1.9.2 version: 1.9.2 + typescript: + specifier: 6.0.3 + version: 6.0.3 yargs: specifier: 18.2.0 version: 18.2.0 @@ -330,9 +333,6 @@ importers: ts-jest: specifier: 29.4.13 version: 29.4.13(@babel/core@7.29.7(supports-color@8.1.1))(@jest/transform@30.5.2(supports-color@8.1.1))(@jest/types@30.5.1)(babel-jest@30.5.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(jest-util@30.5.1)(jest@30.5.2(@types/node@24.13.6)(supports-color@8.1.1))(typescript@6.0.3) - typescript: - specifier: 6.0.3 - version: 6.0.3 test-packages/openui5-snapshot-test: dependencies: