- name
- eslint-rule-development
- description
- Develop a new ESLint rule for @sap-ux/eslint-plugin-fiori-tools. Always use this skill when adding any rule to the plugin, implementing manifest.json/XML/CDS/flex-change validation, writing rule tests with project context, or creating rule documentation — even if the request sounds simple or the user doesn't mention 'ESLint' explicitly. Guides through the complete workflow: diagnostics constant, rule implementation, unit tests, docs, and registration.
- compatibility
- Requires the open-ux-tools monorepo at packages/eslint-plugin-fiori-tools. Assumes pnpm workspace with TypeScript 5+.
- metadata
- {"author":"sap-fiori-tools","version":"0.0.1"}
# Fiori ESLint Rule Developer
Add a new ESLint rule to `@sap-ux/eslint-plugin-fiori-tools` following the established patterns in the monorepo.
## ⚡ Efficiency rules — read before doing anything
1. **Use the `Read` tool directly** — never `Bash cat` or spawn Explore subagents for file reading
2. **Read everything in one parallel batch** — each fast path lists the exact files; read them all at once in a single turn
3. **Run only the new test file** during development — not the full package suite:
```bash
NODE_OPTIONS="--experimental-vm-modules" npx jest --testPathPatterns="sap-[rule-name]" --no-coverage
```
---
## Step 1 — Identify the rule type, then read the reference file
| Type | Use when | Reference file |
|---|---|---|
| **Annotation rule** | Validates `UI.*` OData annotations in `.xml` / `.cds` files. Also, covers rules where text, that is, a label, title, or description, can be an i18n binding | `references/annotation.md` |
| **Manifest JSON rule** | Validates `manifest.json` properties | `references/manifest-json.md` |
| **Flex change file rule** | Validates `webapp/changes/*.change` (Applicable only to OData V2 flex change properties) | `references/flex-change.md` |
| **JavaScript / TypeScript rule** | Validates JS/TS application source code (UI5 patterns, global variables, deprecated APIs) | `references/js-ts-rule.md` |
Infer from the request:
- **Rule name** — `sap-[kebab-case-name]` pattern
- **OData version** — V2 only, V4 only, or both
- **Page scope** — which page types to check. **Default: all page types** (list report, object page, etc.) unless the spec explicitly restricts the scope. Do not limit to one page type based on the examples in the spec.
- **Auto-fix** — yes/no
- **Severity** — `error` or `warn`. Rules in `recommended-for-s4hana` MUST be `warn`.
**OData version determines the linker file — always follow the stated version:**
| OData version | Linker file | Manifest root |
|---|---|---|
| V2 only | `src/project-context/linker/fe-v2.ts` | `sap.ui.generic.app.pages.*` |
| V4 only | `src/project-context/linker/fe-v4.ts` | `sap.ui5.routing.targets.*` |
| Both | both linker files | both roots |
**Read the matching reference file immediately**, then read all the files it lists in a single parallel batch. If the rule spans multiple types (e.g. annotations + manifest, or manifest + flex changes), read all matching reference files and combine their templates and access patterns.
---
## Steps 2–10: Implementation Checklist
### Step 2 — Add diagnostic constant (annotation, manifest JSON, and flex change rules only; skip for JS/TS rules)
In `packages/eslint-plugin-fiori-tools/src/language/diagnostics.ts`:
```typescript
export const MY_RULE = 'sap-my-new-rule';
export interface MyRuleDiagnostic {
type: typeof MY_RULE;
// ... fields from the reference file's "Required diagnostic fields" table
}
// Add MyRuleDiagnostic to the Diagnostic union at the bottom
```
### Step 3 — Implement the rule
Use the template from the reference file for your rule type.
**Code quality requirements for every new or modified function:**
- **JSDoc** — add a JSDoc block (`@param`, `@returns`) to every new function. When modifying an existing function, update its JSDoc to reflect any signature or behaviour changes.
- **Cognitive complexity ≤ 15** — enforced by `sonarjs/cognitive-complexity`. If a function exceeds 15, extract branches or loops into well-named helper functions until the complexity falls within the limit. Do not inline complex logic in a single function to avoid this.
- **Backtick code indicators in messages** — in rule warning messages, wrap all code-like values in backticks: property names, annotation names, property values, entity names, and any other identifier that appears verbatim in source. Plain prose words stay unquoted.
```typescript
// ✅ Correct — code values in backticks
message: "The `UI.LineItem` annotation is missing the required `Criticality` property."
```
### Step 4 — Register the rule
**`src/rules/index.ts`** — add import + entry (alphabetical order):
```typescript
import sapMyNewRule from './sap-my-new-rule.js';
// ...
[MY_RULE]: sapMyNewRule,
```
**`src/index.ts`** — add to `fioriLanguageConfig` rules (Fiori language rules) or `baseFioriToolsRules` (JS/TS rules):
```typescript
'@sap-ux/fiori-tools/sap-my-new-rule': 'warn',
```
If the rule should also apply to S/4HANA projects, add it to the `recommended-for-s4hana` config in `src/index.ts` as well. Rules in this config **must** use `'warn'` severity:
```typescript
// In the recommended-for-s4hana config rules object:
'@sap-ux/fiori-tools/sap-my-new-rule': 'warn',
```
### Step 5 — Write tests
Use the test template from the reference file; run only the new test file (see efficiency rules at the top).
**Always check `message`, never `messageId`, in `errors` arrays.** When a rule message contains interpolated data (e.g. `{{tableType}}`), checking only `messageId` would accept any value for that placeholder and miss regressions. Use the fully resolved string instead:
```typescript
// ✅ Correct — verifies the interpolated value
errors: [
{
message:
'"TreeTable" is not supported in the flexible column layout with a draft-enabled service.'
}
]
// ❌ Wrong — does not verify the data interpolated into the message
errors: [{ messageId: 'sap-my-new-rule' }]
```
If tests show 0 errors when violations are expected, check the debug checklist at the bottom of the reference file.
### Step 6 — Write documentation
Create `packages/eslint-plugin-fiori-tools/docs/rules/sap-[rule-name].md`.
Read `packages/eslint-plugin-fiori-tools/docs/rules/TEMPLATE.md` for structure. Key sections:
- **H1 title** — one sentence describing the rule, with the rule ID in parentheses
- **Intro paragraph** — 2–3 sentences: what it detects, **why it was introduced** (motivation belongs here, not in a separate H2), and what to do instead
- **## Rule Details** — how the rule works; warning message; "The following patterns are considered warnings:" + "The following patterns are not considered warnings" code examples
- **### How to Fix** — steps to remediate (omit if obvious from the examples)
- **## False Positives** — optional; include only if the rule can produce false positives
- **## Bug Report** — link to GitHub issues
- **## Further Reading** — optional; only include if you have a real, verifiable URL
### Step 7 — Update README
In `packages/eslint-plugin-fiori-tools/README.md`, do **two things**:
1. **Add your new rule** at the **top** of the rules table with `new` in the version column:
```markdown
| new | [sap-my-new-rule](docs/rules/sap-my-new-rule.md) | Short description | | ✅ |
```
2. **Backfill any pending `new` versions**: if any rows still show `new` from prior rule additions that have since been released, look up each rule's release version in `packages/eslint-plugin-fiori-tools/CHANGELOG.md` and replace `new` with that version. After this cleanup, your newly added rule should be the only row showing `new`.
### Step 8 — Run full quality gates (once)
Run `lint:fix` to auto-fix ESLint errors and apply Prettier formatting across all modified files, then verify no issues remain:
```bash
# Fix lint errors and apply Prettier formatting
pnpm --filter @sap-ux/eslint-plugin-fiori-tools lint:fix
# Verify no remaining issues
pnpm --filter @sap-ux/eslint-plugin-fiori-tools lint
```
If `lint` reports errors after `lint:fix`:
- **`sonarjs/cognitive-complexity`** — resolve per the cognitive complexity guidance in Step 3.
- **`prettier/prettier`** — a formatting issue could not be auto-fixed. Apply the suggested change manually (usually a line that is too long or a multiline expression that needs restructuring).
- **`@typescript-eslint/no-unsafe-*`** — replace `any` casts or untyped values with proper interfaces or `unknown` + type guards.
Do not proceed to Step 9 until `pnpm lint` exits with code 0.
Then confirm all tests still pass:
```bash
pnpm --filter @sap-ux/eslint-plugin-fiori-tools test
```
### Step 9 — Create changeset
```bash
pnpm cset
```
Select `@sap-ux/eslint-plugin-fiori-tools`, choose `minor` for new rules:
```
FEAT: add sap-my-new-rule rule for [short description]
```
### Step 10 — Report
Summarize what was done:
- **Rule name and type** — rule ID and which type (annotation / manifest JSON / flex change / JS/TS)
- **Files created** — rule implementation, test file, doc file
- **Files modified** — diagnostics.ts, rules/index.ts, src/index.ts, README.md
- **Test results** — number of valid and invalid cases, all passing
- **Changeset** — package, bump type (`minor`), summary line
---
## Quick Reference: Key Files
| Purpose | Path |
|---|---|
| Rule implementation | `packages/eslint-plugin-fiori-tools/src/rules/sap-[name].ts` |
| Rule registry | `packages/eslint-plugin-fiori-tools/src/rules/index.ts` |
| Plugin config & exports | `packages/eslint-plugin-fiori-tools/src/index.ts` |
| Diagnostic constants | `packages/eslint-plugin-fiori-tools/src/language/diagnostics.ts` |
| Annotation helper utilities | `packages/eslint-plugin-fiori-tools/src/project-context/linker/annotations.ts` |
| i18n helper utilities (`extractI18nKey`, `getAttrOrChildText`, `collectSectionLabelKeys`, for example) | `packages/eslint-plugin-fiori-tools/src/project-context/linker/i18n.ts` |
| Annotation index key format | `packages/eslint-plugin-fiori-tools/src/project-context/parser/service.ts` (lines 50-57) |
| Linker types | `packages/eslint-plugin-fiori-tools/src/project-context/linker/types.ts` |
| V2 linker | `packages/eslint-plugin-fiori-tools/src/project-context/linker/fe-v2.ts` |
| V4 linker | `packages/eslint-plugin-fiori-tools/src/project-context/linker/fe-v4.ts` |
| Rule factory | `packages/eslint-plugin-fiori-tools/src/language/rule-factory.ts` |
| Rule fixer | `packages/eslint-plugin-fiori-tools/src/language/rule-fixer.ts` |
| Test helper | `packages/eslint-plugin-fiori-tools/test/test-helper.ts` |
| Rule docs template | `packages/eslint-plugin-fiori-tools/docs/rules/TEMPLATE.md` |
| README rules table | `packages/eslint-plugin-fiori-tools/README.md` |
## `check()` context access by rule type
| Rule type | Use in `check()` | Why |
|---|---|---|
| **Annotation** | `linkedModel.apps` for page iteration; `index.apps[appKey]` for `getIndexedServiceForMainService` | Only check annotations **referenced from pages** — never scan all entity annotations. `pageNames` must list only pages that reference the specific annotation. See `annotation.md` for access patterns and the page-annotation-map template. When text can be an i18n binding, use the two-pass design in `annotation.md#when-the-text-field-can-be-an-i18n-binding`. |
| **Manifest JSON** | `linkedModel.apps` for page iteration; `index.apps[appKey]` for `parsedApp` (manifest URI, manifestObject) | Requires linked pages to find manifest config paths |
| **Flex change** | `context.sourceCode.projectContext.linkedModel.apps` | Guard on `FioriChangeSourceCode` first; linked model provides change file config via `page.lookup['table']` |
| **JavaScript / TypeScript** | Standard ESLint `context` — no `projectContext` | JS/TS rules don't use the Fiori project model; use `Rule.RuleModule`, not `createFioriRule` |
在 GitHub 查看