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.

Note: Currently, linting is supported for UTAM Java only.

Run linting

Linting is run during the page object generation process. Linting results are reported at the end of the generation process.

All warnings are posted in the console after the generation. For example:

WARNING:
linting warning 3001: page object test/lint/hasRootSelector: same root selector "By.cssSelector: root" is used as a root selector in the page object test/lint/hasSameRootSelector
linting warning 3002: page object test/lint/hasDifferentRootSelector: element "sameAsRootBasic" should have type "test.lint.HasRootSelector" because it uses its root selector

If at least one linting error occurs and the throwError configuration parameter is set to true (see the next section), the page object generator throws an exception with the list of all linting errors. For example:

utam.compiler.lint.UtamLintingError: UTAM linting failures:
linting error 2001: page object test/lint/defaultConfig: duplicate selector "By.cssSelector: :scope > *:first-child" for the elements "container2" and "container1"
linting error 2001: page object test/lint/defaultConfig: duplicate selector "By.cssSelector: .two" for the elements "three" and "two"

Configuring linting rules and violations

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

{
    "lintingOutputFile": "test.sarif.json",
    "lint" : {
        "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"
        }
    }
}

String, default is "utam-lint.sarif". This parameter sets the path for a SARIF log file generated by the UTAM linter. The path is relative to the root of the project.

Boolean, default is false. If set to true, the linting process throws an error after reporting errors to the log.

SARIF report

Static Analysis Results Interchange Format (SARIF) is a common way to store linting results.

When UTAM generates page objects from JSON files, it 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"
              }
            }
          ]
        }
      ]
    }
  ]
}

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.

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"
            }
        }
    ]
}