mcpField
F
fix-js-globals

fix-js-globals

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

关于 fix-js-globals

fix-js-globals 是面向 UI5 项目的编码技能,用于修复 UI5 linter 检测到但无法自动处理的 JavaScript 全局访问问题。它按不同模式提供模块导入与代码迁移规则,并强调一次性处理相关全局引用。

主要功能

  1. 01修复 UI5 全局访问问题
  2. 02替换已弃用的 Core API
  3. 03迁移 jQuery.sap 工具调用
  4. 04转换旧式模块与控制器
  5. 05识别并删除未使用赋值

适用场景

  1. 01处理 UI5 linter 的 no-globals 报错
  2. 02将旧式控制器改为 sap.ui.define
  3. 03替换控制器中的控件 ID 查找
  4. 04排查 jQuery.sap API 用法

MCP 相关

文档建议在需要额外 Core 方法替换时使用 UI5 MCP Server 的 get_api_reference 工具。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ui5/plugins-coding-agents --skill fix-js-globals

安装后在 Claude Code 中输入/fix-js-globals确认已加载。

附带文件references/core-api-replacements.mdreferences/example-fix-session.md

详细介绍

修复 JavaScript 全局访问(no-globals)

修复 JavaScript 文件中 UI5 linter 检测到但无法自动修复的 no-globals 错误。运行 npx @ui5/linter --details 获取替换建议和文档链接。

关键规则——应用任何修复前请阅读

  1. jQuery/$ 全局变量——保留 jQuery API 调用:修复 jQuery/$ 全局变量时,只添加 sap/ui/thirdparty/jquery 依赖,并将 $ 替换为 jQuery。不要将标准 jQuery API 调用(jQuery.each、jQuery.extend、jQuery.proxy、jQuery.isEmptyObject 等)替换为原生 JavaScript 等效写法。这些是标准 jQuery 方法,不是已弃用的 SAP API。

  2. 情况 9/10——一次性修复所有全局变量:将文件从 jQuery.sap.declare/require 转换为 sap.ui.define(情况 9 或 10)时,必须在同一轮中修复文件主体内所有其他全局访问模式。没有第二轮——必须一次性全部处理。请阅读下文“在同一轮中应用所有适用情况”一节。

  3. 无用代码——删除,不要导入:如果全局赋值存储的值在文件其他位置从未读取(例如,this.BarColor = sap.ui.core.BarColor,且 this.BarColor 再未出现),请删除整个语句。不要为其添加导入。

  4. 控制器中的 byId 不要使用中间形式:控制器内的 sap.ui.getCore().byId("prefix--id") 或 jQuery("#prefix--id").control(0) 应直接改为 this.byId("id")。不要将其保留为 Element.getElementById("prefix--id")。替换后,移除未使用的 Element 或 jQuery 导入。

  5. 使用 merge,而不是 deepExtend:将 jQuery.sap.extend(true, ...) 替换为来自 sap/base/util/merge 的 merge()。模块 sap/base/util/deepExtend 不存在。

按情况分类的修复策略

1. 赋值到全局命名空间

问题:代码在全局 sap 对象上创建自定义命名空间。

// Before
sap.ui.demo = sap.ui.demo || {};
sap.ui.demo.myApp = { formatter: function() { ... } };

// After — convert to AMD module
sap.ui.define("sap/ui/demo/myApp", [], function() {
    "use strict";
    return { formatter: function() { ... } };
});
1b. sap.ui.define 内部的全局命名空间赋值

问题:文件已位于 sap.ui.define 中,但仍赋值到全局命名空间并返回全局引用(这是移除 jQuery.sap.declare 后遗留的问题)。

linter 不会报告——请手动搜索:grep -rl "your\.project\.namespace\." webapp/ --include="*.js"

// Before
sap.ui.define([], function() {
    "use strict";
    com.example.app.utils.MyScripts = { runTests: function() { ... } };
    return com.example.app.utils.MyScripts;
});

