
- Post History
- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
on 08-17-2022 11:42 AM
Instance Scan
Instance scan is one of the powerful platform capability that has been there since few releases and which aims at maintaining the health of the instance by scanning the platform objects against set of rules to identify the area of improvements and thus controlling the technical debt.
There are articles posted in this community that explains about different scan types and their usage. So creating various different check types are out of scope for this article. However, here's the brief summary about different type of checks-
- Table Check: Though this individual table records are scanned for configuration like validating system property values, ACL existence on table etc.
- Column Check: This type of check scan the entire platform against particular field types (Scripts, XML and HTML) example run a check against Script field to discourage the usage of "getRowCount()" method due to performance impact.
- Script Check: Typically used to inspect the configuration across single/multiple tables via running a script.
- Linter Check: Inspect the platform scripts for issues.
Purpose - Linter Check
This is the most powerful type of instance scan check that provides a way to refine the code by analyzing the syntactical structure of the code and ensuring the best practices and performance standards are met.
At this point it may seems the same could be achieved via "Column Check", however, there is big difference on how the code is scanned i.e. "Column Check" just scans the script field as "String" while "Linter Check" converts the script field into "Abstract Syntax Tree" which itself is a object.
Note*
What is Abstract Syntax Tree?
AST is basically represent the code in syntactical structure or hierarchical tree structure i.e. breaking into several nodes on the tree to separate out code expressions. In other words, Linter Check creates a AST of the script field which then can be parsed against set of rules.
- Simple Expression AST: Let's examine simple expression a * b + c. Then AST for this expression could be constructed as.
- ServiceNow Script AST: Above example was more of visual representation of the expression while in AST expression can also be viewed in terms of simple string broken into nodes and represented with position numbers. Let's examine a very basic line of code in business rule-
(function executeRule(current, previous /*null when async*/) { var grInc = new GlideRecord('incident'); })(current, previous); ********************************************************************************** AST Tree of above expression ********************************************************************************** 0 SCRIPT 0 133 1 EXPR_RESULT 1 132 1 CALL 0 131 1 LP 0 112 1 FUNCTION 0 111 10 NAME 9 11 executeRule 22 NAME 21 7 current 31 NAME 30 8 previous 61 BLOCK 60 51 67 VAR 6 40 71 VAR 4 35 71 NAME 0 5 grInc 79 NEW 8 27 83 NAME 4 11 GlideRecord 95 STRING 16 10 114 NAME 113 7 current 123 NAME 122 8 previous ********************************************************************************** Breakdown of AST Tree ********************************************************************************** Line#> 71 NAME 0 5 grInc Absolute position: 71 Type Name: Name Position relative to parent: 0 Length: 5 Name identifier (if a NAME node): grInc Parent: VAR (line# 71) Root Node: SCRIPT
- Understanding Abstract Syntax Tree structure: AST is about breaking the code into "Nodes" and thus understanding properties of a node is the key to demystify this tree. Let's understand some key properties-
- Type Name: This is the type name of the node i.e. SCRIPT, CALL, NAME, STRING, BLOCK, FUNCTION, VAR are all the nodes type.
- Name Identifier: This is the name of the node only when type name is "NAME". In above example, "executeRule", "current", "previous", "grInc", "GlideRecord" are all name identifiers.
- Absolute Position: Number of characters from start of script to given node.
- Parent: Represents the parent node.
- Root Node: Root node of the Abstract Syntax Tree
Create - Linter Check
Linter Check form (Instance Scan > Checks > Create a new Linter Check) provides a script field that evaluates the Abstract Syntax Tree (available as object) which is constructed automatically when Linter Check runs (against Script Includes, Business Rules, Client Scripts etc. i.e. configuration records with script field).
Follow this Linter Methods which has functions available to use while parsing AST. Most of the these methods corresponds to properties defined in the section <<Understanding Abstract Syntax Tree structure>>
Note* Linter checks can be executed in Instance Scan suites as oppose to Script Check which are executed only in Full Instance Scan. This is important when working the target is either a scoped application or an update set so it can run on use case basis.
Examples
- Use g_form API instead of alert messages: This check will inspect and find scripts that contains "alert" function call in scripts. To build this script one needs to understand the Abstract Syntax Tree in detail and that's where the importance of above section stands. Below is the code that will reside in Linter Check script.
(function(engine) {
engine.rootNode.visit(function(node) {
if (node.getNameIdentifier() &&
node.getTypeName() === 'NAME' &&
node.getParent().getTypeName() === 'CALL' &&
(node.getNameIdentifier() === 'alert')) {
engine.finding.incrementWithNode(node);
}
});
})(engine);
Here's the snapshot of the above Linter Check record-
Below is the client script that will be inspected-
Click "Run Point Scan" and system generated Scan Findings records indicating the client script contains alert message.
2. Enclose code in functions: This is one of the best practices use case suggested in ServiceNow guides. Follow this <<Enclose Code in Functions>> for more information. Below is the code that will reside in Linter Check script.
(function(engine) {
engine.rootNode.visit(function(node) {
if (node.getNameIdentifier() &&
node.getTypeName() === 'NAME' &&
(node.getNameIdentifier() === 'onSubmit' || node.getNameIdentifier() === 'onLoad'|| node.getNameIdentifier() === 'onChange'|| node.getNameIdentifier() === 'onCellEdit')) {
engine.rootNode.visit(function(node) {
if (node.getTypeName() === 'VAR' &&
node.getParent().getTypeName() === 'SCRIPT') {
engine.finding.incrementWithNode(node);
}
});
}
});
})(engine);
Here's the snapshot of the above Linter Check record-
Below is the client script that will be inspected-
Click "Run Point Scan" and system generated Scan Findings records indicating the client script contains alert message.
Note* In client script, there were two variables outside function hence Count=2 in Scan Findings data.
Bonus:
How to generate Abstract Syntax Tree?
Initially understanding the AST structure for any script ma seem convoluted. However, here's the trick you may use yo generate the AST for any script.
- Create a Linter Check <<Print AST Structure>> with below script to log the AST structure-
(function(engine) { engine.rootNode.visit(function(node) { if (node.getAbsolutePosition() === 0 && node.getTypeName() === 'SCRIPT') { gs.info("Print AST Structure " + '\n' + node.debugPrint()); } }); })(engine);
- Create a Combo record <<scan_combo.do>> with Sources=Linter Check <<Print AST Structure>> and Targets=Client Script: Alert Incident State
- Click Execute Scan
- Navigate to System Logs to view the AST. This is similar to one shown in above section <<ServiceNow Script AST>>. This is the KEY on how to utilize AST structure while creating Linter Checks. Here once can easily view "Nodes", Parents, Position etc.
- 2,843 Views
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Really appreciate you putting in the time to write this up. Very useful! Has helped me get started writing a few linter checks.
I had an issue with the check in #2 'Enclose Code in Functions'. In my testing, the second conditional is problematic:
if (node.getTypeName() === 'VAR' &&
node.getParent().getTypeName() === 'SCRIPT') {
engine.finding.incrementWithNode(node);
}
Basically, I had to comment out the check for 'VAR' and do a 'gs.nil(node.getParent())' . As is, the node.getParent().getTypeName() was tossing a failure when executing the check.
This got the behavior I wanted - checks on client scripts pass/failing as expected depending on whether wrapped in an IIFE. I did also run the 'Print AST Structure' check and to my eyes that confirmed the needed change. ('Print AST Structure' another gem!)
So, wanted to see if I should be looking at this a different way. Little difficult to get the head around easily traversing the AST nodes - so, likelihood of it being my glitch reasonably high.
Thanks again. Great stuff!

- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Thanks for sharing. The API documentation cries for articles like your's 🙂
The CodeSanity app contains a set of Instance Scan checks aimed at improving source code and application quality. Developers should run them before shipping a new application version.
Up to now, applying my regular expression skills helped me to get what I want in most cases. I am only using the linter engine in edge cases.
What is your experience?
Vote to make it part of the platform OOTB!
Or install it right away:

- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
It was an incredibly short sided approach to return the AST of the node as a formatted string. Almost every other JavaScript parser returns JSON so that you can easily work with the structure. Really wish this was not the case.