@servicenow/eslint-plugin-aiux-app
v0.4.0
Published
ESLint rules for AIUX scoped app correctness and SDK/compiler contracts
Readme
@servicenow/eslint-plugin-aiux-app
ESLint rules for AIUX scoped-app correctness. Two families:
- SDK/compiler contracts — decorators and runtime APIs that the Now SDK and AIUX build extract statically from your source. The compiler reads these at build time, so a value the linter can't see is a value the build can't serialize. Catching them here turns a silent runtime miss into a build-time error. These rules only fire on APIs imported from
@servicenow/aiux-components-core(or the@servicenow/karuna/aiux-components-corere-export) and Lit'slit/decorators.js, so unrelated local functions with the same names are never flagged. - Glide API call conventions — how
fetch/aiuxFetchcalls to ServiceNow Glide APIs (/api/now,/api/sn_*,/api/x_*) must be written so SSR auth, tracing, batching, timeouts, and GraphQL error handling work. These rules classify the call by AST structure (URL shape,ctx.protocol/ctx.hostnameconstruction, GraphQL endpoint) rather than by import source, and match the call by name (fetch,aiuxFetch,getHeaders, pluswindow.fetch/*.aiuxFetch).
Install
pnpm add -D @servicenow/eslint-plugin-aiux-appUsage
import aiuxApp from '@servicenow/eslint-plugin-aiux-app';
export default [...aiuxApp.recommended];The recommended config sets only the plugin + rules; your config must already
parse decorators (e.g. @babel/eslint-parser with @babel/plugin-proposal-decorators).
Or build your own from the rules export:
export default [
{
plugins: {'@servicenow/aiux-app': {rules: aiuxApp.rules}},
rules: {
'@servicenow/aiux-app/valid-decorators': 'error',
'@servicenow/aiux-app/literal-requirements': 'error',
'@servicenow/aiux-app/no-property-config-mix': 'error'
}
}
];Rules
SDK / compiler contracts
| Rule | Severity | What it catches |
| ------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| valid-decorators | error | AIUX SDK decorators with the wrong literal type (@discoverable('true')), out-of-range enum values (@category('system'), @protectionPolicy('write')), non-array @roles, misspelled/unknown decorators destructured from decorators, @customElement tag names missing a hyphen, @config on a non-property, and @externalConfig without a string key. |
| literal-requirements | error | Non-string-literal arguments to getSystemProperty / isPluginActive / getUserPreference. The build plugin scans these call sites and prefetches the named value; a dynamic argument can't be extracted, so the value is silently absent at runtime. |
| no-property-config-mix | error | A field decorated with both AIUX @config and Lit @property. @config fields must be internal state — a parent setting the attribute after mount conflicts with the constructor assignment. Use @state (or no Lit field decorator). |
| no-page-imports | error | Importing a route page.[jt]s module as a shared dependency. Page modules are route entry points; move shared code to a non-route module and import that instead. |
These map to a contract the compiler enforces silently. The value either makes it into the manifest or it doesn't — there's no degraded middle state to warn about — so the failure mode is a feature that quietly doesn't work on the instance. Linting is the only place this surfaces before deploy.
Glide API call conventions
| Rule | Severity | What it catches |
| -------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| graphql-requires-error-check | error | A fetch / aiuxFetch to /api/now/graphql whose enclosing function does not check the response body for errors after the call (a .errors access or a recognized helper such as resolveApiErrors). GraphQL can return HTTP 200 with errors in the body, so response.ok is not enough. Configure extra helper names via errorCheckHelpers. |
| require-relative-aiux-fetch-url | error | An aiuxFetch to a Glide API built as an absolute or same-origin (${ctx.protocol}://${ctx.hostname}/api/...) URL. Use a relative /api/... URL so SSR can inject auth, tracing, batching, and timeouts. Relative URLs and non-Glide absolute URLs are allowed. |
| no-manual-getheaders-with-aiux-fetch | error | Forwarding getHeaders(ctx) into an aiuxFetch headers option (inline, via a variable, or via a hoisted options object). aiuxFetch owns AIUX header/auth handling; pass only explicit headers like Content-Type. |
| prefer-aiux-fetch-for-glide-api | warn | A raw fetch() to a Glide API that is GraphQL, or any non-relative (absolute / ${origin}-constructed / dynamic-prefix) Glide URL. Prefer aiuxFetch(). Plain relative REST (fetch('/api/now/table/...'), including dynamic table names and query params) is intentionally allowed. |
These classify the URL by AST structure, not by scanning the source text for keywords, so ordinary table/field names (cmdb_ci_database, a ?origin= query param) and comments never trigger a false positive.
Contract drift
valid-decorators hardcodes the AIUX decorator surface (names + enum value sets)
for static analysis, but the source of truth is the decorators export in
@servicenow/aiux-components-core. test/contract.invariant.test.js asserts the
two stay in lockstep — if the SDK adds, removes, or renames a decorator and the
rule isn't updated, that test fails (rather than the rule silently false-flagging
a new decorator as unknownDecorator).