// After — local variable replaces global assignment
sap.ui.define([], function() {
    "use strict";
    var MyScripts = { runTests: function() { ... } };
    return MyScripts;
});

关键规则:从命名空间末尾提取短名称。使用 var ShortName(作用域至关重要)。替换文件中所有对完整命名空间的引用。

1c. sap.ui.define 中全局命名空间的只读引用

问题:文件通过变量赋值从全局命名空间读取,而不是将模块作为依赖项导入。

不会被代码检查器报告——搜索:grep -rn "var .* = your\.project\.namespace\." webapp/ --include="*.js"

// Before
sap.ui.define(["sap/ui/core/mvc/Controller"], function(Controller) {
    var Helper = com.example.app.utils.Helper;
    // ...
});

// After — add as dependency, remove local var assignment
sap.ui.define([
    "com/example/app/utils/Helper",
    "sap/ui/core/mvc/Controller"
], function(Helper, Controller) {
    // Helper is now available via dependency parameter
});

关键规则:

  1. 将点号表示法转换为斜杠表示法:com.example.app.utils.Helper → "com/example/app/utils/Helper"
  2. 在依赖数组开头添加依赖项(参见说明),并添加对应的参数
  3. 删除 var X = global.namespace.X; 这一行
  4. 如果存在多个全局读取,一次性将它们全部添加为依赖项
  5. 确认参数名称与模块的短名称一致
  6. 原子性:每次全局变量→局部变量的替换都必须配对添加一个 sap.ui.define 依赖项。由此引入的循环依赖稍后通过 fix-cyclic-deps 处理
  7. 修复后验证:搜索每个新增的变量名,确认其解析为参数、var/let/const,或 sap.ui.require 调用

替换前,请先阅读目标模块的 return 语句:

模块的 return如何使用依赖参数
返回一个类new MyClass() 或 MyClass.staticMethod()
返回一个包装器MyModule.getInstance()
返回一个实例直接调用 myInstance.method()
不返回任何内容(仅产生副作用)先修复目标模块——添加 return

副作用模块:如果目标模块以 }); 结尾且没有 return,请打开该模块,将全局命名空间赋值替换为局部变量,并添加 return varName;。然后在使用该模块的模块中正常导入。

2. sap.ui.getCore() 调用

问题:sap.ui.getCore() 已弃用;其方法已移至专用模块。

// Before — standalone init script (NOT a controller)
sap.ui.getCore().attachInit(function() { ... });

// After
sap.ui.define(["sap/ui/core/Core"], function(Core) {
    Core.ready().then(function() { ... });
});

注意:Core.ready() 用于启动阶段的初始化脚本。在控制器中,UI5 已完成初始化,因此不要在此处使用。

主要替换方式(完整表格见 references/core-api-replacements.md):

已弃用模块调用
sap.ui.getCore().attachInit(fn)sap/ui/core/CoreCore.ready().then(fn)
sap.ui.getCore().byId(id)sap/ui/core/ElementElement.getElementById(id)——在控制器中优先使用 this.byId()
sap.ui.getCore().getEventBus()sap/ui/core/EventBusEventBus.getInstance()
sap.ui.getCore().getLibraryResourceBundle(lib)sap/ui/core/LibLib.getResourceBundleFor(lib)

如需替换更多 Core 方法,请使用 UI5 MCP Server 的 get_api_reference 工具。

3. 直接访问 sap.ui.core.Core

将 sap/ui/core/Core 添加到依赖数组,并移除全局访问:

// Before: var Core = sap.ui.core.Core;
// After: add "sap/ui/core/Core" to deps, use Core parameter directly
4. jQuery/$ 全局访问

重要:修复方式是添加导入,而不是替换 jQuery API 调用。jQuery.sap.*(包含 .sap.)已弃用,必须替换(情况 4b)。jQuery.*(不含 .sap.)或 jQuery(...) 是标准 jQuery,应保持不变。

// Before
jQuery("#el").addClass("x");
$(".container").css("display", "block");
jQuery.each(items, function(i, item) { ... });

