Skip to content
Back to skills

Source 13c9fe9a292e5a1b151feb5d

ASecurity

A skill for converting UI5 (SAPUI5/OpenUI5) projects to TypeScript.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
developmentjavascripttypescriptgojavabashnodeapidocumentation

Works with

  • terminal
  • cli
  • api
  • mcp

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 4 files and shows the line behind each finding

Scanned November 7, 2026

npx -y skills add gutsofpratik/OpenClaude-Skills --skill source-13c9fe9a292e5a1b151feb5d --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Source 13c9fe9a292e5a1b151feb5d?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Source 13c9fe9a292e5a1b151feb5d
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/gutsofpratik-source-13c9fe9a292e5a1b151feb5d/badge)](https://www.skillsdirectory.com/skills/gutsofpratik-source-13c9fe9a292e5a1b151feb5d)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: ui5-typescript-conversion
description: A skill for converting UI5 (SAPUI5/OpenUI5) projects to TypeScript.
---

# UI5 TypeScript Conversion Guidelines

> How to convert a UI5 (SAPUI5/OpenUI5) project to TypeScript: general rules, project setup changes, code conversion, and test conversion (separate file).

## General Conversion Rules

### Preserve ALL comments

You MUST preserve existing JSDoc, documentation and comments - never remove JSDoc or comments during the conversion. When converting to a class, add `@namespace` but keep ALL existing JSDoc.

Before:
```js
/**
 * My cool controller, it does things.
 */
return Controller.extend("com.myorg.myapp.controller.BaseController", {
    /**
     * Convenience method for accessing the component of the controller's view.
     * @returns {sap.ui.core.Component} The component of the controller's view
     */
    getOwnerComponent: function () {
        return Controller.prototype.getOwnerComponent.call(this);
    },
});
```

After:
```ts
/**
 * My cool controller, it does things.
 * @namespace com.myorg.myapp.controller
 */
export default class BaseController extends Controller {
    /**
     * Convenience method for accessing the component of the controller's view.
     * @returns {sap.ui.core.Component} The component of the controller's view
     */
    public getOwnerComponent(): UIComponent {
        return super.getOwnerComponent() as UIComponent;
    }
}
```

### Be diligent

Carefully respect all guidelines in this document. Before each conversion step, consider all relevant details.

### Go step-by-step

Convert step by step: TypeScript project setup first, then central files other files depend on, so typed versions are available for consumers. `"allowJs": true` in `tsconfig.json` allows semi-converted projects.

### Avoid `any` type

Find the proper type or create an interface instead of `any`:
```ts
// BAD: (this.getOwnerComponent() as any).getContentDensityClass();
// GOOD:
(this.getOwnerComponent() as AppComponent).getContentDensityClass()
```

### Avoid `unknown` casts

Import and use actual UI5 control types. Inspect the XMLView to find which control type you get from `this.byId(...)`. Use specific event types like `Route$PatternMatchedEvent`.

```ts
// BAD: (this.byId("form") as unknown as {setVisible: (v: boolean) => void}).setVisible(false);
// GOOD:
import SimpleForm from "sap/ui/layout/form/SimpleForm";
(this.byId("form") as SimpleForm).setVisible(false);
```

### Create shared type definitions

Create shared types in a central location like `src/types/`.

## Project Setup Conversion

### 1. package.json

Add the following dev dependencies if not already present:

{{dependencies}}

Do not increase existing major versions. Do not remove existing dependencies.

**IMPORTANT**: Also add `@sapui5/types` (or `@openui5/types`) matching the UI5 project version as dev dependency. Framework type and version from ui5.yaml or `get_project_info` MCP tool.

If dependencies changed, ensure `npm install` / `yarn install` is run. The `typescript-eslint` dependency is only relevant when the project already has eslint. Also add `"ts-typecheck": "tsc --noEmit"` script to `package.json`.

### 2. tsconfig.json

Add a tsconfig.json. Use this as reference, adapt paths to the project:

```json
{
	"compilerOptions": {
		"target": "es2023",
		"module": "es2022",
		"moduleResolution": "node",
		"skipLibCheck": true,
		"allowJs": true,
		"strict": true,
		"strictNullChecks": false,
		"strictPropertyInitialization": false,
		"outDir": "./dist",
		"rootDir": "./webapp",
		"types": ["@sapui5/types", "@types/jquery", "@types/qunit"],
		"paths": {
			"com/myorg/myapp/*": ["./webapp/*"],
			"unit/*": ["./webapp/test/unit/*"],
			"integration/*": ["./webapp/test/integration/*"]
		}
	},
	"exclude": ["./webapp/test/e2e/**/*"],
	"include": ["./webapp/**/*"]
}
```

### 3. ui5.yaml

Add `ui5-tooling-transpile-task` and `ui5-tooling-transpile-middleware`:

```yaml
builder:
  customTasks:
    - name: ui5-tooling-transpile-task
      afterTask: replaceVersion
server:
  customMiddleware:
    - name: ui5-tooling-transpile-middleware
      afterMiddleware: compression
    - name: ui5-middleware-livereload
      afterMiddleware: compression
```

Avoid duplicate entries — add to existing `server`/`builder` sections if they exist.

### 4. Eslint configuration

Only when eslint is already set up, enhance it with TypeScript-specific parts. Example eslint v9 `eslint.config.mjs`:

```js
import eslint from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";

export default tseslint.config(
	eslint.configs.recommended,
	...tseslint.configs.recommended,
	...tseslint.configs.recommendedTypeChecked,
	{
		languageOptions: {
			globals: {
				...globals.browser,
				sap: "readonly"
			},
			ecmaVersion: 2023,
			parserOptions: {
				project: true,
				tsconfigRootDir: import.meta.dirname
			}
		}
	},
	{
		ignores: ["eslint.config.mjs"]
	}
);
```

## Application Code Conversion

### Step 1: Change UI5 class syntax to ES class syntax

Convert `SuperClass.extend(...)` to a standard `class`. Properties in the config object (second `extend` parameter) become class members. Annotate the class with `@namespace` in a JSDoc comment (it must immediately precede the class declaration) — the namespace is the part of the full name (first `extend` parameter) that precedes the class name.

Before:
```js
var App = Controller.extend("ui5tssampleapp.controller.App", {
    onInit: function _onInit() {
        // apply content density mode to root view
        this.getView().addStyleClass(this.getOwnerComponent().getContentDensityClass());
    }
});
```

After:
```ts
/**
 * @namespace ui5tssampleapp.controller
 */
export default class App extends Controller {
    public onInit(): void {
        // apply content density mode to root view
        this.getView().addStyleClass((this.getOwnerComponent() as AppComponent).getContentDensityClass());
    }
}
```

### Step 2: Change to ECMAScript modules and imports

Convert `sap.ui.define(...)` to ES imports + `export default`. Convert `sap.ui.require(...)` to imports (no export). Avoid name clashes between imported modules.

Before:
```js
sap.ui.define(["sap/ui/core/mvc/Controller"], function (Controller) {
    class App extends Controller {
        // ... as above
    }
    return App;
});
```

After:
```ts
import Controller from "sap/ui/core/mvc/Controller";

/**
 * @namespace ui5tssampleapp.controller
 */
export default class App extends Controller {
    // ... as above
}
```

Dynamic `sap.ui.require` inside method bodies → dynamic import:
```ts
import("sap/m/MessageBox").then((MessageBox) => { /* ... */ });
```

> Hint: importing `sap/ui/core/Core` provides the singleton instance, not the class.

### Step 3: Standard TypeScript Code Adaptations

- Add type information to method parameters and variables where needed.
- Add missing private member class variables (with types) to the beginning of the class definition. (In JavaScript they are often created on-the-fly during the instance lifetime.)
- Convert `someFunction.bind(...)` to arrow functions (TypeScript does not propagate the bound `this` type into the function body).
- Define further types and structures as needed.

> IMPORTANT: Never use a UI5 type with its global namespace (like `sap.m.Button`). Always import it from the module (like `sap/m/Button`) and use the imported name.

Wrong:
```ts
const b: sap.m.Button;
function getPopup(): sap.ui.core.Popup { /* ... */ }
```

Correct:
```ts
import Button from "sap/m/Button";
import Popup from "sap/ui/core/Popup";

const b: Button;
function getPopup(): Popup { /* ... */ }
```

**Use UI5 control event types**, not browser events like `Event` or `MouseEvent` — UI5 events are different:

```ts
import Button from "sap/m/Button";
import { Button$PressEvent } from "sap/m/Button";
import { Table$RowSelectionChangeEvent } from "sap/ui/table/Table";

export default class Main extends BaseController {
    onPress(oEvent: Button$PressEvent): void {
        const button = oEvent.getSource() as Button;
    }

    onRowSelectionChange(oEvent: Table$RowSelectionChangeEvent): void {
        const selectedContext = oEvent.getParameter("rowContext");
    }
}
```

> For any event XYZ of a UI5 control ABC, types `ABC$XYZEvent` and `ABC$XYZEventParameters` are available.

Use the most specific type that provides all needed properties: `KeyboardEvent`/`MouseEvent` not `Event` for browser events; `Button$PressEvent` not `sap/ui/base/Event`.

### Step 4: Casts for Return Values of Generic Methods

Generic methods return the super-type of all possible types although in practice it will usually be a specific sub-type. Cast the return value to the specific sub-type when needed; derive the actual type from context. This often requires an additional import. Most prominently affected: `core.byId()`/`view.byId()`, `control.getBinding()`, `ownerComponent.getModel()`, `event.getSource()`, `component.getRootControl()`, `this.getOwnerComponent()`.

For the app controller example above, this adds an import of the app's component (`AppComponent`) so the cast can be done — without it, `getOwnerComponent()` returns a `sap.ui.core.Component`, which does not have the `getContentDensityClass` method.

```ts
import Controller from "sap/ui/core/mvc/Controller";
import AppComponent from "../Component";

/**
 * @namespace ui5tssampleapp.controller
 */
export default class App extends Controller {
    public onInit(): void {
        // apply content density mode to root view
        this.getView().addStyleClass((this.getOwnerComponent() as AppComponent).getContentDensityClass());
    }
}
```

Do not cast to a superclass when it's already the returned type. Avoid guessing — skip the cast if the actual type isn't clear.

### Step 5: Solving any Remaining Issues

At this point remaining TypeScript errors should be vastly reduced. Fix clearly recognizable ones. In case of doubt, mention the last remaining issues to the developer.

## UI5 Control TypeScript Conversion Guidelines

Converting custom UI5 controls requires specific patterns beyond the general conversion. This applies to single custom controls within applications and to control libraries.

### The Runtime-Generated Methods Problem (CRITICAL)

**This is the most important aspect to understand.**

UI5 generates getter/setter (and more) methods for properties, aggregations, associations, and events at **runtime**. TypeScript cannot see them at development time. A control with a `text` property in its metadata will have `getText()`/`setText()` at runtime, but TypeScript errors on `control.getText()`. TypeScript also does not know the constructor's settings-object structure. This affects property getters/setters (`getText`, `setText`, `bindText`), aggregation methods (`addItem`, `removeItem`, `getItems`), association methods (`getLabel`, `setLabel`), event methods (`attachPress`, `detachPress`, `firePress`), and the constructor settings object.

### The Solution: @ui5/ts-interface-generator

```sh
npm install --save-dev @ui5/ts-interface-generator@{{ts-interface-generator-version}}
```

Add a script to `package.json` to make subsequent development easier:
```json
{
    "scripts": {
        "watch:controls": "npx @ui5/ts-interface-generator --watch"
    }
}
```

NOTE: if the tsconfig covering the controls is in a subdirectory or has a different name, use `--config path/to/tsconfig.json`.

After converting all controls, run the generator once:
```bash
npm run watch:controls
```

It generates `*.gen.d.ts` files with interfaces for all runtime-generated methods, which TypeScript merges with the control class. Commit these files; never edit them manually.

### Required Constructor Signatures (CRITICAL MANUAL STEP)

Copy the constructor signatures from the generator's terminal output into the beginning of the class body, before the metadata definition:

```ts
export default class MyControl extends Control {
    // The following three lines were generated and should remain as-is to make TypeScript aware of the constructor signatures
    constructor(id?: string | $MyControlSettings);
    constructor(id?: string, settings?: $MyControlSettings);
    constructor(id?: string, settings?: $MyControlSettings) { super(id, settings); }

    static readonly metadata: MetadataOptions = {
        // ...
    };
}
```

### Control Metadata Typing

The control metadata must be typed as `MetadataOptions`:

```ts
import type { MetadataOptions } from "sap/ui/core/Element";

export default class MyControl extends Control {
    static readonly metadata: MetadataOptions = {
        properties: {
            "text": "string"
        }
    };
}
```

- Import from `sap/ui/core/Element` (or the closest base class: `ManagedObject`, `Component`); use `import type` (design-time only).
- Available since UI5 1.110; use `object` for earlier versions.
- Typing prevents issues when inheriting from the control (inherited properties should not be repeated).

### Namespace Annotation Required

The `@namespace` JSDoc annotation is **required** for the transformer to generate correct UI5 class names:

```ts
/**
 * @namespace ui5.typescript.helloworld.control
 */
export default class MyControl extends Control {
    // ...
}
```

### Export Pattern

**Must use `export default` immediately** — a separate export breaks ts-interface-generator:

```ts
// CORRECT:
export default class MyControl extends Control {
    // ...
}

// WRONG:
class MyControl extends Control {
    // ...
}
export default MyControl;
```

### Static Members for Metadata and Renderer

Both metadata and renderer are `static` class members. The renderer can be inline or in a separate file:

```ts
import Control from "sap/ui/core/Control";
import type { MetadataOptions } from "sap/ui/core/Element";
import RenderManager from "sap/ui/core/RenderManager";

/**
 * @namespace ui5.typescript.helloworld.control
 */
export default class MyControl extends Control {
    static readonly metadata: MetadataOptions = {
        properties: {
            "text": "string"
        },
        events: {
            "press": {}
        }
    };

    static renderer = {
        apiVersion: 2,
        render: function (rm: RenderManager, control: MyControl): void {
            rm.openStart("div", control);
            rm.openEnd();
            rm.text(control.getText());
            rm.close("div");
        }
    };

    onclick(): void {
        this.firePress();
    }
}
```

When the renderer is in a separate file (common in libraries), it should stay separate — import it (`import MyControlRenderer from "./MyControlRenderer";`) and assign `static renderer = MyControlRenderer;`.

### Library-Specific Guidelines

When converting entire control libraries (not just single controls in apps), additional steps are required.

#### Library Module with Enums (CRITICAL to avoid XSS issues!)

In `library.ts`, enums must be attached to the global library object for UI5 runtime compatibility:

```ts
import ObjectPath from "sap/base/util/ObjectPath";

export enum ExampleColor {
    Red = "Red",
    Green = "Green",
    Blue = "Blue"
}

// CRITICAL: Attach to global library object
const thisLib = ObjectPath.get("com.myorg.myui5lib") as {[key: string]: unknown};
thisLib.ExampleColor = ExampleColor;
```

**Why this is critical for every enum in the library:**
- Control properties reference types as global names: `type: "com.myorg.myui5lib.ExampleColor"`.
- The UI5 runtime needs to find the enum via this global path to validate the property type.
- Without the attachment, UI5 cannot validate the type → unchecked content can be written to HTML → XSS vulnerability.

#### Path Mapping in tsconfig.json

For libraries, add path mappings for the library namespace:

```json
{
    "compilerOptions": {
        "paths": {
            "com/myorg/mylib/*": ["./src/*"]
        }
    }
}
```

### Control Conversion Checklist

Convert to ES6 class/module with `@namespace` and immediate `export default`; type metadata as `MetadataOptions`; define metadata and renderer as `static` members; install and run `@ui5/ts-interface-generator` and copy the constructor signatures from its output; attach enums to the global library object if in a library; preserve all JSDoc.

## Test Conversion

There are critical, non-obvious patterns for converting UI5 test code from JavaScript to TypeScript. See [the test conversion document](./references/test_conversion.md) for details when tests need to be converted.

Files in this skill

  • PROVENANCE.json253 B
  • SKILL.md16.2 KB
  • UPSTREAM-LICENSE.txt11.1 KB
  • references/test_conversion.md6.4 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.