🔧fix-xml-globals
- プラグイン
- ui5-modernization
- ソース
- GitHub で見る ↗
説明
UI5 linterが報告するが自動修正できない、XMLビュー/フラグメントの問題を修正します。 次のような場合に使用: linterが以下のルールを出力する場合: - `no-globals` — XMLビュー内の**すべて**のグローバル変数アクセス(`sap.*`、`jQuery.*`、およびアプリ名前空間のグローバル変数(例: `com.example.app.utils.Handler.onPress`、`my.app.formatter.method`)) - `no-ambiguous-event-handler` — ドットプレフィックスまたはローカル名のないイベントハンドラー - `no-deprecated-api` — レガシーな `template:require` 構文(スペース区切り) XMLビュー/フラグメントファイルにおいて、グローバル変数・イベントハンドラー・フォーマッター・バインディング内の型参照・ファクトリ関数・`template:require` に関するエラーが発生している場合にトリガーします。 `core:require` 宣言の自動追加、イベントハンドラープレフィックスの修正、`this` を使用する関数への `.bind($control)` の付加を行います。 **重要:** linterはXML内のアプリ名前空間グローバルを `no-globals` として報告します。これらは必ず本スキルで修正してください。`fix-linter-blind-spots` に委ねてはなりません。 XMLビュー内のネイティブHTMLまたはSVGについては、代わりに `fix-xml-native-html` スキルを使用してください。
原文を表示
Fix XML view/fragment issues that UI5 linter reports but cannot auto-fix. Use this skill when linter outputs these rules: - `no-globals` - For ALL global variable access in XML views — sap.*, jQuery.*, AND app-namespace globals (e.g., com.example.app.utils.Handler.onPress, my.app.formatter.method) - `no-ambiguous-event-handler` - For event handlers without dot prefix or local name - `no-deprecated-api` - For legacy template:require syntax (space-separated) Trigger on XML view/fragment files with errors about global variables, event handlers, formatters, type references in bindings, factory functions, or template:require. Automatically adds core:require declarations, fixes event handler prefixes, and handles .bind($control) for functions that use 'this'. IMPORTANT: The linter reports app-namespace globals in XML under `no-globals` — these MUST be fixed by this skill, NOT deferred to fix-linter-blind-spots. For native HTML or SVG in XML views, use the fix-xml-native-html skill instead.
ユースケース
- ✓UI5 linterが no-globals ルールを出力するとき
- ✓no-ambiguous-event-handler エラーが発生しているとき
- ✓no-deprecated-api ルールが報告されたとき
- ✓XMLビュー内のグローバル変数参照を修正する
- ✓イベントハンドラーのプレフィックスを修正する
本文
Fix XML Views/Fragments Global Access
This skill fixes XML view and fragment issues that the UI5 linter detects but cannot auto-fix because they require understanding of module paths and handler locations.
Linter Rules Handled
| Rule ID | Message Pattern | This Skill's Action |
|---|---|---|
no-globals |
Access of global variable '...' (...) | Add core:require and use local name |
no-ambiguous-event-handler |
Event handler '...' must be prefixed by a dot '.' or refer to a local name | Add . prefix for controller methods or add core:require for modules |
no-deprecated-api |
Usage of space-separated list '...' in template:require | Convert to object notation |
When to Use
Apply this skill when you see linter output like:
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
Fix Strategy
1. no-globals - Fix Global Variable Access with core:require
When a formatter, type, or utility function is accessed via global namespace (e.g., my.app.formatter.formatDate), add a core:require declaration and use the local name.
Step 1: Add xmlns:core namespace if not present
<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns:core="sap.ui.core"
xmlns="sap.m">
Step 2: Add core:require on the nearest control that uses the module
Place core:require on the control that uses the global reference. If multiple controls use the same module, place it on their nearest common ancestor. See the "core:require Placement Rules" section below for details.
<!-- Single usage — core:require on the control itself -->
<Text core:require="{formatter: 'my/app/model/formatter'}"
text="{path: 'date', formatter: 'formatter.formatDate'}" />
<!-- Multiple usages — core:require on nearest common ancestor -->
<VBox core:require="{formatter: 'my/app/model/formatter'}">
<Text text="{path: 'date', formatter: 'formatter.formatDate'}" />
<Text text="{path: 'name', formatter: 'formatter.formatName'}" />
</VBox>
Step 3: Update references to use local name
<!-- Before -->
<Text text="{path: 'date', formatter: 'my.app.model.formatter.formatDate'}" />
<!-- After -->
<Text text="{path: 'date', formatter: 'formatter.formatDate'}" />
Step 4: Add .bind($control) for formatters that use this
If a formatter function accesses this internally, add .bind($control) to preserve the control context. This applies only to formatters — never add .bind($control) to event handlers or factory functions.
<!-- If formatter.formatWithContext uses 'this' to access the control -->
<Text core:require="{formatter: 'my/app/model/formatter'}"
text="{path: 'name', formatter: 'formatter.formatWithContext.bind($control)'}" />
2. no-ambiguous-event-handler - Fix Event Handlers
Event handlers must either:
- Start with a dot (
.) to indicate controller method - Use a locally required module name
Controller method (add dot prefix):
<!-- Before -->
<Button press="onPress" />
<!-- After -->
<Button press=".onPress" />
Module method (use core:require):
<!-- Before -->
<Button press="my.app.util.Handler.onPress" />
<!-- After - with core:require -->
<mvc:View
core:require="{
Handler: 'my/app/util/Handler'
}">
<Button press="Handler.onPress" />
</mvc:View>
3. no-deprecated-api - Fix Legacy template:require Syntax
Convert space-separated module list to object notation.
<!-- Before -->
<template:require name="formatter helper" />
<!-- After -->
<template:require name="{
formatter: 'my/app/model/formatter',
helper: 'my/app/util/helper'
}" />
4. Handle Multiple Global References
When multiple globals are used, combine them in a single core:require on their nearest common ancestor.
<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 - Fix Type Property in Bindings
When a global is used in the type property of a binding object, it also needs core:require.
<!-- Before -->
<Input value="{
path: '/amount',
type: 'sap.ui.model.type.Currency'
}" />
<!-- After -->
<Input core:require="{Currency: 'sap/ui/model/type/Currency'}"
value="{
path: '/amount',
type: 'Currency'
}" />
This applies to all UI5 types (Currency, Date, Float, Integer, String, etc.) and custom type classes.
6. no-globals - Fix Factory Functions
Factory functions (e.g., factory attribute on List) follow the same core:require pattern but never use .bind($control).
<!-- Before -->
<List items="{/items}" factory="my.app.util.ListFactory.createItem" />
<!-- After — no .bind($control) for factories -->
<List core:require="{ListFactory: 'my/app/util/ListFactory'}"
items="{/items}" factory="ListFactory.createItem" />
core:require Placement Rules
A module declared via core:require is only accessible to that element and its descendants — not siblings.
- Single control uses the module: place
core:requiredirectly on that control - Multiple controls use the same module: place
core:requireon their nearest common ancestor - All controls are direct children of View: placing on the View root is acceptable
Prefer granular placement over always using the root — it reduces unnecessary scope and makes the dependency clear at the point of use.
For detailed examples (granular placement across nested elements, scope errors with sibling elements), see references/placement-and-binding.md.
When to Use .bind($control)
When a function is called via core:require, it does not automatically receive a this context. Whether to add .bind($control) depends on the usage type — formatter, factory, or event handler:
| Usage Type | .bind($control) |
Why |
|---|---|---|
Formatter (inside {path:..., formatter:'...'} or {parts:[...], formatter:'...'}) |
Yes, if function uses this |
this = the control instance owning the binding. $control resolves to the ManagedObject at runtime. |
Factory (factory= attribute on aggregation binding) |
No — never add .bind() |
Factory functions receive (sId, oContext) as parameters. They don't use this for control context. Adding .bind() is unnecessary and incorrect. |
Event handler (press=, change=, confirm=, select=, valueHelpRequest=, etc.) |
No — never add .bind() |
Event handler resolution in XML with core:require calls the function directly with the event as parameter. Adding .bind() interferes with this resolution. |
How to detect usage type in XML:
- Formatter: appears inside a binding expression —
{path:..., formatter:'Module.fn'}or{parts:[...], formatter:'Module.fn'} - Factory: appears as
factory='Module.fn'attribute on an aggregation binding - Event handler: appears as an event attribute like
press=,change=,confirm=,cancel=,select=,selectionChange=,valueHelpRequest=,tokenUpdate=,close=,delete=,titlePress=
The formatter-specific rule:
- Check the function's implementation — if it uses
this, add.bind($control) - Always use
$control(NOT$controller) — this binds to the control instance - If the function does not use
this, omit.bind($control)
<!-- Formatter that uses 'this' → must bind -->
<Text core:require="{formatter: 'my/app/model/formatter'}"
text="{path: 'name', formatter: 'formatter.formatWithContext.bind($control)'}" />
<!-- Formatter that does NOT use 'this' → no bind needed -->
<Text core:require="{formatter: 'my/app/model/formatter'}"
text="{path: 'date', formatter: 'formatter.formatDate'}" />
<!-- Factory → NEVER bind -->
<List core:require="{ListFactory: 'my/app/util/ListFactory'}"
items="{/items}" factory="ListFactory.createItem" />
<!-- Event handler → NEVER bind -->
<Button core:require="{Handler: 'my/app/util/Handler'}"
press="Handler.onExport" />
Detection: run scripts/verify-this-bind.js audit-fn. The script handles every this form in one pass — member access, dynamic property, bare arg (jQuery.proxy(fn, this), .call(this), .apply(this), .bind(this)), aliased (var self = this; self.foo), and arrow-inherited. Comments and string literals are stripped; nested non-arrow function bodies are excluded (they own their own this); arrow bodies are kept (they inherit). Standard UI5 types (sap.ui.model.type.*) never need binding and the script confirms this. Manual grep is not allowed for batch decisions — the script is canonical.
node scripts/verify-this-bind.js audit-fn --file <module.js> --fn <name> [--fn <name> ...]
Functions reported USES_THIS MUST have .bind($control) appended in XML. Functions reported NO_THIS MUST NOT have it. Do NOT classify by name shape — formatX / decideX / isX are not proof of pure-value formatters; only the source body is.
Why a script and not grep: searching for this\. (the dot form) silently misses bare-this idioms. jQuery.proxy(fn, this) passes this as a positional argument with no dot anywhere — an eyeball or a naive grep walks past it, the formatter looks pure, and .bind($control) is omitted. At runtime the proxied callback ends up with the wrong context and this.getModel(...) throws. The script's \bthis\b scan combined with alias tracking catches every variant in one go. Fall back to grep -nE '\bthis\b' <module.js> only if the script is unavailable.
For full before/after examples (formatter, handler, factory) see references/placement-and-binding.md.
FragmentDefinition Handling
Fragments use <core:FragmentDefinition> instead of <mvc:View>. The same placement principle applies, with one key difference:
Place core:require on the child control, NOT on FragmentDefinition. FragmentDefinition is a structural wrapper, not a real control. Place core:require on the actual root control inside it (e.g., Dialog, VBox) or on the nearest common ancestor of the controls using the module.
<!-- PREFERRED: core:require on Dialog (the actual root control) -->
<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>
Exception: When a fragment has multiple direct children that all need the same module, FragmentDefinition becomes the nearest common ancestor — placing core:require there is acceptable.
For examples of multi-child fragments and scoped modules within fragments, see references/placement-and-binding.md.
Additional Edge Cases
Name Conflicts
When multiple modules share the same class name, use descriptive aliases:
<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>
Formatters with Multiple Parameters
When a binding expression uses a formatter with multiple parts, the core:require is the same — only the global namespace in the formatter reference changes.
<!-- Before -->
<Text text="{
parts: [
{path: 'firstName'},
{path: 'lastName'}
],
formatter: 'my.app.model.formatter.formatFullName'
}" />
<!-- After (with core:require for formatter on the view root) -->
<Text text="{
parts: [
{path: 'firstName'},
{path: 'lastName'}
],
formatter: 'formatter.formatFullName'
}" />
Globals Accessed Inside Expression Bindings
Expression bindings that reference globals via the ${...} syntax also need core:require.
<!-- Before -->
<Text visible="{= ${/count} > 0}" text="{= my.app.model.formatter.formatCount(${/count})}" />
<!-- After -->
<Text visible="{= ${/count} > 0}" text="{= formatter.formatCount(${/count})}" />
App-Namespace Globals in XML
The UI5 linter reports app-namespace globals in XML under the no-globals rule — the same rule used for sap.* and jQuery.* globals. This skill handles ALL no-globals in XML files, including:
- sap namespace —
sap.m.ButtonType.Accept,sap.ui.model.type.Currency→core:requirethe module - jQuery namespace —
jQuery.sap.getModulePath→core:require - App namespace event handlers —
com.example.app.utils.Handler.onPress→core:requirethe utility module - App namespace formatters —
com.example.app.model.formatter.formatDate→core:requirethe formatter module
The fix pattern is identical for all: add core:require attribute with the module path (dots → slashes), replace the dotted global with the local alias.
Before:
<core:FragmentDefinition xmlns="sap.m" xmlns:core="sap.ui.core">
<Button press="com.example.app.utils.Handler.onPress" />
</core:FragmentDefinition>
After:
<core:FragmentDefinition xmlns="sap.m" xmlns:core="sap.ui.core">
<Button core:require="{Handler: 'com/example/app/utils/Handler'}"
press="Handler.onPress" />
</core:FragmentDefinition>
Apply .bind($control) rules from the "When to Use .bind($control)" section — formatters: yes (if function uses this); factories: never; event handlers: never.
Important: Do NOT defer app-namespace XML globals to fix-linter-blind-spots (Phase 3, Step 3.2). That skill handles only JS app-namespace globals which the linter cannot detect. XML app-namespace globals ARE reported by the linter and MUST be fixed here in Phase 3.
Implementation Steps
-
Read the XML view/fragment file
-
Parse to identify linter errors by rule ID:
no-globals: Global namespace references in bindings (formatters, types, event handlers, factories)no-ambiguous-event-handler: Event handlers without proper prefixno-deprecated-api: Legacy template:require syntax
-
For
no-globalserrors:- Add
xmlns:core="sap.ui.core"to the root element if not present - Determine module path: convert dot notation to slash notation (e.g.,
my.app.formatter→my/app/formatter) - Determine placement: find the nearest control using the module, or the nearest common ancestor if multiple controls use it
- For fragments: prefer placing
core:requireon the actual root control (e.g.,Dialog), not onFragmentDefinition - Mandatory per-formatter source audit. For every formatter renamed in this batch, run
node scripts/verify-this-bind.js audit-fn --file <module.js> --fn <name> [--fn <name> ...](batch as many--fnflags as the file has formatters). Functions reportedUSES_THISMUST have.bind($control)appended in the XML formatter ref. Functions reportedNO_THISMUST NOT. Factories and event handlers never bind regardless of theirthisusage. - Build
core:requireobject with all needed modules (use aliases for name conflicts) - Update all references to use the local names
- Add
-
For
no-ambiguous-event-handlererrors:- Use the linter-reported
line:colto locate the exact attribute. Read only that line. - Edit ONLY the value at that coordinate. Prepend
.for a bare handler name; or applycore:require+ alias replacement for a namespace path. - DO NOT search the file for other occurrences of the same handler-name string. The linter has already enumerated every offending site — if a string appears elsewhere and was NOT reported, it is a different attribute (
id,selectedKey,key,name, plain text, …) and MUST NOT be modified. One linter finding → one edit.
- Use the linter-reported
-
For
no-deprecated-api(template:require):- Convert space-separated list to object notation
-
Write the updated file
-
Post-edit verification (mandatory). After all XML edits in this phase and before declaring done:
node scripts/verify-this-bind.js verify-xml \ --xml-root webapp \ --js-roots webapp/utils,webapp/modelExit 0 → done. Exit 1 → fix the listed
MISSING_BINDviolations and re-run. The script is the gate; agent-side eyeballing is not.
Example Fix
Example 1: Basic — formatter + event handler
Given linter output:
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 transformation:
<!-- Before -->
<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>
<!-- After -->
<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>
Example 2: Advanced — granular placement, type binding, .bind($control)
Given linter output:
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
<!-- Before -->
<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>
<!-- After -->
<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>
Placement decisions:
formatterused in two<Text>controls acrosscontent—core:requireonPage(nearest common ancestor)Currencyonly used by one<Input>—core:requiredirectly onInputHandleronly used by one<Button>in footer —core:requiredirectly onButtonHandler.onExportis an event handler — no.bind($control)(event handlers never need it)onBackis a simple controller method — dot prefix added
Notes
-
Place
core:requireon the nearest control that uses the module, or the nearest common ancestor when multiple controls share it — prefer granular placement over always using the root -
Module paths in
core:requireuse forward slashes (/), not dots -
Multiple modules are separated by commas in the JSON object notation
-
Use descriptive aliases to avoid name conflicts between modules with the same class name
-
Always check the usage type first: formatters may need
.bind($control)if the function usesthis; factories and event handlers never need it -
For fragments, place
core:requireon the actual child control (e.g.,Dialog), not onFragmentDefinitionunless it's the only common ancestor -
Ensure the formatter/utility module exists at the specified path
-
For complex cases with dynamic module loading, consider using
sap.ui.requirein the controller instead -
Name-pattern shortcuts are forbidden for
thisdetection. Formatter names likeformatX,decideX,isX,getXare not proof of pure-value behaviour. Common patterns that look pure but readthis:- Reads context-bound state via
this.getModel(...),this.getBindingContext(),this.getId()— often inside helper branches - Passes bare
thisto a callback wrapper such asjQuery.proxy(fn, this),fn.call(this),fn.apply(this), orfn.bind(this)— the call has no.afterthis, so a casual read sees no member access - Aliases
thisfirst (var self = this; ... self.getX()) — the body of the formatter never mentionsthisafter the alias line
Each of these breaks at runtime when the formatter is called without
.bind($control), becausethisis not bound to the control by default. Theverify-this-bind.jsscript catches all of these forms in one pass; eyeballing orgrep 'this\\.'misses them. Always run the script — never skip based on name. - Reads context-bound state via
-
Linter coordinates are authoritative for
no-ambiguous-event-handlerandno-globalsidentifier-rename. Never usegrep/ file-wide regex to "find all uses" of a flagged identifier. The linter has already done that lookup — every offending site is in its output. Editing lines NOT in the linter output is out of scope and almost certainly incorrect: a name that appears as an event handler can also appear as a controlid, aselectedKey, a binding path, or a string literal — those uses are not handlers and rewriting them silently changes runtime semantics (e.g., breakingFragment.byIdlookups). Exception:core:requireinsertion is file-level reasoning (place once on nearest common ancestor); applies once per file, not per finding.
Related Skills
- fix-xml-native-html: For native HTML/SVG replacement in XML views (
no-deprecated-apiwith "native HTML" or "SVG" messages), use fix-xml-native-html - fix-js-globals: For
no-globalsin JavaScript files (controller/utility global access), use fix-js-globals — it handles the JS-side equivalent of this skill's XML fixes
原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。