🔍fix-linter-blind-spots
- プラグイン
- ui5-modernization
- ソース
- GitHub で見る ↗
説明
UI5 linterが**報告しない**ランタイムクラッシュのパターンを、JSファイル内で検出・修正します。 次のような場合に使用: - モダナイズ後にテストが `"ReferenceError"`、`"TypeError: Cannot read properties of undefined"`、または `"X is not defined"` で失敗する - linterの残存エラーがゼロにもかかわらず、テストが実行時にクラッシュする - `modernize-ui5-app` ワークフローのフェーズ3・ステップ3.2(グローバル変数の並列バッチ処理の後、`fix-cyclic-deps` の前に実行) - ユーザーが「モダナイズ後のランタイムエラー」「モダナイズ後のテスト失敗」「グローバル名前空間がまだ使われている」と報告している - `fix-js-globals` および `fix-cyclic-deps` を実行済みだが、それでもテストが失敗する - テスト出力に `"X is not a constructor"` やモジュール読み込みエラーが表示される linterがチェックするのは、JSファイル内の `sap.*` グローバルのみです。 アプリ固有の名前空間パターン(例: `com.example.app.utils.Module`)はlinterの検出対象外となります。 本スキルはそれらを**すべてのJSファイル**(アプリソースおよびテスト)にわたって検出・修正します。 **注意:** 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.
ユースケース
- ✓モダナイズ後のランタイムエラーを修正するとき
- ✓linter合格後もテストが失敗するとき
- ✓ReferenceError等の実行時クラッシュを検出・修正するとき
- ✓アプリ固有の名前空間の未定義エラーを解決するとき
- ✓fix-js-globals実行後も残存するテスト失敗を対応するとき
本文
Fix Linter Blind Spots
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).
Scope
Scans ALL .js files:
- App source:
webapp/controller/,webapp/utils/,webapp/model/, etc. - Test files:
webapp/test/unit/,webapp/test/integration/,webapp/test/opa/, etc.
Prerequisites
Read manifest.json to get the namespace:
<NAMESPACE>— slashes (e.g.,com/example/app)<NAMESPACE-WITH-DOTS>— dots (e.g.,com.example.app)
Detection — Initial Scan
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 type
Each namespace finding includes moduleName (extracted short name, e.g., "DataService") and namespacePath (full dotted path, e.g., "com.example.app.utils.DataService").
Patterns to Fix
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.
Pattern 1: Global Namespace Assignment in sap.ui.define — JS Only (Case 1b)
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:
- Extract the short name from the last segment of the namespace path
- Replace
<FULL.NAMESPACE.PATH>.ModuleName = {withvar ModuleName = { - Replace
return <FULL.NAMESPACE.PATH>.ModuleName;withreturn ModuleName; - Replace any other in-file references to the full namespace with the local variable
- The
varkeyword is essential — omitting it creates an implicit global
Pattern 2: Cross-Module Global Namespace References in JS (Case 1c Extended)
A 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):
- Fragment names:
"com.example.app.fragment.Dialog" - Class extend names:
Controller.extend("com.example.app.controller.Main", { - Log messages:
Log.info("com.example.app.utils.Helper initialized") - XML fragment paths:
"com/example/app/fragment/Dialog" - UI5 API string arguments:
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).
Pattern 3: Case 1c Parameter Name Verification
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:
- Parse the
sap.ui.definedependency array and function parameter list - Find which parameter corresponds to the module being referenced
- If the parameter name differs from the module name:
- Rename the function parameter to match the module's short name
- Update all references in the file from the old parameter name to the new one
- Replace the global reference with the (now correct) parameter name
- Delete any resulting self-assignment lines (
var X = X;)
Pattern 4: Case 1c Atomicity — Never Rename Without Import
Every global→local rename must be paired with an import. A renamed variable without a declaration causes ReferenceError — worse than leaving the global.
Rules:
- When replacing a global reference, always add a
sap.ui.definedependency. Do NOT attempt to detect cycles at this stage — cycles introduced here will be detected and resolved byfix-cyclic-depsin Phase 3, Step 3.3 after all fix phases complete. - After fixing a file, validate: grep for every introduced variable name and confirm it resolves to a function parameter,
var/let/constdeclaration, orsap.ui.requirecall - Well-known globals that don't need imports:
window,document,console,sap,jQuery,QUnit,sinon
Pattern 5: Missing assert Parameter in QUnit.test Callbacks
QUnit 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"));
});
Pattern 6: QUnit Global Assertions → assert / Opa5.assert
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:
- File under
test/opa/ortest/integration/AND inside awaitForsuccess/error callback →Opa5.assert.<assertion> - File under
test/unit/or.qunit.jsinside aQUnit.testcallback →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
Pattern 7: Global Namespace Mocking → sinon.stub
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.
Pattern 8: Legacy "jquery.sap.global" Dependency Path
The 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:
- Replace
"jquery.sap.global"with"sap/ui/thirdparty/jquery"in the dependency array - Keep the corresponding function parameter name unchanged (typically
jQuery) - This applies to both
sap.ui.defineandsap.ui.requiredependency arrays
Implementation Steps
- Read
manifest.jsonfor namespace. Compute<NAMESPACE>,<NAMESPACE-WITH-DOTS>,<NS-ESCAPED> - Run the detection script:
node <skill-dir>/scripts/detect-blind-spots.js <project-root> - Apply Pattern 1 (global assignments) — filter findings by
type: "global_assignment"andtype: "global_return". This must complete before Pattern 2, because Pattern 2 relies on modules returning local variables - Apply Pattern 2 (cross-module global refs) — filter findings by
type: "global_read"andtype: "global_method_call". This resolves the majority of namespace references - Apply Pattern 3 (parameter name verification) and Pattern 4 (atomicity validation) during every Pattern 2 fix
- Apply Patterns 5-6 (QUnit assertions) — filter findings by
type: "missing_assert_param",type: "qunit_global_assertion",type: "bare_global_assertion". Use thereplacementfield from each finding - Apply Pattern 7 (sinon mocking) — filter findings by
type: "global_mock" - Apply Pattern 8 (legacy jQuery dependency) — filter findings by
type: "legacy_jquery_dep". Replace each occurrence with the value in thereplacementfield - Re-run the detection script and verify
findings[]is empty (or only contains items that couldn't be fixed — e.g., Pattern 7 items that need manual review) - Run
npx @ui5/linter --detailsas a secondary check — no new errors should be introduced - For findings that can't be automatically fixed (complex mocking patterns, ambiguous module boundaries), document them in MODERNIZATION-ISSUES.md with the file path, line number, and reason
Completion Gate
Before reporting this skill as complete, verify ALL of the following:
- Detection script returns zero findings: Re-run
node <skill-dir>/scripts/detect-blind-spots.js <project-root>and confirmsummary.total === 0(or all remaining items are documented in MODERNIZATION-ISSUES.md with justification) - No new linter errors: Run
npx @ui5/linter --detailsand confirm no new errors were introduced
Related Skills
- fix-js-globals — applies the same transformations (Patterns 1-4) but only for cases the linter explicitly reports as
no-globalserrors (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. - fix-cyclic-deps — runs after this skill (Phase 3, Step 3.3) to detect and resolve cyclic dependencies introduced by the
sap.ui.defineedges added in Pattern 2 - modernize-test-starter — test infrastructure modernization that often reveals these patterns
原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。