diff --git a/plugins/ui5-modernization/.claude-plugin/plugin.json b/plugins/ui5-modernization/.claude-plugin/plugin.json new file mode 100644 index 0000000..08126d2 --- /dev/null +++ b/plugins/ui5-modernization/.claude-plugin/plugin.json @@ -0,0 +1,22 @@ +{ + "name": "ui5-modernization", + "version": "0.1.0", + "description": "Complete UI5 modernization toolkit with workflow and specialized fix patterns for producing modernized UI5 code in SAPUI5/OpenUI5 applications", + "author": { + "name": "SAP SE" + }, + "homepage": "https://github.com/UI5/plugins-claude", + "repository": "https://github.com/UI5/plugins-claude", + "license": "Apache-2.0", + "keywords": [ + "sap", + "ui5", + "sapui5", + "openui5", + "claude", + "plugin", + "migration", + "linter", + "modernization" + ] +} diff --git a/plugins/ui5-modernization/.mcp.json b/plugins/ui5-modernization/.mcp.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/plugins/ui5-modernization/.mcp.json @@ -0,0 +1 @@ +{} diff --git a/plugins/ui5-modernization/README.md b/plugins/ui5-modernization/README.md new file mode 100644 index 0000000..a68d9c5 --- /dev/null +++ b/plugins/ui5-modernization/README.md @@ -0,0 +1,259 @@ +# UI5 Modernization Plugin + +A comprehensive plugin providing a complete toolkit for modernizing SAPUI5/OpenUI5 applications by removing deprecated APIs and producing modernized UI5 code. + +## Overview + +Modernized UI5 code removes all deprecated APIs and global namespace access. This plugin provides: + +- **Autonomous migration workflow** with end-to-end orchestration +- **Specialized fix skills** for every linter rule category +- **Validation** at every step via UI5 linter integration + +## Skills Included + +This plugin bundles 14 specialized skills for producing modernized UI5 code: + +### Migration Workflow + +- **`/modernize-ui5`** - Autonomous end-to-end workflow: runs linter, applies autofix, uses specialized skills for remaining errors, documents unfixable issues, and generates a summary report + +### Global Namespace Fixes + +- **`/fix-js-globals`** - Fix JavaScript `no-globals` errors: global namespace assignments, delete expressions, `sap.ui.core.Core` direct access, jQuery/$ global calls +- **`/fix-xml-globals`** - Fix XML view/fragment globals: global variable access, ambiguous event handlers, formatters, type references in bindings, factory functions, legacy `template:require` syntax +- **`/fix-pseudo-modules`** - Fix pseudo module and implicit global issues: deprecated enum/DataType pseudo module access, direct `library.EnumName` access, OData expression addons in bindings + +### Deprecated API Fixes + +- **`/fix-deprecated-controls`** - Fix deprecated controls, classes, interfaces, and types with modern replacements (e.g., `sap.m.MessagePage` to `IllustratedMessage`) +- **`/fix-partially-deprecated-apis`** - Fix partially deprecated API usage: `Parameters.get`, `JSONModel.loadData`, `Mobile.init`, `ODataModel.v2.createEntry`, `View.create`, `Fragment.load`, `Router` constructor, string formatters in JS bindings +- **`/fix-table-row-mode`** - Migrate deprecated Table row properties (`visibleRowCountMode`, `visibleRowCount`, `rowHeight`, `fixedRowCount`, `fixedBottomRowCount`, `minAutoRowCount`) to structured `rowMode` aggregation + +### Component and Bootstrap Fixes + +- **`/fix-component-async`** - Fix Component.js async configuration: `IAsyncContentCreation` interface, manifest declaration, redundant async flags +- **`/fix-bootstrap-params`** - Fix HTML bootstrap parameter issues: missing/deprecated parameters (`async`, `compat-version`, `animation`, `binding-syntax`), deprecated theme values, deprecated libraries +- **`/fix-manifest-json`** - Fix manifest.json issues: outdated manifest version, legacy UI5 version, deprecated libraries/components, deprecated view/model types, removed properties + +### Control and Rendering Fixes + +- **`/fix-control-renderer`** - Fix Control renderer issues: missing renderer declaration, string-based renderer, implicit auto-discovery (breaking change in modernized UI5 code), `apiVersion:2` configuration +- **`/fix-csp-compliance`** - Fix Content Security Policy compliance: unsafe inline scripts, inline JavaScript in HTML, inline event handlers +- **`/fix-xml-native-html`** - Fix native HTML and SVG usage in XML views/fragments: replace `html:div`, `html:span`, `html:a` with UI5 controls, SVG with UI5 icons + +### Test Infrastructure + +- **`/fix-test-starter`** - Migrate from legacy QUnit test setup to modern Test Starter concept for `*.qunit.html` and `*.qunit.js` files + +## Quick Start + +### 1. Start with the Migration Workflow + +The main entry point is the autonomous workflow skill which orchestrates the entire migration: + +``` +/modernize-ui5 +``` + +This will autonomously: +- Run UI5 linter to assess the current state +- Apply autofix for issues the linter can resolve +- Use specialized skills for remaining errors +- Document unfixable issues +- Generate a summary report + +### 2. Use Specialized Skills for Specific Issues + +When you encounter specific error patterns, use the targeted skills: + +``` +# Global namespace issues +/fix-js-globals # For no-globals errors in JS files +/fix-xml-globals # For no-globals errors in XML views/fragments +/fix-pseudo-modules # For pseudo module and implicit global issues + +# Deprecated API migrations +/fix-deprecated-controls # For deprecated controls, classes, interfaces +/fix-partially-deprecated-apis # For partially deprecated API calls +/fix-table-row-mode # For deprecated Table row properties + +# Component and bootstrap +/fix-component-async # For Component.js async issues +/fix-bootstrap-params # For HTML bootstrap parameters +/fix-manifest-json # For manifest.json issues + +# Control and rendering +/fix-control-renderer # For control renderer issues +/fix-csp-compliance # For CSP compliance issues +/fix-xml-native-html # For native HTML/SVG in XML views + +# Test infrastructure +/fix-test-starter # For Test Starter migration +``` + +## Understanding Modernized UI5 Code Requirements + +### Critical Success Criteria + +For your app to produce modernized UI5 code, you MUST fix **ALL linter errors (severity 2)** reported by the UI5 linter: + +**Common error categories:** +1. **`no-deprecated-api`** - All deprecated APIs that must be replaced (most common) +2. **`no-globals`** - All global namespace access must use proper imports (most common) +3. **Other rule violations** - Any additional errors specific to your codebase + +**Check your progress:** +```bash +# Count ALL critical errors (must be 0) +npx @ui5/linter --format json 2>&1 | jq '[.[] | select(.messages) | .messages[] | select(.severity == 2)] | length' + +# Count by specific categories (for tracking progress) +npx @ui5/linter --format json 2>&1 | jq '[.[] | select(.messages) | .messages[] | select(.severity == 2)] | group_by(.ruleId) | map({rule: .[0].ruleId, count: length})' +``` + +### Error vs Warning Priority + +- **Errors (severity 2)** 🔴 - MUST fix ALL for modernized UI5 code +- **Warnings (severity 1)** ⚠️ - Can address later (non-blocking) + +The migration workflow focuses on `no-globals` and `no-deprecated-api` as these are the most common error categories, but you must address ALL errors to achieve fully modernized UI5 code. + +## Migration Phases Overview + +The `/modernize-ui5` workflow handles the following phases autonomously: + +``` +Phase 1: Assessment +├── Run linter baseline +├── Count all critical errors (severity 2) +└── Create migration branch + +Phase 2: Autofix +├── Run linter with --fix flag +└── Review and validate autofix changes + +Phase 3: Fix Remaining Errors +├── Apply specialized skills per linter rule +├── Track progress continuously +└── Document unfixable issues + +Phase 4: Validation and Completion +├── Run final linter check (0 errors required) +├── Build verification +└── Generate migration summary report +``` + +## Key Features + +### Autonomous Execution + +The `/modernize-ui5` workflow is designed for **autonomous execution**: + +1. Runs the full migration end-to-end without constant confirmation +2. Applies autofix first, then uses specialized skills for remaining errors +3. Tracks progress via continuous linter error counts +4. Documents unfixable issues separately +5. Generates a comprehensive summary report + +### Validation Tools + +Built-in validation at every step: + +- **Linter integration** - Continuous error count tracking +- **Build verification** - Confirms no breaking changes +- **Detailed guidance** - UI5 Linter `--details` flag for immediate fix guidance +- **UI5 MCP Server** - API documentation and replacements + +## Prerequisites + +Before using this plugin, ensure you have: + +1. **UI5 application** on version 1.71+ (preferably 1.120+) +2. **UI5 linter installed**: `npm install --save-dev @ui5/linter` +3. **Git repository** with clean working directory +4. **Test suite** working (QUnit/OUnit/OPA) +5. **Build system** working (UI5 CLI or Maven) + +## Expected Results + +### Before Migration + +- no-deprecated-api errors: 200-500+ ❌ +- no-globals errors: 100-300+ ❌ +- Build: ✅ Success +- Tests: ✅ Passing + +### After Migration (Modernized UI5 Code) + +- **ALL LINTER ERRORS: 0** ✅ (REQUIRED - any severity 2 error means code is not fully modernized) + - no-deprecated-api errors: 0 ✅ + - no-globals errors: 0 ✅ + - Other rule errors: 0 ✅ +- Linter warnings: <100 ⚠️ (acceptable) +- Build: ✅ Success +- Tests: ✅ Passing + +## Estimated Timeline + +For a large enterprise application (300+ JS files): + +- **Phase 0 (Assessment)**: 1 day +- **Phase 1-5 (Critical Errors)**: 16-27 days +- **Phase 6-7 (Supporting)**: 2 days +- **Phase 8-10 (Validation)**: 5-7 days +- **Total**: 24-40 days + +Smaller applications will take proportionally less time. + +## Troubleshooting + +### Getting Detailed Error Information + +```bash +# Get fix guidance for all errors +npx @ui5/linter --details + +# Get fix guidance for specific file +npx @ui5/linter --details path/to/file.js +``` + +## Support and Resources + +### Official UI5 Documentation + +- [Migration Guide](https://ui5.sap.com/#/topic/9638e4fce4de4fa8ae8f3b6e01d43b68) +- [Deprecated APIs](https://ui5.sap.com/#/topic/a6e6b1f06b1a413d8afe36afc6a8cadf) + +### Using with UI5 MCP Server + +This plugin works great with the UI5 MCP Server for API documentation: + +- Query deprecated APIs +- Find modern replacements +- Get code examples +- View parameter details + +## Installation + +First, add the marketplace (one-time setup): + +```bash +/plugin marketplace add https://github.com/UI5/plugins-claude.git +``` + +Then install this plugin: + +```bash +/plugin install ui5-modernization +``` + +For more details, see the [marketplace README](../../README.md). + +## License + +Apache-2.0 + +## Version + +0.1.0 diff --git a/plugins/ui5-modernization/skills/fix-bootstrap-params/SKILL.md b/plugins/ui5-modernization/skills/fix-bootstrap-params/SKILL.md new file mode 100644 index 0000000..bc8171c --- /dev/null +++ b/plugins/ui5-modernization/skills/fix-bootstrap-params/SKILL.md @@ -0,0 +1,172 @@ +--- +name: fix-bootstrap-params +description: | + Fix HTML bootstrap parameter issues that UI5 linter reports but cannot auto-fix. Use this skill when linter outputs these rules: + - `no-deprecated-api` - For missing/deprecated bootstrap parameters (async, compat-version, animation, binding-syntax, etc.) + - `no-deprecated-theme` - For deprecated theme values in data-sap-ui-theme + - `no-deprecated-library` - For deprecated libraries in data-sap-ui-libs + Trigger on error messages containing: "Missing bootstrap parameter", "Abandoned bootstrap parameter", "Redundant bootstrap parameter", "deprecated value", "bootstrap" + Automatically applies safe fixes to HTML files containing UI5 bootstrap script tags. +--- + +# Fix Bootstrap Parameters + +This skill fixes HTML bootstrap parameter issues that the UI5 linter detects but cannot auto-fix because the changes may affect application behavior. + +## Linter Rules Handled + +| Rule ID | Message Pattern | This Skill's Action | +|---------|-----------------|---------------------| +| `no-deprecated-api` | Missing bootstrap parameter 'data-sap-ui-async' | Add `data-sap-ui-async="true"` | +| `no-deprecated-api` | Missing bootstrap parameter 'data-sap-ui-compat-version' | Add `data-sap-ui-compat-version="edge"` | +| `no-deprecated-api` | Use of deprecated value 'false' for bootstrap parameter 'data-sap-ui-async' | Change to `"true"` | +| `no-deprecated-api` | Use of deprecated value '...' for bootstrap parameter 'data-sap-ui-compat-version' | Change to `"edge"` | +| `no-deprecated-api` | Abandoned bootstrap parameter '...' should be removed | Remove the parameter | +| `no-deprecated-api` | Redundant bootstrap parameter '...' should be removed | Remove the parameter | +| `no-deprecated-api` | Bootstrap parameter '...' should be replaced with '...' | Replace with new parameter | +| `no-deprecated-theme` | Use of deprecated theme '...' | Change to `sap_horizon` | +| `no-deprecated-library` | Use of deprecated library '...' | Remove from libs | + +## When to Use + +Apply this skill when you see linter output like: +``` +index.html:8:3 error Missing bootstrap parameter 'data-sap-ui-async' no-deprecated-api +index.html:9:3 error Missing bootstrap parameter 'data-sap-ui-compat-version' no-deprecated-api +index.html:12:3 error Abandoned bootstrap parameter 'data-sap-ui-no-duplicate-ids' should be removed no-deprecated-api +index.html:15:3 error Use of deprecated theme 'sap_bluecrystal' no-deprecated-theme +``` + +## Fix Strategy + +### 1. Locate the Bootstrap Script Tag + +Find the ` + + + +``` + +#### `no-deprecated-api` - Deprecated Values + +**`data-sap-ui-async="false"`**: Change to `"true"` +**`data-sap-ui-compat-version` with non-edge value**: Change to `"edge"` + +#### `no-deprecated-api` - Abandoned Parameters (remove completely) + +Remove these parameters entirely: +- `data-sap-ui-no-duplicate-ids` - Enforced in modernized UI5 code +- `data-sap-ui-auto-aria-body-role` - Removed in modernized UI5 code +- `data-sap-ui-manifest-first` - Use Component.create manifest option instead +- `data-sap-ui-origin-info` - No longer supported +- `data-sap-ui-areas` - Use Control.placeAt instead +- `data-sap-ui-trace` - No longer supported +- `data-sap-ui-xx-no-less` - No longer supported + +#### `no-deprecated-api` - Redundant Parameters + +- `data-sap-ui-binding-syntax="simple"` - Remove; complex syntax is enforced in modernized UI5 code +- `data-sap-ui-binding-syntax="complex"` - Remove if `compat-version="edge"` is set +- `data-sap-ui-preload` with invalid values - Remove + +#### `no-deprecated-api` - Replaced Parameters + +- `data-sap-ui-animation` → `data-sap-ui-animation-mode` (convert `true`→`full`, `false`→`minimal`) + +#### `no-deprecated-theme` - Deprecated Themes + +Replace with modern theme: +- `sap_bluecrystal` → `sap_horizon` +- `sap_belize` → `sap_horizon` +- `sap_belize_plus` → `sap_horizon` +- `sap_belize_hcb` → `sap_horizon_hcb` +- `sap_belize_hcw` → `sap_horizon_hcw` +- `sap_hcb` → `sap_horizon_hcb` +- `sap_ux` → `sap_horizon` +- `sap_platinum` → `sap_horizon` +- `sap_goldreflection` → `sap_horizon` + +#### `no-deprecated-library` - Deprecated Libraries + +Remove deprecated libraries from `data-sap-ui-libs`: +- `sap.ui.commons` +- `sap.ui.ux3` +- `sap.makit` +- `sap.me` +- `sap.ca.ui` +- `sap.sac.grid` +- `sap.ui.suite` +- `sap.zen.commons` +- `sap.zen.crosstab` +- `sap.zen.dsh` + +## Implementation Steps + +1. Read the HTML file +2. Parse to find the bootstrap script tag +3. For each linter error (identified by rule ID and message): + - If missing parameter: Add the parameter with recommended value + - If deprecated value: Update to recommended value + - If abandoned/redundant: Remove the parameter + - If deprecated theme: Replace with modern theme + - If deprecated library: Remove from libs list +4. Preserve formatting (indentation, line breaks) as much as possible +5. Write the updated file + +## Example Fix + +Given linter output: +``` +index.html:8:3 error Missing bootstrap parameter 'data-sap-ui-async' no-deprecated-api +index.html:8:3 error Missing bootstrap parameter 'data-sap-ui-compat-version' no-deprecated-api +index.html:12:3 error Abandoned bootstrap parameter 'data-sap-ui-no-duplicate-ids' should be removed no-deprecated-api +index.html:10:3 error Use of deprecated theme 'sap_bluecrystal' no-deprecated-theme +``` + +Transform: +```html + + + + + +``` + +## Notes + +- Always add `data-sap-ui-async="true"` before other data attributes for consistency +- Setting `compat-version="edge"` enables complex binding syntax automatically, so `binding-syntax` becomes redundant +- Removed parameters should not leave trailing whitespace or empty lines +- If the file has multiple script tags, only modify the bootstrap tag diff --git a/plugins/ui5-modernization/skills/fix-component-async/SKILL.md b/plugins/ui5-modernization/skills/fix-component-async/SKILL.md new file mode 100644 index 0000000..d7461d2 --- /dev/null +++ b/plugins/ui5-modernization/skills/fix-component-async/SKILL.md @@ -0,0 +1,236 @@ +--- +name: fix-component-async +description: | + Fix Component.js async configuration issues that UI5 linter reports but cannot auto-fix. Use this skill when linter outputs these rules: + - `async-component-flags` - For missing IAsyncContentCreation interface, missing manifest declaration, redundant async flags, async:false errors + - `no-removed-manifest-property` - For async flags in inline manifest v2 + Trigger on Component.js files with errors about async loading, IAsyncContentCreation, manifest declaration. + Automatically adds the IAsyncContentCreation interface and configures manifest properly. +--- + +# Fix Component.js Async Configuration + +This skill fixes Component.js async configuration issues that the UI5 linter detects but cannot auto-fix because they may require understanding of the component's loading behavior. + +## Linter Rules Handled + +| Rule ID | Message Pattern | This Skill's Action | +|---------|-----------------|---------------------| +| `async-component-flags` | Component is not configured for asynchronous loading | Add `IAsyncContentCreation` interface | +| `async-component-flags` | Component does not specify that it uses the descriptor via the manifest.json file | Add `manifest: "json"` | +| `async-component-flags` | Component implements the sap.ui.core.IAsyncContentCreation interface. The redundant 'async' flag ... should be removed | Remove async flag from manifest | +| `async-component-flags` | The 'async' property at '...' must be removed | Remove `async: false` | +| `no-removed-manifest-property` | Property '...' has been removed in Manifest Version 2 | Remove async property | + +## When to Use + +Apply this skill when you see linter output like: +``` +Component.js:5:1 error Component is not configured for asynchronous loading. async-component-flags +Component.js:5:1 warning Component does not specify that it uses the descriptor via the manifest.json file async-component-flags +Component.js:10:5 warning Component implements the sap.ui.core.IAsyncContentCreation interface. The redundant 'async' flag at '/sap.ui5/rootView/async' should be removed async-component-flags +manifest.json:25:9 error The 'async' property at '/sap.ui5/rootView/async' must be removed async-component-flags +``` + +## Fix Strategy + +### 1. `async-component-flags` - Add IAsyncContentCreation Interface + +The `IAsyncContentCreation` interface is the modern way to declare that a component loads content asynchronously. Add it to the component's interfaces array. + +**For sap.ui.define Syntax:** + +```javascript +// Before +sap.ui.define([ + "sap/ui/core/UIComponent" +], function(UIComponent) { + "use strict"; + + return UIComponent.extend("my.app.Component", { + metadata: { + manifest: "json" + } + }); +}); + +// After +sap.ui.define([ + "sap/ui/core/UIComponent" +], function(UIComponent) { + "use strict"; + + return UIComponent.extend("my.app.Component", { + metadata: { + manifest: "json", + interfaces: ["sap.ui.core.IAsyncContentCreation"] + } + }); +}); +``` + +### 2. `async-component-flags` - Add Manifest Declaration + +If the component doesn't declare it uses a manifest, add `manifest: "json"` to the metadata. + +```javascript +// Before +metadata: { + // no manifest declaration +} + +// After +metadata: { + manifest: "json" +} +``` + +### 3. `async-component-flags` - Remove Redundant Async Flags + +When `IAsyncContentCreation` interface is implemented, the async flags in manifest.json become redundant and should be removed. + +**In manifest.json:** + +```json +// Before +{ + "sap.ui5": { + "rootView": { + "viewName": "my.app.view.Main", + "type": "XML", + "async": true + }, + "routing": { + "config": { + "async": true, + ... + } + } + } +} + +// After +{ + "sap.ui5": { + "rootView": { + "viewName": "my.app.view.Main", + "type": "XML" + }, + "routing": { + "config": { + ... + } + } + } +} +``` + +### 4. `async-component-flags` - Remove async: false + +If `async: false` is explicitly set, this must be removed as it prevents asynchronous loading. + +### 5. Handle Inline Manifest in Component.js + +If the manifest is defined inline in Component.js (not in a separate manifest.json), apply the same fixes to the inline manifest object. + +```javascript +// Before +metadata: { + manifest: { + "sap.ui5": { + "rootView": { + "async": true, + ... + } + } + } +} + +// After +metadata: { + manifest: { + "sap.ui5": { + "rootView": { + ... + } + } + }, + interfaces: ["sap.ui.core.IAsyncContentCreation"] +} +``` + +## Implementation Steps + +1. Read the Component.js file +2. Determine the syntax style (sap.ui.define or ES6 class) +3. For `async-component-flags` errors: + - Check if `IAsyncContentCreation` interface is already declared + - If not, add the interface to `interfaces` array in metadata + - Check if `manifest: "json"` is declared, add if missing +4. Check the manifest.json (or inline manifest) for redundant async flags +5. Remove `async` properties from rootView and routing/config +6. Write the updated files + +## Example Fix + +Given linter output: +``` +Component.js:5:1 error Component is not configured for asynchronous loading. async-component-flags +``` + +**Component.js transformation:** + +```javascript +// Before +sap.ui.define([ + "sap/ui/core/UIComponent", + "sap/ui/model/json/JSONModel" +], function(UIComponent, JSONModel) { + "use strict"; + + return UIComponent.extend("my.app.Component", { + metadata: { + manifest: "json" + }, + + init: function() { + UIComponent.prototype.init.apply(this, arguments); + // ... + } + }); +}); + +// After +sap.ui.define([ + "sap/ui/core/UIComponent", + "sap/ui/model/json/JSONModel" +], function(UIComponent, JSONModel) { + "use strict"; + + return UIComponent.extend("my.app.Component", { + metadata: { + manifest: "json", + interfaces: ["sap.ui.core.IAsyncContentCreation"] + }, + + init: function() { + UIComponent.prototype.init.apply(this, arguments); + // ... + } + }); +}); +``` + +## Notes + +- The `IAsyncContentCreation` interface was introduced in UI5 1.89 - ensure minUI5Version is compatible +- When adding the interface, also ensure `manifest: "json"` is present for proper async loading +- The interface makes `async: true` flags redundant - they can be safely removed +- Components that extend UIComponent (not just Component) can use IAsyncContentCreation +- If the component inherits from a custom base component that already implements IAsyncContentCreation, no changes are needed +- **`IAsyncContentCreation` vs manifest v2**: These are independent concerns. Updating manifest `_version` to `"2.0.0"` does NOT automatically enable async content creation — `IAsyncContentCreation` must still be explicitly implemented in Component.js. Conversely, adding `IAsyncContentCreation` does not require manifest v2. + +## Related Skills + +- **fix-manifest-json**: When adding `IAsyncContentCreation`, the manifest.json `async` flags become redundant — use fix-manifest-json to update `_version`, remove async properties, and rename routing configuration +- **fix-js-globals**: If Component.js uses global access patterns (e.g., `sap.ui.getCore()`), use fix-js-globals to convert them to proper module imports diff --git a/plugins/ui5-modernization/skills/fix-control-renderer/SKILL.md b/plugins/ui5-modernization/skills/fix-control-renderer/SKILL.md new file mode 100644 index 0000000..3724e02 --- /dev/null +++ b/plugins/ui5-modernization/skills/fix-control-renderer/SKILL.md @@ -0,0 +1,468 @@ +--- +name: fix-control-renderer +description: | + Fix Control renderer issues that UI5 linter reports but cannot auto-fix. Use this skill when linter outputs these rules: + - `no-deprecated-control-renderer-declaration` - For missing renderer declaration, string-based renderer declaration, implicit renderer auto-discovery (UI5 1.x loads `Renderer` automatically — removed in modernized UI5 code) + - `no-deprecated-api` - For missing apiVersion:2 in renderer, missing IconPool import when using oRm.icon(), deprecated rerender() override + - `ui5-class-declaration` - For non-static renderer property in ES6 classes + Trigger on error messages about: "missing a renderer declaration", "Deprecated declaration of renderer", "deprecated renderer", "apiVersion", "IconPool", "rerender", "renderer must be a static property" + Automatically converts legacy renderer patterns to modern apiVersion: 2 format with proper module imports. Handles implicit renderer auto-discovery where UI5 1.x automatically loaded a renderer from the default path (e.g., sap/m/ButtonRenderer for sap/m/Button) — in modernized UI5 code this must be explicit. +--- + +# Fix Control Renderer Issues + +This skill fixes Control renderer issues that the UI5 linter detects but cannot auto-fix because they require understanding of the control's rendering behavior and module dependencies. + +## Linter Rules Handled + +| Rule ID | Message Pattern | This Skill's Action | +|---------|-----------------|---------------------| +| `no-deprecated-control-renderer-declaration` | Control '...' is missing a renderer declaration | Add `renderer: null` or import renderer | +| `no-deprecated-control-renderer-declaration` | Deprecated declaration of renderer '...' for control '...' | Import renderer module and assign directly | +| `no-deprecated-api` | Use of deprecated renderer detected. Define explicitly the {apiVersion: 2} | Add `apiVersion: 2` to renderer object | +| `no-deprecated-api` | "sap/ui/core/IconPool" module must be imported when using RenderManager's icon() method | Add IconPool import | +| `no-deprecated-api` | Override of deprecated method 'rerender' in control '...' | Remove override, move code to lifecycle hooks | +| `ui5-class-declaration` | The control renderer must be a static property | Make renderer property static | + +## When to Use + +Apply this skill when you see linter output like: +``` +MyControl.js:5:1 error Control 'my.app.control.MyControl' is missing a renderer declaration no-deprecated-control-renderer-declaration +MyControl.js:10:5 error Deprecated declaration of renderer 'my.app.control.MyControlRenderer' for control 'my.app.control.MyControl' no-deprecated-control-renderer-declaration +MyControl.js:15:5 error Use of deprecated renderer detected. Define explicitly the {apiVersion: 2} parameter no-deprecated-api +MyControl.js:20:5 error "sap/ui/core/IconPool" module must be imported when using RenderManager's icon() method no-deprecated-api +MyControl.js:25:5 warning The control renderer of 'MyControl' must be a static property ui5-class-declaration +``` + +## Background: Why apiVersion: 2? + +UI5's rendering framework evolved from an immediate DOM manipulation model (apiVersion 1) to a semantic rendering model (apiVersion 2 and 4). The key differences: + +- **apiVersion 1 (deprecated)**: Direct DOM manipulation via `oRm.write()`, `oRm.writeAttribute()`, etc. +- **apiVersion 2**: Semantic methods like `oRm.openStart()`, `oRm.openEnd()`, `oRm.text()`, `oRm.close()` +- **apiVersion 4**: Same as 2, with additional performance optimizations (for modernized UI5 code) + +Without explicit `apiVersion`, UI5 assumes legacy rendering which causes synchronous loading and performance issues. + +### Implicit Renderer Auto-Discovery (Removed in Modernized UI5 Code) + +In UI5 1.x, if a control doesn't declare a `renderer` property, the framework automatically tries to load a renderer module by appending `Renderer` to the control's module path. For example, for `sap/m/Button`, it checks whether `sap/m/ButtonRenderer` exists — if so, that module is loaded and used as the renderer. + +This implicit auto-discovery is **removed in modernized UI5 code**. Every control must explicitly declare its renderer. If a control relied on auto-discovery and has no `renderer` property, it will break at runtime. The linter flags this as `no-deprecated-control-renderer-declaration` with the message "Control '...' is missing a renderer declaration". + +The migration workflow is: check whether a `Renderer` module exists at the default path → if yes, import it via `sap.ui.define` → assign it to the `renderer` property. + +## Fix Strategy + +### 1. Missing Renderer Declaration + +**Problem**: Control class doesn't declare a renderer at all. + +```javascript +// Before - triggers no-deprecated-control-renderer-declaration +sap.ui.define([ + "sap/ui/core/Control" +], function(Control) { + "use strict"; + + return Control.extend("my.app.control.MyControl", { + metadata: { + properties: { ... } + } + // No renderer declaration! + }); +}); +``` + +**Fix Strategy A - No rendering needed** (control is abstract or uses child controls): +```javascript +// After - explicitly declare no renderer +sap.ui.define([ + "sap/ui/core/Control" +], function(Control) { + "use strict"; + + return Control.extend("my.app.control.MyControl", { + metadata: { + properties: { ... } + }, + + renderer: null + }); +}); +``` + +**Fix Strategy B - Renderer exists in separate file** (including implicit auto-discovery): + +In UI5 1.x, many controls rely on the framework's implicit auto-discovery — they have no `renderer` property, but a `Renderer.js` file exists at the default path and gets loaded automatically. Since this auto-discovery is removed in modernized UI5 code, you need to make the import explicit. + +**How to find the renderer:** +1. Derive the expected renderer path: take the control's module path and append `Renderer`. For `my/app/control/MyControl`, check for `my/app/control/MyControlRenderer`. +2. Look for the file in the project (e.g., `MyControlRenderer.js` in the same directory as the control). +3. If the renderer file exists, import it and assign it. If it doesn't exist, use Fix Strategy A (`renderer: null`) or Fix Strategy C (inline renderer). + +```javascript +// After - import and assign the renderer module +sap.ui.define([ + "sap/ui/core/Control", + "./MyControlRenderer" +], function(Control, MyControlRenderer) { + "use strict"; + + return Control.extend("my.app.control.MyControl", { + metadata: { + properties: { ... } + }, + + renderer: MyControlRenderer + }); +}); +``` + +**Important**: After importing the renderer, also check whether the renderer module itself has `apiVersion: 2`. If not, that's a separate linter finding — see section 3 "Missing apiVersion in Renderer" below. + +**Fix Strategy C - Add inline renderer**: +```javascript +// After - define renderer inline with apiVersion: 2 +sap.ui.define([ + "sap/ui/core/Control" +], function(Control) { + "use strict"; + + return Control.extend("my.app.control.MyControl", { + metadata: { + properties: { ... } + }, + + renderer: { + apiVersion: 2, + render: function(oRm, oControl) { + oRm.openStart("div", oControl); + oRm.class("myControl"); + oRm.openEnd(); + // Render content here + oRm.close("div"); + } + } + }); +}); +``` + +### 2. String-Based Renderer Declaration + +**Problem**: Renderer declared as string causes synchronous loading. + +```javascript +// Before - triggers no-deprecated-control-renderer-declaration +sap.ui.define([ + "sap/ui/core/Control" +], function(Control) { + "use strict"; + + return Control.extend("my.app.control.MyControl", { + metadata: { ... }, + + renderer: "my.app.control.MyControlRenderer" // String = sync loading! + }); +}); +``` + +**Fix Strategy**: Import the renderer module and assign directly. + +```javascript +// After - import renderer module +sap.ui.define([ + "sap/ui/core/Control", + "my/app/control/MyControlRenderer" +], function(Control, MyControlRenderer) { + "use strict"; + + return Control.extend("my.app.control.MyControl", { + metadata: { ... }, + + renderer: MyControlRenderer + }); +}); +``` + +### 3. Missing apiVersion in Renderer + +**Problem**: Renderer object or function without apiVersion declaration. + +```javascript +// Before - triggers no-deprecated-api +renderer: { + render: function(oRm, oControl) { + oRm.write(""); + oRm.write(""); + } +} + +// OR - function without apiVersion +renderer: function(oRm, oControl) { + oRm.write("
"); + oRm.write("
"); +} +``` + +**Fix Strategy**: Add `apiVersion: 2` and convert to semantic rendering API. + +```javascript +// After - with apiVersion: 2 and semantic methods +renderer: { + apiVersion: 2, + render: function(oRm, oControl) { + oRm.openStart("div", oControl); + oRm.openEnd(); + oRm.close("div"); + } +} +``` + +**apiVersion 1 to apiVersion 2 Method Conversions:** + +| Old Method (apiVersion 1) | New Method (apiVersion 2) | +|---------------------------|---------------------------| +| `oRm.write("")` | `oRm.openEnd()` or `oRm.voidEnd()` | +| `oRm.write("")` | `oRm.close("tag")` | +| `oRm.write(text)` | `oRm.text(text)` | +| `oRm.writeControlData(oCtrl)` | Pass control as 2nd arg: `oRm.openStart("div", oControl)` | +| `oRm.addClass("cls")` | `oRm.class("cls")` | +| `oRm.writeAttribute("name", val)` | `oRm.attr("name", val)` | + +For the complete conversion table with examples, read `references/renderer-api-mapping.md`. + +### 4. Missing IconPool Import + +**Problem**: Using `oRm.icon()` without importing IconPool. + +```javascript +// Before - triggers no-deprecated-api +sap.ui.define([ + "sap/ui/core/Control" +], function(Control) { + "use strict"; + + return Control.extend("my.app.control.MyControl", { + renderer: { + apiVersion: 2, + render: function(oRm, oControl) { + oRm.openStart("div", oControl); + oRm.openEnd(); + oRm.icon("sap-icon://accept"); // IconPool not imported! + oRm.close("div"); + } + } + }); +}); +``` + +**Fix Strategy**: Add IconPool to the imports. The import is required even though it's not directly referenced in code. + +```javascript +// After - IconPool imported +sap.ui.define([ + "sap/ui/core/Control", + "sap/ui/core/IconPool" // Required for oRm.icon() +], function(Control, IconPool) { + "use strict"; + + return Control.extend("my.app.control.MyControl", { + renderer: { + apiVersion: 2, + render: function(oRm, oControl) { + oRm.openStart("div", oControl); + oRm.openEnd(); + oRm.icon("sap-icon://accept"); + oRm.close("div"); + } + } + }); +}); +``` + +### 5. Deprecated rerender() Override + +**Problem**: Overriding `rerender()` method no longer works in UI5 1.121+. + +```javascript +// Before - triggers no-deprecated-api +sap.ui.define([ + "sap/ui/core/Control" +], function(Control) { + "use strict"; + + return Control.extend("my.app.control.MyControl", { + renderer: { ... }, + + rerender: function() { + // Custom logic before rerendering + this._prepareForRender(); + Control.prototype.rerender.apply(this, arguments); + // Custom logic after rerendering + this._finishRender(); + } + }); +}); +``` + +**Fix Strategy**: Move pre-render logic to `onBeforeRendering()` and post-render logic to `onAfterRendering()`. + +```javascript +// After - use lifecycle hooks instead +sap.ui.define([ + "sap/ui/core/Control" +], function(Control) { + "use strict"; + + return Control.extend("my.app.control.MyControl", { + renderer: { ... }, + + onBeforeRendering: function() { + // Called before each render (initial + re-renders) + this._prepareForRender(); + }, + + onAfterRendering: function() { + // Called after each render (initial + re-renders) + this._finishRender(); + } + }); +}); +``` + +## Implementation Steps + +1. **Run linter with --details** to get additional context: + ```bash + npx @ui5/linter --details + ``` + +2. **Identify the error pattern** from linter output (rule ID + message) + +3. **Determine the control's rendering needs**: + - Does the control need custom rendering? + - Is there an existing separate renderer file? Check the default path: `Renderer.js` in the same directory (UI5 1.x auto-discovery path) + - Does the renderer use `oRm.icon()`? + +4. **Apply the appropriate transformation**: + - For missing declaration: Check if `Renderer.js` exists at the default path (auto-discovery). If yes, import and assign it. If no, add `renderer: null` or create an inline renderer + - For string declaration: Convert to module import + - For missing apiVersion: Add `apiVersion: 2` and convert render methods + - For IconPool: Add the import to sap.ui.define dependencies + - For rerender override: Move logic to lifecycle hooks + - For non-static: Add `static` keyword (ES6 classes) + +5. **Verify the fix** by re-running the linter + +## Example Fix Session + +Given linter output: +``` +npx @ui5/linter --details + +MyControl.js:5:1 error Deprecated declaration of renderer 'my.app.control.MyControlRenderer' for control 'my.app.control.MyControl' no-deprecated-control-renderer-declaration + Details: Defining the control renderer by its name may lead to synchronous loading of the control renderer module. +MyControl.js:20:5 error Use of deprecated renderer detected. Define explicitly the {apiVersion: 2} parameter in the renderer object no-deprecated-api + Details: See: https://ui5.sap.com/#/topic/c9ab34570cc14ea5ab72a6d1a4a03e3f +``` + +**Before:** +```javascript +sap.ui.define([ + "sap/ui/core/Control" +], function(Control) { + "use strict"; + + return Control.extend("my.app.control.MyControl", { + metadata: { + properties: { + text: { type: "string", defaultValue: "" } + } + }, + + renderer: "my.app.control.MyControlRenderer" + }); +}); + +// MyControlRenderer.js (separate file) +sap.ui.define([], function() { + "use strict"; + + var MyControlRenderer = {}; + + MyControlRenderer.render = function(oRm, oControl) { + oRm.write(""); + oRm.writeEscaped(oControl.getText()); + oRm.write(""); + }; + + return MyControlRenderer; +}); +``` + +**After:** +```javascript +sap.ui.define([ + "sap/ui/core/Control", + "./MyControlRenderer" +], function(Control, MyControlRenderer) { + "use strict"; + + return Control.extend("my.app.control.MyControl", { + metadata: { + properties: { + text: { type: "string", defaultValue: "" } + } + }, + + renderer: MyControlRenderer + }); +}); + +// MyControlRenderer.js (separate file) - updated +sap.ui.define([], function() { + "use strict"; + + var MyControlRenderer = { + apiVersion: 2 + }; + + MyControlRenderer.render = function(oRm, oControl) { + oRm.openStart("div", oControl); + oRm.class("myControl"); + oRm.openEnd(); + oRm.text(oControl.getText()); + oRm.close("div"); + }; + + return MyControlRenderer; +}); +``` + +## Notes + +- Controls that extend these base classes do NOT need a renderer declaration: + - `sap/ui/core/mvc/View` + - `sap/ui/core/XMLComposite` + - `sap/ui/core/webc/WebComponent` + - `sap/uxap/BlockBase` + +- `apiVersion: 4` is also valid and provides additional optimizations for modernized UI5 code + +- When converting from apiVersion 1 to 2, ensure all `write()` calls are properly converted to semantic methods + +- The IconPool import is needed at module load time for icon font registration, even if `IconPool` variable is not used in code + +## Related Skills + +- **fix-js-globals**: For `no-globals` errors in non-renderer JavaScript files (e.g., controllers, utilities), use fix-js-globals — it handles `sap.ui.define` dependency additions and global access replacement +- **fix-pseudo-modules**: If renderer code also has enum or DataType pseudo module imports, use fix-pseudo-modules for those specific issues diff --git a/plugins/ui5-modernization/skills/fix-control-renderer/references/renderer-api-mapping.md b/plugins/ui5-modernization/skills/fix-control-renderer/references/renderer-api-mapping.md new file mode 100644 index 0000000..22badce --- /dev/null +++ b/plugins/ui5-modernization/skills/fix-control-renderer/references/renderer-api-mapping.md @@ -0,0 +1,94 @@ +# Renderer API Mapping Reference + +This reference contains the complete mapping from apiVersion 1 to apiVersion 2 renderer methods. + +## apiVersion 1 to apiVersion 2 Method Conversions + +| Old Method (apiVersion 1) | New Method (apiVersion 2) | Notes | +|---------------------------|---------------------------|-------| +| `oRm.write("")` | `oRm.openEnd()` | After openStart | +| `oRm.write("/>")` | `oRm.voidEnd()` | After voidStart | +| `oRm.write("")` | `oRm.close("tag")` | Close non-void element | +| `oRm.write(text)` | `oRm.text(text)` | For text content | +| `oRm.writeEscaped(text)` | `oRm.text(text)` | Auto-escapes in v2 | +| `oRm.writeControlData(oCtrl)` | `oRm.openStart("div", oControl)` | Pass control as 2nd arg | +| `oRm.writeAttribute("name", val)` | `oRm.attr("name", val)` | Attribute | +| `oRm.addClass("cls")` | `oRm.class("cls")` | CSS class | +| `oRm.writeClasses()` | (automatic) | Called by openEnd() | +| `oRm.addStyle("prop", val)` | `oRm.style("prop", val)` | Inline style | +| `oRm.writeStyles()` | (automatic) | Called by openEnd() | +| `oRm.renderControl(oChild)` | `oRm.renderControl(oChild)` | Unchanged | +| `oRm.write(oRm.getAccessibilityState(...))` | `oRm.accessibilityState(oControl, {...})` | ARIA attributes | +| `oRm.writeIcon(src, classes, attrs)` | `oRm.icon(src, classes, attrs)` | Icon rendering | + +## Complete Example: apiVersion 1 to 2 Migration + +### Before (apiVersion 1) +```javascript +renderer: function(oRm, oControl) { + oRm.write(""); + + oRm.write(""); + oRm.writeEscaped(oControl.getText()); + oRm.write(""); + + oRm.renderControl(oControl.getAggregation("content")); + + oRm.write(""); +} +``` + +### After (apiVersion 2) +```javascript +renderer: { + apiVersion: 2, + render: function(oRm, oControl) { + oRm.openStart("div", oControl); + oRm.class("myControl"); + oRm.class(oControl.getStyleClass()); + oRm.style("width", oControl.getWidth()); + oRm.attr("title", oControl.getTitle()); + oRm.openEnd(); + + oRm.openStart("span"); + oRm.class("myControl-text"); + oRm.openEnd(); + oRm.text(oControl.getText()); + oRm.close("span"); + + oRm.renderControl(oControl.getAggregation("content")); + + oRm.close("div"); + } +} +``` + +## Key Differences + +1. **Structure**: apiVersion 2 uses object with `apiVersion` and `render` properties +2. **Control ID**: Pass control as second argument to `openStart()` instead of `writeControlData()` +3. **Classes/Styles**: No need to call `writeClasses()`/`writeStyles()` - automatic with `openEnd()` +4. **Text escaping**: `text()` auto-escapes, no need for `writeEscaped()` +5. **Void elements**: Use `voidStart()`/`voidEnd()` for self-closing elements + +## Valid apiVersions + +| Version | Description | +|---------|-------------| +| `apiVersion: 2` | Semantic rendering, UI5 1.67+ | +| `apiVersion: 4` | Same as 2, with performance optimizations for modernized UI5 code | + +Both versions use the same method names; version 4 adds internal optimizations. diff --git a/plugins/ui5-modernization/skills/fix-csp-compliance/SKILL.md b/plugins/ui5-modernization/skills/fix-csp-compliance/SKILL.md new file mode 100644 index 0000000..b6e6137 --- /dev/null +++ b/plugins/ui5-modernization/skills/fix-csp-compliance/SKILL.md @@ -0,0 +1,433 @@ +--- +name: fix-csp-compliance +description: | + Fix Content Security Policy (CSP) compliance issues that UI5 linter reports but cannot auto-fix. Use this skill when linter outputs: + - `csp-unsafe-inline-script` with message "Use of unsafe inline script" + Trigger on: inline JavaScript in HTML files, script tags without src attribute, onclick/onload handlers in HTML. + Also use when user mentions 'security headers', 'inline script warning', 'CSP policy', 'unsafe-inline', or 'Content-Security-Policy'. + Provides guidance on moving inline scripts to external files for CSP compliance. +--- + +# Fix CSP Compliance - Unsafe Inline Scripts + +This skill fixes Content Security Policy (CSP) compliance issues that the UI5 linter detects but cannot auto-fix because they require restructuring code into external files. + +## Linter Rule Handled + +| Rule ID | Message Pattern | Severity | This Skill's Action | +|---------|-----------------|----------|---------------------| +| `csp-unsafe-inline-script` | Use of unsafe inline script | Warning | Move to external JS file | + +## When to Use + +Apply this skill when you see linter output like: +``` +index.html:15:5 warning Use of unsafe inline script csp-unsafe-inline-script +test.html:20:5 warning Use of unsafe inline script csp-unsafe-inline-script +``` + +## Background: Why CSP Matters + +Content Security Policy (CSP) is a security feature that helps prevent: +- Cross-Site Scripting (XSS) attacks +- Data injection attacks +- Unauthorized script execution + +Inline scripts are considered unsafe because an attacker who manages to inject HTML can also inject malicious JavaScript. CSP-compliant apps use `script-src 'self'` which blocks inline scripts. + +Documentation: [Content Security Policy](https://ui5.sap.com/#/topic/fe1a6dba940e479fb7c3bc753f92b28c) + +## Detection + +The linter flags ` + + + + +``` + +**Not flagged:** +```html + + + + + +``` + +## Fix Strategy + +### 1. Basic Inline Script → External File + +**Problem**: Inline JavaScript in HTML. + +```html + + + + + + My App + + + + + + + +``` + +**Fix Strategy**: Move inline scripts to external files. + +```html + + + + + + My App + + + + + + +``` + +```javascript +// config.js +window.myConfig = { + apiUrl: "/api/v1", + debug: true +}; +``` + +```javascript +// my/app/init.js +sap.ui.define([], function() { + "use strict"; + + return { + start: function() { + // Initialization code + } + }; +}); +``` + +### 2. UI5 Bootstrap with Inline Init → data-sap-ui-on-init + +**Problem**: Inline script after UI5 bootstrap. + +```html + + + +``` + +**Fix Strategy**: Use `data-sap-ui-on-init` attribute. + +```html + + +``` + +```javascript +// webapp/init.js +sap.ui.define([ + "sap/m/Shell", + "sap/ui/core/ComponentContainer" +], function(Shell, ComponentContainer) { + "use strict"; + + new Shell({ + app: new ComponentContainer({ + name: "my.app", + async: true + }) + }).placeAt("content"); +}); +``` + +### 3. Configuration Data → JSON or Module + +**Problem**: Inline configuration object. + +```html + + +``` + +**Fix Strategy A**: External JSON file loaded at runtime. + +```html + + +``` + +```javascript +// config.js - loads JSON +(function() { + var xhr = new XMLHttpRequest(); + xhr.open("GET", "config.json", false); // Sync for config + xhr.send(); + window.APP_CONFIG = JSON.parse(xhr.responseText); +})(); +``` + +```json +// config.json +{ + "apiEndpoint": "https://api.example.com", + "features": { + "darkMode": true, + "analytics": false + } +} +``` + +**Fix Strategy B**: UI5 module with configuration. + +```javascript +// my/app/config.js +sap.ui.define([], function() { + "use strict"; + + return { + apiEndpoint: "https://api.example.com", + features: { + darkMode: true, + analytics: false + } + }; +}); + +// Usage in other modules +sap.ui.define(["my/app/config"], function(config) { + console.log(config.apiEndpoint); +}); +``` + +### 4. Inline Event Handlers → External Scripts + +**Problem**: Inline event handlers in HTML attributes. + +```html + + + + +``` + +**Fix Strategy**: Use external script with event listeners. + +```html + + + + +``` + +```javascript +// app.js +document.addEventListener("DOMContentLoaded", function() { + document.getElementById("myButton").addEventListener("click", handleClick); + document.getElementById("logo").addEventListener("error", function() { + handleError(this); + }); + init(); +}); + +function handleClick() { + // Click handler +} + +function handleError(element) { + // Error handler +} + +function init() { + // Initialization +} +``` + +### 5. Test HTML Files + +**Problem**: QUnit test files with inline scripts. + +```html + + + + + + + + +
+ + +``` + +**Fix Strategy**: Use Test Starter (also fixes `prefer-test-starter`). + +```html + + + + + + + +
+ + +``` + +### 6. Dynamic Script Content + +**Problem**: Script content generated dynamically. + +```html + + +``` + +**Fix Strategy**: Use data attributes or meta tags. + +```html + + + + +``` + +```javascript +// app.js +var userId = document.querySelector('meta[name="user-id"]').content; +var token = document.querySelector('meta[name="csrf-token"]').content; +``` + +## Implementation Steps + +1. **Identify all inline scripts** from linter output + +2. **Categorize each script**: + - UI5 initialization → Use `data-sap-ui-on-init` + - Configuration → External JSON or JS module + - Event handlers → External script with `addEventListener` + - Test boilerplate → Use Test Starter + +3. **Create external files** for the script content + +4. **Update HTML** to reference external files + +5. **Test the application** to ensure functionality is preserved + +6. **Configure CSP headers** on your server: + ``` + Content-Security-Policy: script-src 'self'; object-src 'none' + ``` + +## Common Patterns + +| Inline Pattern | CSP-Compliant Solution | +|----------------|------------------------| +| `` | ` + + + + + + + + + +
+
+ + +``` + +**Fix Strategy**: Use Test Starter's `runTest.js`. + +```html + + + + + + Unit Tests + + + + +
+
+ + +``` + +### 2. Create Testsuite Configuration + +**Problem**: No testsuite configuration file. + +**Fix Strategy**: Create `testsuite.qunit.js` (or `.html` for HTML-based suite). + +```javascript +// testsuite.qunit.js +sap.ui.define(function() { + "use strict"; + + return { + name: "Unit Tests for my.app", + defaults: { + page: "test-resources/my/app/test/unit/{name}.qunit.html", + qunit: { + version: 2 + }, + sinon: { + version: 4 + }, + ui5: { + theme: "sap_horizon" + }, + coverage: { + only: ["my/app"] + } + }, + tests: { + "AllTests": { + title: "All Unit Tests", + module: "./AllTests.qunit" + }, + "controller/Main": { + title: "Main Controller Tests", + module: "./controller/Main.qunit" + }, + "model/formatter": { + title: "Formatter Tests", + module: "./model/formatter.qunit" + } + } + }; +}); +``` + +### 3. Convert Test JS File + +**Problem**: Test file using Core.ready() or without sap.ui.define. + +```javascript +// Before - formatter.qunit.js (old style) +sap.ui.getCore().attachInit(function() { + "use strict"; + + sap.ui.require([ + "my/app/model/formatter", + "sap/ui/thirdparty/sinon", + "sap/ui/thirdparty/sinon-qunit" + ], function(formatter) { + + QUnit.module("formatter"); + + QUnit.test("formatValue", function(assert) { + assert.equal(formatter.formatValue(1), "One"); + }); + }); +}); +``` + +**Fix Strategy**: Use sap.ui.define with QUnit module. + +```javascript +// After - formatter.qunit.js (Test Starter style) +sap.ui.define([ + "my/app/model/formatter", + "sap/ui/qunit/utils/createAndAppendDiv" +], function(formatter, createAndAppendDiv) { + "use strict"; + + // Create fixture div if needed + createAndAppendDiv("content"); + + QUnit.module("formatter"); + + QUnit.test("formatValue", function(assert) { + assert.equal(formatter.formatValue(1), "One"); + }); +}); +``` + +### 4. Convert Testsuite HTML to Test Starter + +**Problem**: Testsuite HTML with manual jsUnitTestSuite. + +```html + + + + + + Test Suite + + + + + + + + +``` + +**Fix Strategy**: Use Test Starter's `createSuite.js`. + +```html + + + + + + Test Suite + + + + + + +``` + +### 5. Convert jsUnitTestSuite JS File + +**Problem**: Using jsUnitTestSuite in JavaScript. + +```javascript +// Before - testsuite.qunit.js (old style) +sap.ui.define(function() { + "use strict"; + + var oSuite = new jsUnitTestSuite(); + oSuite.addTestPage("test-resources/my/app/test/unit/AllTests.qunit.html"); + + return oSuite; +}); +``` + +**Fix Strategy**: Return configuration object instead. + +```javascript +// After - testsuite.qunit.js (Test Starter style) +sap.ui.define(function() { + "use strict"; + + return { + name: "Test Suite for my.app", + defaults: { + qunit: { + version: 2 + }, + sinon: { + version: 4 + }, + ui5: { + theme: "sap_horizon" + } + }, + tests: { + "unit/AllTests": { + title: "All Unit Tests" + }, + "integration/AllJourneys": { + title: "All Integration Tests" + } + } + }; +}); +``` + +## Complete Migration Example + +### Directory Structure +``` +webapp/ +├── test/ +│ ├── testsuite.qunit.html # Main test suite entry +│ ├── testsuite.qunit.js # Test suite configuration +│ ├── unit/ +│ │ ├── AllTests.qunit.js # Unit test entry +│ │ └── model/ +│ │ └── formatter.qunit.js # Individual test file +│ └── integration/ +│ └── AllJourneys.qunit.js # Integration test entry +``` + +### testsuite.qunit.html +```html + + + + + Test Suite + + + + + +``` + +### testsuite.qunit.js +```javascript +sap.ui.define(function() { + "use strict"; + + return { + name: "Test Suite for my.app", + defaults: { + qunit: { + version: 2 + }, + sinon: { + version: 4 + }, + ui5: { + theme: "sap_horizon", + resourceroots: { + "my.app": "../../" + } + }, + coverage: { + only: ["my/app"], + never: ["my/app/test"] + } + }, + tests: { + "unit/AllTests": { + title: "All Unit Tests" + }, + "integration/AllJourneys": { + title: "All Integration Tests", + ui5: { + preload: "async" + } + } + } + }; +}); +``` + +### unit/AllTests.qunit.js +```javascript +sap.ui.define([ + "./model/formatter.qunit" +], function() { + "use strict"; + // This file just loads all unit tests +}); +``` + +### unit/model/formatter.qunit.js +```javascript +sap.ui.define([ + "my/app/model/formatter" +], function(formatter) { + "use strict"; + + QUnit.module("Formatter Functions"); + + QUnit.test("should format value correctly", function(assert) { + // Test implementation + assert.ok(true, "Test passed"); + }); +}); +``` + +## Implementation Steps + +1. **Create testsuite.qunit.js** with test configuration object + +2. **Update testsuite.qunit.html** to use `createSuite.js` + +3. **Update individual test HTML files** to use `runTest.js` + +4. **Convert test JS files**: + - Wrap in `sap.ui.define` + - Remove `Core.ready()` / `Core.attachInit()` wrappers + - Remove `sap.ui.require` wrappers (use sap.ui.define dependencies) + +5. **Remove redundant code**: + - Remove QUnit/Sinon script tags (handled by Test Starter) + - Remove coverage setup (configured in testsuite) + - Remove manual UI5 bootstrapping + +6. **Verify** by running the tests + +## Notes + +- Testsuite files (`testsuite.*.qunit.js`) are allowed to use `Core.ready()` - they're exempt from the warning +- The Test Starter automatically handles QUnit, Sinon, and coverage setup +- Resource roots and other UI5 config go in the testsuite configuration +- Individual test files should only contain test code, not boilerplate +- The `page` property in defaults determines the URL pattern for test pages diff --git a/plugins/ui5-modernization/skills/fix-xml-globals/SKILL.md b/plugins/ui5-modernization/skills/fix-xml-globals/SKILL.md new file mode 100644 index 0000000..8e149a2 --- /dev/null +++ b/plugins/ui5-modernization/skills/fix-xml-globals/SKILL.md @@ -0,0 +1,444 @@ +--- +name: fix-xml-globals +description: | + Fix XML view/fragment issues that UI5 linter reports but cannot auto-fix. Use this skill when linter outputs these rules: + - `no-globals` - For accessing global variables in XML views (e.g., my.app.formatter.method, sap.ui.model.type.Currency, factory functions) + - `no-ambiguous-event-handler` - For event handlers without dot prefix or local name + - `no-deprecated-api` - For legacy template:require syntax (space-separated) + Trigger on XML view/fragment files with errors about global variables, event handlers, formatters, type references in bindings, factory functions, or template:require. + Automatically adds core:require declarations, fixes event handler prefixes, and handles .bind($control) for functions that use 'this'. + For native HTML or SVG in XML views, use the fix-xml-native-html skill instead. +--- + +# Fix XML Views/Fragments Global Access + +This skill fixes XML view and fragment issues that the UI5 linter detects but cannot auto-fix because they require understanding of module paths and handler locations. + +## Linter Rules Handled + +| Rule ID | Message Pattern | This Skill's Action | +|---------|-----------------|---------------------| +| `no-globals` | Access of global variable '...' (...) | Add `core:require` and use local name | +| `no-ambiguous-event-handler` | Event handler '...' must be prefixed by a dot '.' or refer to a local name | Add `.` prefix for controller methods or add `core:require` for modules | +| `no-deprecated-api` | Usage of space-separated list '...' in template:require | Convert to object notation | + +## When to Use + +Apply this skill when you see linter output like: +``` +Main.view.xml:15:5 error Access of global variable 'formatter' (my.app.model.formatter) no-globals +Main.view.xml:20:5 warning Event handler 'onPress' must be prefixed by a dot '.' or refer to a local name no-ambiguous-event-handler +Main.view.xml:25:5 error Usage of space-separated list 'formatter helper' in template:require no-deprecated-api +``` + +## Fix Strategy + +### 1. `no-globals` - Fix Global Variable Access with core:require + +When a formatter, type, or utility function is accessed via global namespace (e.g., `my.app.formatter.formatDate`), add a `core:require` declaration and use the local name. + +**Step 1: Add xmlns:core namespace if not present** +```xml + +``` + +**Step 2: Add core:require on the nearest control that uses the module** + +Place `core:require` on the **control that uses the global reference**. If multiple controls use the same module, place it on their **nearest common ancestor**. See the "core:require Placement Rules" section below for details. + +```xml + + +``` + +```xml + + + + + +``` + +**Step 3: Update references to use local name** +```xml + + + + + +``` + +**Step 4: Add `.bind($control)` if the function uses `this`** + +If the formatter, event handler, or factory function accesses `this` internally, add `.bind($control)` to preserve the control context. Always use `$control` (not `$controller`). + +```xml + + +``` + +### 2. `no-ambiguous-event-handler` - Fix Event Handlers + +Event handlers must either: +- Start with a dot (`.`) to indicate controller method +- Use a locally required module name + +**Controller method (add dot prefix):** +```xml + +