// After — add dependency, rename $ to jQuery, keep all API calls unchanged
sap.ui.define([..., "sap/ui/thirdparty/jquery"], function(..., jQuery) {
    jQuery("#el").addClass("x");
    jQuery(".container").css("display", "block");
    jQuery.each(items, function(i, item) { ... });
});

绝不要替换以下标准 jQuery 方法——它们在 UI5 中并未弃用:jQuery.each、jQuery.extend、jQuery.proxy、jQuery.isEmptyObject、jQuery.isArray、jQuery.inArray、jQuery.grep、jQuery.map、jQuery.type、jQuery.trim。

4a. 用于查找 UI5 控件的 jQuery DOM 查询 → this.byId()

问题:在控制器中,通过 jQuery("#prefix--id").control(0) 或 Element.closestTo() 获取 UI5 控件。

检测模式——以下写法都应改为 this.byId("<local-id>"):

  • jQuery("#<anything>--<id>").control(0)
  • Element.closestTo(jQuery("#<anything>--<id>")[0])
  • sap.ui.getCore().byId("<full-id>"),其中 ID 包含视图前缀
  • Element.getElementById("<full-id>"),其中 ID 包含 --

局部 ID 是最后一个 -- 之后的部分。替换后,移除未使用的 jQuery/Element 导入。

4b. jQuery.sap.* 工具访问

问题:jQuery.sap.* 调用是已弃用的 UI5 工具,需要使用专用替代模块。

// Before
jQuery.sap.log.info("msg");
var sId = jQuery.sap.uid();

// After
sap.ui.define([..., "sap/base/Log", "sap/base/util/uid"], function(..., Log, uid) {
    Log.info("msg");
    var sId = uid();
});

运行 npx @ui5/linter --details 查看建议的替代方案。完整表格见 references/core-api-replacements.md。

jQuery.sap.extend 的选择:

  • 深拷贝(第一个参数为 true)→ sap/base/util/merge → merge({}, obj1, obj2)
  • 扁平对象(单层属性)→ Object.assign({}, obj1, obj2)(无需导入)
  • 绝不要转换为 jQuery.extend(...)(会引入不必要的依赖)
  • 绝不要使用 sap/base/util/deepExtend(该模块不存在)
5. 条件式/探测式全局访问

问题:代码会检查某个全局对象是否存在:if (sap.ui.fl && sap.ui.fl.Utils) { ... }

修复:对于始终可用的模块,将其添加为 sap.ui.define 依赖。对于确实可选的模块,使用同步 sap.ui.require:

var FlUtils = sap.ui.require("sap/ui/fl/Utils");
if (FlUtils) { FlUtils.getComponentClassName(this); }

如需延迟加载,请使用异步方式:sap.ui.require(["module/path"], function(Mod) { ... })。

6. 自定义命名空间定义

与情况 1 的模式相同,但适用于非 SAP 命名空间(window.mycompany.myapp = {...})。将其转换为返回该对象的 sap.ui.define 模块。使用方通过依赖项导入。

7. 未导入的绑定类型字符串
// Before — global reference as string
value: { path: "/amount", type: "sap.ui.model.type.Float" }

// After — import type module, use class reference
value: { path: "/amount", type: new FloatType() }  // FloatType from "sap/ui/model/type/Float"
8. delete 表达式

delete sap.ui.core.someTempProperty——这通常意味着代码存在问题。请彻底移除,或改用局部对象。

9. sap.ui.controller()——通过全局工厂定义控制器

范围:普通控制器定义。不适用于 Fiori Elements V2 扩展(请对这些扩展使用 fix-fiori-elements-extensions)。

检测:grep -rn 'sap\.ui\.controller(' webapp/ --include="*.js"

  • 两个参数 sap.ui.controller("name", {...}) = 定义 → 在此处修复
  • 一个参数 sap.ui.controller("name") = 实例查找 → 在 MODERNIZATION-ISSUES.md 中记录
模式 A:位于现有的 sap.ui.define 中
// Before
sap.ui.define(["sap/m/MessageBox"], function(MessageBox) {
    return sap.ui.controller("my.app.controller.Main", { ... });
});

// After — add Controller dep, replace factory with extend
sap.ui.define([
    "sap/ui/core/mvc/Controller",
    "sap/m/MessageBox"
], function(Controller, MessageBox) {
    return Controller.extend("my.app.controller.Main", { ... });
});
模式 B:不含 sap.ui.define(旧版模块系统)
// Before
jQuery.sap.declare("my.app.controller.Detail");
jQuery.sap.require("sap.ui.core.mvc.Controller");
sap.ui.controller("my.app.controller.Detail", { onInit: function() { ... } });

// After
sap.ui.define(["sap/ui/core/mvc/Controller"], function(Controller) {
    "use strict";
    return Controller.extend("my.app.controller.Detail", { onInit: function() { ... } });
});

步骤:移除 jQuery.sap.declare/require。用 sap.ui.define 包裹。将点号表示法依赖项转换为斜杠表示法。将 sap.ui.controller 替换为 Controller.extend。添加 return。添加 "use strict"。对文件主体应用所有内联修复(参见“应用所有修复”部分)。

边缘情况
  • 缺少 return:Controller.extend() 只会返回类——务必在其前添加 return
  • 定义前存在模块级变量:保持原样,只需用 return 包裹 extend 调用
  • 控制器名称必须与文件路径匹配:保留现有名称,即使不匹配(可能是有意为之)
  • 混合文件(定义 + 实例查找):修复定义,并在 MODERNIZATION-ISSUES.md 中记录实例查找
10. jQuery.sap.declare/require——旧版模块定义

结构转换方式与情况 9 的模式 B 相同,但适用于非控制器模块:

// Before
jQuery.sap.declare("my.app.util.Formatter");
jQuery.sap.require("sap.ui.core.format.DateFormat");
my.app.util.Formatter = { formatDate: function(oDate) { ... } };

// After
sap.ui.define(["sap/ui/core/format/DateFormat"], function(DateFormat) {
    "use strict";
    return { formatDate: function(oDate) { ... } };
});

关键规则:移除 jQuery.sap.declare。将 jQuery.sap.require 转换为依赖项。移除全局赋值,改为 return 该对象。如果已有 sap.ui.define,则将剩余的 require 合并到现有依赖数组中。动态/条件式 require → sap.ui.require(["..."], callback)。存在多个 declare 或导出不明确时 → 标记为需要人工审核。应用所有内联修复(参见“应用所有修复”部分)。

11. 将运行时全局对象作为模块导入

问题:通过全局命名空间链访问 sap.ushell.Container 等运行时模块。

// Before
if (sap.ushell && sap.ushell.Container) {
    sap.ushell.Container.getService("CrossApplicationNavigation");
}

// After — add as dependency
sap.ui.define(["sap/ushell/Container", ...], function(Container, ...) {
    if (Container && Container.getService) {
        Container.getService("CrossApplicationNavigation");
    }
});

测试侧:使用 sinon 直接替身替换导入的模块。不要设置全局命名空间链(window.sap.ushell = {...})。sinon 和 QUnit 是 Test Starter 全局对象,无需导入。

sap.ui.define(["sap/ushell/Container"], function(Container) {
    var oSandbox = sinon.createSandbox();
    QUnit.test("...", function(assert) {
        oSandbox.stub(Container, "setDirtyFlag");
        // ...
    });
});
12. jQuery.sap.sjax 现代化后的同步 XHR 防护

将 jQuery.sap.sjax 现代化为原生 XMLHttpRequest 后,务必通过状态检查来保护 xhr.responseText:

var xhr = new XMLHttpRequest();
xhr.open("GET", sUrl, false);
xhr.send();
if (xhr.readyState === 4 && xhr.status === 200) {
    var oData = JSON.parse(xhr.responseText);
} else {
    Log.error("Failed to load: " + sUrl);
}
上下文失败时的回退方式
模拟服务响应处理程序oXhr.respondJSON(200, {}, JSON.stringify({"d": {"results": []}}))
对响应执行 JSON.parse返回 {}(这样现有的 if (oResponse.data) 防护就能正常工作)
初始化时加载配置使用 Log.error(...) 提前返回

关键:在一次处理中应用所有适用情形

当文件触发 Case 9 或 10 时,在同一次处理中修复所有全局访问模式:

  1. 有 jQuery("#...") 或 jQuery(...) 调用吗?→ Case 4a 或 4
  2. 有 sap.ui.getCore().byId(...) 调用吗?→ Case 2,然后如果在控制器中则应用 Case 4a
  3. 有 jQuery.sap.* 调用吗?→ Case 4b
  4. 有内联的 sap.ui.model.*、sap.m.*、sap.ui.core.* 类引用吗?→ 导入依赖
  5. 有应用命名空间的全局引用吗?→ 导入依赖
  6. 替换后有未使用的导入吗?→ 从依赖数组和参数中移除
  7. 有 this.X = importedModule.X,且其他地方不再读取 this.X 吗?→ 删除(无用代码)

实施步骤

  1. 运行 npx @ui5/linter --details 获取替换建议
  2. 找出错误模式并确定情形类型
  3. 应用相应的转换(添加依赖、替换全局引用、删除无用代码)
  4. 将 jQuery DOM 查找替换为 this.byId() 后,移除未使用的导入
  5. 确认没有其他文件依赖已移除的全局赋值

备注

  • 依赖插入位置——关键:始终将新依赖添加到数组开头(并将对应参数添加到函数开头)。许多旧文件中的依赖数量与参数数量不匹配(末尾有无对应参数的副作用导入)。添加到末尾会导致现有映射错位;添加到开头则可保留这些映射。

    // Before (3 deps, 2 params — mismatch is common in legacy code)
    sap.ui.define(["sap/ui/core/mvc/Controller", "sap/m/MessageToast", "some/sideEffect/Module"
    ], function(Controller, MessageToast) { ... });
    
    // After — new dep added at BEGINNING
    sap.ui.define(["sap/ui/core/Element", "sap/ui/core/mvc/Controller", "sap/m/MessageToast", "some/sideEffect/Module"
    ], function(Element, Controller, MessageToast) { ... });
    
  • 参数名称应与模块的默认导出名称一致(例如,sap/base/Log 对应 Log)

  • 测试文件中允许将 QUnit、sinon 作为全局变量

  • 允许使用全局变量 sap.ui.define、sap.ui.require、sap.ui.loader.config

  • 对于可选依赖,使用 sap.ui.require("module/path")(同步;如果尚未加载则返回 undefined)

  • 对于延迟加载,使用 sap.ui.require(["module/path"], callback)(异步)

示例修复流程

如需查看结合多种情形的完整前后示例,请阅读 references/example-fix-session.md。

相关技能

  • fix-fiori-elements-extensions:适用于使用 registerControllerExtensions 或 manifest sap.ui.controllerExtensions 的 Fiori Elements V2 应用中的 sap.ui.controller()
  • fix-pseudo-modules:适用于 no-pseudo-modules 和 no-implicit-globals 错误(枚举导入、DataType 导入、OData 表达式函数)
  • fix-control-renderer:适用于渲染器相关问题(no-deprecated-control-renderer-declaration、apiVersion、IconPool、rerender)
  • fix-xml-globals:适用于 XML 视图/片段中的 no-globals(格式化程序、通过 core:require 调用事件处理程序)
  • fix-linter-blind-spots:适用于 linter 无法检测、会导致运行时出错的全局命名空间模式(sap.* 之外的应用专属命名空间)。其中的 Case 1b 和 1c 与该技能中的模式 1-4 有重叠。
  • fix-cyclic-deps:如果 Case 1c 的修复会导致循环依赖,请改用延迟 sap.ui.require,而不是常规的 sap.ui.define 依赖

评论

支持 Markdown