brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
with dependency variable, keep all jQuery API calls unchanged\n4b. **jQuery.sap.* utilities**: `jQuery.sap.log`, `jQuery.sap.uid`, etc. — replace with dedicated modules\n5. **Conditional/probing access**: `if (sap.ui.something)`\n6. **Custom namespace definitions**: Non-UI5 module namespaces\n7. **Binding type strings**: `type: \"sap.ui.model.type.Integer\"` without import\n8. **sap.ui.getCore() calls**: Needs special transformation\n9. **sap.ui.controller() factory**: Controller definition or instantiation via deprecated global\n10. **jQuery.sap.declare/require**: Legacy module definitions without sap.ui.define wrapper\n11. **Runtime globals as module imports**: `sap.ushell.Container` and other runtime-provided modules accessed via global namespace chains — add as `sap.ui.define` dependency\n12. **Sync XHR guards**: After modernizing `jQuery.sap.sjax` to native `XMLHttpRequest`, guard `xhr.responseText` with `readyState === 4 && status === 200`\n\n## Key Rules — Read Before Applying Any Fix\n\n1. **jQuery/$ globals — preserve jQuery API calls**: When fixing jQuery/$ globals, ONLY add the `sap/ui/thirdparty/jquery` dependency and replace `
fix-js-globalsFix JavaScript `no-globals` errors that UI5 linter reports but cannot auto-fix. Use this skill when linter outputs: - `no-globals` rule with message "Access of global variable '...' (...)" in JS files Cases handled (linter CANNOT auto-fix): - Assignments to global namespaces: `sap.myNamespace = {...}` - Global namespace assignment/read inside sap.ui.define …
Independent GitHub snapshot · Automated classification · Original documentation governs
name: fix-js-globals
description: |
Fix JavaScript no-globals errors that UI5 linter reports but cannot auto-fix. Use this skill when linter outputs:
no-globals rule with message "Access of global variable '...' (...)" in JS files
Cases handled (linter CANNOT auto-fix):sap.myNamespace = {...}delete sap.somethingsap/ui/thirdparty/jquery — do NOT replace jQuery API callsif (sap.ui.something)This skill fixes no-globals errors in JavaScript files that the UI5 linter detects but cannot auto-fix. The linter's auto-fix only works for simple read-access patterns; this skill handles the complex cases.
| Rule ID | Message Pattern |
|---|---|
no-globals | Access of global variable '...' (...) |
Run the linter with the --details flag to get additional information about deprecated APIs and their replacements:
npx @ui5/linter --details
This provides:
Example output with --details:
App.controller.js:5:1 error Access of global variable 'getCore' (sap.ui.getCore) no-globals
Details: Do not use global variables to access UI5 modules or APIs.
See: https://ui5.sap.com/#/topic/28fcd55b04654977b63dacbee0552712
sap.m.Button → adds to sap.ui.define dependenciessap.ui.require(...) (allowed)sap.myNamespace = {...}
1b. Global namespace assignment inside sap.ui.define: my.app.Module = {...}; return my.app.Module; — already in AMD module but still using global namespace for assignment/return
1c. Global namespace read-only reference inside sap.ui.define: var x = com.example.app.utils.Helper — already in AMD module but reads from a global namespace instead of importing it as a dependencydelete sap.somethingjQuery(...), $(".selector"), jQuery.each(), jQuery.extend(), jQuery.proxy() — add sap/ui/thirdparty/jquery dependency, replace $ with dependency variable, keep all jQuery API calls unchanged
4b. jQuery.sap. utilities*: jQuery.sap.log, jQuery.sap.uid, etc. — replace with dedicated modulesif (sap.ui.something)type: "sap.ui.model.type.Integer" without importsap.ushell.Container and other runtime-provided modules accessed via global namespace chains — add as sap.ui.define dependencyjQuery.sap.sjax to native XMLHttpRequest, guard xhr.responseText with readyState === 4 && status === 200jQuery/$ globals — preserve jQuery API calls: When fixing jQuery/$ globals, ONLY add the sap/ui/thirdparty/jquery dependency and replace $ with jQuery. Do NOT replace standard jQuery API calls (jQuery.each, jQuery.extend, jQuery.proxy, jQuery.isEmptyObject, etc.) with native JavaScript equivalents. These are standard jQuery methods, not deprecated SAP APIs.
Case 9/10 — fix ALL globals in a single pass: When converting a file from jQuery.sap.declare/require to sap.ui.define (Case 9 or 10), you MUST also fix ALL other global-access patterns inside the file body in the same pass. After the structural conversion, scan the entire file for: jQuery("#prefix--id").control(0) → this.byId("id"), sap.ui.getCore().byId(...) → this.byId(...), jQuery.sap.* utilities → dedicated modules, inline class references like new sap.ui.model.json.JSONModel() → imported dependency. There is no second pass — everything must be handled at once. Read the "Apply ALL Applicable Cases in a Single Pass" section below.
Dead code — delete, don't import: If a global assignment stores a value that is never read anywhere else in the file (e.g., this.BarColor = sap.ui.core.BarColor where this.BarColor never appears again), delete the entire statement. Do NOT add an import for it — that would introduce an unused dependency.
No intermediate forms for byId in controllers: sap.ui.getCore().byId("prefix--id") or jQuery("#prefix--id").control(0) inside a controller → this.byId("id") directly. Never leave it as Element.getElementById("prefix--id") — if the ID contains -- and you're inside the owning controller, collapse it to this.byId() in one step. After replacing, if Element or jQuery are no longer used elsewhere in the file, remove them from the dependency array.
merge, not deepExtend: jQuery.sap.extend(true, ...) → merge() from sap/base/util/merge. The module sap/base/util/deepExtend does NOT exist — importing it will cause a runtime error. The module is called merge and it performs deep copy.
Problem: Code creates custom namespaces on the global sap object.
// Before - CANNOT be auto-fixed
sap.ui.demo = sap.ui.demo || {};
sap.ui.demo.myApp = {
formatter: function() { ... }
};
Fix Strategy: Convert to proper AMD module definition.
// After - myApp.js
sap.ui.define("sap/ui/demo/myApp", [], function() {
"use strict";
return {
formatter: function() { ... }
};
});
Problem: File is already wrapped in sap.ui.define but still assigns to a deeply-nested global namespace and returns the global reference. This is a leftover from modernization where jQuery.sap.declare was removed but the global assignment pattern was not cleaned up.
Important — not reported by linter: The UI5 linter does NOT flag this pattern as a no-globals error because the global namespace path (e.g., com.example.app.test.utils.MyTestScripts) is not in the sap.* namespace that the linter checks. You must actively search for this pattern using grep:
# Search for global namespace assignments inside sap.ui.define modules
grep -rl "your\.project\.namespace\." webapp/ --include="*.js"
Look for lines matching: <project.namespace>.<path>.<ModuleName> = { and return <project.namespace>.<path>.<ModuleName>;
// Before - CANNOT be auto-fixed
sap.ui.define([], function() {
"use strict";
com.example.app.test.utils.MyTestScripts = {
runTests: function(Given, When, Then) { ... },
checkSomething: function() { ... }
};
return com.example.app.test.utils.MyTestScripts;
});
Fix Strategy: Replace the global namespace assignment with a local variable and return that instead. The module path in sap.ui.define already ensures this file is loadable by its namespace — the global assignment is redundant.
// After
sap.ui.define([], function() {
"use strict";
var MyTestScripts = {
runTests: function(Given, When, Then) { ... },
checkSomething: function() { ... }
};
return MyTestScripts;
});
Key rules:
com.example.app.test.utils.MyTestScripts → MyTestScripts)var ShortName — the var keyword is essential to scope the variable locally; omitting it would create an implicit globalreturn full.namespace.path.ShortName; with return ShortName;sap.*Problem: File is already wrapped in sap.ui.define but reads from a global namespace via a local variable assignment instead of importing the module as a dependency. The referenced module exists as a proper AMD module elsewhere — but this consumer file still accesses it through the legacy global namespace.
This is a different pattern from 1b: there is no assignment to the global namespace, only a read from it. The file does not define the module — it consumes it.
Important — not reported by linter: Like 1b, the UI5 linter does NOT flag this pattern when the namespace is outside sap.*. Search for it with grep:
# Search for read-only global namespace references inside sap.ui.define modules
grep -rn "var .* = your\.project\.namespace\." webapp/ --include="*.js"
Look for lines matching: var <LocalName> = <project.namespace>.<path>.<ModuleName>;
// Before - CANNOT be auto-fixed
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function(Controller) {
"use strict";
var Helper = com.example.app.utils.Helper;
return Controller.extend("com.example.app.controller.Main", {
onInit: function() {
Helper.doSomething();
}
});
});
Fix Strategy: Add the referenced module as a dependency to sap.ui.define and remove the local variable assignment. The dependency parameter replaces the local variable — since they typically share the same name, all downstream usage works without further changes.
// After
sap.ui.define([
"com/example/app/utils/Helper",
"sap/ui/core/mvc/Controller"
], function(Helper, Controller) {
"use strict";
return Controller.extend("com.example.app.controller.Main", {
onInit: function() {
Helper.doSomething();
}
});
});
Key rules:
Convert the dot-notation namespace to a slash-notation module path: com.example.app.utils.Helper → "com/example/app/utils/Helper"
Add the module path to the sap.ui.define dependency array (at the beginning, per the dependency insertion rule in the Notes section)
Add a corresponding parameter name to the factory function (matching the local variable name that was used)
Remove the var <Name> = <global.namespace>; line — the dependency parameter now serves the same purpose
If the file has multiple read-only global namespace references, add all of them as dependencies in a single pass
Parameter name verification: Before replacing a global reference, verify that the sap.ui.define function parameter name matches the module's short name. If the module is already a dependency but the parameter has a different name (e.g., function(Formatter) for a DataService module), rename the parameter to match the module name first, then replace global references. Delete any resulting var X = X; self-assignment lines.
Atomicity — never rename without an import: Every global→local replacement MUST be paired with a sap.ui.define dependency. A renamed variable without a declaration causes ReferenceError — worse than leaving the global. 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 steps complete.
Post-fix validation: After fixing a file, grep for every introduced variable name and confirm it resolves to a function parameter, var/let/const declaration, or sap.ui.require call. Well-known globals that don't need imports: window, document, console, sap, jQuery, QUnit, sinon, assert.
Concrete example — stashed enum/constant that is never consumed:
// Before (app_before): global stash that nothing ever reads
this.BarColor = sap.ui.core.BarColor; // ← this.BarColor never used elsewhere
// WRONG fix: importing the library just to keep the dead assignment
// sap.ui.define(["sap/ui/core/library"], function(coreLibrary) {
// this.BarColor = coreLibrary.BarColor; ← STILL dead code!
// CORRECT fix: DELETE both the assignment and the import entirely
// (no sap/ui/core/library import, no this.BarColor line)
How to verify: grep the entire project (JS files, XML views, JSON models) for any read of this.BarColor, BarColor, or {BarColor}. If the only occurrence is the assignment itself → it's dead code → delete it.
CRITICAL — Before replacing global access, read the target module's return statement:
Not every module exports a usable value. Before replacing global.namespace.Module with a dependency parameter, open the target module file and check what it returns:
Module's return | Dependency parameter | How to use |
|---|---|---|
Returns a class (return MyClass;) | MyClass | new MyClass() or MyClass.staticMethod() |
Returns a wrapper (return { getInstance: fn }) | MyModule | MyModule.getInstance() — NOT new MyModule() |
Returns an instance (return new MyClass()) | myInstance | myInstance.method() directly |
| Returns nothing (side-effect only) | Fix the module first — see below | Then import normally |
Side-effect modules (no AMD export) — fix the target module first:
Some libraries (especially custom reuse libraries) register on the global namespace but do NOT return a value. Instead of working around this with an unused _ parameter, fix the target module to export properly:
com.example.lib.myLibrary = {...})return statement that returns that object// BEFORE — side-effect module (no AMD export, registers on global):
sap.ui.define(["sap/base/Log"], function(Log) {
"use strict";
com.example.lib.myLibrary = {
doSomething: function() { Log.info("done"); }
};
});
// AFTER — proper AMD module with return:
sap.ui.define(["sap/base/Log"], function(Log) {
"use strict";
var myLibrary = {
doSomething: function() { Log.info("done"); }
};
return myLibrary;
});
Then in the consuming module, import normally via the dependency parameter:
// Consuming module — use the dependency parameter, NOT the global
sap.ui.define([
"com/example/lib/myLibrary",
"sap/base/Log"
], function(myLibrary, Log) {
"use strict";
myLibrary.doSomething();
});
How to detect side-effect modules: The target module ends with }); without a preceding return, or assigns to a global (com.x.y = {...}) without returning it.
Procedure for all Case 1c fixes:
return statement at the end of the sap.ui.define factoryProblem: sap.ui.getCore() returns the Core singleton, but sap.ui.core.Core is the class.
Note: attachInit / Core.ready() is for the early boot phase of UI5, typically used in standalone scripts or index.html initialization, NOT inside controllers. When a controller's onInit is called, UI5 is already fully initialized.
// Before - in a standalone init script (NOT a controller)
sap.ui.getCore().attachInit(function() {
// Bootstrap application
new sap.m.Shell({
app: new sap.ui.core.ComponentContainer({ ... })
}).placeAt("content");
});
Fix Strategy: Use the modern replacement APIs via sap.ui.define.
// After - in a standalone init script
sap.ui.define([
"sap/ui/core/Core",
"sap/m/Shell",
"sap/ui/core/ComponentContainer"
], function(Core, Shell, ComponentContainer) {
"use strict";
Core.ready().then(function() {
// Bootstrap application
new Shell({
app: new ComponentContainer({ ... })
}).placeAt("content");
});
});
Common sap.ui.getCore() Method Replacements:
| Deprecated | Replacement Module | Replacement Call |
|---|---|---|
sap.ui.getCore().attachInit(fn) | sap/ui/core/Core | Core.ready().then(fn) |
sap.ui.getCore().byId(id) | sap/ui/core/Element | Element.getElementById(id) — BUT see Case 4a: inside a controller, if the ID contains the view prefix (e.g., "container-myapp---viewName--controlId"), prefer this.byId("controlId") instead |
sap.ui.getCore().getEventBus() | sap/ui/core/EventBus | EventBus.getInstance() |
sap.ui.getCore().getLibraryResourceBundle(lib) | sap/ui/core/Lib | Lib.getResourceBundleFor(lib) |
sap.ui.getCore().loadLibrary(lib, {async:true}) | sap/ui/core/Lib | Lib.load({name: lib}) |
Important: Most Core APIs Have Been Moved
In modern UI5, most methods on sap.ui.core.Core have been moved to dedicated modules. Use the UI5 MCP Server's get_api_reference tool to check deprecation status and find replacements.
For complete replacement tables including extended Core methods, removed APIs, and jQuery.sap.* replacements, read references/core-api-replacements.md.
Problem: Accessing the Core class directly vs the singleton.
// Before - CANNOT be auto-fixed
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function(Controller) {
var Core = sap.ui.core.Core;
// ...
});
Fix Strategy: Add the module to sap.ui.define dependencies.
// After
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/core/Core"
], function(Controller, Core) {
// Use Core directly
});
Problem: Using jQuery or $ as a global without importing the jQuery module.
IMPORTANT: The fix is NOT to replace jQuery API calls. All standard jQuery APIs are fine to use — both instance methods (.find(), .addClass(), etc.) and static methods (jQuery.each(), jQuery.extend(), jQuery.proxy(), etc.). The only issue is that jQuery/$ must be loaded through a proper module dependency.
How to tell the difference: jQuery.sap.* (with .sap.) = deprecated UI5 utility, must be replaced (see 4b below). jQuery.* (without .sap.) or jQuery(...) = standard jQuery, keep as-is, just add import.
// Before - CANNOT be auto-fixed (jQuery/$ used as global)
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function(Controller) {
return Controller.extend("my.app.App", {
onAfterRendering: function() {
jQuery("#myElement").addClass("highlight");
$(".container").css("display", "block");
jQuery.each(aItems, function(i, item) { /* ... */ });
var oMerged = jQuery.extend(true, {}, oDefaults, oSettings);
var fnCallback = jQuery.proxy(this._handleResult, this);
}
});
});
Fix Strategy: Add sap/ui/thirdparty/jquery to dependencies. Replace bare $ with jQuery. Keep all jQuery API calls as-is — do NOT replace jQuery.each with forEach, do NOT replace jQuery.extend with Object.assign, do NOT replace jQuery.proxy with Function.prototype.bind.
// After - jQuery loaded as a proper dependency
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/thirdparty/jquery"
], function(Controller, jQuery) {
return Controller.extend("my.app.App", {
onAfterRendering: function() {
jQuery("#myElement").addClass("highlight");
jQuery(".container").css("display", "block");
jQuery.each(aItems, function(i, item) { /* ... */ });
var oMerged = jQuery.extend(true, {}, oDefaults, oSettings);
var fnCallback = jQuery.proxy(this._handleResult, this);
}
});
});
Key rules:
"sap/ui/thirdparty/jquery" to the dependency array, name the parameter jQuery$ references with jQuery (since $ is a global alias that won't exist once globals are removed)NEVER replace standard jQuery methods with native equivalents. This is WRONG:
| WRONG (do NOT do this) | CORRECT (keep as-is) |
|---|---|
jQuery.proxy(fn, ctx) → fn.bind(ctx) | Keep jQuery.proxy(fn, ctx) unchanged |
jQuery.each(arr, fn) → arr.forEach(fn) | Keep jQuery.each(arr, fn) unchanged |
jQuery.extend({}, a, b) → Object.assign({}, a, b) | Keep jQuery.extend({}, a, b) unchanged |
jQuery.isArray(x) → Array.isArray(x) | Keep jQuery.isArray(x) unchanged |
jQuery.isEmptyObject(x) → Object.keys(x).length === 0 | Keep jQuery.isEmptyObject(x) unchanged |
jQuery.inArray(v, arr) → arr.indexOf(v) | Keep jQuery.inArray(v, arr) unchanged |
jQuery.grep(arr, fn) → arr.filter(fn) | Keep jQuery.grep(arr, fn) unchanged |
jQuery.map(arr, fn) → arr.map(fn) | Keep jQuery.map(arr, fn) unchanged |
jQuery.type(x) → typeof x | Keep jQuery.type(x) unchanged |
jQuery.trim(s) → s.trim() | Keep jQuery.trim(s) unchanged |
Standard jQuery APIs are not deprecated in UI5. UI5 ships jQuery as sap/ui/thirdparty/jquery and all standard jQuery methods remain fully supported. The only fix needed is adding the module dependency — never rewrite the API calls themselves. Replacing jQuery methods with native equivalents would change behavior (e.g., jQuery.extend does deep copy with true flag, jQuery.each handles both arrays and objects, jQuery.proxy preserves identity for later unbinding) and is not required by the linter.
Problem: Using jQuery("#<component-prefix>--<control-id>") followed by .control(0) or Element.closestTo() to get a reference to a UI5 control inside a controller. This is fragile (depends on generated ID prefixes) and unnecessary when you're inside the controller that owns the view.
// Before — fragile jQuery lookup with hardcoded ID prefix
onAfterRendering() {
const avatarDOM = jQuery("#container-todo---app--avatar-profile");
const avatarCtr = avatarDOM.control(0); // or Element.closestTo(avatarDOM[0])
avatarCtr.setSrc(Helper.resolvePath('./img/logo_ui5.png'));
}
Fix Strategy: Replace with this.byId("<local-id>"). The local ID is the part after the last -- separator. This eliminates the need for both the jQuery import and the Element.closestTo import (if they are not used elsewhere in the file).
// After — clean controller API
onAfterRendering() {
const oAvatar = this.byId("avatar-profile");
oAvatar.setSrc(Helper.resolvePath('./img/logo_ui5.png'));
}
Detection patterns:
jQuery("#<anything>--<id>").control(0) → this.byId("<id>")Element.closestTo(jQuery("#<anything>--<id>")[0]) → this.byId("<id>")sap.ui.getCore().byId("<full-id>") inside a controller where the ID contains the view prefix → this.byId("<local-id>")Element.getElementById("<full-id>") inside a controller where the ID contains -- (view prefix separator) → this.byId("<local-id>") (strip everything up to and including the last --)IMPORTANT: Apply Case 4a AFTER Case 2 (sap.ui.getCore().byId → Element.getElementById). If Case 2 produces Element.getElementById("container-app---view--controlId") inside a controller, immediately convert it to this.byId("controlId") in the same pass — do NOT leave the intermediate form.
After replacing: If jQuery and/or Element are no longer used anywhere else in the file, remove them from the dependency array and function parameters.
Problem: Using jQuery.sap.* utility methods — these are deprecated APIs with dedicated replacement modules. This is a different case from plain jQuery DOM access above.
// Before - CANNOT be auto-fixed
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function(Controller) {
return Controller.extend("my.app.App", {
onInit: function() {
jQuery.sap.log.info("Controller initialized");
var sId = jQuery.sap.uid();
}
});
});
Fix Strategy: Replace jQuery.sap.* calls with their dedicated replacement modules.
// After
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/base/Log",
"sap/base/util/uid"
], function(Controller, Log, uid) {
return Controller.extend("my.app.App", {
onInit: function() {
Log.info("Controller initialized");
var sId = uid();
}
});
});
Common jQuery.sap. Replacements:*
Run npx @ui5/linter --details to get the suggested replacement module for each jQuery.sap.* API. For the complete table, read references/core-api-replacements.md.
| Deprecated | Replacement Module | Replacement Call |
|---|---|---|
jQuery.sap.log.* | sap/base/Log | Log.info(), Log.error(), etc. |
jQuery.sap.uid | sap/base/util/uid | uid() |
jQuery.sap.extend(true, ...) | See below | See below |
jQuery.sap.encodeHTML | sap/base/security/encodeXML | encodeXML(text) |
jQuery.sap.extend replacement decision:
true (deep copy): use sap/base/util/merge → merge({}, obj1, obj2). Note: The module name is merge, NOT deepExtend — there is no sap/base/util/deepExtend module.Object.assign({}, obj1, obj2) — no import needed.sap/base/util/merge → merge({}, obj1, obj2).Object.assign unless deep copy is demonstrably required. Inspect the objects being merged — if all properties are primitives or you only need a shallow copy, Object.assign is the correct modern replacement.jQuery.sap.extend(...) to jQuery.extend(...) — that would introduce a new dependency on sap/ui/thirdparty/jquery for no reason. jQuery.sap.extend is a deprecated wrapper and must be replaced with either Object.assign or merge(), never with the raw jQuery equivalent.sap/base/util/deepExtend — this module does NOT exist. The correct deep-copy module is sap/base/util/merge.Problem: Code checks if a global exists before using it.
// Before - CANNOT be auto-fixed (conditional access)
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function(Controller) {
return Controller.extend("my.app.App", {
onInit: function() {
if (sap.ui.fl && sap.ui.fl.Utils) {
sap.ui.fl.Utils.getComponentClassName(this);
}
}
});
});
Fix Strategy: For modules that are always available in your target environment, add them as a sap.ui.define dependency (see Case 11 for runtime globals like sap.ushell.Container). For truly optional modules that may not be loaded, use synchronous sap.ui.require which returns undefined if the module is not loaded:
// After — synchronous require for truly optional modules
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function(Controller) {
return Controller.extend("my.app.App", {
onInit: function() {
var FlUtils = sap.ui.require("sap/ui/fl/Utils");
if (FlUtils) {
FlUtils.getComponentClassName(this);
}
}
});
});
Alternative: For lazy loading, use async sap.ui.require(["sap/ushell/Container"], function(Container) { ... }, function() { /* error */ }) with a callback.
Problem: Application defines its own namespace structure.
// Before - CANNOT be auto-fixed
window.mycompany = window.mycompany || {};
window.mycompany.myapp = window.mycompany.myapp || {};
window.mycompany.myapp.utils = {
helper: function() { ... }
};
Fix Strategy: Convert to proper sap.ui.define module.
// After - mycompany/myapp/utils.js
sap.ui.define([], function() {
"use strict";
return {
helper: function() { ... }
};
});
Other files then consume it via sap.ui.define(["mycompany/myapp/utils"], function(utils) { ... }).
Problem: Using type as string in binding without importing the module.
// Before - triggers no-globals (inside a controller)
var oInput = new Input({
value: {
path: "/amount",
type: "sap.ui.model.type.Float" // Global reference as string
}
});
Fix Strategy: Import the type module and use the class reference.
// After - add "sap/ui/model/type/Float" to sap.ui.define dependencies
var oInput = new Input({
value: {
path: "/amount",
type: new FloatType() // FloatType from dependency
}
});
Problem: Deleting properties from global namespace.
// Before - CANNOT be auto-fixed
delete sap.ui.core.someTempProperty;
Fix Strategy: This is usually a code smell. Either:
Problem: Using the deprecated sap.ui.controller() global factory to define a controller. This is the two-argument form that defines a controller class.
Scope: This case handles plain controller definitions in custom UI5 apps. It does NOT handle Fiori Elements V2 extensions using registerControllerExtensions — use fix-fiori-elements-extensions for those (the indicator is sap.ui.controllerExtensions in manifest.json or registerControllerExtensions in Component.js).
Detection:
grep -rn 'sap\.ui\.controller(' webapp/ --include="*.js" | grep -v "^\s*//"
Categorize each match:
sap.ui.controller("name", { ... }) — definition, fix it heresap.ui.controller("name") — instance lookup, document in MODERNIZATION-ISSUES.md (sync-to-async refactoring required)sap.ui.define wrapperThe most common case: file already uses sap.ui.define but defines the controller via the deprecated global.
// Before
sap.ui.define([
"sap/m/MessageBox",
"sap/base/Log"
], function(MessageBox, Log) {
"use strict";
return sap.ui.controller("my.app.controller.Main", {
onInit: function() {
Log.info("initialized");
},
onPress: function() {
MessageBox.show("Hello");
}
});
});
// After
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageBox",
"sap/base/Log"
], function(Controller, MessageBox, Log) {
"use strict";
return Controller.extend("my.app.controller.Main", {
onInit: function() {
Log.info("initialized");
},
onPress: function() {
MessageBox.show("Hello");
}
});
});
Steps:
"sap/ui/core/mvc/Controller" to the dependency array (first position)Controller as the corresponding function parametersap.ui.controller("name", { with Controller.extend("name", {sap.ui.define (legacy module system)Files using jQuery.sap.declare/jQuery.sap.require or no module wrapper at all.
// 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() {
// ...
}
});
});
Steps:
jQuery.sap.declare and jQuery.sap.require callssap.ui.define([...], function(...) { ... });sap.ui.controller("name", { with Controller.extend("name", {return before Controller.extend(...) so the module exports the controller class"use strict"; inside the factory functionjQuery("#prefix--id").control(0) → this.byId("id"), replace sap.ui.getCore().byId("prefix--id") → this.byId("id"), replace jQuery.sap.* utilities → dedicated modules, replace inline sap.ui.model.Filter etc. → dependency importsA controller file defines itself with sap.ui.controller("name", {...}) AND uses sap.ui.controller("otherName") (single argument) to look up other controller instances.
Action:
MODERNIZATION-ISSUES.mdMissing return statement: The old sap.ui.controller() API registered the controller globally as a side effect. Controller.extend() only returns the class — it doesn't register globally. If the original code doesn't return the result, you must add return:
// Before (broken after modernization without return)
sap.ui.define(["sap/ui/core/mvc/Controller"], function(Controller) {
"use strict";
sap.ui.controller("my.app.controller.Main", { /* ... */ });
// No return! Works with sap.ui.controller but NOT with Controller.extend
});
// After (fixed)
sap.ui.define(["sap/ui/core/mvc/Controller"], function(Controller) {
"use strict";
return Controller.extend("my.app.controller.Main", { /* ... */ });
});
Module-level variables before definition: Keep them as-is — just wrap the Controller.extend(...) with return.
Controller name must match file path: The string in Controller.extend("my.app.controller.Main", {...}) should match the file's module path. A file at webapp/controller/Main.controller.js in an app with namespace my.app should be "my.app.controller.Main". If the existing name doesn't match the file path, keep the existing name (may be intentional).
Key rules:
sap.ui.define, do NOT wrap again — just add the sap/ui/core/mvc/Controller dependency if missingsap.ui.controller("name", { ... }) (with object literal = definition) → Controller.extend("name", { ... })sap.ui.controller("name") (no object literal = instantiation) → document in MODERNIZATION-ISSUES.md, do NOT auto-fixsap/ui/core/mvc/ControllerProblem: Using deprecated jQuery.sap.declare() and jQuery.sap.require() for module management.
// Before - CANNOT be auto-fixed (no-deprecated-api: jQuery.sap.declare, jQuery.sap.require)
jQuery.sap.declare("my.app.util.Formatter");
jQuery.sap.require("sap.ui.core.format.DateFormat");
my.app.util.Formatter = {
formatDate: function(oDate) {
var oDateFormat = sap.ui.core.format.DateFormat.getDateTimeInstance();
return oDateFormat.format(oDate);
}
};
Fix Strategy: Wrap in sap.ui.define, convert jQuery.sap.require calls to dependency array, remove global assignment, return the module object.
// After
sap.ui.define([
"sap/ui/core/format/DateFormat"
], function(DateFormat) {
"use strict";
return {
formatDate: function(oDate) {
var oDateFormat = DateFormat.getDateTimeInstance();
return oDateFormat.format(oDate);
}
};
});
Key rules:
jQuery.sap.declare() statements — module names are inferred from file paths in AMDjQuery.sap.require("sap.m.Button") to dependency "sap/m/Button" (dot notation → path notation)my.app.util.Formatter = {...}) and return the object from the factory functionsap.ui.define, do NOT wrap again — merge remaining jQuery.sap.require calls into the existing dependency arrayjQuery.sap.require is inside a function (dynamic/conditional), replace with sap.ui.require(["sap/m/MessageBox"], function(MessageBox) { ... }) insteadjQuery.sap.declare calls or no clear single export, flag for manual review — do not attempt automatic modernizationjQuery("#prefix--id").control(0) → this.byId("id"), replace sap.ui.getCore().byId(...) → this.byId(...), replace remaining jQuery.sap.* → dedicated modules, replace inline class references (e.g., new sap.ui.model.json.JSONModel(...)) → imported dependency variablesProblem: Runtime-provided modules like sap.ushell.Container are accessed via global namespace chains. Under strict AMD loading or Test Starter, these globals may not exist because the FLP shell isn't bootstrapped.
// Before - global namespace chain access
sap.ui.define(["sap/ui/core/mvc/Controller"], function(Controller) {
return Controller.extend("com.example.app.controller.Main", {
onNavBack: function() {
if (sap.ushell && sap.ushell.Container) {
var oNav = sap.ushell.Container.getService("CrossApplicationNavigation");
oNav.toExternal({ target: { shellHash: "#" } });
}
},
onSave: function() {
sap.ushell.Container.setDirtyFlag(true);
}
});
});
Fix Strategy: Add the runtime module as a sap.ui.define dependency. Replace all global namespace chain access with the imported variable. Convert guarded access patterns to guard on the imported variable.
// After - proper module import
sap.ui.define([
"sap/ushell/Container",
"sap/ui/core/mvc/Controller"
], function(Container, Controller) {
return Controller.extend("com.example.app.controller.Main", {
onNavBack: function() {
if (Container && Container.getService) {
var oNav = Container.getService("CrossApplicationNavigation");
oNav.toExternal({ target: { shellHash: "#" } });
}
},
onSave: function() {
Container.setDirtyFlag(true);
}
});
});
Test-side pattern: Because UI5's sap.ui.define loader caches module return values, the test and app source both receive the same object reference. Stub the imported module directly — do NOT set up global namespace chains. sinon is globally available via the Test Starter infrastructure — no need to import it:
// WRONG — setting up global namespace in test
window.sap.ushell = { Container: { setDirtyFlag: function() {} } };
// CORRECT — stub the imported module (sinon is a Test Starter global)
sap.ui.define(["sap/ushell/Container"], function(Container) {
var oSandbox = sinon.createSandbox();
// ...
QUnit.module("Navigation", {
afterEach: function() { oSandbox.restore(); }
});
QUnit.test("setDirtyFlag is called", function(assert) {
oSandbox.stub(Container, "setDirtyFlag");
// ... trigger action ...
assert.ok(Container.setDirtyFlag.calledOnce);
});
});
Common runtime globals to convert: sap.ushell.Container and other runtime-provided modules accessed via global namespace chains. Cross-reference fix-linter-blind-spots for the broader detection pass across all files.
Problem: When modernizing jQuery.sap.sjax to native synchronous XMLHttpRequest, the resulting code blindly uses xhr.responseText or JSON.parse(xhr.responseText) without checking whether the request succeeded. If the target file doesn't exist or the server returns an error (e.g., 404 HTML), JSON.parse throws on HTML content.
// Before — jQuery.sap.sjax (deprecated)
var oResponse = jQuery.sap.sjax({
url: sUrl,
dataType: "json"
});
if (oResponse.data) { ... }
// After modernization (WRONG — no guard)
var xhr = new XMLHttpRequest();
xhr.open("GET", sUrl, false);
xhr.send();
var oData = JSON.parse(xhr.responseText); // crashes if file missing or 404
// After modernization (CORRECT — with guard)
var xhr = new XMLHttpRequest();
xhr.open("GET", sUrl, false);
xhr.send();
if (xhr.readyState === 4 && xhr.status === 200) {
var oData = JSON.parse(xhr.responseText);
// ... use oData ...
} else {
Log.error("Failed to load: " + sUrl);
}
Fix Strategy: Always wrap xhr.responseText usage with a readyState === 4 && status === 200 check. Choose the fallback based on context:
| Context | Guard Pattern | Fallback |
|---|---|---|
| Mock server response handler | if (xhr.readyState === 4 && xhr.status === 200) | oXhr.respondJSON(200, {}, JSON.stringify({"d": {"results": []}})) |
| JSON.parse of response | if (xhr.readyState === 4 && xhr.status === 200) | Return empty object {} — lets existing if (oResponse.data) guards work |
| Init-time config/manifest loading | if (xhr.readyState !== 4 || xhr.status !== 200) { Log.error(...); return; } | Early return with error log |
| Shared helper function | Check inside the helper, return {} on failure | All callers already guard with oResponse.data |
Detection: After modernizing jQuery.sap.sjax, scan for xhr.responseText or JSON.parse(xhr.responseText) that is NOT preceded by a readyState/status check within the same block.
When a file triggers Case 9 or Case 10 (structural conversion from legacy module system to sap.ui.define), you MUST also fix ALL other global-access patterns in the same file during the same pass. Do NOT stop after the structural conversion — scan the entire file body for:
sap.ui.getCore().byId("prefix--id") → this.byId("id") (in controllers)jQuery("#prefix--id").control(0) → this.byId("id") (in controllers)jQuery.sap.* utilities → dedicated replacement modulesjQuery/$ global references → add sap/ui/thirdparty/jquery importsap.ui.core.Core direct access → add module dependencysap.ui.demo.myApp.Module global namespace → use imported dependencyThe structural conversion (Case 9/10) creates the sap.ui.define wrapper, but the code INSIDE the controller methods still contains inline global patterns. These inline patterns MUST be fixed in the same operation. Do not leave them for a "second pass" — there is no second pass.
Checklist after Case 9/10 conversion:
jQuery("#...") or jQuery(...) calls? → Apply Case 4a or Case 4sap.ui.getCore().byId(...) calls? → Apply Case 2 (and then Case 4a if inside a controller)jQuery.sap.* calls remaining? → Apply Case 4bsap.ui.model.*, sap.m.*, sap.ui.core.* inline class references? → Convert to dependency importssap.ui.demo.todo.util.Helper)? → Convert to dependency importsthis.X = importedModule.X assignments where this.X is never read elsewhere in the file or referenced in XML views? → DELETE the assignment AND the import — it's dead code (a stashed global reference that nothing consumes)npx @ui5/linter --details
sap.ui.define dependency arraythis.byId(), remove jQuery/Element imports if no longer referencedFor a comprehensive before/after example combining multiple case types (jQuery.sap.*, jQuery DOM, conditional globals), read references/example-fix-session.md.
Dependency insertion position — critical: Always add new dependencies and their corresponding function parameters at the beginning of their respective lists, not at the end or in alphabetical order. Many legacy UI5 files have mismatches between the dependency array length and the function parameter count (e.g., trailing side-effect imports without parameters, or arrays that drifted out of sync over time). Inserting in the middle or at the end can shift the mapping between existing dependencies and parameters, silently passing the wrong module to existing code. Inserting at the beginning preserves all existing positional mappings.
// Before — existing code may have a dep/param mismatch (3 deps, 2 params)
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast",
"some/sideEffect/Module"
], function(Controller, MessageToast) {
var oCore = sap.ui.getCore(); // global access to fix
});
// After — new dependency added at the BEGINNING
sap.ui.define([
"sap/ui/core/Element",
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast",
"some/sideEffect/Module"
], function(Element, Controller, MessageToast) {
var oControl = Element.getElementById("myId");
});
The function parameter names should match the module's default export name (e.g., Log for sap/base/Log)
Some globals like QUnit and sinon are intentionally allowed in test files
sap.ui.define, sap.ui.require, and sap.ui.loader.config are allowed globals
Use sap.ui.require("module/path") (synchronous, returns undefined if not loaded) for optional runtime dependencies
Use sap.ui.require(["module/path"], callback) (async) for lazy loading
sap.ui.controller() in Fiori Elements V2 apps with registerControllerExtensions or manifest sap.ui.controllerExtensions — that's a different modernization path (ControllerExtension class + manifest registration + handler reference updates)no-pseudo-modules and no-implicit-globals errors (enum imports, DataType imports, OData expression functions), use fix-pseudo-modulesno-deprecated-control-renderer-declaration, apiVersion, IconPool, rerender), use fix-control-rendererno-globals in XML views/fragments (formatters, event handlers via core:require), use fix-xml-globalssap.*), use fix-linter-blind-spots. Cases 1b and 1c overlap with patterns 1-4 in that skill.sap.ui.require instead of normal sap.ui.define deps — see fix-cyclic-deps for cycle detection