Linting

UTAM provides the ability to statically analyze JSON files to prevent common bad practices, such as duplication of page objects or violations of the encapsulation pattern.

We encourage you not to disable any linting rules and to make sure that your page objects comply with recommended best practices.

This documentation is relevant for UTAM Java versions after 2.0.4 and UTAM JavaScript versions after 2.1.2. Prior to these versions UTAM also produced console output from the linter, this is no longer the case.

Run linting

Linting is run during the page object compilation process. All warnings and errors are printed into a Static Analysis Results Interchange Format (SARIF) report generated after compilation. SARIF is a common format to store linting results. UTAM creates utam-lint.sarif in the SARIF JSON format, which looks like this:

{
    "$schema": "https://json.schemastore.org/sarif-2.1.0.json",
    "version": "2.1.0",
    "runs": [
        {
            "tool": {
                "driver": {
                    "name": "UTAM",
                    "semanticVersion": "2.1.0",
                    "informationUri": "https://github.com/salesforce/utam-java",
                    "rules": [
                        {
                            "id": "ULR01",
                            "name": "Unique local selectors",
                            "shortDescription": {
                                "text": "Check for unique selectors inside the same file. By default, this is a warning because a list element can have the same selector."
                            }
                        }
                        // other rules
                    ]
                }
            },
            // all page objects being analyzed
            "artifacts": [
                {
                    "description": {
                        "text": "page object utam/pageObjects/test"
                    },
                    "location": {
                        "uri": "src/test/resources/lint/changeDefaultConfig/test.utam.json",
                        "uriBaseId": "%srcroot%",
                        "index": 0
                    }
                }
            ],
            // violations
            "results": [
                {
                    "ruleId": "ULR02",
                    "message": {
                        "text": "root description is missing",
                        "id": "2002"
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "src/test/resources/lint/changeDefaultConfig/test.utam.json",
                                    "uriBaseId": "%srcroot%",
                                    "index": 0
                                },
                                "region": {
                                    "startLine": 2
                                }
                            }
                        }
                    ],
                    "fixes": [
                        {
                            "description": {
                                "text": "add \"author\" property to the root description"
                            }
                        }
                    ]
                }
            ]
        }
    ]
}

Linting report

The SARIF file is in JSON format. To view (pretty print) SARIF files in a terminal or in your IDE, use available tools, such as sarif-fmt or Sarif VSCode extension.

If at least one linting violation of level error occurs and the throwError configuration parameter is set to true (see the next section), the page object compiler throws an exception.

A file in SARIF format can be integrated with development tools such as GitHub to show linting rule violations: SARIF GitHub

GitHub documentation is here.

Configuring linting rules and violations

All linting rules and violation types (error or warning) are configurable as lint properties in the compiler configuration. If a compiler configuration doesn't have a lint section, default values are applied.

{
    "lint": {
        "lintingOutputFile": "utam-lint.sarif",
        "disable": false,
        "throwError": false,
        "<name of the rule>": {
            "violation": "error",
            "exclude": []
        },
        "<name of the rule>": {
            "violation": "error",
            "exclude": ["my/test/component1", "my/test/component2"]
        },
        "<name of the rule>": {
            "violation": "warning"
        }
    }
}

Linting rules

Note: Rules with error codes that start with 2 are applicable for one page object only. Rules with error codes that start with 3 are relevant for multiple page objects only, such as detecting duplicates from the earlier example.

duplicateSelectors

Example:

{
    "elements": [
        {
            "name": "element1",
            "selector": {
                "css": "css"
            }
        },
        {
            // violation - same selector as element1
            "name": "element2",
            "selector": {
                "css": "css"
            },
            "elements": [
                {
                    // not a violation - element has a different parent
                    "name": "element3",
                    "selector": {
                        "css": "css"
                    }
                },
                {
                    // not a violation - element is a list
                    "name": "element4",
                    "selector": {
                        "css": "css",
                        "returnAll": true
                    }
                }
            ]
        }
    ]
}

requiredRootDescription

Example:

{
    // violation - description is empty
    "elements": []
}

requiredAuthor

Example:

{
    // violation - description has no author
    "description": "description is a string"
}
{
    // not a violation - author is provided
    "description": {
        "text": ["description is a string"],
        "author": "myTeam"
    }
}

requiredMethodDescription

Example:

{
    "description": "root description here",
    "methods": [
        {
            // violation - description is empty
            "name": "doSomething",
            "compose": []
        }
    ]
}

requiredSingleShadowRoot

Example:

{
    "elements": [
        {
            "name": "myChildComponent",
            "selector": {
                "css": "my-child-component"
            },
            // violation - myChildComponent should be a different page object
            "shadow": {
                "elements": []
            }
        }
    ]
}

duplicateRootSelectors

Example:

{
    // first page object
    "root": true,
    "selector": {
        "css": "my-root"
    }
}
{
    // second page object
    "root": true,
    "selector": {
        // violation - same root selector
        "css": "my-root"
    }
}

elementCantHaveRootSelector

Example:

{
    // first page object my/test/Component
    "root": true,
    "selector": {
        "css": "my-root"
    }
}
{
    // second page object
    "elements": [
        {
            // violation - element should have the proper type
            "name": "element1",
            "selector": {
                "css": "my-root"
            }
        },
        {
            // not a violation - element has the proper type
            "name": "element2",
            "type": "my/test/Component",
            "selector": {
                "css": "my-root"
            }
        }
    ]
}

duplicateCustomSelectors

Example:

{
    // first page object
    "elements": [
        {
            "name": "customElement",
            "type": "my/test/Component",
            "selector": {
                "css": "my-test-component"
            }
        },
        {
            "name": "basicElement",
            "selector": {
                "css": ".myClass"
            }
        }
    ]
}
{
    // second page object
    "elements": [
        {
            // violation - set type to "my/test/Component"
            "name": "basicElement1",
            "selector": {
                "css": "my-test-component"
            }
        },
        {
            // not a violation - basic elements can have the same selectors
            "name": "basicElement2",
            "selector": {
                "css": ".myClass"
            }
        }
    ]
}

requiredMetadata

Example:

{
    // Compiler configuration lint section
    "lint": {
        "lintingOutputFile": "utam-lint.sarif",
        "disable": false,
        "throwError": false,
        "requiredMetadata": {
            "violation": "warning",
            "exclude": [],
            "additionalConfiguration": {
                "requiredProperties": [
                    {
                        "name": "firstRequiredProperty"
                    },
                    {
                        "name": "secondRequiredProperty",
                        "values": ["allowedValue1", "allowedValue2", "allowedValue3"]
                    }
                ]
            }
        }
    }
}
{
    //  violation: page object has no metadata property
    "elements": [
        {
            "name": "basicElement1",
            "selector": {
                "css": ".my-test-component"
            }
        }
    ]
}
{
    "metadata": {
        // violation: metadata object does not contain "firstRequiredProperty"
        // property with a non-empty value

        // violation: "secondRequiredProperty" value is not one of the acceptable
        // values for the property
        "secondRequiredProperty": "invalidValue"
    },
    "elements": [
        {
            "name": "basicElement1",
            "selector": {
                "css": ".my-test-component"
            }
        }
    ]
}