mcpField
F
fix-xml-globals

fix-xml-globals

github.com/ui5开发44146 次安装1 周前更新
代码仓库

关于 fix-xml-globals

面向开发者的 UI5 XML 视图与片段修复技能,处理需要理解模块路径和处理器位置的 lint 问题。它用局部 `core:require` 声明替代全局引用,并规定事件处理器语法及格式化器绑定方式。

主要功能

  1. 01用 `core:require` 替换全局引用
  2. 02按控件作用域放置模块声明
  3. 03修正含糊的事件处理器
  4. 04转换旧式 `template:require` 语法
  5. 05检查格式化器的 `this` 绑定

适用场景

  1. 01解决全局格式化器引用问题
  2. 02修复 XML 事件处理器警告
  3. 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.md
scripts/
README.md
verify-this-bind.js
verify-this-bind.test.js
scripts/__fixtures__/
nested_function_shadow.js
no_this.js
not_found.js
this_in_comment.js
this_in_string.js
uses_this_alias.js
uses_this_arrow.js
uses_this_bare.js
uses_this_member.js
uses_this_nested_in_if.js
scripts/__fixtures__/xml/
has_bind.fragment.xml
multi_line.fragment.xml
needs_bind.fragment.xml
no_this_no_bind.fragment.xml

详细介绍

修复 XML 视图/片段中的全局访问

此技能用于修复 UI5 linter 能检测到但无法自动修复的 XML 视图和片段问题,因为这些问题需要理解模块路径和处理器的位置。

处理的 Linter 规则

规则 ID消息模式此技能的操作
no-globalsAccess of global variable '...' (...)添加 core:require 并使用本地名称
no-ambiguous-event-handlerEvent handler '...' must be prefixed by a dot '.' or refer to a local name为控制器方法添加 . 前缀,或为模块添加 core:require
no-deprecated-apiUsage 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:'...'} 中)是,前提是函数使用 thisthis = 拥有该绑定的控件实例。$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,包括:

  1. sap 命名空间 — sap.m.ButtonType.Accept、sap.ui.model.type.Currency → 使用 core:require 引入模块
  2. jQuery 命名空间 — jQuery.sap.getModulePath → 使用 core:require 引入模块
  3. 应用命名空间事件处理器 — com.example.app.utils.Handler.onPress → 使用 core:require 引入工具模块
  4. 应用命名空间格式化器 — 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 中修复。

实现步骤

  1. 读取 XML 视图/片段文件

  2. 解析并按规则 ID 识别检查器错误:

    • no-globals:绑定中的全局命名空间引用(格式化器、类型、事件处理程序、工厂)
    • no-ambiguous-event-handler:未添加正确前缀的事件处理程序
    • no-deprecated-api:旧版模板 require 语法
  3. 对于 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 对象(名称冲突时使用别名)
    • 更新所有引用,使其使用本地名称
  4. 对于 no-ambiguous-event-handler 错误:

    • 使用检查器报告的 line:col 定位确切的属性。只读取该行。
    • 只编辑该坐标处的值。对于裸处理程序名称,在前面加上 .;对于命名空间路径,则应用 core:require 并替换为别名。
    • 不要在文件中搜索同一处理程序名称字符串的其他出现位置。检查器已列出所有违规位置;如果某个字符串出现在其他位置但未被报告,那它就是不同的属性(id、selectedKey、key、name、纯文本等),不得修改。一个检查器发现 → 一处编辑。
  5. 对于 no-deprecated-api(模板 require):

    • 将以空格分隔的列表转换为对象表示法
  6. 写入更新后的文件

  7. 编辑后验证(必须执行)。 完成本阶段所有 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 端情况

评论

支持 Markdown