7 Get Started Writing Custom Audit Rules
You use an Oracle JET application that you want to audit as the project for creating and testing custom audit rules. You can configure the application to run Oracle JAF and test the custom rules against the files of the target application. You can zip the application folder containing the implemented custom audit rules for use by other developers to audit their JET applications as a custom rule pack.
Set up the Custom Audit Rules Test Project
Writing custom audit rules is an iterative development process that ideally starts with an existing Oracle JET application project that you can use to test your custom audit rules against.
Before you start writing custom audit rules, choose an Oracle JET application that contains the actual files that you intend your custom rules to audit. This application will become a kind of development environment for writing and testing of the custom rules. You can then implement custom rules as JavaScript files within a folder that you add to the root of the test application. Once you configure the test application to run Oracle JAF and invoke the audit rules in your custom rules folder, you can easily iterate over the target file set of the test application in a test/debug audit cycle.
Tip:
By default, Oracle JAF audits the application files located in the src
folder of the JET application. To avoid auditing the source code of your custom audit rules, create the custom rules folder at the root level of your test application.
The custom rules folder that you add to the test application will have the following contents, including the JavaScript (.js
) files that implement your custom audit rules:
rule-1.js }
rule-2.js } these are your custom rule files
. . . }
rules.json mandatory file describing the rule properties
msgid.json optional file associating rules with message ID's
The rules.json
file is a single rules definition file that you must define within the custom rules folder to describe the properties of your custom audit rules. The rules definition file can include comments and has the following structure.
/*-----------------------------------------------------------*/
/* Test 'rulePack' definition */
/*-----------------------------------------------------------*/
{
"title" : "A descriptive title for the rule pack",
"prefix" : "ABCD", <-- the prefix prepended to message ids
"version" : "1.1.0", <-- the rule pack version
"rules" : {
"rule-1" : {
// Standard rule options
"severity": "major",
// Additional optional user rule options
"customOpts": {
"maxLevel": 3
}
},
"rule-2" : { ... },
. . .
}
}
The prefix property identifies custom rules as belonging to a common rule set. At runtime, Oracle JAF will prepend the prefix you specify to the message IDs of emitted diagnostic messages. The prefix you specify helps users to identify the invoked audit rules.
Before You Begin:
-
Choose an Oracle JET application that you can use to test your custom audit rules against. This application will serve as the project where you will implement custom audit rules.
-
Install Oracle JAF from npm. For details, see Install the Oracle JET Audit Framework.
Define the Runtime Properties of Custom Audit Rules
Use the rules property of the rules.json
file to declare the rules in a rule pack, including the properties of individual custom audit rules.
All custom audit rules in the rule pack must be declared in the rules property
of the rules.json
file. Properties that you can define include standard
system properties when you want to override a default value defined by JAF. You can also
include optional properties when you want to pass property values to the custom rule at
runtime, but these properties must be enclosed in an additional customOpts
property.
Here is a basic example of a user-defined rule definition:
"rules": {
"my-rule": {
// standard system properties
"$required" : "true",
"severity": "info",
"filetype": "html",
// optional properties
"customOpts": {
"maxLevel": 1
}
}
}
This declaration specifies that a custom rule exists that is referred to as
"my-rule" and that it is implemented in the file my-rule.js
,
in the same folder as the rules.json
file. It includes a number of
standard system properties ($required, severity, and filetype).
Additionally it declares the rule-specific property maxLevel. This property is
not inspected by JAF, and will be passed to the rule in a Rule
context
object when it is invoked. The custom rule implementation handles the passed values to
achieve the desired audit result.
Some property names are reserved by JAF and cannot be re-purposed by the custom audit rule. The following JAF system properties are reserved and all properties are optional on the rule declaration. If you do not add these properties to the custom audit rule declaration, JAF will assign a default value. For example, unless you specifically define the severity property, the custom audit rule will be associated with the severity level critical.
JAF system properties | Description |
---|---|
inservice | Rules are assumed to be in service, unless this property is set to false. This setting overrides the enabled property and suppresses the use of the configuration ruleMods property to attempt to enable the rule. Rules not in service do not participate in an audit. The default value is true. |
enabled | Enables or disables the custom audit rule. All custom rules are enabled by default. |
severity | Classifies the severity level of the custom audit rule. By default Oracle JAF defines a set of levels that you can assign: info, minor, major, critical (default), blocker. Use this property to specify the severity of the custom rule so that users can restrict the audit by rule severity level. For example, see Restrict Audit Rule Severity Level. |
status | Associates a development status with the custom audit rule. May be production (default), alpha, beta, or deprecated. |
filetype |
Specify the file types for which the custom audit rule will be invoked. By default the custom rule is not restricted to a file type. May be html and/or css, and/or js and/or json. For example:
or
The filetype property is ignored by custom hook rules declared for startup/closedown phases, since these are not file related. For all other audit rules, you should specify this property. |
group | Specify the group or groups to which the custom audit rule is assigned. Use this property to assign the custom rule to a group of any name so that users can restrict the audit by rule group. For example:
|
jetver | Specifies the Oracle JET release version or versions required to invoke the custom audit rule. It the property is omitted, the custom rule will operate across all JET versions. The format supports semantic versioning, as used in programs like npm. For example: or For more information about this property and semantic versioning, see Audit with Specific JET and ECMA Script Versions.
|
theme | Specify a JET theme if the rule is theme dependent. The value is compared with the configuration property theme (or its default), and the rule is disabled if there is no match. Can be specified as a string or an array of theme strings. For example, or
|
amd | Specifies that an audit rule cannot be used in AMD mode if the rule performs any I/O. It can be omitted in all other cases. The property is ignored if not running in AMD mode. It is recommended that you set amd : false if the rule performs any file I/O to prevent failures for future AMD usage. |
issueTag | Specify a string that is to be passed through to the output Issue object for an issue flagged by this rule. This string is not inspected by JAF, and its value may be encoded as required by the processing routine examining the string in the output JSON (or object if API/AMD mode). |
customOpts |
Declares an object container for user custom rule option properties. Prior to JAF 10.0.0, custom properties were not permitted to be prefixed by the values shown below, since they were reserved for internal JAF use only, and use by custom written rules was prohibited. This restriction was removed in JAF 5.11.0 when customOpts was introduced. Top-level user options were deprecated in 5.11.0, and customOpts became mandatory in JAF 10.0.0. Option properties may not be prefixed with the following if not contained in customOpts.
|
$required | Designates that this rule cannot be disabled by the configuration
property ruleMods and, indirectly, by the ruleNames property. This is
typically used for rules that perform pack set-up or other non-audit related
functions. Rules marked $required are loaded/registered (in the order found
in rules.json ) ahead of all other rules. Specify true to
make running of this rule during pack set-up mandatory. The default value is
false.
|
$... | Other properties starting with $ are for internal JAF use only, and may not be referenced in a configuration file. |
The custom audit rule's properties may be overridden at runtime by users though the oraclejafconfig.json
file configuration property ruleMods, as described in Configure Audit Rule Runtime Properties. Note also that a rule can be designated as one that must not be disabled at runtime by setting the $required
property to true
.
Define the Message ID of Custom Audit Rules
The message ID that Oracle JAF uses to report an issue can be generated by default by JAF or you can optionally define the IDs to better document custom audit rules.
When JAF reports an issue, it includes a unique message ID of the format ppp-nnnn
, where ppp
is the prefix of the rule pack, and nnnn
is the message ID. The custom audit rule can supply the message number in a number of ways.
The message ID can be either hardcoded, or it can be obtained from some user-defined custom mechanism (for example, by using a rule pack extension), and specified in the Issue
constructor.
Alternatively, you may use the optional msgid.json
definition file
to associate a rule name and message ID within a rule pack. The format of a
msgid.json
file is shown below:
{
"rulename1" : "1234",
"rulename2" : "1235"
. . .
}
You can annotate a msgid.json
file with //
and
/* */
comments.
At runtime, if no ID is specified for an Issue
when it is added to the Reporter
instance, JAF will attempt to resolve it by looking for a file named msgid.json
within the same folder as the rule .js
files and the mandatory rules.json
file. In this case, JAF uses the rule name as the message lookup key to obtain the message number
If a msgid.json
file is used, for flexibility, it is also possible to change the default lookup key from the rule name to a unique key that you specify in your audit rule handler by using Issue.setMsgKey()
.
var issue = new ruleContext.Issue(". . .") ;
. . .
issue.setMsgKey("some key value") ;
1234
) directly on the Issue
object your audit rule handler function creates.var issue = newruleContext.Issue("some rule message", "1234");
Your handler function may also set the message ID subsequently on an Issue
object
var issue = newruleContxt.Issue("some rule message");
. . .
issue.setMsgId("1234");
Here is an example of how to obtain the message ID through some custom mechanism. In this example, a custom rule pack extension is used.
var RPExtension = ruleContext.rulePack.getExtension() ; // get the rulepack's extension object
var myMsgIdAssigner = RPExtension.assignMsgId ; // assumes the rule pack has created a routine for assigning message ID's
var issue = new ruleContext.Issue("some rule message", myMsgIdAssigner(ruleContext)) ; // (myMsgIdAssigner could use ruleContext.ruleName)
Refer to Rule Issue Class Methods for a description of the Issue
constructor and available methods.
Refer to Implement Custom Rules Using the Audit Lifecycle for an example of a custom rule pack extension that you might create for use in a startup hook rule.
Implement the Custom Audit Rules
A custom audit rule is a JavaScript file that you implement and that exports certain public functions.
When you implement custom audit rules in your project, you add a .js
file with the same name as the rule you declare in the project's rules.json
file. To illustrate how to implement audit rules, we'll describe three rules of increasing complexity that audit HTML files for excessive levels of HTML heading nesting:
-
custom-check-heading-levels-1.js
-
custom-check-heading-levels-2.js
-
custom-check-heading-levels-3.js
The rules.json
file for these rules declares the CUSTOM
rule pack like this:
{
"title": "Example Custom Audits",
"prefix": "CUSTOM",
"version": "1.0.0",
"rules": {
"custom-check-heading-levels-1" : {},
"custom-check-heading-levels-2" : {},
"custom-check-heading-levels-3": {
"filetype": "html",
"customOpts": {
"maxLevel": 4
}
}
}
}
The implementation of each rule will use the same JavaScript regular expression to match and extract the numerical part of an HTML heading tag passed from the target audit files. At runtime, JAF processes the HTML files in the JET application and a rule listener that we register in the audit rule passes each HTML tag to an event handler function that our audit rules implement. We will vary the rule handler function implementation to illustrate ways it might use the results of the regular expression matching. Additionally, as the rules.json
file sample shows, the third rule declaration differs since it defines a default value for the maxLevel property. The third audit rule illustrates how to make an audit rule configurable by the end-user of the JAF audit.
Version 1 - Report Heading Levels Greater Than H4
To qualify as a valid rule, a custom audit rule must export the following four methods:
/**
* Copyright (c) 2018, 2022, Oracle and/or its affiliates.
* Licensed under The Universal Permissive License (UPL), Version 1.0
* as shown at https://oss.oracle.com/licenses/upl/
*/
/*--------------------------------------------------------------------------------*/
/* JAF Rule: 'CustomHeadingLevelsAuditBasic' */
/* Purpose : */
/*--------------------------------------------------------------------------------*/
const RULENAME = "CustomHeadingLevelsAuditBasic";
const DESCRIPTION = "This rule checks that excessive levels of header nesting have
not been used on HTML pages by raising an error whenever a
heading tag greater than H4 is used";
const SHORT_DESCRIPTION = "'Checks HTML files for any use of tags <h5> and above";
class Rule {
getName() {
return RULENAME;
}
getDescription() {
return DESCRIPTION;
}
getShortDescription() {
return SHORT_DESCRIPTION;
}
register(regContext) {
return ({
tag: this._doHeaderLevelAudit
}
)
}
. . .
}
module.exports = Rule;
The first three methods in our custom audit rule implementation return usage information that you supply about the audit rule. This information will be passed to Oracle JAF whenever the end-user interacts with the ojaf
command line interface to request additional details about the audit rule that emitted a particular diagnostic message.
The fourth method register()
is the required entry point to every custom audit rule. This method is called during JAF startup, and you will use it to declare a node listener for specific types of data found during the file set audit. The method returns a context object that contains the events for the specified node listener type. Rules that you write to audit file data are called node audit rules because the register()
method returns node data on the context object created by JAF from the Abstract Syntax Tree (AST) it generates on the target file.
Note:
The register()
method that you implement in your rule's .js
file can also declare listeners for events triggered by JAF on the different phases of the JAF audit lifecycle. This set of listener types provides you with hooks into the audit engine and any rules that you write for these hooks do not rely on file data. For more information about writing hook rules, see About Hook Rule Invocation.
In this version of the heading level audit rule, the register()
method specifies the tag
listener type to check all HTML tags in the audited file set. To handle events triggered by the processed tags, the rule needs to implement the audit handler function for the ruleContext
object and other arguments passed into our handler function. Our implementation invokes the doHeaderLevelAudit
handler function in response to the listener event. In the case of the registered tag
listener, a ruleContext
object and a tagElementName
string get passed in as arguments to our function.
/**
* Copyright (c) 2018, 2022, Oracle and/or its affiliates.
* Licensed under The Universal Permissive License (UPL), Version 1.0
* as shown at https://oss.oracle.com/licenses/upl/
*/
. . .
class Rule {
. . .
register(regContext) {
return ({
tag: this._doHeaderLevelAudit
}
)
}
_doHeaderLevelAudit(ruleContext, tagElementName) {
. . .
}
}
module.exports = Rule;
In this version of the heading level audit rule, we hardcode the heading level so the rule reports a heading level that exceeds H4 in the rule diagnostic message. Then in JavaScript we define a regular expression that allows us to match and extract the numerical part of an HTML <H*>
tag from the passed in tagElementName
string. If a match is found, we check the number portion extracted by the regular expression to see if it is greater than the hardcoded limit of 4. Finally, our implementation needs to report the issue by creating an instance of the Issue
object with a diagnostic message and an optional message ID for the audit rule. Then a Reporter
instance allows us to call addIssue()
to allow JAF to output the audit results.
Note:
Hardcoding a unique audit rule message ID in your audit rule handler function is one way to document your custom audit rule. The ID you define will appear in the audit output as ppp-nnnn
, where ppp
is the rule pack prefix and nnnn
is the message ID. If your audit rule does not define a message ID and one cannot be found in the optional msgid.json
file, JAF will generate the rule message ID at runtime for you. For more information, see Define the Message ID of Custom Audit Rules.
/**
* Copyright (c) 2018, 2022, Oracle and/or its affiliates.
* Licensed under The Universal Permissive License (UPL), Version 1.0
* as shown at https://oss.oracle.com/licenses/upl/
*/
. . .
class Rule {
. . .
_doHeaderLevelAudit(ruleContext, tagElementName) {
//Define a regular expression that will allow us to match extract the numerical part of an HTML <H*> tag
const matchHeader = new RegExp(/^[h](\d*)$/, 'i');
//Check the tag being processed against the Regular Expression
const matches = tagElementName.match(matchHeader);
//A not-null result means it's some kind of header tag, so now we check the number portion extracted by the
//regular expression to see if it is greater than the hardcoded limit of 4 in this case
if (matches !== null) {
const headerLevel = parseInt(matches[1]);
if (headerLevel > 4) {
//Report the issue
const issue = new ruleContext.Issue("Header level 4 exceeded", "001");
ruleContext.reporter.addIssue(issue, ruleContext, 'minor');
}
}
}
}
module.exports = Rule;
Next let's modify this sample rule to improve our rule's diagnostic message.
Version 2 - Include Heading Tag Information in Report
In this sample, our revised heading level audit rule continues to register the tag
listener type to trigger the doHeaderLevelAudit
audit handler function. However, in this version we enhance the diagnostic message to include the heading text and heading tags. The audit handler function logic tests node data on the children.length
and children.type
properties of the ruleContext.node
object passed to our handler. If the content is a simple header string, we assign the node ruleContext.data
to the variable headerText
, formatted with the heading tags in problemHeader
and passed to the Issue
instance that we create. Finally, the call to addIssue()
to output the audit result on the Reporter
object remains unchanged.
Tip:
Test your audit rules in a development tool that can invoke the Oracle JET ojaf
utility, such as VS Code, to more easily visualize the runtime context object properties and their data.
/**
* Copyright (c) 2018, 2022, Oracle and/or its affiliates.
* Licensed under The Universal Permissive License (UPL), Version 1.0
* as shown at https://oss.oracle.com/licenses/upl/
*/
class Rule {
. . .
_doHeaderLevelAudit(ruleContext, tagElementName) {
//Define a regular expression that will allow us to match extract the numerical part of an HTML <H*> tag
const matchHeader = new RegExp(/^[h](\d*)$/, 'i');
//Check the tag being processed against the Regular Expression
const matches = tagElementName.match(matchHeader);
//A not-null result means it's some kind of header tag, so now we check the number portion extracted by the
//regular expression to see if it is greater than the hardcoded limit of 4 in this case
if (matches !== null) {
const headerLevel = parseInt(matches[1]);
if (headerLevel > 4) {
//In this enhanced version, before we report the issue let's get the actual
//tag information to add to the report
//Only report the actual content for the simple case though otherwise use ellipsis
let headerText = '...';
if (ruleContext.node.children.length === 1 && ruleContext.node.children[0].type === 'text') {
headerText = ruleContext.node.children[0].data;
}
const problemHeader = `<${tagElementName}>${headerText}</${tagElementName}>`;
const issue = new ruleContext.Issue(`Header level 4 exceeded for element: ${problemHeader}`, "002");
ruleContext.reporter.addIssue(issue, ruleContext, 'minor');
}
}
}
}
module.exports = Rule;
Notice also that our custom audit rules pass in a severity level as an argument to addIssue()
. If you do not hardcode the severity level or define the severity system property in the rule declaration in your project's rules.json
file, then JAF will assign the custom audit rule the default severity level critical. In our samples, we hardcode the severity level minor for all three custom audit rules. For details about severity and other system properties that your custom audit rules can define, see Define the Runtime Properties of Custom Audit Rules.
Next let's modify this sample rule to illustrate a configurable audit rule that will allow end-users to configure the audit heading level before they run the audit.
Version 3 - Configure the Audit Rule for a Heading Level
Every rule pack must contain a rules.json
file with the list of audit rules that Oracle JAF loads at audit startup. If the rule is configurable, then the rules.json
file specifies the property on the declaration line like this maxlevel property we declare in this final version of our heading level audit rule that checks against a configurable heading level.
{
"title": "My Custom Audit Rules",
"prefix": "CUSTOM",
"version": "1.0.0",
"rules": {
"custom-check-heading-levels-3": {
"customOpts": {
"maxLevel": 4
}
}
}
}
To use the configurable property maxLevel, our audit rule sample calls getRuleOption()
to query rule pack information on the Register
context object passed in when the rule pack is loaded at startup. We assign the value to configuredLevel
and the audit handler function tests the value using the same logic described for the previous version of the rule. If the node data for the heading tag exceeds the configured level, then we report the issue and output the message for the offending heading tag together with the configuredLevel
value.
/**
* Copyright (c) 2018, 2022, Oracle and/or its affiliates.
* Licensed under The Universal Permissive License (UPL), Version 1.0
* as shown at https://oss.oracle.com/licenses/upl/
*/
. . .
class Rule {
. . .
_doHeaderLevelAudit(ruleContext, tagElementName) {
//Before we start, in this version, find out what the configured max level is from
//the rules.json declaration for our custom rule
const configuredLevel = ruleContext.rulePack.getRuleCustomOptions().maxLevel;
//Define a regular expression that will allow us to match extract the numerical part of an HTML <H*> tag
const matchHeader = new RegExp(/^[h](\d*)$/, 'i');
//Check the tag being processed against the Regular Expression
const matches = tagElementName.match(matchHeader);
//A not-null result means it's some kind of header tag, so now we check the number portion extracted by the
//regular expression to see if it is greater than the hardcoded limit of 4 in this case
if (matches !== null) {
const headerLevel = parseInt(matches[1]);
//This time we check against the configured level passed in with the options
if (headerLevel > configuredLevel) {
//In this enhanced version, before we report the issue let's get the actual tag information to add to the report
//Only report the actual content for the simple case though otherwise use ellipsis
let headerText = '...';
if (ruleContext.node.children.length === 1 && ruleContext.node.children[0].type === 'text') {
headerText = ruleContext.node.children[0].data;
}
const problemHeader = `<${tagElementName}>${headerText}</${tagElementName}>`;
const issue = new ruleContext.Issue(`Header level ${configuredLevel} exceeded for element: ${problemHeader}`, "003");
ruleContext.reporter.addIssue(issue, ruleContext, 'minor');
}
}
}
}
module.exports = Rule;
This concludes our walkthrough of a basic node rule. As an exercise, you may reuse the sample code of the three heading level audit rules to create a custom rule pack to audit the HTML source files of your JET application. The rule pack you create will contain a .js
implementation file for each audit rule and a rules.json
file to declare the rules. In each implementation file, be sure to include the required methods shown in the sample described for rule version 1 that for brevity the samples omit in rule versions 2 and 3. For more information about creating a custom rule pack that you can reference in an Oracle JAF audit, see Reference the Custom Audit Rules in an Audit.
Note:
End users register your custom rule pack by editing the
oraclejafconfig.json
file in their JET application to define the
rulePacks property. They can also define the ruleMods property to override
default values declared within your custom rule pack rule definitions. For details about
how end users enable custom rule packs to audit their JET application, see Audit with Custom Rule Packs, and for details about how end users may override properties of
configurable audit rules in their audit runs, see Configure Audit Rule Runtime Properties.
Reference the Custom Audit Rules in an Audit
Use the rulePacks property of the oraclejafconfig.json
file to register the custom audit rules in your project to be loaded by JAF at audit runtime.
An audit rule is a JavaScript file that exports certain public functions. Rules with a common diagnostic purpose, for example, specific to a group of user-defined Web Components, can be placed in a folder and that folder's location referenced in the configuration file. A group of associated rules is referred to in JAF as a rule pack. A rule pack may also be zipped for distribution to other users. The Oracle JAF configuration file will reference the location of the zip file in this case.
The zip file or folder should have the contents as described in Set up the Custom Audit Rules Test Project.
To declare the rule pack, edit the generated oraclejafconfig.json
file rulePacks property to specify the path to the custom rule pack folder or zip file.
"rulePacks" : [
{
"path" : "path/to/myrulepack.zip",
"enabled" : [true (default) | false]
"status" : ["all" (default), "production", "deprecated", "beta", "alpha"]
},
{
"path" : "path/to/my/rulepack/folder",
"enabled" : [true (default) | false]
"status" : ["all" (default), "production", "deprecated", "beta", "alpha"]
},
...
]
The enabled property is optional and provides the ability to easily disable a complete rule pack. If omitted, the default is enabled.
The specified path can be relative. If relative, it is considered to be relative to the location of the configuration file, or the configuration file's base property, if defined.