关于 fix-xml-globals
面向开发者的 UI5 XML 视图与片段修复技能,处理需要理解模块路径和处理器位置的 lint 问题。它用局部 `core:require` 声明替代全局引用,并规定事件处理器语法及格式化器绑定方式。
主要功能
- 01用 `core:require` 替换全局引用
- 02按控件作用域放置模块声明
- 03修正含糊的事件处理器
- 04转换旧式 `template:require` 语法
- 05检查格式化器的 `this` 绑定
适用场景
- 01解决全局格式化器引用问题
- 02修复 XML 事件处理器警告
- 03更新旧式模块声明
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ui5/ plugins-coding-agents --skill fix-xml-globals安装后在 Claude Code 中输入/fix-xml-globals确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/placement-and-binding.mdscripts/README.mdverify-this-bind.jsverify-this-bind.test.jsscripts/__fixtures__/nested_function_shadow.jsno_this.jsnot_found.jsthis_in_comment.jsthis_in_string.jsuses_this_alias.jsuses_this_arrow.jsuses_this_bare.jsuses_this_member.jsuses_this_nested_in_if.jsscripts/__fixtures__/xml/has_bind.fragment.xmlmulti_line.fragment.xmlneeds_bind.fragment.xmlno_this_no_bind.fragment.xml详细介绍
修复 XML 视图/片段中的全局访问
此技能用于修复 UI5 linter 能检测到但无法自动修复的 XML 视图和片段问题,因为这些问题需要理解模块路径和处理器的位置。
处理的 Linter 规则
| 规则 ID | 消息模式 | 此技能的操作 |
|---|---|---|
no-globals | Access of global variable '...' (...) | 添加 core:require 并使用本地名称 |
no-ambiguous-event-handler | Event handler '...' must be prefixed by a dot '.' or refer to a local name | 为控制器方法添加 . 前缀,或为模块添加 core:require |
no-deprecated-api | Usage of space-separated list '...' in template:require | 转换为对象表示法 |
适用场景
遇到如下 linter 输出时,请使用此技能:
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
修复策略
1. no-globals - 使用 core:require 修复全局变量访问
当通过全局命名空间访问格式化器、类型或实用函数时(例如,my.app.formatter.formatDate),添加 core:require 声明并使用本地名称。
步骤 1:如果尚未声明,则添加 xmlns:core 命名空间
<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns:core="sap.ui.core"
xmlns="sap.m">
步骤 2:在使用该模块的最近控件上添加 core:require
将 core:require 放在使用全局引用的控件上。如果多个控件使用同一模块,则将其放在这些控件的最近公共祖先上。详情请参阅下方的“core:require 放置规则”一节。
<Text core:require="{formatter: 'my/app/model/formatter'}"
text="{path: 'date', formatter: 'formatter.formatDate'}" />
<VBox core:require="{formatter: 'my/app/model/formatter'}">
<Text text="{path: 'date', formatter: 'formatter.formatDate'}" />
<Text text="{path: 'name', formatter: 'formatter.formatName'}" />
</VBox>
步骤 3:更新引用,改用本地名称
<Text text="{path: 'date', formatter: 'my.app.model.formatter.formatDate'}" />
<Text text="{path: 'date', formatter: 'formatter.formatDate'}" />
步骤 4:为使用 this 的格式化器添加 .bind($control)
如果格式化器函数内部访问了 this,则添加 .bind($control) 以保留控件上下文。此规则仅适用于格式化器——绝不要为事件处理器或工厂函数添加 .bind($control)。
<Text core:require="{formatter: 'my/app/model/formatter'}"
text="{path: 'name', formatter: 'formatter.formatWithContext.bind($control)'}" />
2. no-ambiguous-event-handler - 修复事件处理器
事件处理器必须满足以下任一条件:
- 以点号(
.)开头,以表示控制器方法 - 使用本地引入的模块名称
控制器方法(添加点号前缀):
<Button press="onPress" />
<Button press=".onPress" />
模块方法(使用 core:require):
<Button press="my.app.util.Handler.onPress" />
<mvc:View
core:require="{
Handler: 'my/app/util/Handler'
}">
<Button press="Handler.onPress" />
</mvc:View>
3. no-deprecated-api - 修复旧版 template:require 语法
将以空格分隔的模块列表转换为对象表示法。
<template:require name="formatter helper" />
<template:require name="{
formatter: 'my/app/model/formatter',
helper: 'my/app/util/helper'
}" />
4. 处理多个全局引用
使用多个全局对象时,将它们合并到位于最近公共祖先上的单个 core:require 中。
<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns:core="sap.ui.core"
xmlns="sap.m"
core:require="{
formatter: 'my/app/model/formatter',
types: 'my/app/model/types',
utils: 'my/app/util/utils'
}">
5. no-globals - 修复绑定中的 Type 属性
当绑定对象的 type 属性中使用全局对象时,也需要添加 core:require。
<Input value="{
path: '/amount',
type: 'sap.ui.model.type.Currency'
}" />
<Input core:require="{Currency: 'sap/ui/model/type/Currency'}"
value="{
path: '/amount',
type: 'Currency'
}" />
这适用于所有 UI5 类型(Currency、Date、Float、Integer、String 等)和自定义类型类。
6. no-globals - 修复工厂函数
工厂函数(例如,List 上的 factory 属性)遵循相同的 core:require 模式,但绝不要使用 .bind($control)。
<List items="{/items}" factory="my.app.util.ListFactory.createItem" />
<List core:require="{ListFactory: 'my/app/util/ListFactory'}"
items="{/items}" factory="ListFactory.createItem" />
core:require 放置规则
通过 core:require 声明的模块仅对该元素及其后代可用,对同级元素不可用。
- 单个控件使用该模块:将
core:require直接放在该控件上 - 多个控件使用同一模块:将
core:require放在它们的最近公共祖先上 - 所有控件都是 View 的直接子元素:将其放在 View 根元素上也可以
优先选择细粒度的放置方式,而不是总放在根元素上——这样可以减少不必要的作用域,并让依赖关系在使用位置一目了然。
有关详细示例(嵌套元素中的细粒度放置,以及同级元素导致的作用域错误),请参阅 references/placement-and-binding.md。
何时使用 .bind($control)
通过 core:require 调用函数时,不会自动获得 this 上下文。是否添加 .bind($control) 取决于使用类型——格式化器、工厂函数或事件处理程序:
| 使用类型 | .bind($control) | 原因 |
|---|---|---|
格式化器(位于 {path:..., formatter:'...'} 或 {parts:[...], formatter:'...'} 中) | 是,前提是函数使用 this | this = 拥有该绑定的控件实例。$control 在运行时会解析为 ManagedObject。 |
工厂函数(聚合绑定上的 factory= 属性) | 否——绝不添加 .bind() | 工厂函数会将 (sId, oContext) 作为参数接收。它们不会使用 this 获取控件上下文。添加 .bind() 没有必要,且不正确。 |
事件处理程序(press=、change=、confirm=、select=、valueHelpRequest= 等) | 否——绝不添加 .bind() | XML 中通过 core:require 解析事件处理程序时,会直接调用函数并将事件作为参数传入。添加 .bind() 会干扰此解析过程。 |
如何在 XML 中判断使用类型:
- 格式化器:出现在绑定表达式中——
{path:..., formatter:'Module.fn'}或{parts:[...], formatter:'Module.fn'} - 工厂函数:作为聚合绑定上的
factory='Module.fn'属性出现 - 事件处理程序:作为事件属性出现,例如
press=、change=、confirm=、cancel=、select=、selectionChange=、valueHelpRequest=、tokenUpdate=、close=、delete=、titlePress=
格式化器专用规则:
- 检查函数的实现——如果使用了
this,就添加.bind($control) - 始终使用
$control(而非$controller)——这会将this绑定到控件实例 - 如果函数没有使用
this,则省略.bind($control)
<Text core:require="{formatter: 'my/app/model/formatter'}"
text="{path: 'name', formatter: 'formatter.formatWithContext.bind($control)'}" />
<Text core:require="{formatter: 'my/app/model/formatter'}"
text="{path: 'date', formatter: 'formatter.formatDate'}" />
<List core:require="{ListFactory: 'my/app/util/ListFactory'}"
items="{/items}" factory="ListFactory.createItem" />
<Button core:require="{Handler: 'my/app/util/Handler'}"
press="Handler.onExport" />
检测方式:运行 scripts/verify-this-bind.js audit-fn。 该脚本会一次性处理所有形式的 this——成员访问、动态属性、裸参数(jQuery.proxy(fn, this)、.call(this)、.apply(this)、.bind(this))、别名形式(var self = this; self.foo)以及从外层继承 this 的箭头函数。注释和字符串字面量会被剔除;嵌套的非箭头函数体会被排除(它们拥有自己的 this);箭头函数体会保留(它们继承 this)。标准 UI5 类型(sap.ui.model.type.*)从不需要绑定,脚本会确认这一点。批量判断时不允许手动使用 grep——该脚本是权威依据。
node scripts/verify-this-bind.js audit-fn --file <module.js> --fn <name> [--fn <name> ...]
脚本报告为 USES_THIS 的函数,XML 中必须追加 .bind($control)。报告为 NO_THIS 的函数则不得添加。不要根据名称形式分类——formatX / decideX / isX 并不能证明它们是纯值格式化器;判断依据只能是函数体本身。
为何使用脚本而不是 grep:搜索 this\.(点号形式)会悄无声息地漏掉裸 this 用法。jQuery.proxy(fn, this) 会将 this 作为位置参数传入,前后没有点号——人工查看或简单的 grep 都会忽略它,误以为格式化器没有使用 this,从而漏掉 .bind($control)。运行时,被代理的回调最终会获得错误的上下文,导致 this.getModel(...) 抛出异常。脚本通过 \bthis\b 扫描并结合别名追踪,一次性捕获所有变体。仅当脚本不可用时,才退而使用 grep -nE '\bthis\b' <module.js>。
完整的前后示例(格式化器、处理程序、工厂函数)请参阅 references/placement-and-binding.md。
FragmentDefinition 处理
片段使用 <core:FragmentDefinition>,而不是 <mvc:View>。适用相同的放置原则,但有一个关键区别:
将 core:require 放在子控件上,而不是 FragmentDefinition 上。 FragmentDefinition 是结构性包装器,并非真正的控件。将 core:require 放在其中实际的根控件(例如 Dialog、VBox)上,或放在使用该模块的控件最近的公共祖先上。
<core:FragmentDefinition xmlns="sap.m" xmlns:core="sap.ui.core">
<Dialog title="Settings"
core:require="{formatter: 'my/app/model/formatter', Actions: 'my/app/util/Actions'}">
<content>
<Input value="{path: 'name', formatter: 'formatter.toUpperCase'}" />
<Button text="Save" press="Actions.onSave" />
</content>
</Dialog>
</core:FragmentDefinition>
例外: 当片段有多个需要同一模块的直接子项时,FragmentDefinition 就是最近的公共祖先,此时将 core:require 放在那里是可以接受的。
有关多子项片段和片段内作用域模块的示例,请参阅 references/placement-and-binding.md。
其他边缘情况
名称冲突
当多个模块共享同一个类名时,请使用描述性别名:
<mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:core="sap.ui.core"
core:require="{
ReportFormatter: 'my/app/report/Formatter',
KPIFormatter: 'my/app/kpi/Formatter'
}">
<Input value="{path: 'report', formatter: 'ReportFormatter.format'}" />
<Text text="{path: 'kpi', formatter: 'KPIFormatter.format'}" />
</mvc:View>
多部分格式化器 / 表达式绑定
多部分绑定和表达式绑定遵循相同的 core:require 模式——只需将带点号的全局名称替换为本地别名。表达式绑定示例:
<Text text="{= formatter.formatCount(${/count})}" />
XML 中的应用命名空间全局变量
UI5 linter 会在 no-globals 规则下报告 XML 中的应用命名空间全局变量——这与用于 sap.* 和 jQuery.* 全局变量的规则相同。此技能处理 XML 文件中的所有 no-globals,包括:
- sap 命名空间 —
sap.m.ButtonType.Accept、sap.ui.model.type.Currency→ 使用core:require引入模块 - jQuery 命名空间 —
jQuery.sap.getModulePath→ 使用core:require引入模块 - 应用命名空间事件处理器 —
com.example.app.utils.Handler.onPress→ 使用core:require引入工具模块 - 应用命名空间格式化器 —
com.example.app.model.formatter.formatDate→ 使用core:require引入格式化器模块
所有情况的修复模式都相同:添加带有模块路径(点号 → 斜杠)的 core:require 属性,并将带点号的全局名称替换为本地别名。
之前:
<core:FragmentDefinition xmlns="sap.m" xmlns:core="sap.ui.core">
<Button press="com.example.app.utils.Handler.onPress" />
</core:FragmentDefinition>
之后:
<core:FragmentDefinition xmlns="sap.m" xmlns:core="sap.ui.core">
<Button core:require="{Handler: 'com/example/app/utils/Handler'}"
press="Handler.onPress" />
</core:FragmentDefinition>
应用“何时使用 .bind($control)”一节中的 .bind($control) 规则——格式化器:需要(如果函数使用 this);工厂:绝不需要;事件处理器:绝不需要。
重要: 不要将应用命名空间 XML 全局变量推迟到 fix-linter-blind-spots(Phase 3,Step 3.2)处理。该技能只处理 linter 无法检测的 JS 应用命名空间全局变量。linter 会报告 XML 应用命名空间全局变量,必须在 Phase 3 中修复。
实现步骤
-
读取 XML 视图/片段文件
-
解析并按规则 ID 识别检查器错误:
no-globals:绑定中的全局命名空间引用(格式化器、类型、事件处理程序、工厂)no-ambiguous-event-handler:未添加正确前缀的事件处理程序no-deprecated-api:旧版模板require语法
-
对于
no-globals错误:- 如果根元素中没有
xmlns:core="sap.ui.core",则添加该属性 - 确定模块路径:将点号表示法转换为斜杠表示法(例如,
my.app.formatter→my/app/formatter) - 确定放置位置:找到使用该模块的最近控件;如果多个控件使用该模块,则找到它们的最近公共祖先
- 对于片段:优先将
core:require放在实际的根控件上(例如,Dialog),而不是FragmentDefinition上 - 必须逐个审核格式化器源码。 对于本批次中重命名的每个格式化器,都要运行
node scripts/verify-this-bind.js audit-fn --file <module.js> --fn <name> [--fn <name> ...](根据文件中格式化器的数量,尽可能批量添加--fn标志)。对于报告为USES_THIS的函数,必须在 XML 格式化器引用中追加.bind($control)。对于报告为NO_THIS的函数,不得追加。无论工厂和事件处理程序是否使用this,都不得绑定。 - 使用所有必需模块构建
core:require对象(名称冲突时使用别名) - 更新所有引用,使其使用本地名称
- 如果根元素中没有
-
对于
no-ambiguous-event-handler错误:- 使用检查器报告的
line:col定位确切的属性。只读取该行。 - 只编辑该坐标处的值。对于裸处理程序名称,在前面加上
.;对于命名空间路径,则应用core:require并替换为别名。 - 不要在文件中搜索同一处理程序名称字符串的其他出现位置。检查器已列出所有违规位置;如果某个字符串出现在其他位置但未被报告,那它就是不同的属性(
id、selectedKey、key、name、纯文本等),不得修改。一个检查器发现 → 一处编辑。
- 使用检查器报告的
-
对于
no-deprecated-api(模板require):- 将以空格分隔的列表转换为对象表示法
-
写入更新后的文件
-
编辑后验证(必须执行)。 完成本阶段所有 XML 编辑后、宣布完成前:
node scripts/verify-this-bind.js verify-xml \ --xml-root webapp \ --js-roots webapp/utils,webapp/model退出码为 0 → 完成。退出码为 1 → 修复列出的
MISSING_BIND违规并重新运行。该脚本是最终检查;不能仅靠 Agent 自行目视检查。
修复示例
示例 1:基础 — 格式化器 + 事件处理程序
检查器输出如下:
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 转换:
<mvc:View
controllerName="my.app.controller.Main"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m">
<Page title="Main">
<content>
<Text text="{path: 'date', formatter: 'my.app.model.formatter.formatDate'}" />
<Button text="Submit" press="onPress" />
</content>
</Page>
</mvc:View>
<mvc:View
controllerName="my.app.controller.Main"
xmlns:mvc="sap.ui.core.mvc"
xmlns:core="sap.ui.core"
xmlns="sap.m"
core:require="{
formatter: 'my/app/model/formatter'
}">
<Page title="Main">
<content>
<Text text="{path: 'date', formatter: 'formatter.formatDate'}" />
<Button text="Submit" press=".onPress" />
</content>
</Page>
</mvc:View>
示例 2:高级 — 细粒度放置、类型绑定、.bind($control)
检查器输出如下:
OrderDetail.view.xml:8:9 error Access of global variable 'formatter' (my.app.model.formatter) no-globals
OrderDetail.view.xml:10:13 error Access of global variable 'Currency' (sap.ui.model.type.Currency) no-globals
OrderDetail.view.xml:14:13 error Access of global variable 'Handler' (my.app.util.Handler) no-globals
OrderDetail.view.xml:18:9 warning Event handler 'onBack' must be prefixed by a dot '.' no-ambiguous-event-handler
<mvc:View controllerName="my.app.controller.OrderDetail"
xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m">
<Page title="Order">
<content>
<Text text="{path: 'status', formatter: 'my.app.model.formatter.formatStatus'}" />
<VBox>
<Input value="{path: '/amount', type: 'sap.ui.model.type.Currency'}" />
<Text text="{path: 'note', formatter: 'my.app.model.formatter.formatNote'}" />
</VBox>
</content>
<footer>
<Bar>
<contentRight>
<Button text="Export" press="my.app.util.Handler.onExport" />
</contentRight>
</Bar>
</footer>
<headerContent>
<Button press="onBack" icon="sap-icon://nav-back" />
</headerContent>
</Page>
</mvc:View>
<mvc:View controllerName="my.app.controller.OrderDetail"
xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:core="sap.ui.core">
<Page title="Order"
core:require="{formatter: 'my/app/model/formatter'}">
<content>
<Text text="{path: 'status', formatter: 'formatter.formatStatus'}" />
<VBox>
<Input core:require="{Currency: 'sap/ui/model/type/Currency'}"
value="{path: '/amount', type: 'Currency'}" />
<Text text="{path: 'note', formatter: 'formatter.formatNote'}" />
</VBox>
</content>
<footer>
<Bar>
<contentRight>
<Button core:require="{Handler: 'my/app/util/Handler'}"
text="Export" press="Handler.onExport" />
</contentRight>
</Bar>
</footer>
<headerContent>
<Button press=".onBack" icon="sap-icon://nav-back" />
</headerContent>
</Page>
</mvc:View>
放置决策:
- 两个
<Text>控件在content中使用formatter— 将core:require放在Page上(最近公共祖先) - 只有一个
<Input>使用Currency— 将core:require直接放在Input上 - 只有页脚中的一个
<Button>使用Handler— 将core:require直接放在Button上 Handler.onExport是事件处理程序 — 无需.bind($control)(事件处理程序永远不需要绑定)onBack是简单的控制器方法 — 已添加点号前缀
说明
-
将
core:require放在使用该模块的最近控件上;如果多个控件共用该模块,则放在最近的共同祖先上——优先采用细粒度放置,而不是始终放在根节点上 -
core:require中的模块路径使用正斜杠(/),而不是点号 -
在 JSON 对象表示法中,多个模块以逗号分隔
-
使用描述性别名,避免类名相同的模块之间发生命名冲突
-
始终先检查用法类型:如果格式化器函数使用
this,可能需要.bind($control);工厂和事件处理程序则不需要 -
对于片段,应将
core:require放在实际的子控件(例如Dialog)上,而不是放在FragmentDefinition上,除非它是唯一的共同祖先 -
确保格式化器/工具模块存在于指定路径
-
对于动态加载模块的复杂情况,可以考虑在控制器中使用
sap.ui.require -
禁止根据名称模式来判断
this的使用情况。formatX、decideX、isX、getX等格式化器名称并不能证明函数只处理纯值。以下常见模式看似纯值处理,但实际上会读取this:- 通过
this.getModel(...)、this.getBindingContext()、this.getId()读取上下文绑定的状态——这类调用通常位于辅助分支中 - 将裸
this传给回调包装器,例如jQuery.proxy(fn, this)、fn.call(this)、fn.apply(this)或fn.bind(this)——调用中this后没有.,粗略查看时可能看不到成员访问 - 先为
this创建别名(var self = this; ... self.getX())——格式化器主体在别名行之后不会再提到this
如果不使用
.bind($control)就调用格式化器,这些情况都会在运行时出错,因为默认情况下this不会绑定到控件。verify-this-bind.js脚本会一次性捕获所有这些形式;只靠目测或grep 'this\\.'会漏掉它们。始终运行该脚本——不要根据名称跳过。 - 通过
-
对于
no-ambiguous-event-handler和no-globals的标识符重命名,Linter 给出的坐标具有权威性。 绝不要使用grep/全文件正则表达式来“查找所有用法”。Linter 已经完成了查找,所有违规位置都列在其输出中。编辑 Linter 输出中未列出的行超出了处理范围,而且几乎肯定是错误的:一个看似事件处理程序的名称,也可能用作控件id、selectedKey、绑定路径或字符串字面量——这些用法并非事件处理程序,改写它们会悄然改变运行时语义(例如破坏Fragment.byId查找)。例外:插入core:require需要在文件级别进行判断(放在最近的共同祖先上);每个文件只需插入一次,不需要针对每条发现分别插入。
相关技能
- fix-xml-native-html:对于 XML 视图中原生 HTML/SVG 的替换(
no-deprecated-api消息中包含“native HTML”或“SVG”),请使用 fix-xml-native-html - fix-js-globals:对于 JavaScript 文件中的
no-globals(控制器/工具的全局访问),请使用 fix-js-globals——它处理本技能中 XML 修复对应的 JS 端情况