eslint-rule-development

v2026.09.24

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.

GitHub
Install command
npx skhub add sap/eslint-rule-development
Markdown
SKILL.md

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:
    NODE_OPTIONS="--experimental-vm-modules" npx jest --testPathPatterns="sap-[rule-name]" --no-coverage
    

Step 1 — Identify the rule type, then read the reference file

TypeUse whenReference file
Annotation ruleValidates UI.* OData annotations in .xml / .cds filesreferences/annotation.md
Manifest JSON ruleValidates manifest.json propertiesreferences/manifest-json.md
Flex change file ruleValidates webapp/changes/*.change (Applicable only to OData V2 flex change properties)references/flex-change.md
JavaScript / TypeScript ruleValidates 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 versionLinker fileManifest root
V2 onlysrc/project-context/linker/fe-v2.tssap.ui.generic.app.pages.*
V4 onlysrc/project-context/linker/fe-v4.tssap.ui5.routing.targets.*
Bothboth linker filesboth 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:

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.
    // ✅ 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):

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):

'@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:

// 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:

// ✅ 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:

    |  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:

# 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:

pnpm --filter @sap-ux/eslint-plugin-fiori-tools test

Step 9 — Create changeset

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

PurposePath
Rule implementationpackages/eslint-plugin-fiori-tools/src/rules/sap-[name].ts
Rule registrypackages/eslint-plugin-fiori-tools/src/rules/index.ts
Plugin config & exportspackages/eslint-plugin-fiori-tools/src/index.ts
Diagnostic constantspackages/eslint-plugin-fiori-tools/src/language/diagnostics.ts
Annotation helper utilitiespackages/eslint-plugin-fiori-tools/src/project-context/linker/annotations.ts
Annotation index key formatpackages/eslint-plugin-fiori-tools/src/project-context/parser/service.ts (lines 50-57)
Linker typespackages/eslint-plugin-fiori-tools/src/project-context/linker/types.ts
V2 linkerpackages/eslint-plugin-fiori-tools/src/project-context/linker/fe-v2.ts
V4 linkerpackages/eslint-plugin-fiori-tools/src/project-context/linker/fe-v4.ts
Rule factorypackages/eslint-plugin-fiori-tools/src/language/rule-factory.ts
Rule fixerpackages/eslint-plugin-fiori-tools/src/language/rule-fixer.ts
Test helperpackages/eslint-plugin-fiori-tools/test/test-helper.ts
Rule docs templatepackages/eslint-plugin-fiori-tools/docs/rules/TEMPLATE.md
README rules tablepackages/eslint-plugin-fiori-tools/README.md

check() context access by rule type

Rule typeUse in check()Why
AnnotationlinkedModel.apps for page iteration; index.apps[appKey] for getIndexedServiceForMainServiceOnly 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.
Manifest JSONlinkedModel.apps for page iteration; index.apps[appKey] for parsedApp (manifest URI, manifestObject)Requires linked pages to find manifest config paths
Flex changecontext.sourceCode.projectContext.linkedModel.appsGuard on FioriChangeSourceCode first; linked model provides change file config via page.lookup['table']
JavaScript / TypeScriptStandard ESLint context — no projectContextJS/TS rules don't use the Fiori project model; use Rule.RuleModule, not createFioriRule
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

Apache-2.0

Source path

.agents/skills/eslint-rule-development

Default branch

main

Latest commit

43ac217

Tree SHA

3a94a0b