ESLint plugin for ServiceNow scriptlets
npm install @admc.com/eslint-plugin-sn- Motivation, Goals, and Concepts
- Installation
- Setup
- Usage
* Color Output Work-around
- Rule Policies
- Customization
- Supported ServiceNow scriptlet types
* Supported Now
* Planned
- Development
- Provided Rules
This plugin handles the global-object and rule variants by switching ESLint environments and rules
based on the ServiceNow table and usually an _alt_.
"alt" is our term for distinguishing between _alt_-ernate JavaScript environments for the same
scriptlet field.
For example, linting of field sys_script.script must specify alt of either `global orscoped-es5;
and linting of field sys_script_client.script must specify alt of either iso or noniso.
From version 3.1.5 if you end logical file names with -condition.js then the specified scope
is altered to accommodate the server-side condition scriptlet. This has not been tested
comprehensively yet.
The provided config generator uses alts in overrides/files entries, and you can add or
customize with overrides/file entries in your own "sneslintrc.json" file.
Our design leverages ESLint override/files entries.
Normally ESLint override/files entries are matched against input file paths.
We instead use this switching to provide the needed ServiceNow capability toggling by internally
generating pseudopaths which always contain the targeted ServiceNow table and an alt.
You can see the mappings between pseudo paths and ServiceNow script capabilities in file
"exports.js".
You can override or add your own mappings of pseudo paths with an "sneslintrc.json" file.
You can also add to the available global variable lists by adding local list files.
See the customize section below for details.
The snLint snlint-wrapper script decouples eslint from filepaths on your system, passing
pseudo paths TO ESLint. This allows you to
1. Code const statements in server and MID scripts. We transform these ES5 'const' statements
to 'var' to satisfy ESLint.
1. Specify _alt_ with -a switch, such as 'scoped' vs. 'global' for ServiceNow app scope;
or 'iso' vs. 'noniso' for Client Script isolation mode.
(From scripting perspective app scope doesn't matter for client scripts).
Each table has a default alt value, so the switch is optional.
(Consequently, for cases where table has only one alt value, the -a switch adds no benefit).
1. Indicate target ServiceNow table (so we know which rules to apply) with -t switch, OR
1. If you do not specify -t (which overrides) then target table is determined by the directory
name in which each script resides.
Related module @admc.com/sn-developer provides other conveniences for ServiceNow developers,
including TinyMCE ESLint configurations and a system to do scriptlet development on your workstation
rather than using the web editor (TinyMCE).
npm i -g @admc.com/eslint-plugin-sn
`
UNIX users will need root privileges, so run this as root or under sudo.To use just with your own project, install locally:
`
npm init -y # If a ./package.json or ./node_modules/ is not already present
npm i @admc.com/eslint-plugin-sn
`
(Without a ./package.json or ./node_modules/ present, npm may install the package to
another cascade directory).Setup
With global installation
`
snLint -s
snLint -g
`
With local project installation
`
npm exec snLint -- -s
npm exec snLint -- -g
`ServiceNow has several very poor sys_script_validator records which will prevent you from
using modern JavaScript constructs from web editor, for .condition fields and fields of
types 'Script' and 'Script (Plain)'.
See directory resources/sys_script_validator for suggested sys_script_validator replacement
scriptlets, and I recommend that you deactivate the record for type 'Condition String'.
Usage
To get invocation syntax help:
`
snLint -h # with global installation
npm exec snLint -- -h # with local project installation
`
Do read the brief output from this command for essential information about specifying files,
target tables, and alts. The switch list in the output provides a concise list of the many
features.Single-line scriptlet fields (most importantly .condition fields) may indeed hold newlines, but
the newlines are hidden whenever displayed in web UI, even when using 'Show XML'. They aren't
normalized to a single space, they are completely skipped. In ServiceNow, a single-line scriptlet
is any string field with max length set to less than 256, as well as fields of type 'Condition
String'. Using newlines is useful only here if you edit these scriptlets externally, in which
case it can greatly reduce mess of larger scriptlets if you use newlines.
Especially useful to use IIFEs and/or try/catch blocks in condition scriptlets.
$3
As a work-around for a mingw or git-for-windows glitch, if Node.js can't determine tty interactivity
correctly, then you can export env node variable FORCE_COLOR to true.
You can check your system with
`
node -p process.stdout.isTTY
`Rule Policies
You are encouraged to override the provided policies. I do.
Generally the rules are strict but rules that are opinion-driven or which have valid exception cases
have the level set to warn.
Be aware that you can't selectively override ESLint rule options. If you override a rule then you
must specify all options that will apply for that rule.Of special note:
1. Function placeholder param checking for no-unused-vars is strict with level error and args=all,
but we have an exception for names matching pattern ^\_dummy.
Use this exception pattern when you need to skip unused sequential parameters.
Customization
See file "snglobals/README.txt" for instructions on how to customize the global JavaScript object
lists, to prevent ESLint from generating 'no-undef' violations, without having to code
eslint-disable directives.The provided globals were generated from a fresh San Diego instance with default plugins plus
the Discovery plugin.
To support a new target table and/or alt, add new override elements to your 'sneslitrc.json'
file, with your files values including the table and (optional) alt.
You need to also add a
customTables element to "sneslintrc.json".
The value is a mapping from new table name to the list of supported alt names, with the default alt name first.
If you are modifying an out-of-the-box table (adding or removing alts),
just specify the new alt list and it will override.Our globals list for intra-scoped-SI accesses is purposefully over-liberal.
There is no difficulty restricting intra-scope access (including to or from global) because the
references must always start with a scope name, and we already have all SI scopes in our list.
We handle global-to-global SI accesses by providing a complete list of global SIs.
But for non-global intra-scope accesses we only check that any scope has the SI name defined.
To restrict accurately we would have to maintain a separate list for every non-global scope, and
that's just not practical.
If you want to narrow down intra-scope SI accesses for specific scopes, you're welcome to define
new alts for this purpose, defining "sneslintrc.json" environments, override elements.
If you only edit scripts in one or a few scopes, then a much easier customizaton procedure is
documented in the "snglobals/README.txt" file, to replace the list of all scopedSIs with just those
that you should be accessing.
Supported ServiceNow scriptlet types
A "default" alt is the one that will be used if no alt is specified to an invocation of snLint (or a programmatic call to snLint.lintFile without specifying an alt).
(The default is set by virtue of being the first member of the ootbTables and customTables mappings).Special simplification when a table supports just one alt.
These are clearly distinguished by the supported alt value being "all".
As "all" is the only alt for these tables, it is always the default alt.
Consequently, when calling snLint for any single-alt/"all" table, calling with no alt (undefined) is the same thing as specifying "all".
Alt global-es12 is being added with minor version 3.19.
This supports global scope scripts with the "Turn on ECMAScript 2021 (ES12) mode" toggle on.
Non-global legacy scopes use the scoped-es12 for the same purpose.
Integration developers can query table sys_es_latest_script to programmatically determine the current setting of the toggle.
N.b. as of 2025-12-19 the toggle doesn't work as has no effect on the following tables/fields due to PRB1971520, so for now you shouldn't use global-es12 for these scriptlets.
* cert_audit
* sysauto_script.script, but not .condition (PRB may apply to all editable .scripts of sysauto_script descendant tables)
* sys_atf_step_config.step_execution_generator, .description_generator
* sys_processor
* sys_script_fix
$3
Alphabetically.
If .fieldname is not specified for a table, then the field is "script".
* cert_audit
* sysauto_script.script, but not .condition (PRB may apply to all editable .scripts of sysauto_script descendant tables)
* sys_atf_step_config.step_execution_generator, .description_generator
* sys_processor
* sys_script_fix
|Table |Alts (default bolded)
|--- |---
|catalog_script_client |iso, noniso
|catalog_ui_policy.script_true |iso, noniso
|catalog_ui_policy.script_false |iso, noniso
|cert_audit[^l] |global, scoped-es5[^a], scoped-es12, global-es12[^m][^n]
|ecc_agent_script |all
|ecc_agent_script_include |all
|expert_script_client |iso, noniso
|sa_pattern |all
|sa_pattern_prepost_script |global, scoped-es5[^a], scoped-es12[^b], global-es12[^n]
|sc_cat_item_producer |global, scoped-es5[^a], scoped-es12[^b], global-es12[^n]
|sp_widget.client_script |all
|sp_widget.link |all[^h]
|sp_widget.script |global, scoped-es5[^a], scoped-es12[^b], global-es12[^n]
|sysauto_script |global, scoped-es5[^a], scoped-es12[^b], global-es12[^m][^n]
|sysauto_script.condition[^d] |global, scoped-es5, scoped-es12, global-es12[^n]
|sysevent_in_email_action[^j] |global, scoped-es5, scoped-es12, global-es12[^n]
|sysevent_script_action |global, scoped-es5[^a], scoped-es12[^b], global-es12[^n], global-es12[^n]
|sys_atf_step_config.description_generator[^j] |global, global-es12[^m][^n]
|sys_atf_step_config.step_execution_generator[^j] |global, scoped-es5, scoped-es12, global-es12[^m][^n]
|sys_processor |global, scoped-es5[^a], scoped-es12[^b], global-es12[^m][^n]
|sys_script |global, scoped-es5[^a], scoped-es12[^b], global-es12[^n], global-es12[^n]
|sys_script.condition[^d] |global, scoped-es5, scoped-es12, global-es12[^n], global-es12[^n]
|sys_script_client |iso, noniso
|sys_script_email |global, scoped-es5[^a], scoped-es12[^b], global-es12[^n], global-es12[^n]
|sys_script_fix |global, scoped-es5[^a], scoped-es12[^b], global-es12[^n], global-es12[^m][^n]
|sys_script_include |global, scoped-es5[^a], scoped-es12[^b], global-es12[^n]
|sys_script_validator |all
|sys_security_acl |global, scoped-es5[^a], scoped-es12[^b], global-es12[^n]
|~~sys_security_acl.condition[^d]~~ |~~global, scoped-es5, scoped-es12~~ This not a script field
|sys_transform_entry |global, scoped-es5[^a], scoped-es12[^b], global-es12[^n]
|sys_transform_map |global, scoped-es5[^a], scoped-es12[^b], global-es12[^n]
|sys_transform_script |global, scoped-es5[^a], scoped-es12[^b], global-es12[^n]
|sys_ui_action.script |[^a] global, scoped-es5, iso, noniso, iso_global, noniso_global, iso_scoped-es5, noniso_scoped-es5, iso_scoped-es12[^b], noniso_scoped-es12[^b] [^o]
|sys_ui_action.client_script_v2[^c] |all
|sys_ui_action.condition[^d] |global, scoped-es5, scoped-es12, globa-es12[^n]
|sys_ui_context_menu[^i] |all (this is the action_script)
|sys_ui_page.client_script |all
|sys_ui_page.processing_script |global, scoped-es5[^a] scoped-es12[^a], globa-es12[^n]
|sys_ui_policy.script_true |iso, noniso
|sys_ui_policy.script_false |iso, noniso
|sys_ui_script |all
|sys_ux_client_script[^e] |all
|sys_ux_client_script_include[^f]|all
|sys_ux_data_broker_transform[^g]|global, scoped-es5, scoped-es12, globa-es12[^n]
|sys_ux_data_broker_scriptlet[^g]|all
|sys_variable_value.value[^k] |sin_global, sss_global, sss_scoped-es5, sss_scoped-es12, sin_scoped-es5, sin_scoped-es12
|sys_web_service |global, scoped-es5[^a], scoped-es12[^b], global-es12[^n]
|sys_ws_operation |global, scoped-es5[^a], scoped-es12[^b], global-es12[^n][^a]: The listed altscope constants are for major version 3.
For versions 2.. use
scoped instead of scoped-es5; and use these variants in place
of the sys_ui_action \\_\ constants: iso_globalaction, noniso_globalaction,
iso_scopedaction, noniso_scopedaction`.The 8 alt variants for the sys_ui_action script are necessary to support the different JavaScript requirements depending on combination of settings: Action name, Isolate script, Client.
[^1]: no-sysid and validate-gliderecord-calls rules default to error level for server-side scriptlets and warn level for client-side scriptlets
[^2]: The sn-workaround-iife rule is applied to some specific server tables'
[^3]: Rule 'single-fn' introduced with minor version 3.3.
[^4]: Rules 'no-arrow-fn' and 'no-backticks' added with minor version 3.7.
[^5]: Rule 'no-uiscript-curlref superseded by 'no-backtick-with minor version 3.8.
[^6]: Rule 'controller-fn' added with minor version 3.8.
[^7]: Rule 'controller-fn' added with patch version 3.9.5