oxc-plugin-servicenow
v2.0.0
Published
First-class Oxlint + oxfmt tooling for ServiceNow Fluent (.now.ts) and classic ServiceNow JavaScript.
Maintainers
Readme
oxc-plugin-servicenow
First-class oxlint + oxfmt tooling for:
- ServiceNow Fluent — TypeScript DSL in
.now.tsfiles, powered by@servicenow/sdk - Classic ServiceNow JavaScript — Business Rules, Client Scripts, Script Includes, UI Actions, and everything else still running on the restricted platform engine
The plugin is written against the official @oxlint/plugins API (definePlugin + defineRule + createOnce) and is wrapped with eslintCompatPlugin, so the same package works in oxlint and ESLint 9+.
npm install -D oxc-plugin-servicenow oxlint oxfmtSupported package entry points
| Entry point | Supported exports |
| --- | --- |
| oxc-plugin-servicenow | Default plugin, plugin, configs, and the ServiceNowSettings, RuleConfigMap, and RuleName types. |
| oxc-plugin-servicenow/analysis | analyzeProvenance, getScriptContext, and their read-only public types. |
| oxc-plugin-servicenow/oxfmt | The TypeScript oxfmt configuration exports. |
| oxc-plugin-servicenow/oxfmt.recommended.json | The JSON oxfmt preset. |
| oxc-plugin-servicenow/package.json | Package metadata through Node package exports. |
Other source and dist paths are internal. Do not import them.
analyzeProvenance(context, ast?) analyzes context.sourceCode.ast by default. Pass an explicit AST only when its nodes are the ones you will query. Explicit trees are cached independently and use their own lexical bindings rather than borrowing the host parser's scope graph.
Why this exists
ServiceNow apps now live in two worlds at once.
Fluent .now.ts files are declarative metadata. They should import from @servicenow/sdk/core, declare $id: Now.ID['…'], and keep business logic out of the metadata object.
Classic scripts still run on a restricted, mode-dependent engine. Compatibility and ES5 Standards reject many modern features. ES2021 supports Promise, async/await, and optional chaining, but still disallows async iteration, WeakRef, and FinalizationRegistry. current.update() in a Business Rule retriggers the rule engine. Client-side GlideRecord is slow and often blocked. Hardcoded sys_ids rot the moment the app is installed on another instance.
This package does not treat every non-Fluent file as ES5. Unknown JavaScript mode stays unknown. Mode-specific rules skip rather than guess.
Existing ESLint plugins (eslint-plugin-servicenow, eslint-plugin-sn) cover parts of the classic world and nothing of Fluent. This package covers both, on the Oxc toolchain.
Quick start — oxlint
.oxlintrc.json
{
"jsPlugins": [
{ "name": "servicenow", "specifier": "oxc-plugin-servicenow" }
],
"settings": {
"servicenow": {
"javascriptMode": "unknown"
}
},
"rules": {
"servicenow/no-hardcoded-sysid": "error",
"servicenow/no-gs-now": "error",
"servicenow/require-query-before-next": "error",
"servicenow/no-client-gliderecord": "error",
"servicenow/no-br-current-update": "error",
"servicenow/no-sync-glideajax": "error",
"servicenow/no-delete-multiple-with-windowing": "error",
"servicenow/require-callback-for-getreference": "error",
"servicenow/require-glideajax-sysparm-name": "error",
"servicenow/validate-glideaggregate-calls": "error",
"servicenow/no-now-id-as-reference": "error",
"servicenow/no-glideajax-getanswer": "error",
"servicenow/no-duplicate-fluent-id": "error",
"servicenow/no-glideelement-in-collection": "error",
"servicenow/no-gliderecord-query-modifier-after-query": "error",
"servicenow/require-business-rule-wrapper": "error",
"servicenow/no-unfiltered-gliderecord-bulk-operation": "warn",
"servicenow/no-async-iterators": "error",
"servicenow/no-weak-references": "error",
"servicenow/fluent-proper-imports": "error",
"servicenow/fluent-directives": "warn",
"servicenow/require-fluent-id": "error"
}
}Or copy the maps exported by the package:
// oxlint.config.ts
import { defineConfig } from "oxlint";
import servicenow, { configs } from "oxc-plugin-servicenow";
export default defineConfig({
jsPlugins: [{ name: "servicenow", specifier: "oxc-plugin-servicenow" }],
rules: configs.recommendedRules,
});oxlint JS plugins are alpha. Custom file parsers and type-aware rules are not supported. This plugin stays within the supported ESTree visitor API. See JS plugins and writing JS plugins.
Presets
| Preset | Intent |
| --- | --- |
| configs.recommendedRules | High-confidence rules that stay quiet when the runtime mode or surface is unknown. |
| configs.classicEs5Rules | Compatibility / ES5 engine bans (Promise, async/await, ?., WeakMap, …). |
| configs.es2021Rules | Features still unavailable after ES2021, including universal restrictions and release-dependent BigInt typed-array support. |
| configs.clientRules | Client-side API rules. |
| configs.aclRules | ACL-specific review rules. |
| configs.businessRuleRules | Business Rule rules. |
| configs.fluentRules | Fluent .now.ts metadata rules. |
| configs.strictRules | Recommended plus warn-level performance and naming guidance. Does not promote heuristics to errors. |
| configs.policyRules | Optional organizational and migration policy (no-hardcoded-table-names, no-complex-fluent-logic, no-packages-calls). |
| configs.securityRules | Opt-in privilege-sensitive review rules such as no-system-query-bypass. |
Quick start — oxfmt
oxfmt does not currently support custom formatting plugins. The supported extension point is a recommended configuration with file-type overrides.
oxfmt.config.ts
import { defineConfig } from "oxfmt";
import { recommendedOxfmtConfig } from "oxc-plugin-servicenow/oxfmt";
export default defineConfig(recommendedOxfmtConfig);.oxfmtrc.json
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"extends": []
}Copy the JSON preset shipped with the package:
cp node_modules/oxc-plugin-servicenow/oxfmt.recommended.json .oxfmtrc.jsonWhat the preset does:
| Files | Style |
| --- | --- |
| **/*.now.ts | TypeScript / Fluent — single quotes, trailing commas, width 100 |
| **/*.{server,client,br,si,acl}.js, **/*.ui-action.js, src/{server,client}/**, ACL directories | Classic Studio style — double quotes, no trailing commas, width 120. Includes compound .client.ui-action.js and .server.ui-action.js suffixes. |
| **/.now/**, keys.ts | Ignored (SDK sync artefacts) |
Then:
npx oxfmt --write ..now.ts is TypeScript. oxfmt already knows how to format it; the preset just picks Fluent-friendly options.
ESLint 9+
The plugin is wrapped with eslintCompatPlugin, so every createOnce rule also has a create shim.
// eslint.config.js
import servicenow from "oxc-plugin-servicenow";
export default [servicenow.configs.flat.recommended];export default [servicenow.configs.flat.strict];The flat presets set files so ESLint 10 opens classic *.js / *.cjs / *.mjs and Fluent *.now.ts / *.now.tsx. ESLint 10's default glob is JS/CJS/MJS only.
configs.flat.client selects client-script filenames and supplies the client surface, but deliberately does not guess application scope. Merge settings.servicenow.scope: "scoped" when using it for a scoped application; no-client-gliderecord stays silent for global or unknown scope because ServiceNow still documents the global client API.
configs.flat.acl selects boundary-delimited ACL and access-control export names plus ACL directories, then derives the ACL surface from that same filename evidence. Contradictory paths such as src/client/read.acl.js stay unclassified. Its advisory query rule is also available in strict and security; recommended remains unchanged.
oxlint parses TypeScript itself. ESLint uses its default JS parser, so type annotations (import type, : string) in .now.ts fail to parse when you use only plugin.configs.flat.recommended.
For typed Fluent files, compose the recommended (or strict) preset with a TypeScript parser. This package tests typescript-eslint 8.x with ESLint 9. typescript-eslint 8 does not accept ESLint 10 as a peer. Type-aware linting is not required.
// eslint.config.js — typed Fluent composition
import servicenow from "oxc-plugin-servicenow";
import tseslint from "typescript-eslint";
export default [
{
files: ["**/*.now.ts", "**/*.now.tsx"],
languageOptions: {
parser: tseslint.parser,
parserOptions: {
sourceType: "module",
ecmaVersion: "latest",
},
},
},
servicenow.configs.flat.recommended,
];Ordinary TypeScript outside *.now.ts / *.now.tsx stays unaffected unless you add those files to the config yourself.
To run these rules on server TypeScript (src/server/**/*.ts), add a files override. Set settings.servicenow.javascriptMode to es2021 or es5 for those files. ecmaLatest and // @sn-es-latest still map to es2021 for one major-release cycle.
Runtime context
The plugin models four independent dimensions. It does not collapse them into one scriptType.
| Dimension | Values | Why it matters | | --- | --- | --- | | Authoring form | classic / Fluent | Instance script versus SDK metadata | | JavaScript mode | Compatibility / ES5 / ES2021 / unknown | Language-feature support | | Surface | client / server / ACL / Business Rule / UI Action / Script Include / scheduled / fix | Available APIs | | Scope | global / scoped / unknown | API availability | | Confidence | explicit / filename / inferred / unknown | Whether mode-specific rules may run |
Authority order: explicit settings, then filename conventions, then conservative source inference. Unknown JavaScript mode never assumes ES5.
UI Actions can be client, server, or mixed:
{
"settings": {
"servicenow": {
"surfaces": ["ui-action", "client"]
}
}
}Settings
Configure once. Invalid keys, types, or conflicting values throw a configuration error with the full path.
{
"settings": {
"servicenow": {
"javascriptMode": "es2021",
"surfaces": ["business-rule"],
"scope": "scoped",
"scopePrefix": "x_acme",
"allowedSysIds": ["97c04b3b1b12100043ab85e5bd0713e2"],
"allowedTables": ["x_acme_widget"],
"release": "australia",
"fluentSdkVersion": "4.4.1",
"businessRuleSourceFormat": "full-script"
}
}
}| Field | Meaning |
| --- | --- |
| javascriptMode | compatibility, es5, es2021, or unknown (default) |
| authoring | classic, fluent, or auto |
| surfaces | auto or a non-empty array. Supports acl for Access Control scripts. Mixed UI Actions must omit deprecated scriptType and use values such as ["ui-action","client","server"]. |
| scope | global, scoped, or unknown |
| scopePrefix | Application scope prefix such as x_acme |
| allowedSysIds | 32-character lowercase sys_ids that no-hardcoded-sysid ignores |
| allowedTables | Table names that no-hardcoded-table-names ignores |
| release | Optional release selector: "zurich" or "australia". Omission uses only facts shared by every supported release. |
| fluentSdkVersion | Fluent SDK semver the manifest should evaluate. This is independent from the instance release. |
| businessRuleSourceFormat | full-script, body-only, or unknown |
| scriptType | Deprecated. Use authoring and surfaces. |
| ecmaLatest | Deprecated. true maps to javascriptMode: "es2021". false does not assume ES5. |
Australia support is release-aware rather than a renamed Zurich default:
The generated Australia JavaScript engine update ledger maps every official Rhino update row to an implemented diagnostic, a deliberate metadata-only disposition, or explicit pending research. Pending rows are not counted as supported.
| Engine capability | Zurich ES2021 | Australia ES2021 | ES5 Standards |
| --- | --- | --- | --- |
| Object.hasOwn() | Not Supported | Supported | Not Supported |
| BigInt64Array / BigUint64Array | Not Supported | Supported | Not Supported |
| TypedArray.from() / TypedArray.of() | Not Supported | Supported | Typed arrays Disallowed |
| BigInt.asUintN() / BigInt.asIntN() narrowing | Incorrect edge cases | Corrected | BigInt Not Supported |
| Array.from() mapper thisArg semantics | Incorrect primitive/omitted cases | Corrected | Array.from() Not Supported |
| Function.prototype.call() / .apply() thisArg semantics | Incorrect and execution-path-dependent | Corrected | Not corrected (ES2021-only update) |
| Nested block function hoisting | Incorrect | Corrected | Release-dependent (Australia corrected) |
| Constructing shorthand object methods | Permitted (non-standard) | Throws TypeError | Ordinary method syntax Not Supported; async/generator methods Disallowed |
| Private instance members | Not Supported | Not Supported | Not Supported |
| DataView BigInt getters | Not Supported | Not Supported | Not Supported |
| Function.prototype.toString() source text for methods and computed property names | Disallowed | Supported | Disallowed |
ServiceNow publishes feature-table columns for ES2021 and ES5 Standards, while documenting Compatibility as a distinct third mode. The plugin deliberately applies each feature-table ES5 cell to Compatibility mode as package policy; capability metadata marks those inferred cells separately from official table cells. Update-ledger entries explicitly marked for all modes, such as Australia's variable-length Date fractions, are modeled directly for Compatibility instead of using that inference. “Not Supported” retains ServiceNow's precise meaning: the feature has not been validated for that release and mode, unlike “Disallowed,” which produces a platform error.
The narrow Function.prototype.toString() delta is recorded as compatibility knowledge but has no lint diagnostic: static analysis cannot prove that code depends on exact returned method source text without unacceptable false positives. The Function.prototype.call() / .apply() correction is also metadata-only. Before the upstream fix, nullish-thisArg handling depended both on function strictness and on Rhino's interpreted-versus-compiled execution path; source analysis cannot prove which legacy path ServiceNow will select. no-unhoisted-block-function-use covers the deterministic hoisting delta: it reports only a binding-proven read before a nested-block declaration in the same execution body, excluding deferred functions, classes, switch cases, mutable bindings, and dynamic scope. no-object-method-constructor covers Australia's stricter method construction: it reports only direct new calls whose shorthand object-method identity is stable through immutable object and method aliases. no-incorrect-bigint-asuintn is intentionally narrower: it reports only literal negative-input calls where the pre-Australia and specified unsigned results are provably different. no-incorrect-array-from-thisarg likewise requires a stable native Array.from, a syntax-proven mapper, and either a static primitive third argument or a non-strict mapper that reads its own this with no third argument. ServiceNow's unversioned Australia reference URLs were reviewed with the official Australia release label and March 12, 2026 update date; those source markers are pinned beside the capability tables so a later default-documentation change cannot silently relabel the review. Australia release notes identify the SDK 4.4 family. The plugin keeps fluentSdkVersion independent so users can select a reviewed declaration manifest; that setting does not assert that an SDK version is compatible with a particular instance.
Mixed-repository composition:
import { defineConfig } from "oxlint";
import { configs } from "oxc-plugin-servicenow";
export default defineConfig({
jsPlugins: [{ name: "servicenow", specifier: "oxc-plugin-servicenow" }],
rules: configs.recommendedRules,
overrides: [
{
files: ["src/server/**/*.js", "**/*.si.js"],
settings: { servicenow: { javascriptMode: "es2021", surfaces: ["server"] } },
rules: configs.es2021Rules,
},
{
files: ["**/*.br.js"],
settings: { servicenow: { javascriptMode: "es5", surfaces: ["business-rule"] } },
rules: { ...configs.classicEs5Rules, ...configs.businessRuleRules },
},
{
files: ["**/*.client.js"],
settings: { servicenow: { surfaces: ["client"], scope: "scoped" } },
rules: configs.clientRules,
},
],
});Per-file // @sn-es-latest still maps to es2021 with inferred confidence. Prefer javascriptMode in settings.
Rules
Classic ServiceNow
| Rule | Preset | Fix | What it catches |
| --- | --- | --- | --- |
| no-hardcoded-sysid | recommended | | Hardcoded 32-character sys_ids break when an app is installed on another instance |
| prefer-glideaggregate | strict | | GlideRecord.getRowCount() (and iterate-to-count loops) load every matching row |
| no-client-gliderecord | recommended | | Proven platform GlideRecord calls are unsupported in scoped client applications |
| no-gs-now | recommended | | gs.now() and gs.nowDateTime() return timezone-sensitive display strings |
| require-query-before-next | recommended | | Require a documented, scope-supported GlideRecord query executor before .next() or ._next() |
| validate-gliderecord-calls | off | | Deprecated alias |
| no-br-current-update | recommended | | current.update() retriggers other Business Rules and can recurse |
| no-hardcoded-table-names | policy | | Optional organizational policy |
| no-packages-calls | policy | | Optional migration policy |
| no-delete-multiple-with-windowing | recommended | | setLimit() and chooseWindow() do not limit deleteMultiple() |
| require-callback-for-getreference | recommended | | g_form.getReference(field) without a callback is a synchronous server request |
| require-glideajax-sysparm-name | recommended | | GlideAjax requires a non-empty addParam("sysparm_name", method) before getXML / getXMLAnswer / getXMLWait |
| validate-glideaggregate-calls | recommended | | A proven GlideAggregate must call query() before next() or getAggregate() |
| no-glideajax-getanswer | recommended | | getAnswer() belongs to synchronous GlideAjax |
| no-glideelement-in-collection | recommended | | Direct GlideRecord field access and path-proven local aliases are GlideElements tied to the cursor |
| no-gliderecord-query-modifier-after-query | recommended | | Filters and result-shaping calls after a documented query executor do not change the open cursor |
| require-business-rule-wrapper | recommended | | Full-script Business Rules must wrap logic in the standard IIFE so top-level variables do not leak |
| no-display-value-date-comparison | strict | | Do not relationally compare GlideDateTime.getDisplayValue() strings |
| no-unfiltered-gliderecord-bulk-operation | recommended | | updateMultiple() / deleteMultiple() without a proven restricting filter can touch every row |
| no-gliderecord-query-in-acl | strict | | Review proven GlideRecord, GlideRecordSecure, and GlideAggregate query executions on an ACL's immediate evaluation path |
| no-gliderecord-query-in-loop | strict | | A query inside a proven record cursor loop is an N+1 pattern |
| prefer-setnocount-with-choosewindow | strict | | The reviewed Zurich and Australia-scoped GlideRecord references document that query() after chooseWindow() runs COUNT(*) unless setNoCount() or setLimit() skips it |
| no-system-query-bypass | security | | Opt-in security review for documented ACL-bypass query APIs |
| no-sync-glideajax | recommended | | getXMLWait() blocks the browser and does not work in Service Portal |
Instance engine (mode-specific)
These rules run only when javascriptMode is known, except features that ServiceNow documents as unavailable in every instance mode for the selected release.
| Rule | Preset | What it catches |
| --- | --- | --- |
| no-promise | classic-es5 | Compatibility and ES5 Standards modes do not implement Promises |
| no-async-await | classic-es5 | async/await is not implemented in Compatibility or ES5 Standards mode |
| no-bigint | classic-es5 | BigInt literals and BigInt() are unsupported in Compatibility or ES5 Standards mode |
| no-incorrect-array-from-thisarg | es2021 | Zurich throws when Array.from receives an explicit primitive mapper thisArg—even for an empty source, because conversion precedes iteration—and gives a non-strict mapper the wrong this when that argument is omitted |
| no-unhoisted-block-function-use | classic-es5 | Before Australia, ServiceNow does not correctly hoist nested block function declarations to block entry |
| no-object-method-constructor | es2021 | ServiceNow Australia enforces ECMAScript's non-constructible shorthand object methods, while Zurich's ES2021 engine incorrectly permits them |
| no-incorrect-bigint-asuintn | es2021 | Zurich can return a negative input unchanged from BigInt.asUintN() when the requested width exceeds the input's signed byte representation; Australia corrects the ES2021 behavior |
| no-at-method | classic-es5 | .at() is not implemented in Compatibility or ES5 Standards mode |
| no-weak-references | recommended | WeakRef and FinalizationRegistry are disallowed in every instance JavaScript mode, including ES2021 |
| no-map-set | classic-es5 | ServiceNow supports Map and Set in ES2021 but not in Compatibility or ES5 Standards mode in either Zurich or Australia |
| no-weak-collections | classic-es5 | WeakMap and WeakSet are disallowed in Compatibility and ES5 Standards mode |
| no-object-hasown | classic-es5 | Object.hasOwn() is Not Supported in Zurich ES2021 and Australia ES5; Australia ES2021 Supports it |
| no-unsupported-date-fraction | classic-es5 | Australia adds variable-length ISO fractional-second parsing to all JavaScript modes, while Zurich accepts fractional seconds only when exactly three digits are present |
| no-unsupported-set-methods | es2021 | Set.prototype.intersection(), union(), difference(), symmetricDifference(), isSubsetOf(), isSupersetOf(), and isDisjointFrom() are available in Australia ES2021 but not Zurich ES2021 |
| no-unsupported-static-methods | es2021 | Error.isError(), Promise.try(), and Promise.withResolvers() are available in Australia ES2021 but not Zurich ES2021 |
| no-typed-arrays | classic-es5 | General TypedArray constructors and DataView construction are Disallowed by the ES5 cell, while BigInt64Array and BigUint64Array are Not Supported there |
| no-proxy | classic-es5 | Proxy is unsupported in Compatibility and ES5 Standards mode |
| no-unsupported-syntax | classic-es5 | The ES5 table marks ordinary object shorthand methods Not Supported and async/generator methods Disallowed |
| no-async-iterators | recommended | for await…of and async generators are disallowed in every instance JavaScript mode, including ES2021 |
Fluent (.now.ts)
| Rule | Preset | Fix | What it catches |
| --- | --- | --- | --- |
| fluent-proper-imports | recommended | | Fluent entity and column APIs must be imported from the module recorded in the selected SDK manifest |
| fluent-directives | recommended | | Validate documented ServiceNow Fluent SDK directive names and placement |
| prefer-now-include | strict | | Large inline script / HTML / CSS payloads belong in their own file and should be loaded with Now.include() |
| require-fluent-id | recommended | | Fluent entities must declare $id when the selected SDK manifest marks the imported factory as requiring an id |
| fluent-naming-convention | strict | | .now.ts files and Now.ID keys should be kebab-case |
| no-complex-fluent-logic | policy | | Optional architectural policy |
| no-now-id-as-reference | recommended | | Now.ID[...] is a metadata identity, not a reference |
| no-duplicate-fluent-id | recommended | | Two Fluent definitions that share the same static Now.ID key as $id collide |
Examples
Runnable profile projects live under examples/:
| Project | Context |
| --- | --- |
| classic-compatibility | Compatibility-mode server scripts |
| classic-es5 | ES5 Standards server scripts |
| es2021 | ES2021 server scripts |
| client | Client Scripts and Catalog Client Scripts |
| business-rule | Full-script Business Rules |
| ui-action | Client, server, and mixed UI Actions |
| fluent | Fluent .now.ts metadata |
| mixed | One repository with several surfaces |
Classic Business Rule — bad
var assignmentGroup = "97c04b3b1b12100043ab85e5bd0713e2";
current.assignment_group = assignmentGroup;
current.u_opened = gs.now();
current.update();error servicenow/no-hardcoded-sysid Hardcoded sys_id '97c04b3b…'
error servicenow/no-gs-now gs.now() is timezone-unsafe
error servicenow/no-br-current-update current.update() retriggers other rulesClassic Business Rule — good
current.assignment_group = gs.getProperty("x_acme.default_assignment_group");
current.u_opened = new GlideDateTime();
current.work_notes = "Assigned by default routing";Fluent — bad
import { BusinessRule } from "@servicenow/sdk";
BusinessRule({
table: "incident",
name: "Log state",
script: `
(function executeRule(current) {
var gr = new GlideRecord("sys_journal_field");
// …dozens of lines of logic…
})(current);
`,
});Fluent — good
import { BusinessRule } from "@servicenow/sdk/core";
import { logStateChange } from "../server/log-state-change";
BusinessRule({
$id: Now.ID["log-state-change"],
table: "incident",
name: "Log state change",
when: "after",
action: ["update"],
script: logStateChange,
});Or, for a raw script file:
script: Now.include("../server/log-state-change.server.js"),Migration from ESLint ServiceNow plugins
| Old (eslint-plugin-servicenow / eslint-plugin-sn) | New |
| --- | --- |
| servicenow/no-hardcoded-sysids | servicenow/no-hardcoded-sysid |
| servicenow/no-promise | servicenow/no-promise |
| servicenow/no-async-await | servicenow/no-async-await |
| servicenow/no-bigint-and-dataview | servicenow/no-bigint + servicenow/no-typed-arrays |
| sn/no-gs-now | servicenow/no-gs-now |
| sn/no-client-gliderecord | servicenow/no-client-gliderecord |
| sn/no-gr-count-iterate | servicenow/prefer-glideaggregate |
| sn/validate-gliderecord-calls | servicenow/require-query-before-next |
| sn/no-br-current-update | servicenow/no-br-current-update |
| servicenow/no-at-method | servicenow/no-at-method |
| servicenow/no-packages-calls | servicenow/no-packages-calls |
| servicenow/no-weak-references | servicenow/no-weak-references + servicenow/no-weak-collections |
| servicenow/no-proxy-internal-calls | servicenow/no-proxy |
| servicenow/no-regexp-lookbehind / no-private-class-methods | servicenow/no-unsupported-syntax |
| (none) | servicenow/no-sync-glideajax |
| (none) | servicenow/fluent-* and prefer-now-include |
- Install
oxc-plugin-servicenow+oxlint. - Drop in
.oxlintrc.jsonwith the recommended rule map. - Optionally keep ESLint around for rules oxlint does not implement yet (
eslint-plugin-oxlintto disable overlap). - Replace Prettier with oxfmt using the shipped preset.
- Delete
eslint-plugin-servicenow/eslint-plugin-snonce the diagnostics match.
Official docs
- ServiceNow Fluent
- ServiceNow SDK 3.0 /
Now.include - ServiceNow SDK release notes (Australia)
- JavaScript modes (Australia)
- JavaScript engine feature support (Zurich)
- JavaScript engine feature support (Australia)
- JavaScript engine updates (Australia)
- Avoid
current.update()in Business Rules (KB0715782) - oxlint JS plugins
- oxfmt configuration
Current oxlint JS plugin limitations
These are platform limits, not bugs in this package:
- JS plugins are alpha and the API may still move.
- No type-aware rules (Fluent
Tablegenerics are not inspected). - No custom parsers —
.now.tsis linted as TypeScript, which is what we want. - Rule options / suggestions / tokens are supported; some older ESLint APIs are not.
- Prefer
createOnce+before()(this plugin does) so oxlint can skip files whose node types the rule never visits.
Migrating to 2.0.0
2.0.0 is a major release because presets and settings change behavior.
| Rule | 1.1 preset | 1.1 | 2.0 | Replacement profile | Required action |
| --- | --- | --- | --- | --- | --- |
| servicenow/fluent-naming-convention | recommended | warn | off | configs.strictRules (warn) | Select configs.strictRules (warn). |
| servicenow/no-async-await | recommended | error | off | configs.classicEs5Rules (error) | Select configs.classicEs5Rules (error). |
| servicenow/no-async-iterators | recommended | off | error | configs.classicEs5Rules (error)configs.es2021Rules (error) | Review the off-to-error severity change. |
| servicenow/no-at-method | recommended | warn | off | configs.classicEs5Rules (error) | Select configs.classicEs5Rules (error). |
| servicenow/no-bigint | recommended | error | off | configs.classicEs5Rules (error) | Select configs.classicEs5Rules (error). |
| servicenow/no-complex-fluent-logic | recommended | warn | off | configs.policyRules (warn) | Select configs.policyRules (warn). |
| servicenow/no-delete-multiple-with-windowing | recommended | off | error | configs.businessRuleRules (error) | Review the off-to-error severity change. |
| servicenow/no-duplicate-fluent-id | recommended | off | error | configs.fluentRules (error) | Review the off-to-error severity change. |
| servicenow/no-glideajax-getanswer | recommended | off | error | configs.clientRules (error) | Review the off-to-error severity change. |
| servicenow/no-glideelement-in-collection | recommended | off | error | configs.businessRuleRules (error) | Review the off-to-error severity change. |
| servicenow/no-gliderecord-query-modifier-after-query | recommended | off | error | configs.businessRuleRules (error) | Review the off-to-error severity change. |
| servicenow/no-now-id-as-reference | recommended | off | error | configs.fluentRules (error) | Review the off-to-error severity change. |
| servicenow/no-packages-calls | recommended | error | off | configs.policyRules (warn) | Select configs.policyRules (warn). |
| servicenow/no-promise | recommended | error | off | configs.classicEs5Rules (error) | Select configs.classicEs5Rules (error). |
| servicenow/no-proxy | recommended | error | off | configs.classicEs5Rules (error) | Select configs.classicEs5Rules (error). |
| servicenow/no-typed-arrays | recommended | error | off | configs.classicEs5Rules (error)configs.es2021Rules (error) | Select configs.classicEs5Rules (error)configs.es2021Rules (error). |
| servicenow/no-unfiltered-gliderecord-bulk-operation | recommended | off | warn | Enable the rule explicitly | Review the off-to-warn severity change. |
| servicenow/no-unsupported-syntax | recommended | error | off | configs.classicEs5Rules (error)configs.es2021Rules (error) | Select configs.classicEs5Rules (error)configs.es2021Rules (error). |
| servicenow/no-weak-references | recommended | off | error | configs.classicEs5Rules (error)configs.es2021Rules (error) | Review the off-to-error severity change. |
| servicenow/prefer-glideaggregate | recommended | warn | off | configs.strictRules (warn) | Select configs.strictRules (warn). |
| servicenow/prefer-now-include | recommended | warn | off | configs.strictRules (warn) | Select configs.strictRules (warn). |
| servicenow/require-business-rule-wrapper | recommended | off | error | configs.businessRuleRules (error) | Review the off-to-error severity change. |
| servicenow/require-callback-for-getreference | recommended | off | error | configs.clientRules (error) | Review the off-to-error severity change. |
| servicenow/require-glideajax-sysparm-name | recommended | off | error | configs.clientRules (error) | Review the off-to-error severity change. |
| servicenow/require-query-before-next | recommended | off | error | configs.businessRuleRules (error) | Review the off-to-error severity change. |
| servicenow/validate-glideaggregate-calls | recommended | off | error | configs.businessRuleRules (error) | Review the off-to-error severity change. |
| servicenow/validate-gliderecord-calls | recommended | warn | off | Enable the rule explicitly | Replace it with servicenow/require-query-before-next. |
| servicenow/fluent-directives | strict | error | warn | configs.recommendedRules (warn)configs.fluentRules (warn) | Review the error-to-warn severity change. |
| servicenow/fluent-naming-convention | strict | error | warn | Enable the rule explicitly | Review the error-to-warn severity change. |
| servicenow/no-async-await | strict | error | off | configs.classicEs5Rules (error) | Select configs.classicEs5Rules (error). |
| servicenow/no-at-method | strict | error | off | configs.classicEs5Rules (error) | Select configs.classicEs5Rules (error). |
| servicenow/no-bigint | strict | error | off | configs.classicEs5Rules (error) | Select configs.classicEs5Rules (error). |
| servicenow/no-complex-fluent-logic | strict | error | off | configs.policyRules (warn) | Select configs.policyRules (warn). |
| servicenow/no-delete-multiple-with-windowing | strict | off | error | configs.recommendedRules (error)configs.businessRuleRules (error) | Review the off-to-error severity change. |
| servicenow/no-display-value-date-comparison | strict | off | warn | Enable the rule explicitly | Review the off-to-warn severity change. |
| servicenow/no-duplicate-fluent-id | strict | off | error | configs.recommendedRules (error)configs.fluentRules (error) | Review the off-to-error severity change. |
| servicenow/no-glideajax-getanswer | strict | off | error | configs.recommendedRules (error)configs.clientRules (error) | Review the off-to-error severity change. |
| servicenow/no-glideelement-in-collection | strict | off | error | configs.recommendedRules (error)configs.businessRuleRules (error) | Review the off-to-error severity change. |
| servicenow/no-gliderecord-query-in-acl | strict | off | warn | configs.aclRules (warn)configs.securityRules (warn) | Review the off-to-warn severity change. |
| servicenow/no-gliderecord-query-in-loop | strict | off | warn | Enable the rule explicitly | Review the off-to-warn severity change. |
| servicenow/no-gliderecord-query-modifier-after-query | strict | off | error | configs.recommendedRules (error)configs.businessRuleRules (error) | Review the off-to-error severity change. |
| servicenow/no-hardcoded-table-names | strict | warn | off | configs.policyRules (warn) | Select configs.policyRules (warn). |
| servicenow/no-now-id-as-reference | strict | off | error | configs.recommendedRules (error)configs.fluentRules (error) | Review the off-to-error severity change. |
| servicenow/no-packages-calls | strict | error | off | configs.policyRules (warn) | Select configs.policyRules (warn). |
| servicenow/no-promise | strict | error | off | configs.classicEs5Rules (error) | Select configs.classicEs5Rules (error). |
| servicenow/no-proxy | strict | error | off | configs.classicEs5Rules (error) | Select configs.classicEs5Rules (error). |
| servicenow/no-typed-arrays | strict | error | off | configs.classicEs5Rules (error)configs.es2021Rules (error) | Select configs.classicEs5Rules (error)configs.es2021Rules (error). |
| servicenow/no-unfiltered-gliderecord-bulk-operation | strict | off | warn | configs.recommendedRules (warn) | Review the off-to-warn severity change. |
| servicenow/no-unsupported-syntax | strict | error | off | configs.classicEs5Rules (error)configs.es2021Rules (error) | Select configs.classicEs5Rules (error)configs.es2021Rules (error). |
| servicenow/prefer-glideaggregate | strict | error | warn | Enable the rule explicitly | Review the error-to-warn severity change. |
| servicenow/prefer-now-include | strict | error | warn | Enable the rule explicitly | Review the error-to-warn severity change. |
| servicenow/prefer-setnocount-with-choosewindow | strict | off | warn | Enable the rule explicitly | Review the off-to-warn severity change. |
| servicenow/require-business-rule-wrapper | strict | off | error | configs.recommendedRules (error)configs.businessRuleRules (error) | Review the off-to-error severity change. |
| servicenow/require-callback-for-getreference | strict | off | error | configs.recommendedRules (error)configs.clientRules (error) | Review the off-to-error severity change. |
| servicenow/require-glideajax-sysparm-name | strict | off | error | configs.recommendedRules (error)configs.clientRules (error) | Review the off-to-error severity change. |
| servicenow/require-query-before-next | strict | off | error | configs.recommendedRules (error)configs.businessRuleRules (error) | Review the off-to-error severity change. |
| servicenow/validate-glideaggregate-calls | strict | off | error | configs.recommendedRules (error)configs.businessRuleRules (error) | Review the off-to-error severity change. |
| servicenow/validate-gliderecord-calls | strict | error | off | Enable the rule explicitly | Replace it with servicenow/require-query-before-next. |
- Replace
settings.servicenow.ecmaLatestwithjavascriptMode. - Replace
settings.servicenow.scriptTypewithauthoringandsurfaces. - Remove ES5-only rules from a context-neutral
recommendedmap. Addconfigs.classicEs5Ruleswhere the app is Compatibility or ES5. - Replace
validate-gliderecord-callswithrequire-query-before-next. - Do not expect autofixes from
no-gs-now,prefer-glideaggregate,no-at-method,no-weak-references, orfluent-proper-imports. - Treat unknown mode as unknown. Valid ES2021 code must not be rejected unless you opt into
classic-es5. - Configure the
typescript-eslintparser before an ESLint flat preset selects typed*.now.tsor*.now.tsxfiles. - Upgrade oxfmt from the 1.1 peer floor of
>=0.16.0to>=0.64.0. - Set
settings.servicenow.releaseto"zurich"or"australia"when the target is known. Omit it to use only cross-release facts. - Import shared analysis only from
oxc-plugin-servicenow/analysis.
The 2.0 root no longer exports these 1.1 implementation details: rules,
recommendedOxfmtConfig, oxfmtRecommended, applyRules, ruleCatalog,
PACKAGE_NAME, PACKAGE_VERSION, and PLUGIN_NAME. It also removes the
ScriptKind, LintMessage, and LintSourceOptions root types. Import oxfmt
configuration from /oxfmt. Test harnesses and catalog data have no public
replacement.
Tested compatibility
These declared ranges are validated by the repository test suite.
| Component | Tested range |
| --- | --- |
| Node | 20.19.0, 22.14.0, 24.16.0, 26.7.0 |
| oxlint | 1.79.0 and 1.79.0 (>=1.79.0 <2) |
| ESLint | 9.0.0, 9.39.5, and 10.8.1 (>=9.0.0 <11) |
| oxfmt | 0.64.0 and 0.64.0 (>=0.64.0 <1) |
| ServiceNow engine tables | zurich, australia |
| Fluent SDK | 3.0.0, 3.0.1, 3.0.2, 3.0.3, 4.0.0, 4.0.1, 4.0.2, 4.1.0, 4.1.1, 4.2.0, 4.3.0, 4.4.0, 4.4.1, 4.5.0, 4.6.0, 4.6.1, 4.7.0, 4.7.1, 4.7.2, 4.8.0, 4.8.1, 4.9.0, 4.9.1, 4.9.2, 4.10.0, 4.10.1, 4.11.0 |
Development
npm install
npm run validatenpm run validate checks workflow action pins and the compatibility matrix; runs lint, format, project and fixture typechecking, build, tests, and Fluent-manifest verification; then checks evidence, acceptance, generated-documentation consistency, benchmarks, and the release artifact with a packed consumer.
See Contributing, Write a ServiceNow lint rule, and Non-goals.
Rules live in src/rules/. Each rule has:
createOnce(withbefore()to skip irrelevant files)meta.docs/meta.messages- unit tests under
tests/rules/
Use getScriptContext and analyzeProvenance. Do not match platform APIs by name alone.
Autofixes require proof that the rewrite preserves semantics, plus exact output, syntax-validity, idempotence, and comment-preservation tests. Otherwise emit a diagnostic only.
The test harness walks an oxc-parser ESTree AST. CI also runs the built plugin under real oxlint and ESLint.
License
MIT
