Automatic linting of SAS code with SASjs

As software projects expand in size and complexity, maintaining consistent code standards becomes crucial. These code standards enhance the security, readability, extensibility, and maintainability of software projects. Building on a previous post, we will demonstrate how to use the SASjs library in conjunction with GitHub actions to ensure consistent SAS code quality throughout a repository.

Why enforce coding standards?

Security: Consistent coding practices help prevent vulnerabilities and ensure that security measures are uniformly applied across the codebase.

Readability: Improved readability makes it easier for developers to understand and review each other's code, facilitating effective collaboration and onboarding new team members.

Extensibility: Standardized code allows developers to add new features or modify existing ones with minimal disruption, ensuring the codebase can evolve smoothly over time.

Maintainability: Consistent coding practices reduce code complexity, making it easier to debug, update, and refactor, ultimately leading to a more stable and reliable software product.

Checking for SAS Code Standard Violations with a SASjs Lint

To begin using SASjs to lint our code, we need to install it. SASjs is available through the Node Package Manager (NPM). Follow these steps for installation:

Install NodeJS

If you are a Windows user, install NodeJS

If you are a Mac/Linux user, it is better to use the Node Version Manager (NVM), which can be installed with the following command:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
source ~/.bashrc
nvm install --lts

The SASjs CLI can now be installed with npm i -g @sasjs/cli or upgraded with npm i -g @sasjs/cli@latest

Navigate to your project in a CLI and run sasjs lint

Configuring SASjs to use your Code Standards

To configure SASjs to lint with your rules, you must set up a .sasjs file in your project's home directory. The options available are listed in Table 1.

Option Name | Type | Default | Description
noGremlins | Boolean | True | Don’t allow invisible characters
allowedGremlins | List of strings | None | Hex codes representing allowed gremlins
defaultHeader | String | None | A default header to be added to files when sasjs lint fix is run
hasDoxygenHeader | Boolean | True | Identifies where a doxygen header does not begin in line 1
hasMacroNameInMend | Boolean | True | Checks if the macro name is in the %mend statement
hasMacroParentheses | Boolean | True | Checks if there are spaces between the macro name and the opening parentheses
hasRequiredMacroOptions | Boolean | False | Checks if macros have the options listed in requiredMacroOptions
requiredMacroOptions | List of Strings | None | Macro options to enforce with hasRequiredMacroOptions
ignoreList | List of Strings | None | List of files or directories to ignore
indentationMultiple | Integer | 2 | Check to ensure the number of leading spaces is divided evenly by this number
lineEndings | String | “off” | Check the line endings to ensure files are configured correctly. Options (lf, crlf, off)
lowerCaseFileNames | Boolean | True | Checks that file names are always lowercase
maxDataLineLength | Integer | 80 | Maximum line length for Data lines. Overrides maxLineLength
maxHeaderLineLength | Integer | 80 | Maximum line length for the program header. Overrides maxLineLength
maxLineLength | Integer | 80 | Maximum line length.
noEncodedPasswords | Boolean | True | Ensures no rows contain a sas00X or sasenc type password
noNestedMacros | Boolean | True | Checks if macros are defined inside another macro
noSpacesInFileNames | Boolean | True | Checks if any file names have spaces in them
noTabs | Boolean | True | Makes sure no tabs are used
noTrailingSpaces | Boolean | True | Checks for lines with trailing spaces
severityLevel | Dict | | Allows the user to set the severity level to “warn” or “error” for each option

Table 1: List of linting options provided by SASJS

An example .sasjs configuration for the default values is displayed here:

{
"hasDoxygenHeader": true,
"hasMacroNameInMend": true,
"hasMacroParentheses": true,
"ignoreList": ["sasjsbuild/", "sasjsresults/"],
"indentationMultiple": 2,
"lineEndings": "off",
"lowerCaseFileNames": true,
"maxDataLineLength": 80,
"maxHeaderLineLength": 80,
"maxLineLength": 80,
"noEncodedPasswords": true,
"noNestedMacros": true,
"noGremlins": true,
"noSpacesInFileNames": true,
"noTabs": true,
"noTrailingSpaces": true,
"defaultHeader": "/** Default File Header Here **/",
"severityLevel": {
"hasDoxygenHeader": "warn",
"maxLineLength": "warn",
"noTrailingSpaces": "error"
}
}

Using GitHub Actions to Ensure Code Standards in a Protected Branch

Now we are ready to set up checks in GitHub. This will ensure the code that throws a linting error based on our configuration does not get merged into the main branch.

In the .github/workflows directory, create a file sasjs-lint.yaml

Copy the following into your sasjs-lint.yaml file if you are using the default git hub run environments,:

• on:
• pull_request:
• branches:
• - main
• - release*
•
• jobs:
• build:
• runs-on: ubuntu-latest
•
• strategy:
• matrix:
• node-version: [lts/*]
•
• steps:
• - uses: actions/checkout@v2
• - name: Use Node.js ${{ matrix.node-version }}
• uses: actions/setup-node@v2
• with:
• node-version: ${{ matrix.node-version }}
• cache: npm
• - name: Check Code Style
• run: npm run lint

If you are using a custom build that already contains sasjs/cli you can use the following:

4. name: sasjs-lint
5. on:
6. pull_request:
7. branches:
8. - main
9. - release*
10.
11. jobs:
12. sasjs-lint:
13. runs-on: custom-build
14.
15. container:
16. image: custom-image
17. options: --user root
18.
19. steps:
20. - uses: actions/checkout@v2
21. - name: Run the lint (code quality) check
22. run: sasjs lint

For any pull request targeting the main branch or a release branch, if a setting with a severity level marked as "error" is triggered, the workflow will fail and block the process. This will prevent merging any code that does not match the designated standards.

Summary

Using SASjs, we can customize our SAS code standards. We can then leverage GitHub Actions to ensure that any code merged into our protected branches meets these standards. This will allow us to improve the security, readability, extensibility, and maintainability of our code.