关于 fix-fiori-elements-extensions
fix-fiori-elements-extensions 用于 UI5 现代化期间处理已弃用的 Fiori Elements V2 控制器扩展。它会区分清单注册与程序化注册:前者仅报告不修改,后者迁移至 ControllerExtension 和清单注册。
主要功能
- 01区分两种扩展注册模式
- 02仅报告清单注册的控制器,不修改
- 03将程序化注册迁移到 ControllerExtension
- 04将注册项移入清单
- 05更新处理器引用并运行 Linter 检查
适用场景
- 01处理使用已弃用控制器 API 的 UI5 应用
- 02识别需要手动现代化的扩展
- 03迁移 registerControllerExtensions 用法
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ui5/ plugins-coding-agents --skill fix-fiori-elements-extensions安装后在 Claude Code 中输入/fix-fiori-elements-extensions确认已加载。
详细介绍
修复 Fiori Elements 控制器扩展
此技能用于在 UI5 现代化改造期间处理 Fiori Elements V2 控制器扩展。分为两种情况,需采取不同操作:
- 情况 B(最常见):通过 manifest 中的
controllerName注册扩展 → 仅报告。请勿修改控制器文件。 保留原样,并告知用户哪些文件需要手动处理。 - 情况 A(较少见):使用
registerControllerExtensions注册扩展 → 执行完整的现代化改造,改用ControllerExtension.extend()并在 manifest 中注册。
处理的 Linter 规则
| 规则 ID | 消息模式 | 此技能的操作 |
|---|---|---|
no-deprecated-api | 使用已弃用的 registerControllerExtensions | 情况 A:现代化改造为 manifest + ControllerExtension |
no-deprecated-api | 使用已弃用的 sap.ui.controller | 情况 B(如果 manifest 中有 controllerName):仅报告,不修复;情况 A(如果使用 registerControllerExtensions):ControllerExtension 类 |
no-deprecated-api | 使用已弃用的 Controller(来自 sap/ui/core/mvc/Controller) | 情况 B:仅报告,不修复 |
快速判断:我属于哪种情况?
Is there a `controllerName` entry in manifest.json under
sap.ui5/extends/extensions/sap.ui.controllerExtensions with a SIMPLE key
(no "#" in the key)?
├── YES → Case B: DO NOT MODERNIZE. Report the file(s) and move on.
│ (regardless of whether source uses sap.ui.controller() or Controller.extend())
└── NO → Is there a registerControllerExtensions call in JS?
├── YES → Case A: modernize to ControllerExtension.extend() + manifest with #stableId key
└── NO → This skill does not apply
适用场景
情况 B(最常见)——当 manifest 在简单键下已有 controllerName 条目,且控制器文件使用 sap.ui.controller() 或 Controller.extend() 时:
// manifest.json — controllerName registration already present (simple key, no "#")
"sap.ui.controllerExtensions": {
"sap.suite.ui.generic.template.ListReport.view.ListReport": {
"controllerName": "my.app.ext.controller.ListReportExt"
}
}
源控制器可能如下所示:
// Variant 1: sap.ui.controller() — NO sap.ui.define wrapper
sap.ui.controller("my.app.ext.controller.ListReportExt", {
onInit: function() { ... },
onCustomAction: function() { ... }
});
// Variant 2: Controller.extend() — inside sap.ui.define
sap.ui.define(["sap/ui/core/mvc/Controller", ...], function(Controller, ...) {
return Controller.extend("my.app.ext.controller.ListReportExt", { ... });
});
会触发情况 B 的 Linter 输出:
MyExtension.controller.js:1:1 error Use of deprecated function 'sap.ui.controller' no-deprecated-api
MyExtension.controller.js:3:5 error Use of deprecated function 'Controller' (from 'sap/ui/core/mvc/Controller') no-deprecated-api
情况 B 的操作:请勿修改这些文件。 向用户报告这些文件,并继续进行其余的现代化改造。
情况 A(较少见,仅适用于较旧的应用)——当你看到如下 Linter 输出时应用:
Component.js:25:5 error Use of deprecated function 'registerControllerExtensions' no-deprecated-api
或者当应用具有如下模式时:
// In Component.js or elsewhere — NO manifest entry exists yet
this.getExtensionComponent().registerControllerExtensions("sap.suite.ui.generic.template.ListReport.view.ListReport", {
onInit: function() { ... },
onAction: function() { ... }
});
修复策略
具体操作取决于扩展在 manifest 中的注册方式。继续之前,请先确定适用的情况。
情况判断
| Manifest 注册方式 | 源代码(任一形式) | 操作 |
|---|---|---|
manifest 中的 controllerName(简单键,无 #stableId) | sap.ui.controller()、Controller.extend() 或普通对象 | 情况 B:仅报告 — 请勿修改文件 |
JS 中的 registerControllerExtensions(无 manifest 条目) | sap.ui.controller() 或内联对象 | 情况 A:使用 ControllerExtension.extend() + override,并添加以 #stableId 为键的 manifest 条目 |
关键区别在于 manifest 的注册格式:
- 情况 B(最常见):
controllerName已在 manifest 的简单键下(例如,"sap.suite.ui.generic.template.ListReport.view.ListReport": { "controllerName": "..." })。请勿对这些文件进行现代化改造。 报告后继续处理。 - 情况 A:manifest 中尚无
controllerName——扩展通过registerControllerExtensions以编程方式注册。现代化改造会添加采用#stableId键格式的 manifest 条目,并使用ControllerExtension.extend()。
检测命令:
# Check manifest registration format
grep -B1 -A2 "controllerName" webapp/manifest.json
# Case B indicator: controllerName in manifest with simple key (no # in key)
# If the key does NOT contain "#", it's the merging mechanism → Case B → REPORT ONLY
# Case A indicator: registerControllerExtensions in JS
grep -rl "registerControllerExtensions" webapp/ --include="*.js"
情况 B:报告并跳过(最常见)
对于通过清单中的 controllerName 注册的扩展(不含 #stableId 的简单键),请勿修改控制器文件。这些文件需要谨慎地手动现代化,留给开发者处理。
步骤:
- 找出 manifest.json 中
controllerName条目引用的所有控制器文件(位于不含#的键下) - 按以下格式向用户报告:
⚠️ Fiori Elements controller extensions (Case B) — skipped, requires manual modernization:
- webapp/ext/controller/ListReportExt.controller.js
- webapp/ext/controller/ObjectPageExt.controller.js
These files use deprecated APIs (sap.ui.controller / Controller.extend) but are Fiori Elements
controller extensions registered via controllerName in the manifest. They require careful manual
modernization to a plain-object return pattern. The linter errors for these files will persist until
they are modernized manually.
- 在此技能中请勿:
- 修改控制器 .js 文件以更改控制器扩展模式(不要转换为普通对象)
- 更改清单注册
- 添加禁用代码检查器的注释
- 尝试普通对象现代化模式
- 其他技能仍可处理这些文件中的无关修复(例如,
fix-js-globals可替换方法体中的sap.ui.getCore()调用,fix-linter-blind-spots可处理命名空间模式)。只有sap.ui.controller()/Controller.extend()结构本身保持不变。 - 继续执行其余现代化工作(其他技能、其他代码检查器发现项)
情况 A:步骤 1 — 将注册从 JS 移至 manifest.json
仅适用于情况 A(在 JS 中使用 registerControllerExtensions 的扩展)。
将控制器扩展注册从 JavaScript 代码移至 sap.ui.controllerExtensions 下的清单中。
键格式:<FLOORPLAN_CONTROLLER>#<STABLE_ID_OF_VIEW>
// Before - no controller extensions in manifest
// After - manifest.json
{
"sap.ui5": {
"extends": {
"extensions": {
"sap.ui.controllerExtensions": {
"sap.suite.ui.generic.template.ListReport.view.ListReport#myApp::sap.suite.ui.generic.template.ListReport.view.ListReport::EntitySet": {
"controllerName": "my.app.ext.ListReportExtension"
},
"sap.suite.ui.generic.template.ObjectPage.view.Details#myApp::sap.suite.ui.generic.template.ObjectPage.view.Details::EntitySet": {
"controllerName": "my.app.ext.ObjectPageExtension"
}
}
}
}
}
}
查找正确的键:
- 键的格式为:
<FloorplanController>#<StableViewId> - Floorplan 控制器名称与视图名称相同(例如,
sap.suite.ui.generic.template.ListReport.view.ListReport) - 稳定视图 ID 通常为:
<AppId>::<FloorplanViewName>::<EntitySet> - 检查应用现有清单中的
sap.ui.generic.app/pages配置,以查找实体集和页面 ID
情况 A:步骤 2 — 重构控制器文件
⚠️ 此步骤仅适用于情况 A(使用
sap.ui.controller()工厂函数的扩展)。 如果扩展已使用Controller.extend()并通过清单中的controllerName注册,请改用下方的情况 B。
将控制器文件从 sap.ui.controller 风格转换为 ControllerExtension 类。
之前:
sap.ui.controller("my.app.ext.ListReportExtension", {
onInit: function() {
// lifecycle code
},
onBeforeRebindTable: function(oEvent) {
// framework override
},
onCustomAction: function(oEvent) {
// custom method
}
});
之后:
sap.ui.define([
"sap/ui/core/mvc/ControllerExtension"
], function(ControllerExtension) {
"use strict";
return ControllerExtension.extend("my.app.ext.ListReportExtension", {
// Lifecycle and framework overrides go inside "override"
override: {
onInit: function() {
// extensionAPI is injected by the framework into the extension instance
this._extensionAPI = this.extensionAPI;
},
onBeforeRebindTable: function(oEvent) {
// framework override
}
},
// Custom methods go OUTSIDE "override"
onCustomAction: function(oEvent) {
// custom method
}
});
});
重构的主要规则:
- 扩展
sap/ui/core/mvc/ControllerExtension,而不是使用sap.ui.controller - 生命周期方法(
onInit、onExit、onBeforeRendering、onAfterRendering)→ 放入override中 - 框架回调方法(
onBeforeRebindTable、onBeforeRebindChart、onListNavigationExtension、adaptNavigationParameterExtension等)→ 放入override中 - 自定义操作方法(自定义按钮/操作的处理函数)→ 放在
override之外 - 通过
this.extensionAPI访问extensionAPI(框架会将其注入扩展实例)
情况 A:步骤 3 — 更新处理函数引用
在清单和 XML 注解中,将所有事件处理函数引用从旧格式更新为新的限定格式。
之前:
// In manifest extensions or annotations
"Actions": {
"MyAction": {
"id": "MyAction",
"text": "Do Something",
"press": "onCustomAction"
}
}
之后:
"Actions": {
"MyAction": {
"id": "MyAction",
"text": "Do Something",
"press": ".extension.my.app.ext.ListReportExtension.onCustomAction"
}
}
引用格式:.extension.<full.controller.name>.<methodName>
重要:使用 ControllerExtension.extend("my.app.ext.ListReportExtension", ...) 和清单中的 controllerName 所声明的点分隔模块名称,而不是 sap.ui.define 导入中使用的斜杠分隔路径(例如,"my/app/ext/ListReportExtension")。
适用于以下内容:
- 清单操作定义中的
press处理函数 - 自定义列/节处理函数引用
- 任何之前只使用方法名称的事件处理函数引用
实施步骤
情况 B 实施(仅报告)
- 识别通过清单中的
controllerName注册的扩展控制器(简单键,不含#) - 向用户报告——列出每个文件路径,并注明需要手动现代化
- 不要修改这些文件、对应的清单条目或处理函数引用
- 继续执行其他现代化任务
Case A 实现(registerControllerExtensions → ControllerExtension)
- 识别代码库中的所有
registerControllerExtensions调用(通常位于 Component.js 或辅助文件中) - 针对每项注册:
a. 确定 floorplan 控制器名称和视图稳定 ID
b. 确定扩展控制器名称/路径
c. 在
manifest.json的sap.ui5/extends/extensions/sap.ui.controllerExtensions下添加条目 - 针对每个扩展控制器文件:
a. 将
sap.ui.controller(...)替换为sap.ui.define([ControllerExtension], ...)b. 将生命周期/框架方法移至override部分 c. 自定义方法保留在override外 d. 更新extensionAPI访问方式 - 针对所有处理程序引用:
a. 在 manifest.json 中搜索使用简单方法名的 action
press处理程序 b. 在 XML 注解文件中搜索处理程序引用 c. 更新为.extension.<module>.<method>格式 - 从 Component.js 中移除
registerControllerExtensions调用 - 验证:重新运行 linter
完整改造示例
之前 — Component.js:
sap.ui.define([
"sap/suite/ui/generic/template/lib/AppComponent"
], function(AppComponent) {
"use strict";
return AppComponent.extend("my.app.Component", {
metadata: {
manifest: "json"
},
init: function() {
AppComponent.prototype.init.apply(this, arguments);
this.getExtensionComponent().registerControllerExtensions(
"sap.suite.ui.generic.template.ListReport.view.ListReport", {
onInit: function() {
// lifecycle code
},
onCustomAction: function(oEvent) {
// custom handler
}
}
);
}
});
});
之后 — Component.js:
sap.ui.define([
"sap/suite/ui/generic/template/lib/AppComponent"
], function(AppComponent) {
"use strict";
return AppComponent.extend("my.app.Component", {
metadata: {
manifest: "json"
}
// registerControllerExtensions call removed — now in manifest.json
});
});
之后 — manifest.json(新增内容):
{
"sap.ui5": {
"extends": {
"extensions": {
"sap.ui.controllerExtensions": {
"sap.suite.ui.generic.template.ListReport.view.ListReport#my.app::sap.suite.ui.generic.template.ListReport.view.ListReport::Products": {
"controllerName": "my.app.ext.ListReportExtension"
}
}
}
}
}
}
之后 — ext/ListReportExtension.controller.js:
sap.ui.define([
"sap/ui/core/mvc/ControllerExtension"
], function(ControllerExtension) {
"use strict";
return ControllerExtension.extend("my.app.ext.ListReportExtension", {
override: {
onInit: function() {
this._extensionAPI = this.extensionAPI;
}
},
onCustomAction: function(oEvent) {
// Custom action handler — accessible as
// ".extension.my.app.ext.ListReportExtension.onCustomAction"
}
});
});
说明
- 此改造仅适用于 Fiori Elements V2 模板应用(使用
sap.suite.ui.generic.template) - Fiori Elements V4(使用
sap.fe.templates)采用不同的扩展机制 — 此技能不适用 - 实际中 Case B 更常见 — 通过 manifest 中的
controllerName注册的扩展。这些扩展不会自动改造 — Agent 会报告它们并继续处理 - Case A 仅出现在从未将
registerControllerExtensions改造为较新方式的旧应用中 - 稳定视图 ID 的格式因 floorplan 和配置而异 — 请检查运行中应用的 DOM 或 floorplan 文档
- 如果应用使用
templateSpecific.json引用处理程序,也必须更新这些引用(仅适用于 Case A) - 完成 Case A 改造后,扩展控制器文件应放在与其模块名称相匹配的路径下
相关技能
- fix-js-globals:用于自定义应用中的纯
sap.ui.controller()定义(Case 9),或扩展控制器也使用全局访问模式(sap.ui.getCore()、jQuery.sap.*)的情况。对于与控制器扩展模式本身无关的问题,其他技能也可以应用于 Case B 控制器文件(例如,替换方法体中的全局 API 用法)。 - fix-manifest-json:控制器扩展的 manifest 更改是增量式的 — 与 fix-manifest-json 中的版本/路由更新不冲突
- fix-linter-blind-spots:仍可修复 Case B 控制器文件中的命名空间模式和其他盲点