--- 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 (removed in modern UI5) - `no-deprecated-api` - For missing apiVersion:2 in renderer objects, missing IconPool import when using oRm.icon(), deprecated rerender() override. NOTE: For Library.init() apiVersion errors, use fix-library-init instead. - `ui5-class-declaration` - For non-static renderer property in ES6 classes Trigger on: "missing a renderer declaration", "Deprecated declaration of renderer", "deprecated renderer", "apiVersion" (in renderer context), "IconPool", "rerender", "renderer must be a static property" Converts legacy renderer patterns to modern apiVersion: 2 format with proper module imports. --- # 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 modern UI5) Without explicit `apiVersion`, UI5 assumes legacy rendering which causes synchronous loading and performance issues. ### Implicit Renderer Auto-Discovery (Removed in modern UI5) 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 modern UI5**. 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 modernization 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 modern UI5, 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 modern UI5 - 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 - **fix-library-init**: For `Library.init()` / `Lib.init()` apiVersion errors ("Deprecated call to ... Use the {apiVersion: 2} parameter"), use fix-library-init — it handles library initialization, not renderer objects