Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/bundle-typescript6.md
Original file line number Diff line number Diff line change
@@ -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.
205 changes: 205 additions & 0 deletions docs/TS7.md

Large diffs are not rendered by default.

7 changes: 2 additions & 5 deletions packages/ts-interface-generator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Comment thread
akudev marked this conversation as resolved.
"yargs": "18.2.0"
},
"peerDependencies": {
"typescript": ">=5.2.0 <7.0.0"
},
"files": [
"dist"
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ export function getProgramInfo(
const options = program.getCompilerOptions();
if (options.baseUrl) {
basePath = path.normalize(options.baseUrl);
} else if ((options as Record<string, unknown>).pathsBasePath) {
// When "paths" is specified without "baseUrl" (which is removed in TS7),
// TS6 internally populates the "pathsBasePath" property to the tsconfig directory.
basePath = path.normalize(
(options as Record<string, unknown>).pathsBasePath as string,
);
Comment thread
akudev marked this conversation as resolved.
}

// loop all files, filter for relevant ones, and extract knowledge about all their module exports
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 baseUrl is absent (as in TS7 projects).
config.baseUrl = testCaseDir;
}
return config;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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); }
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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); }
}
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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/*": ["./*"]
}
}
}
32 changes: 25 additions & 7 deletions packages/ts-interface-generator/src/typeScriptEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function initialize(
{ noEmit: true },
ts.sys,
ts.createSemanticDiagnosticsBuilderProgram,
options.watchMode ? reportDiagnostic : undefined,
reportDiagnostic,
Comment thread
akudev marked this conversation as resolved.
options.watchMode ? reportWatchStatusChanged : undefined,
);

Expand Down Expand Up @@ -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;
Comment thread
akudev marked this conversation as resolved.
Comment thread
akudev marked this conversation as resolved.
}

// 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,
}),
);
}
}

/**
Expand Down Expand Up @@ -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}`,
);
}
}
Expand Down
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading