UI5アプリの最新化後、JavaScriptファイルにおいてUI5リンター(コード検査ツール)では検出されない実行時エラーのパターンを修正するスキルです。 次のような場合に使用してください: - 最新化後のテストが「ReferenceError」「TypeError: Cannot read properties of undefined」「X is not defined」などのエラーで失敗する - リンターではエラーが0件と表示されているのに、実行時にはテストがクラッシュする - modernize-ui5-app ワークフローのフェーズ3ステップ3.2(パラレルグローバル修正バッチの後、fix-cyclic-deps の前に実行) - ユーザーが「最新化後の実行時エラー」「最新化後のテスト失敗」「グローバル名前空間がまだ使われている」などと述べている - fix-js-globals と fix-cyclic-deps を実行した後も、なおテストが失敗している - テスト出力で「X is not a constructor」またはモジュール読み込みエラーが表示される **このスキルが必要な理由:** リンターは sap.* という標準的なグローバル変数のみをチェックします。一方、アプリ独自の名前空間パターン(例:com.example.app.utils.Module)はリンターには認識されません。このスキルは、アプリのソースコード、テストコードを含むすべてのJavaScriptファイルにおいて、そうした見落とされた独自の名前空間パターンを検出して修正します。 **注記:** XMLファイル内のアプリ名前空間グローバル変数については、`fix-xml-globals` スキルで処理されます。このスキルではありません。
Fix runtime-breaking patterns in JS files that the UI5 linter does NOT report. Use this skill when: - Tests fail after modernization with "ReferenceError", "TypeError: Cannot read properties of undefined", or "X is not defined" - The linter shows zero remaining errors but tests still crash at runtime - Phase 3, Step 3.2 of the modernize-ui5-app workflow (runs after the parallel globals batch, before fix-cyclic-deps) - User mentions "runtime errors after modernization", "test failures after modernization", "global namespace still used" - After running fix-js-globals and fix-cyclic-deps, but tests STILL fail - User sees "X is not a constructor" or module loading errors in test output The linter only checks sap.* globals in JS. App-specific namespace patterns (e.g., com.example.app.utils.Module) are invisible to it. This skill detects and fixes those across ALL JS files — app source AND test. NOTE: App-namespace globals in XML files are handled by `fix-xml-globals` — NOT by this skill.
This skill fixes patterns that cause runtime failures but are not reported by the UI5 linter. The linter's no-globals rule only checks sap.* namespaces in JS files. App-specific global namespace patterns in JavaScript — assignments, cross-module references, QUnit 1.x assertions, and sinon mocking via global chains — are invisible to it.
IMPORTANT — Scope clarification: This skill handles app-namespace globals in JavaScript files only. App-namespace globals in XML files (event handlers, formatters, factory functions using dotted app paths) ARE reported by the linter under the no-globals rule and are handled by fix-xml-globals in Phase 3 — NOT by this skill. Do NOT fix XML app-namespace globals here.
These patterns work in the old bootstrap model (where jQuery.sap.declare builds global namespace chains) but fail under strict AMD module loading (Test Starter, modern UI5).
Scans ALL .js files:
webapp/controller/, webapp/utils/, webapp/model/, etc.webapp/test/unit/, webapp/test/integration/, webapp/test/opa/, etc.Read manifest.json to get the namespace:
<NAMESPACE> — slashes (e.g., com/example/app)<NAMESPACE-WITH-DOTS> — dots (e.g., com.example.app)Run the bundled detection script from <skill-dir>/scripts/detect-blind-spots.js:
node <skill-dir>/scripts/detect-blind-spots.js <project-root>
The script uses a character-level scanner to distinguish namespace occurrences in bare code (findings) from those inside string literals or comments (skipped). This avoids false positives like sap.ui.controller("com.example.app.controller.Detail") or Controller.extend("com.example.app.controller.Main", { which are string arguments, not global code references.
Output (JSON to stdout, progress to stderr):
findings[] — actionable items classified by type: global_assignment, global_read, global_return, global_mock, global_method_call, missing_assert_param, qunit_global_assertion, bare_global_assertionskipped[] — namespace occurrences deliberately ignored (in strings/comments)summary — counts by typeEach namespace finding includes moduleName (extracted short name, e.g., "DataService") and namespacePath (full dotted path, e.g., "com.example.app.utils.DataService").
Fix patterns in this order — Pattern 1 must complete before Pattern 2 (because Pattern 2 relies on modules having proper return statements from Pattern 1 fixes). Patterns 3–4 apply during every Pattern 2 fix.
A JS file wrapped in sap.ui.define assigns to a global namespace and returns the global reference. This is a leftover from jQuery.sap.declare removal where the global assignment was not cleaned up.
Scope: JavaScript files only. The linter does NOT report app-namespace globals in JS. (App-namespace globals in XML files ARE reported by the linter and are fixed by fix-xml-globals in Phase 3.)
Detection: Use findings from the detection script filtered by type: "global_assignment" and type: "global_return".
Before:
sap.ui.define(["sap/base/Log"], function(Log) {
"use strict";
com.example.app.utils.DataService = {
fetchData: function() { Log.info("fetching"); },
processData: function(aItems) { return aItems.map(/*...*/); }
};
return com.example.app.utils.DataService;
});
After:
sap.ui.define(["sap/base/Log"], function(Log) {
"use strict";
var DataService = {
fetchData: function() { Log.info("fetching"); },
processData: function(aItems) { return aItems.map(/*...*/); }
};
return DataService;
});
Rules:
<FULL.NAMESPACE.PATH>.ModuleName = { with var ModuleName = {return <FULL.NAMESPACE.PATH>.ModuleName; with return ModuleName;var keyword is essential — omitting it creates an implicit globalA JS file references another module via the global namespace chain instead of importing it. Three sub-cases:
Detection: Use findings from the detection script filtered by type: "global_read", type: "global_method_call", or type: "global_mock". The script's character-level scanner automatically skips namespace occurrences inside string literals and comments — no manual filtering needed.
Sub-case A — Module already imported under a different name:
// Before: dep loaded as oDependency but also accessed via global
sap.ui.define(["com/example/app/utils/Helper"], function(oDependency) {
var oHelper = com.example.app.utils.Helper; // redundant global read
oHelper.doSomething();
});
// After: delete the redundant line, use the function parameter
sap.ui.define(["com/example/app/utils/Helper"], function(oDependency) {
oDependency.doSomething();
});
If the parameter name doesn't match the module name (as here: oDependency vs Helper), also apply Pattern 3 to rename it.
Sub-case B — Module not imported:
// Before: no import, accessed via global
sap.ui.define(["sap/base/Log"], function(Log) {
var oHelper = com.example.app.utils.Helper;
oHelper.doSomething();
});
// After: add as dependency
sap.ui.define([
"com/example/app/utils/Helper",
"sap/base/Log"
], function(Helper, Log) {
Helper.doSomething();
});
CRITICAL — Before applying Sub-case B, read the target module's return statement to determine the correct replacement pattern. Not every module exports a usable value — some are side-effect-only (no AMD export). For side-effect modules, fix the target module first by adding a return statement and removing the global namespace assignment, then import normally. See fix-js-globals/SKILL.md § Case 1 → "Before replacing global access, read the target module's return statement" for the full decision table and procedure.
For test files, use test-resources/<NAMESPACE>/ prefix instead of <NAMESPACE>/test/.
Sub-case C — Self-reference (module references itself via global):
// Before: module object assigned to local var, then referenced via global
var DataService = { /*...*/ };
var svc = com.example.app.utils.DataService; // self-reference
svc.process();
// After: use the local variable directly
var DataService = { /*...*/ };
DataService.process();
What the detection script automatically skips — string literals:
These contain the namespace but are NOT code references (they appear in the script's skipped[] output):
"com.example.app.fragment.Dialog"Controller.extend("com.example.app.controller.Main", {Log.info("com.example.app.utils.Helper initialized")"com/example/app/fragment/Dialog"sap.ui.controller("com.example.app.controller.Detail")Only fix patterns where the namespace is used as a value (assigned to a variable, used in a property access chain, or passed as a function argument that expects an object, not a string).
Before replacing a global reference with a local variable name, verify the sap.ui.define function parameter name matches the module's short name.
Problem scenario:
sap.ui.define(["com/example/app/utils/DataService"], function(Formatter) {
// Parameter named "Formatter" but module is "DataService"
var oSvc = com.example.app.utils.DataService; // global read
});
Naively replacing the global with DataService fails — the parameter is Formatter, not DataService.
Fix steps:
sap.ui.define dependency array and function parameter listvar X = X;)Every global→local rename must be paired with an import. A renamed variable without a declaration causes ReferenceError — worse than leaving the global.
Rules:
sap.ui.define dependency. Do NOT attempt to detect cycles at this stage — cycles introduced here will be detected and resolved by fix-cyclic-deps in Phase 3, Step 3.3 after all fix phases complete.var/let/const declaration, or sap.ui.require callwindow, document, console, sap, jQuery, QUnit, sinonassert Parameter in QUnit.test CallbacksQUnit 2.x (enforced by Test Starter) requires assert as a function parameter. QUnit 1.x made it a global.
Detection: Use findings from the detection script filtered by type: "missing_assert_param".
Before:
QUnit.test("formats value correctly", function() {
assert.equal(formatter.format(1), "One");
assert.ok(formatter.isValid("test"));
});
After:
QUnit.test("formats value correctly", function(assert) {
assert.equal(formatter.format(1), "One");
assert.ok(formatter.isValid("test"));
});
QUnit 1.x allowed calling assertion methods directly on the QUnit object (QUnit.ok(...), QUnit.equal(...)). QUnit 2.x removed these — assertions go through the assert parameter (unit tests) or Opa5.assert (OPA tests).
Detection: Use findings from the detection script filtered by type: "qunit_global_assertion" and type: "bare_global_assertion". Each finding includes assertionName and replacement (either assert.<name> or Opa5.assert.<name> depending on file location).
Context detection:
test/opa/ or test/integration/ AND inside a waitFor success/error callback → Opa5.assert.<assertion>test/unit/ or .qunit.js inside a QUnit.test callback → assert.<assertion> (using the function parameter)OPA example:
// Before:
success: function(aItems) {
QUnit.ok(aItems.length > 0, "Items found");
ok(true, "Test passed"); // bare global
}
// After:
success: function(aItems) {
Opa5.assert.ok(aItems.length > 0, "Items found");
Opa5.assert.ok(true, "Test passed");
}
Verify the file imports sap/ui/test/Opa5 before using Opa5.assert. If not, add it as a dependency.
Unit test example:
// Before:
QUnit.test("test name", function(assert) {
QUnit.ok(result);
QUnit.equal(a, b);
});
// After:
QUnit.test("test name", function(assert) {
assert.ok(result);
assert.equal(a, b);
});
Full list of assertions to replace:
ok, equal, notEqual, deepEqual, notDeepEqual, strictEqual, notStrictEqual, propEqual, notPropEqual, throws, expect, push
Tests that mock dependencies by overwriting properties on the global namespace chain fail when the chain doesn't exist.
Detection: Use findings from the detection script filtered by type: "global_mock". These cover both the jQuery.extend backup and the function-override patterns that involve the app namespace. Mocking patterns that back up sap.* sub-namespaces (e.g., sap.ushell.Container) are out of scope — those are framework globals, not app-namespace globals.
Look for the backup-override-restore pattern:
var oBackup = jQuery.extend(true, {}, com.example.app.utils);
com.example.app.utils.Module.method = function() { /*mock*/ };
// ... test ...
com.example.app.utils = oBackup;
Before (test):
sap.ui.define(["com/example/app/model/formatter"], function(Formatter) {
QUnit.test("show button", function(assert) {
var oBackup = jQuery.extend(true, {}, com.example.app.utils);
com.example.app.utils.Actions.getInvalidItems = function() { return []; };
// ... test using Formatter which calls Actions.getInvalidItems ...
com.example.app.utils = oBackup;
});
});
After (test):
sap.ui.define([
"com/example/app/model/formatter",
"com/example/app/utils/Actions"
], function(Formatter, Actions) {
QUnit.test("show button", function(assert) {
var oStub = sinon.stub(Actions, "getInvalidItems").returns([]);
// ... test ...
oStub.restore();
});
});
sinon is globally available via the Test Starter infrastructure — no need to import it as a sap.ui.define dependency.
Companion fix required: If the app source module (e.g., Formatter.js) also accesses the stubbed module via the global namespace, fix it to import that module as a sap.ui.define dependency. Otherwise the sinon stub won't intercept the call — the app code would be accessing a different object. These cases will already appear in the detection script's findings[] as global_read or global_method_call — fix them as part of Pattern 2.
"jquery.sap.global" Dependency PathThe dependency "jquery.sap.global" is deprecated and must be replaced with "sap/ui/thirdparty/jquery".
Detection: Use findings from the detection script filtered by type: "legacy_jquery_dep". Each finding includes a replacement field with the correct value ("sap/ui/thirdparty/jquery").
Before:
sap.ui.define([
"sap/ui/core/mvc/Controller",
"jquery.sap.global"
], function(Controller, jQuery) {
"use strict";
// ...
});
After:
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/thirdparty/jquery"
], function(Controller, jQuery) {
"use strict";
// ...
});
Rules:
"jquery.sap.global" with "sap/ui/thirdparty/jquery" in the dependency arrayjQuery)sap.ui.define and sap.ui.require dependency arraysmanifest.json for namespace. Compute <NAMESPACE>, <NAMESPACE-WITH-DOTS>, <NS-ESCAPED>node <skill-dir>/scripts/detect-blind-spots.js <project-root>type: "global_assignment" and type: "global_return". This must complete before Pattern 2, because Pattern 2 relies on modules returning local variablestype: "global_read" and type: "global_method_call". This resolves the majority of namespace referencestype: "missing_assert_param", type: "qunit_global_assertion", type: "bare_global_assertion". Use the replacement field from each findingtype: "global_mock"type: "legacy_jquery_dep". Replace each occurrence with the value in the replacement fieldfindings[] is empty (or only contains items that couldn't be fixed — e.g., Pattern 7 items that need manual review)npx @ui5/linter --details as a secondary check — no new errors should be introducedBefore reporting this skill as complete, verify ALL of the following:
node <skill-dir>/scripts/detect-blind-spots.js <project-root> and confirm summary.total === 0 (or all remaining items are documented in MODERNIZATION-ISSUES.md with justification)npx @ui5/linter --details and confirm no new errors were introducedno-globals errors (i.e., sap.* globals). This skill handles app-namespace globals the linter CANNOT detect. Both skills produce the same kind of fix; the difference is how the problem is discovered.sap.ui.define edges added in Pattern 2原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。