references/lint.md
# Run, Fix, and Scope ESLint on a Fiori Project
Run ESLint on a SAP Fiori project to check code quality and optionally auto-fix issues. Includes an **app-scope mode** that reproduces the exact file set checked by the Application Information panel and Page Map.
## App-scope mode — show only application issues (consistent with Application Information / Page Map)
Use this mode when the user says things like:
- "show all application issues for my app"
- "show UX consistency issues"
- "issues for app xyz"
- "same issues as Application Information" / "same issues as Page Map"
### Exact file scope used by Application Information / Page Map
The Application Information panel and Page Map do **not** lint all files in `webapp/`. They lint a precise, curated
set of files — nothing more. Running `npx eslint .` or `npx eslint webapp/` will produce a higher,
inconsistent count because it picks up JavaScript/TypeScript source files, test files, and other
files that the tooling never checks.
**Files linted by Application Information / Page Map (derived from the IDE extension source):**
| File | Standalone | CAP |
|---|:---:|:---:|
| `webapp/manifest.json` | ✅ | ✅ |
| `webapp/changes/**/*propertyChange.change` (OData V2 only) | ✅ | ✅ |
| All local XML files listed in the manifest via `localUri` (`ODataAnnotation`, `OData`, etc.) | ✅ | ❌ |
| `<app-folder>/<app-name>/**/*.cds` (app-level only) | ❌ | ✅ |
Key rules:
- Only `*propertyChange.change` files are included from the `changes/` folder — not all `.change` files; these only exist in OData V2 projects
- For CAP projects, only `.cds` files within the specific app directory are linted — not the project root, `db/`, `srv/`, or sibling apps
- For CAP projects, XML annotation files are **not** linted — the annotations live in `.cds` files instead
- For standalone projects, `.cds` files are **not** linted — only the manifest, all localUri XML files from manifest dataSources, and change files
### Detecting OData version
The OData version is declared explicitly in `manifest.json` under `sap.app.dataSources[*].settings.odataVersion`. A V4 project will have `"odataVersion": "4.0"` on its main data source; V2 projects either have `"2.0"` or omit the field entirely.
```bash
# Returns "v4" if any dataSource declares odataVersion 4.x, otherwise "v2"
node --input-type=commonjs -e "
const m = require('./webapp/manifest.json');
const ds = Object.values(m['sap.app']?.dataSources ?? {});
const isV4 = ds.some(d => (d.settings?.odataVersion ?? '').startsWith('4'));
console.log(isV4 ? 'v4' : 'v2');
"
```
- Result `v4` → **OData V4** — skip `.change` files
- Result `v2` → **OData V2** — include `*propertyChange.change` files
### Why counts differ without this scoping
Running `npx eslint .` or `npx eslint webapp/` picks up `.js`, `.ts`, controller files, test files,
and more — none of which Application Information or Page Map ever checks. This produces more issues and a higher
count than what the tooling shows.
### App-scope lint commands
Always target the exact file set explicitly. Substitute real paths where shown.
> **Windows (all commands in this section)**: Both the standalone and CAP commands use `find` and `$()` subshell expansion. They require **Git Bash** or **WSL** — they will not run in cmd.exe or PowerShell.
**Standalone Fiori app:**
```bash
# From the app root (where eslint.config.mjs and webapp/ are)
# 1. manifest.json
npx eslint webapp/manifest.json
# 2. propertyChange files — OData V2 projects only (skip if V4)
# Array assignment splits find output into separate elements (works in bash and zsh)
CHANGE_FILES=($(find webapp/changes -name "*propertyChange.change" 2>/dev/null))
[ ${#CHANGE_FILES[@]} -gt 0 ] && npx eslint "${CHANGE_FILES[@]}"
# 3. All local XML files declared in manifest dataSources via localUri
# (covers annotation files, metadata.xml, service.xml, etc.)
XML_FILES=($(node --input-type=commonjs -e "
const m = require('./webapp/manifest.json');
const ds = Object.values(m['sap.app']?.dataSources ?? {});
ds.filter(d => d.settings?.localUri).forEach(d => console.log('webapp/' + d.settings.localUri));
"))
[ ${#XML_FILES[@]} -gt 0 ] && npx eslint "${XML_FILES[@]}"
# Or combine all three in one pass:
CHANGE_FILES=($(find webapp/changes -name "*propertyChange.change" 2>/dev/null))
XML_FILES=($(node --input-type=commonjs -e "
const m = require('./webapp/manifest.json');
const ds = Object.values(m['sap.app']?.dataSources ?? {});
ds.filter(d => d.settings?.localUri).forEach(d => console.log('webapp/' + d.settings.localUri));
"))
npx eslint webapp/manifest.json "${CHANGE_FILES[@]}" "${XML_FILES[@]}"
```
**CAP project — specific app:**
```bash
# From the CAP project root
APP=app/incidents # replace with your app folder
# 1. manifest.json
npx eslint "$APP/webapp/manifest.json"
# 2. propertyChange files — OData V2 projects only (skip if V4)
CHANGE_FILES=($(find "$APP/webapp/changes" -name "*propertyChange.change" 2>/dev/null))
[ ${#CHANGE_FILES[@]} -gt 0 ] && npx eslint "${CHANGE_FILES[@]}"
# 3. CDS files (app-level only)
# The glob is intentionally quoted — ESLint resolves it internally, not the shell.
npx eslint "$APP/**/*.cds"
```
### Identifying the app name and local XML files
```bash
# Find webapp/ locations (CAP)
find app -maxdepth 2 -type d -name "webapp" -not -path "*/node_modules/*"
# Extract all local XML file paths from manifest (any dataSource with a localUri)
node --input-type=commonjs -e "
const m = require('./webapp/manifest.json');
const ds = m['sap.app']?.dataSources ?? {};
Object.values(ds).filter(d => d.settings?.localUri).forEach(d => console.log('webapp/' + d.settings.localUri));
"
```
If the user names a specific app, match by the parent folder of `webapp/`. If ambiguous, list found
apps and ask the user to confirm.
### Reporting app-scope results
Present results with a clear scope label so the user knows the count matches the tooling:
```
Application Issues (Application Information / Page Map scope):
Standalone: manifest.json + all localUri XML files from manifest dataSources + *propertyChange.change files (V2 only)
CAP: manifest.json + <app>/**/*.cds + *propertyChange.change files (V2 only)
- X errors
- Y warnings
- Files affected: [list]
- Most common rules: [top 3]
```
When listing individual issues, use the table format described in Step 7.
If the count still differs from what the user sees in Application Information or Page Map, check:
1. **Missing annotation files** — the manifest may reference annotation URIs not covered by the glob above; extract them with the node snippet above
2. **ESLint config `files` glob** — the config may restrict which file types are checked (e.g. only `*.xml` but not `*.json`)
3. **Rule set variant** — confirm the config uses `recommended-for-s4hana` vs `recommended` to match what the tooling expects
4. **Plugin version** — Application Information and Page Map each load the plugin from the project's own `node_modules`; make sure `@sap-ux/eslint-plugin-fiori-tools` is installed there
## Step 1 — Verify ESLint is configured
Detect whether this is a standalone Fiori app or a CAP project, then check for a valid ESLint config.
### Detect project type
**1a — Check if this is a CAP project** by looking for `@sap/cds` in `package.json` (most reliable, works regardless of folder layout):
```bash
grep -q '"@sap/cds"' package.json 2>/dev/null && echo "cap" || echo "standalone"
```
**1b — If CAP: get the configured app folder** using the CDS CLI (avoids hardcoding `app/`):
```bash
npx cds env get folders.app 2>/dev/null
```
If the command fails, fall back to `app/`. Use the resolved path as `<app-folder>` in all steps below.
### Standalone Fiori app — check root config
```bash
ls eslint.config.mjs eslint.config.js eslint.config.cjs 2>/dev/null
```
Then verify the found config references `@sap-ux/eslint-plugin-fiori-tools`:
```bash
grep -l "@sap-ux/eslint-plugin-fiori-tools" eslint.config.mjs eslint.config.js eslint.config.cjs 2>/dev/null
```
### CAP project — check app subfolders only (NOT the root)
```bash
# Find eslint configs inside <app-folder> subfolders, skipping node_modules
find <app-folder> -name "eslint.config.mjs" -not -path "*/node_modules/*" 2>/dev/null
```
Then verify each found config references `@sap-ux/eslint-plugin-fiori-tools`:
```bash
find <app-folder> -name "eslint.config.mjs" -not -path "*/node_modules/*" 2>/dev/null | while read config; do
if grep -q "@sap-ux/eslint-plugin-fiori-tools" "$config"; then
echo "✅ $config — plugin configured"
else
echo "❌ $config — missing @sap-ux/eslint-plugin-fiori-tools"
fi
done
```
**Decision tree — what to do next:**
1. **Config found and references `@sap-ux/eslint-plugin-fiori-tools`** → proceed to Step 2.
2. **Legacy config found** (`.eslintrc`, `.eslintrc.js`, `.eslintrc.cjs`, `.eslintrc.json`, `.eslintrc.yml`) → follow [migrate.md](migrate.md) first, then return here.
3. **No config found at all** → follow [setup.md](setup.md) to create a fresh config, then return here.
To detect a legacy config:
```bash
ls .eslintrc .eslintrc.js .eslintrc.cjs .eslintrc.json .eslintrc.yml .eslintrc.yaml 2>/dev/null
```
For CAP projects, also check each app subfolder:
```bash
find <app-folder> -name ".eslintrc*" -not -path "*/node_modules/*" 2>/dev/null
```
## Step 2 — Locate the app to lint
Determine the **app-level directory** to lint — this is where `eslint.config.mjs` lives. Running ESLint from there (with `.` as the target) lets the config control which files are linted, covering all source files.
- **Standalone Fiori app**: project root (where `eslint.config.mjs` is)
- **CAP project**: each `<app-folder>/<app-name>/` subfolder (where its `eslint.config.mjs` is)
For CAP projects, list available apps:
```bash
find <app-folder> -name "eslint.config.mjs" -not -path "*/node_modules/*" 2>/dev/null
```
If the user specified a particular app or path, use that. Otherwise lint the detected location(s).
## Step 3 — Run lint (check mode)
Run ESLint from the app-level directory (where `eslint.config.mjs` is) using `.` as the target. This ensures the config is picked up correctly and all files the config covers are linted.
### Standalone Fiori app:
```bash
npx eslint .
```
### CAP project — specific app:
```bash
# Run from the app subfolder (where eslint.config.mjs is)
# Note: cd && ... is for terminal use only — not safe as a package.json script on Windows
cd <app-folder>/<app-name> && npx eslint .
```
### CAP project — all apps:
```bash
# Find and lint each app that has its own eslint.config.mjs
find <app-folder> -name "eslint.config.mjs" -not -path "*/node_modules/*" | while read config; do
appdir=$(dirname "$config")
echo "=== Linting $appdir ==="
(cd "$appdir" && npx eslint . 2>&1)
done
```
### With detailed output format:
```bash
# More readable output with file/line references
npx eslint . --format stylish
```
## Step 4 — Interpret the output
ESLint output shows:
- **Errors** (`error`): Must be fixed — these violate required Fiori coding standards
- **Warnings** (`warning`): Should be reviewed — best practice suggestions
Example output:
```
/path/to/webapp/controller/App.controller.js
12:5 error Local storage must not be used @sap-ux/fiori-tools/sap-no-localstorage
24:1 warning DOM access is not recommended @sap-ux/fiori-tools/sap-no-dom-access
✖ 2 problems (1 error, 1 warning)
0 errors and 0 warnings potentially fixable with the `--fix` option.
```
Summarize the results for the user:
- Total errors and warnings
- Which files are affected
- The most common rule violations
- Whether any issues are auto-fixable
## Step 5 — Auto-fix issues (optional)
Many ESLint rules support automatic fixing. Run with `--fix` to apply safe fixes:
### Standalone:
```bash
npx eslint . --fix
```
### CAP — specific app:
```bash
# Note: cd && ... is for terminal use only — not safe as a package.json script on Windows
cd <app-folder>/<app-name> && npx eslint . --fix
```
**IMPORTANT**: The `--fix` flag modifies files in place. Before running:
1. Confirm with the user that they want auto-fixes applied
2. Recommend they have a clean git state or backup so fixes can be reviewed/reverted
After fixing, show what changed:
```bash
git diff --stat 2>/dev/null || echo "(git not available to show diff)"
```
## Step 6 — Handle unfixable issues
Issues not fixed by `--fix` require manual code changes. For each unfixable error:
1. Read the relevant source file
2. Identify the problematic code pattern
3. Suggest or apply the correct Fiori-compliant alternative
### Common Fiori ESLint violations and fixes:
| Rule | Problem | Fix |
|---|---|---|
| `sap-no-localstorage` | `localStorage.setItem(...)` | Use `sap.ui.util.Storage` instead |
| `sap-no-sessionstorage` | `sessionStorage.getItem(...)` | Use `sap.ui.util.Storage` instead |
| `sap-no-dom-access` | `document.getElementById(...)` | Use UI5 control APIs instead |
| `sap-no-inner-html-write` | `element.innerHTML = ...` | Avoid; use UI5 controls for rendering |
| `sap-no-global-variable` | Using undeclared globals | Declare in `globals` config or import |
| `sap-no-hardcoded-url` | Hardcoded absolute URLs | Use relative paths or manifest datasources |
| `sap-no-navigator` | `navigator.userAgent` | Avoid browser detection; use UI5 APIs |
| `sap-flex-enabled` | Missing `flexEnabled: true` in manifest | Add `"flexEnabled": true` to `sap.ui5` section |
## Step 7 — Report summary
After linting (and optional fixing), provide a clear summary:
```
ESLint Results:
- X errors found (N auto-fixed, M require manual fix)
- Y warnings found (P auto-fixed, Q require manual fix)
- Files with issues: [list key files]
- Most common violations: [top 3 rules]
```
When listing individual issues in a table, always include the source file as the first column and a separate Line column. Put the path as bare inline code `` `file:///absolute/path:line` `` — markdown link syntax breaks the `:line` suffix before VS Code's terminal link detector can parse it:
```
| File | Line | Rule | Issue |
|---|---|---|---|
| `file:///abs/path/webapp/localService/metadata.xml:123` | 123 | sap-description-column-label | MyField has generic label … |
```
If everything is clean:
```
✅ No ESLint issues found
```
## Tips
- Run `npx eslint . --fix-dry-run` to preview what auto-fixes would change without applying them
- Use `npx eslint . --rule '@sap-ux/fiori-tools/sap-no-localstorage: error'` to check a single rule
- Add `// eslint-disable-next-line @sap-ux/fiori-tools/<rule-name>` to suppress a specific rule on one line when a violation is intentional and documentedreferences/migrate.md
# Migrate Fiori ESLint to Flat Config
Migrate from legacy ESLint config (`.eslintrc`, `.eslintrc.js`, `eslint@8`, `eslint-plugin-fiori-custom`) to ESLint 10 flat config using `@sap-ux/eslint-plugin-fiori-tools@10`.
## Step 1 — Detect project type and what needs migrating
### Detect project type
**1a — Check if this is a CAP project** by looking for `@sap/cds` in `package.json` (most reliable, works regardless of folder layout):
```bash
grep -q '"@sap/cds"' package.json 2>/dev/null && echo "cap" || echo "standalone"
```
**1b — If CAP: get the configured app folder** using the CDS CLI (avoids hardcoding `app/`):
```bash
npx cds env get folders.app 2>/dev/null
```
If the command fails, fall back to `app/`. Use the resolved path as `<app-folder>` in all steps below.
### Scan for legacy ESLint artifacts
**Standalone Fiori app — check root:**
```bash
# Check for legacy config files
ls .eslintrc .eslintrc.js .eslintrc.cjs .eslintrc.json .eslintrc.yml .eslintrc.yaml 2>/dev/null
# Check for .eslintignore
ls .eslintignore 2>/dev/null
# Check current ESLint and plugin versions
grep -E '"eslint"|"fiori-custom"|"eslint-plugin-fiori"' package.json
```
**CAP project — check each app subfolder:**
```bash
find <app-folder> -name ".eslintrc*" -not -path "*/node_modules/*" 2>/dev/null
find <app-folder> -name "package.json" -not -path "*/node_modules/*" | xargs grep -l "eslint" 2>/dev/null
```
### 1c — Detect the webapp path
Before migrating, determine the actual webapp folder name — it may not be `webapp/`. Check `ui5.yaml` for a custom path mapping, then fall back to finding `manifest.json`:
```bash
# Check ui5.yaml for a custom webapp path
grep -A5 "paths:" ui5.yaml 2>/dev/null | grep "webapp:"
# If not configured in ui5.yaml, find manifest.json to locate the webapp root
find . -maxdepth 4 -name "manifest.json" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null
```
The directory containing `manifest.json` is the webapp root — use that path as `<webapp-path>` in the steps below. Do **not** assume `webapp/`.
## Step 2 — Use the automatic migration tool (recommended)
The `@sap-ux/create` tool can automatically migrate ESLint config. Run this from the app root (the folder containing the webapp folder):
```bash
npx --yes @sap-ux/create@latest convert eslint-config
```
For help and options:
```bash
npx --yes @sap-ux/create@latest convert eslint-config --help
```
**What the tool does automatically:**
1. Creates `eslint.config.mjs` with the flat config format
2. Migrates ignore patterns from `.eslintignore`
3. Updates `package.json` to ESLint 10 + `@sap-ux/eslint-plugin-fiori-tools@^10`
4. Removes the old `.eslintrc*` file
5. Removes the old `.eslintignore` file
If the automatic tool succeeds, jump to Step 5 to verify.
## Step 3 — Manual migration (if automatic tool fails or custom rules exist)
### 3a. Create eslint.config.mjs
The config file goes at the **app level** (next to the webapp folder resolved in Step 1b):
**Basic migration (was using `plugin:@sap-ux/eslint-plugin-fiori-tools/defaultJS`):**
```javascript
import fioriTools from '@sap-ux/eslint-plugin-fiori-tools';
export default [
...fioriTools.configs.recommended
];
```
**With custom ignores (migrate from .eslintignore):**
```javascript
import fioriTools from '@sap-ux/eslint-plugin-fiori-tools';
export default [
{
ignores: [
'dist',
'target',
'localService',
'backup'
// Add any other patterns from your old .eslintignore here
]
},
...fioriTools.configs.recommended
];
```
**If the project had custom rules on top of the defaults:**
```javascript
import fioriTools from '@sap-ux/eslint-plugin-fiori-tools';
export default [
...fioriTools.configs.recommended,
{
rules: {
// Migrate any custom rule overrides here
// Old: "fiori-custom/sap-no-localstorage": "error"
// New: "@sap-ux/fiori-tools/sap-no-localstorage": "error"
}
}
];
```
### 3b. Migrate rule references in source code
If source files have ESLint disable comments using the old `fiori-custom/` prefix, update them:
```bash
# Find all references to old rule prefix (replace <webapp-path> with the path resolved in Step 1b)
grep -r "fiori-custom/" <webapp-path>/ --include="*.js" --include="*.ts" -l 2>/dev/null
```
Replace `fiori-custom/` with `@sap-ux/fiori-tools/`:
Examples:
- `// eslint-disable fiori-custom/sap-browser-api-warning` → `// eslint-disable @sap-ux/fiori-tools/sap-browser-api-warning`
- `// eslint-disable-next-line fiori-custom/sap-no-localstorage` → `// eslint-disable-next-line @sap-ux/fiori-tools/sap-no-localstorage`
## Step 4 — Update package.json dependencies
Update `eslint` to version 10 and `@sap-ux/eslint-plugin-fiori-tools` to version 10+. Remove `eslint-plugin-fiori-custom` if present.
Detect the package manager:
```bash
ls package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null
```
**npm:**
```bash
npm uninstall eslint-plugin-fiori-custom
npm install --save-dev eslint@^10 @sap-ux/eslint-plugin-fiori-tools@^10
```
**pnpm:**
```bash
pnpm remove eslint-plugin-fiori-custom
pnpm add --save-dev eslint@^10 @sap-ux/eslint-plugin-fiori-tools@^10
```
**yarn:**
```bash
yarn remove eslint-plugin-fiori-custom
yarn add --dev eslint@^10 @sap-ux/eslint-plugin-fiori-tools@^10
```
**For CAP projects**: Run from the app subfolder if it has its own `package.json`, or from the root if packages are shared.
## Step 5 — Verify the migration
Run ESLint to confirm no configuration errors (replace `<webapp-path>` with the path resolved in Step 1b):
```bash
# Check config is valid (should print config JSON, not an error)
npx eslint --print-config <webapp-path>/Component.js 2>&1 | head -5
# Run lint on the webapp
npx eslint <webapp-path>/ 2>&1 | head -40
```
If ESLint reports config errors, common fixes:
- **"Cannot find module '@sap-ux/eslint-plugin-fiori-tools'"** → Plugin not installed, run install command from Step 4
- **"FlatConfig is not supported"** → Using ESLint 8, upgrade to ESLint 10
- **"Unknown rule"** → Old rule prefix still in use, check for remaining `fiori-custom/` references
## What changed between ESLint 8 and 9+
| ESLint 8 (legacy) | ESLint 10 (flat config) |
|---|---|
| `.eslintrc` / `.eslintrc.js` | `eslint.config.mjs` |
| `.eslintignore` | `ignores` array in config |
| `extends: [...]` | Spread `...` configs into array |
| `plugins: { "fiori-custom": ... }` | Included in `fioriTools.configs.recommended` |
| `fiori-custom/` rule prefix | `@sap-ux/fiori-tools/` rule prefix |
| `plugin:@sap-ux/.../defaultJS` | `fioriTools.configs.recommended` |
references/setup.md
# Set Up ESLint for a Fiori Project
Set up ESLint with `@sap-ux/eslint-plugin-fiori-tools` for a SAP Fiori or CAP project.
## Step 1 — Detect project type and app location
**1a — Check if this is a CAP project** by looking for `@sap/cds` in `package.json` (most reliable, works regardless of folder layout):
```bash
grep -q '"@sap/cds"' package.json 2>/dev/null && echo "cap" || echo "standalone"
```
**1b — If CAP: get the configured app folder** using the CDS CLI (avoids hardcoding `app/`):
```bash
npx cds env get folders.app 2>/dev/null
```
This returns the actual app folder path (e.g. `app`, `applications`, or a custom name). If the command fails, fall back to checking for `app/`.
**1c — Find the Fiori app(s)** by locating `manifest.json` files one level below the app folder:
```bash
# Replace <app-folder> with the result from 1b
find <app-folder> -maxdepth 2 -name "manifest.json" 2>/dev/null
```
For a standalone Fiori app, search from the project root:
```bash
find . -maxdepth 3 -name "manifest.json" 2>/dev/null
```
**1d — Determine the webapp path** by checking `ui5.yaml` first (the `resources.configuration.paths.webapp` key), then falling back to `manifest.json` location. The directory containing `manifest.json` is the webapp root (e.g. `webapp/`, `src/`, or any custom path). Do **not** assume `webapp/`.
```bash
# Check ui5.yaml for a custom webapp path
grep -A5 "paths:" ui5.yaml 2>/dev/null | grep "webapp:"
# If not set, find manifest.json to locate the webapp root
find . -maxdepth 4 -name "manifest.json" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null
```
## Step 2 — Determine the correct config location
**IMPORTANT**: The `eslint.config.mjs` must be placed at the **app level** (the folder containing the webapp root), not the CAP project root.
| Project type | Config location |
|---|---|
| Standalone Fiori | Project root (next to the webapp folder) |
| CAP — single app | `<app-folder>/<app-name>/` (next to the webapp folder) |
| CAP — multiple apps | One `eslint.config.mjs` per app, each next to its own webapp folder |
## Step 3 — Check if config already exists
Before creating anything, check if a config already exists:
```bash
# Standalone
ls eslint.config.mjs eslint.config.js .eslintrc .eslintrc.js .eslintrc.json .eslintrc.yml 2>/dev/null
# CAP app level (use the path resolved in Step 1)
ls <app-folder>/<app-name>/eslint.config.mjs <app-folder>/<app-name>/.eslintrc 2>/dev/null
```
If a config already exists, inform the user and offer to:
1. Leave it as-is (if it already uses `@sap-ux/eslint-plugin-fiori-tools`)
2. Use [migrate.md](migrate.md) to migrate it to flat config syntax
## Step 4 — Determine the right configuration
Ask the user (or infer from project context) which config variant to use:
- **`recommended`** — For most Fiori freestyle and Fiori elements projects. Lints JS/TS in the webapp folder (resolved in Step 1d).
- **`recommended-for-s4hana`** — For S/4HANA Fiori elements apps. Adds annotation validation for `manifest.json`, `*.xml`, and `*.cds` files.
Use `recommended-for-s4hana` if you detect any of these signals:
- Project has CDS files (`*.cds`)
- `manifest.json` references `sap.fe.templates` or `sap.ovp`
- User mentions "S/4HANA", "Fiori elements", or "annotations"
## Step 5 — Check if the plugin is installed
```bash
# Check package.json for the plugin
cat package.json | grep "eslint-plugin-fiori-tools"
```
If missing, install it. Choose the package manager the project uses:
```bash
# Detect package manager
ls package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null
```
Install commands:
```bash
# npm
npm install --save-dev @sap-ux/eslint-plugin-fiori-tools eslint
# pnpm
pnpm add --save-dev @sap-ux/eslint-plugin-fiori-tools eslint
# yarn
yarn add --dev @sap-ux/eslint-plugin-fiori-tools eslint
```
**Note**: For CAP projects, run the install command from the **app subfolder** that has its own `package.json`. If the app shares the root `package.json`, install at the root.
## Step 6 — Create the eslint.config.mjs
Create the config file at the location determined in Step 2.
### Recommended config (standalone Fiori or CAP app):
```javascript
import fioriTools from '@sap-ux/eslint-plugin-fiori-tools';
export default [
...fioriTools.configs.recommended
];
```
### Recommended-for-S/4HANA config:
```javascript
import fioriTools from '@sap-ux/eslint-plugin-fiori-tools';
export default [
...fioriTools.configs['recommended-for-s4hana']
];
```
### With custom ignores (add if needed):
```javascript
import fioriTools from '@sap-ux/eslint-plugin-fiori-tools';
export default [
{
ignores: ['dist', 'target', 'localService', 'backup']
},
...fioriTools.configs.recommended
];
```
## Step 7 — Add a lint script to package.json (optional)
If the project's `package.json` has a `scripts` section but no `lint` script, offer to add one:
```json
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix"
}
}
```
For CAP apps where the config is in a subfolder, choose the approach that fits the project layout:
| Approach | Command | Prerequisite |
|---|---|---|
| Change directory | `cd <app-folder>/<app-name> && eslint <webapp-path>/` | None |
| Explicit config flag | `eslint --config <app-folder>/<app-name>/eslint.config.mjs <app-folder>/<app-name>/<webapp-path>/` | None |
| npm workspaces | `npm run lint --workspace=<app-folder>/<app-name>` | Root `package.json` must declare the app as a workspace |
Check first:
```bash
node --input-type=commonjs -e "const p=require('./package.json'); console.log(p.workspaces)"
```
If this prints the app paths, the `--workspace` flag will work.
## Step 8 — Verify the setup
Run ESLint to verify the config works (replace `<webapp-path>` with the path resolved in Step 1d):
```bash
npx eslint --print-config <webapp-path>/Component.js 2>/dev/null | head -20
```
Or run a quick lint to confirm no config errors:
```bash
npx eslint <webapp-path>/ --max-warnings 9999 2>&1 | head -20
```
## Important notes
- ESLint 10 requires Node.js >= 18.18.0
- The plugin expects the webapp path relative to the config file location — use the path resolved in Step 1d, do not assume `webapp/`
- Do NOT place the config at the CAP project root if apps have their own `package.json` — ESLint will not resolve the plugin correctly
- Use `.eslintignore` patterns in the `ignores` array of `eslint.config.mjs` (flat config has no `.eslintignore` support)
- The `recommended` config already includes ignores for `target/`, `localService/`, `backup/`, and `*.d.ts` files
SKILL.md
---
name: sap-fiori-eslint-plugin
description: >
Configure, migrate, or run ESLint with @sap-ux/eslint-plugin-fiori-tools in SAP Fiori projects
(standalone or CAP). Use when ESLint is missing and the user wants to add it or add code quality
checks; when an existing .eslintrc or eslint@8 config needs upgrading to ESLint 9 flat config;
when the user wants to run linting, fix lint errors, or ESLint is broken or not working; or when
the user wants to see the application issues or UX consistency issues for a specific app — e.g.
"show application issue(s) for my project <project_name>", "show application issues for app X",
"UX consistency issues", "issues consistent with Page Map",
"same issues as Application Information".
compatibility: Requires Node.js with npm, pnpm, or yarn. Designed for SAP Fiori freestyle and Fiori elements projects (standalone or inside a CAP project).
metadata:
author: sap-fiori-tools
version: "0.0.2"
---
# SAP Fiori ESLint Plugin
Work with `@sap-ux/eslint-plugin-fiori-tools` on SAP Fiori projects: set up ESLint from scratch, migrate from a legacy configuration, or run and fix lint issues.
## Determine which task to perform
Identify the user's intent from their request:
| User says / situation | Task | Reference |
|---|---|---|
| "Set up ESLint", "Add ESLint", no `eslint.config.mjs` exists | **Set up** | [references/setup.md](references/setup.md) |
| "Migrate ESLint", `.eslintrc` / eslint@8 present, upgrade ESLint | **Migrate** | [references/migrate.md](references/migrate.md) |
| "Run ESLint", "Check my code", "Fix lint errors", `eslint.config.mjs` exists | **Lint** | [references/lint.md](references/lint.md) |
| "Show all application issues", "show UX consistency issues", "issues for app X", "same issues as Page Map/App Info" | **App issues only** | [references/lint.md](references/lint.md) — follow the **App-scope mode** section |
If the intent is unclear, check the project state:
```bash
# Check for existing ESLint config (any format)
ls eslint.config.mjs eslint.config.js .eslintrc .eslintrc.js .eslintrc.cjs .eslintrc.json .eslintrc.yml .eslintrc.yaml 2>/dev/null
```
- **No config found** → follow [references/setup.md](references/setup.md)
- **Legacy config found** (`.eslintrc*`) → follow [references/migrate.md](references/migrate.md)
- **Flat config found** (`eslint.config.mjs`) → follow [references/lint.md](references/lint.md)
If the intent is still unclear, ask the user to clarify whether they want to set up ESLint, migrate an existing config, or run linting.