@sasjs/lint 2.6.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.git-hooks/pre-commit +18 -0
- package/.github/CONTRIBUTING.md +27 -0
- package/.github/workflows/build.yml +13 -7
- package/.github/workflows/publish.yml +12 -1
- package/.pre-commit-config.yaml +16 -0
- package/README.md +49 -1
- package/package.json +12 -12
- package/rules/file/index.d.ts +3 -0
- package/rules/file/index.js +7 -1
- package/rules/file/index.js.map +1 -1
- package/rules/file/noSingleAsteriskComments.d.ts +7 -0
- package/rules/file/noSingleAsteriskComments.js +219 -0
- package/rules/file/noSingleAsteriskComments.js.map +1 -0
- package/rules/file/noUndeclaredMacros.d.ts +7 -0
- package/rules/file/noUndeclaredMacros.js +77 -0
- package/rules/file/noUndeclaredMacros.js.map +1 -0
- package/rules/file/noUnusedMacros.d.ts +7 -0
- package/rules/file/noUnusedMacros.js +67 -0
- package/rules/file/noUnusedMacros.js.map +1 -0
- package/sasjslint-schema.json +53 -2
- package/scripts/generate-macro-names.mjs +101 -0
- package/types/LintConfig.js +9 -0
- package/types/LintConfig.js.map +1 -1
- package/utils/getLintConfig.js +1 -1
- package/utils/getLintConfig.js.map +1 -1
- package/utils/headerMacros.d.ts +51 -0
- package/utils/headerMacros.js +309 -0
- package/utils/headerMacros.js.map +1 -0
- package/utils/index.d.ts +3 -0
- package/utils/index.js +3 -0
- package/utils/index.js.map +1 -1
- package/utils/parseMacroCalls.d.ts +28 -0
- package/utils/parseMacroCalls.js +230 -0
- package/utils/parseMacroCalls.js.map +1 -0
- package/utils/sasMacros.d.ts +16 -0
- package/utils/sasMacros.generated.d.ts +6 -0
- package/utils/sasMacros.generated.js +136 -0
- package/utils/sasMacros.generated.js.map +1 -0
- package/utils/sasMacros.js +22 -0
- package/utils/sasMacros.js.map +1 -0
- package/scripts/audit.mjs +0 -131
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Scan staged changes for secrets before anything else runs.
|
|
4
|
+
# The binary is pinned by the @nogoo9/gitleaks devDependency and lands in
|
|
5
|
+
# node_modules/.bin after `npm i` in the repo root. Commits are blocked until
|
|
6
|
+
# it is installed so no clone silently skips the secrets scan.
|
|
7
|
+
gitleaks_bin=node_modules/.bin/gitleaks
|
|
8
|
+
|
|
9
|
+
if [ ! -x "$gitleaks_bin" ]; then
|
|
10
|
+
echo "gitleaks not found - run 'npm i' in the repo root to install it"
|
|
11
|
+
exit 1
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
if ! "$gitleaks_bin" protect --staged --redact; then
|
|
15
|
+
echo "gitleaks detected a potential secret in your staged changes"
|
|
16
|
+
echo "Remove the secret, or add a .gitleaksignore entry if it is a false positive"
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
package/.github/CONTRIBUTING.md
CHANGED
|
@@ -52,3 +52,30 @@ If you are using VS Code for development, you can automatically fix your code to
|
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
If you are using another editor, or are unable to install the extension, you can run `npm run lint:fix` to fix the formatting after you've made your changes.
|
|
55
|
+
|
|
56
|
+
## Git hooks
|
|
57
|
+
|
|
58
|
+
The repo ships its own git hooks in [`.git-hooks/`](../.git-hooks):
|
|
59
|
+
|
|
60
|
+
- `pre-commit` - scans staged changes for secrets with gitleaks, and requires the `@nogoo9/gitleaks` devDependency to be installed
|
|
61
|
+
- `commit-msg` - verifies the commit message follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) standard
|
|
62
|
+
|
|
63
|
+
The repo `.npmrc` sets `ignore-scripts=true`, so the `prepare` script that would normally set `core.hooksPath` during `npm i` never runs. After cloning (or if your commits are not being checked), activate the hooks with a one-time command, run from the repo root:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
git config core.hooksPath ./.git-hooks
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The pre-commit hook runs the gitleaks binary provided by the `@nogoo9/gitleaks` devDependency, so make sure `npm i` has run in the repo root before your first commit.
|
|
70
|
+
|
|
71
|
+
If you use the [pre-commit](https://pre-commit.com) framework, the repo also ships a [`.pre-commit-config.yaml`](../.pre-commit-config.yaml) that pins the upstream gitleaks hook to an exact release tag. Activate it with `pre-commit install`.
|
|
72
|
+
|
|
73
|
+
## CI is the enforcement point
|
|
74
|
+
|
|
75
|
+
The [build workflow](./workflows/build.yml) scans the full history for secrets with gitleaks, runs `npm audit`, checks code style and runs the tests. The local hooks are convenience - CI is what a red finding must fail.
|
|
76
|
+
|
|
77
|
+
## The release toolchain lives in the workflow
|
|
78
|
+
|
|
79
|
+
semantic-release is not a project dependency. Every `@semantic-release` plugin declares a `semantic-release` peer, so adding one to `devDependencies` makes npm auto-install semantic-release along with the `npm` package it bundles - and that bundled npm carries advisories this repo cannot fix.
|
|
80
|
+
|
|
81
|
+
Instead, [publish.yml](./workflows/publish.yml) installs semantic-release, `@semantic-release/exec` and `@semantic-release/git` on the fly, pinned to exact versions. When you add or remove an entry in `release.plugins` (package.json), update that command to match.
|
|
@@ -24,18 +24,24 @@ jobs:
|
|
|
24
24
|
cache: npm
|
|
25
25
|
- name: Install Dependencies
|
|
26
26
|
run: npm ci
|
|
27
|
+
- name: Scan for secrets (gitleaks)
|
|
28
|
+
# The local pre-commit hook is suppressed entirely when .npmrc sets
|
|
29
|
+
# ignore-scripts=true (the prepare script that would activate the
|
|
30
|
+
# hooks never runs), so CI is the enforcement point: a leaked
|
|
31
|
+
# credential must fail the build, not just a commit.
|
|
32
|
+
# Uses the same @nogoo9/gitleaks binary the pre-commit hook pins - the
|
|
33
|
+
# gitleaks-action requires a license key for organization repos.
|
|
34
|
+
run: node_modules/.bin/gitleaks detect --source . --redact --no-banner -v
|
|
27
35
|
- name: Security Audit
|
|
28
|
-
#
|
|
29
|
-
# @semantic-release/npm
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
run: npm
|
|
36
|
+
# The release toolchain is not a project dependency (see publish.yml),
|
|
37
|
+
# so the `npm` package bundled by @semantic-release/npm never reaches
|
|
38
|
+
# this tree - which is what made the old exemption-based audit script
|
|
39
|
+
# necessary. A plain audit is clean.
|
|
40
|
+
run: npm audit
|
|
33
41
|
- name: Check Code Style
|
|
34
42
|
run: npm run lint
|
|
35
43
|
- name: Run Unit Tests
|
|
36
44
|
run: npm test
|
|
37
|
-
- name: Install rimraf
|
|
38
|
-
run: npm i -g rimraf
|
|
39
45
|
- name: Build Package
|
|
40
46
|
run: npm run package:lib
|
|
41
47
|
env:
|
|
@@ -64,7 +64,18 @@ jobs:
|
|
|
64
64
|
run: npm run publishInit
|
|
65
65
|
|
|
66
66
|
- name: Semantic Release
|
|
67
|
-
|
|
67
|
+
# The release toolchain is deliberately not a project dependency: every
|
|
68
|
+
# @semantic-release plugin declares a `semantic-release` peer, which npm
|
|
69
|
+
# auto-installs together with the `npm` package semantic-release bundles
|
|
70
|
+
# - and that bundled npm carries advisories nothing here can fix.
|
|
71
|
+
# Installing it here, pinned, keeps the dependency tree clean. Keep
|
|
72
|
+
# these versions in step with `release.plugins` in package.json.
|
|
73
|
+
run: |
|
|
74
|
+
npx --yes \
|
|
75
|
+
--package semantic-release@25.0.9 \
|
|
76
|
+
--package @semantic-release/exec@7.1.0 \
|
|
77
|
+
--package @semantic-release/git@11.0.1 \
|
|
78
|
+
semantic-release
|
|
68
79
|
env:
|
|
69
80
|
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} # <-- app token, not secrets.GITHUB_TOKEN
|
|
70
81
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Hooks for the pre-commit framework - https://pre-commit.com
|
|
2
|
+
#
|
|
3
|
+
# The repo also ships native hooks in .git-hooks/, covering the secret scan, a
|
|
4
|
+
# 2MB commit size limit and Conventional Commits. But .npmrc sets
|
|
5
|
+
# ignore-scripts=true, so the `prepare` script that would activate them never
|
|
6
|
+
# runs on `npm i` - a fresh clone commits unchecked until the one-time command
|
|
7
|
+
# in .github/CONTRIBUTING.md is run by hand.
|
|
8
|
+
#
|
|
9
|
+
# This file is the standard route instead: install the framework once with
|
|
10
|
+
# `pre-commit install`, and the secret scan runs on every commit. The hook is
|
|
11
|
+
# pinned to an exact gitleaks release tag.
|
|
12
|
+
repos:
|
|
13
|
+
- repo: https://github.com/gitleaks/gitleaks
|
|
14
|
+
rev: v8.30.1
|
|
15
|
+
hooks:
|
|
16
|
+
- id: gitleaks
|
package/README.md
CHANGED
|
@@ -35,11 +35,14 @@ Configuration is via a `.sasjslint` file with the following structure (these are
|
|
|
35
35
|
"maxLineLength": 80,
|
|
36
36
|
"noEncodedPasswords": true,
|
|
37
37
|
"noNestedMacros": true,
|
|
38
|
+
"noSingleAsteriskComments": false,
|
|
38
39
|
"noGremlins": true,
|
|
39
40
|
"noSpacesInFileNames": true,
|
|
40
41
|
"noTabs": true,
|
|
41
42
|
"noTrailingSpaces": true,
|
|
42
|
-
"
|
|
43
|
+
"noUndeclaredMacros": true,
|
|
44
|
+
"noUnusedMacros": true,
|
|
45
|
+
"defaultHeader": "/**{lineEnding} @file{lineEnding} @brief <Your brief here>{lineEnding}{lineEnding} <h4> SAS Macros </h4>{lineEnding}{lineEnding}**/"
|
|
43
46
|
}
|
|
44
47
|
```
|
|
45
48
|
|
|
@@ -78,10 +81,14 @@ The default header is as follows:
|
|
|
78
81
|
/**
|
|
79
82
|
@file
|
|
80
83
|
@brief <Your brief here>
|
|
84
|
+
|
|
81
85
|
<h4> SAS Macros </h4>
|
|
86
|
+
|
|
82
87
|
**/
|
|
83
88
|
```
|
|
84
89
|
|
|
90
|
+
A blank line separates the `<h4> SAS Macros </h4>` section from the header content before it and from whatever follows the list. The same blank-line convention applies to the `<h4> Other Macros </h4>` section.
|
|
91
|
+
|
|
85
92
|
If creating a new value, use `{lineEnding}` instead of `\n`, eg as follows:
|
|
86
93
|
|
|
87
94
|
```json
|
|
@@ -237,6 +244,23 @@ Where macros are defined inside other macros, they are recompiled every time the
|
|
|
237
244
|
- Default: true
|
|
238
245
|
- Severity: WARNING
|
|
239
246
|
|
|
247
|
+
### noSingleAsteriskComments
|
|
248
|
+
|
|
249
|
+
SAS supports a comment statement that begins with a single asterisk and runs to the next semicolon, for example `* some text;`. This style is easy to mistype - a missing terminating semicolon turns the rest of the program into a comment - and it cannot be nested, so the SASjs framework recommends block comments (`/* ... */`) instead.
|
|
250
|
+
|
|
251
|
+
This rule reports a warning for each comment statement that begins with a single asterisk. It ignores asterisks inside quoted strings, `%str()` / `%nrstr()` arguments, macro comments (`%* ... ;`), data sections (`datalines` / `cards`), `proc lua` / `proc groovy` submit blocks and arithmetic expressions (`a * b`, `b ** a`, `select * from`), so it only fires on genuine comment statements.
|
|
252
|
+
|
|
253
|
+
- Default: false
|
|
254
|
+
- Severity: WARNING
|
|
255
|
+
|
|
256
|
+
Example:
|
|
257
|
+
|
|
258
|
+
```json
|
|
259
|
+
{
|
|
260
|
+
"noSingleAsteriskComments": true
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
240
264
|
### noSpacesInFileNames
|
|
241
265
|
|
|
242
266
|
The 'beef' we have with spaces in filenames is twofold:
|
|
@@ -271,6 +295,27 @@ This will highlight lines with trailing spaces. Trailing spaces serve no useful
|
|
|
271
295
|
- Default: true
|
|
272
296
|
- severity: WARNING
|
|
273
297
|
|
|
298
|
+
### noUndeclaredMacros
|
|
299
|
+
|
|
300
|
+
SASjs programs declare the macros they use in the `<h4> SAS Macros </h4>` section of the header, so that the compiler can assemble the job before it runs. This rule reports a warning for each macro that a file invokes without declaring it - either by defining it in the file, or by listing it under `<h4> SAS Macros </h4>` or `<h4> Other Macros </h4>`.
|
|
301
|
+
|
|
302
|
+
The macros that ship with SAS (`%scan`, `%index`, `%sysfunc` and the other macro functions, along with the macro language keywords such as `%if`, `%then` and `%do`) are always considered declared, so they are never reported. That list is generated from the `macroStatements` and `macroFunctions` groups of `@sasjs/sas-language` (`npm run generate:macros`), so it tracks the SAS language data rather than being maintained here. Macros made available through the `SASAUTOS` system option are not visible to the linter and will be reported.
|
|
303
|
+
|
|
304
|
+
The warning is resolved by adding the macro name to the header. Running `sasjs lint fix` (or saving in the SASjs VS Code extension with `formatOnSave`) adds the missing macros automatically, as a unique list sorted alphabetically.
|
|
305
|
+
|
|
306
|
+
- Default: true
|
|
307
|
+
- Severity: WARNING
|
|
308
|
+
|
|
309
|
+
### noUnusedMacros
|
|
310
|
+
|
|
311
|
+
The counterpart of `noUndeclaredMacros`. This rule reports a warning for each macro listed under `<h4> SAS Macros </h4>` that the file itself never invokes, which usually means the header is out of date.
|
|
312
|
+
|
|
313
|
+
A macro can be referenced indirectly - for example by another macro in the dependency chain, or through a dynamic `%¯o` call - without appearing as a direct invocation in the file, so the warning is advisory.
|
|
314
|
+
|
|
315
|
+
Running `sasjs lint fix` (or saving in the SASjs VS Code extension with `formatOnSave`) removes the unused entries from the header. Together with `noUndeclaredMacros`, this leaves the `<h4> SAS Macros </h4>` section listing exactly the macros the file uses.
|
|
316
|
+
|
|
317
|
+
- Default: true
|
|
318
|
+
- Severity: WARNING
|
|
274
319
|
|
|
275
320
|
## severityLevel
|
|
276
321
|
|
|
@@ -300,6 +345,8 @@ We've already implemented the following rules:
|
|
|
300
345
|
|
|
301
346
|
- Add the macro name to the %mend statement
|
|
302
347
|
- Add a doxygen header template if none exists
|
|
348
|
+
- Add undeclared macros to the `<h4> SAS Macros </h4>` section
|
|
349
|
+
- Remove unused macros from the `<h4> SAS Macros </h4>` section
|
|
303
350
|
- Remove trailing spaces
|
|
304
351
|
|
|
305
352
|
We're looking to implement the following rules:
|
|
@@ -307,6 +354,7 @@ We're looking to implement the following rules:
|
|
|
307
354
|
- Change tabs to spaces
|
|
308
355
|
- zap gremlins
|
|
309
356
|
- fix line endings
|
|
357
|
+
- convert single asterisk comments to block comments
|
|
310
358
|
|
|
311
359
|
We are also investigating some harder stuff, such as automatic indentation and code layout
|
|
312
360
|
|
package/package.json
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
"description": "Linting and formatting for SAS code",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"test": "jest --coverage",
|
|
6
|
-
"build": "
|
|
6
|
+
"build": "rimraf build && tsc",
|
|
7
|
+
"generate:macros": "node ./scripts/generate-macro-names.mjs",
|
|
7
8
|
"preinstall": "node checkNodeVersion",
|
|
8
9
|
"prebuild": "node checkNodeVersion",
|
|
9
10
|
"publishInit": "cp -r ./build/* . && rm -rf ./build && rm -rf ./src && rm tsconfig.json",
|
|
10
11
|
"package:lib": "npm run build && cp ./package.json ./checkNodeVersion.js build && cp README.md build && cd build && npm version \"5.0.0\" && npm pack",
|
|
11
12
|
"lint:fix": "npx prettier --write \"{src,test}/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
|
|
12
13
|
"lint": "npx prettier --check \"{src,test}/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
|
|
13
|
-
"prepare": "git rev-parse --git-dir && git config core.hooksPath ./.git-hooks || true"
|
|
14
|
-
"audit": "node scripts/audit.mjs"
|
|
14
|
+
"prepare": "git rev-parse --git-dir && git config core.hooksPath ./.git-hooks || true"
|
|
15
15
|
},
|
|
16
16
|
"publishConfig": {
|
|
17
17
|
"access": "public"
|
|
@@ -59,18 +59,18 @@
|
|
|
59
59
|
},
|
|
60
60
|
"homepage": "https://github.com/sasjs/lint#readme",
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@
|
|
63
|
-
"@
|
|
62
|
+
"@nogoo9/gitleaks": "8.30.1-post.2",
|
|
63
|
+
"@sasjs/sas-language": "0.3.0",
|
|
64
64
|
"@types/jest": "30.0.0",
|
|
65
|
-
"@types/node": "26.
|
|
66
|
-
"jest": "30.5.
|
|
67
|
-
"
|
|
68
|
-
"ts-jest": "29.4.
|
|
69
|
-
"typescript": "
|
|
65
|
+
"@types/node": "26.6.3",
|
|
66
|
+
"jest": "30.5.2",
|
|
67
|
+
"rimraf": "6.1.3",
|
|
68
|
+
"ts-jest": "29.4.14",
|
|
69
|
+
"typescript": "6.0.3"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
72
|
"@sasjs/utils": "3.6.2",
|
|
73
|
-
"ignore": "7.0.
|
|
73
|
+
"ignore": "7.0.11"
|
|
74
74
|
},
|
|
75
|
-
"version": "
|
|
75
|
+
"version": "3.0.0"
|
|
76
76
|
}
|
package/rules/file/index.d.ts
CHANGED
|
@@ -3,5 +3,8 @@ export { hasMacroNameInMend } from './hasMacroNameInMend';
|
|
|
3
3
|
export { hasMacroParentheses } from './hasMacroParentheses';
|
|
4
4
|
export { lineEndings } from './lineEndings';
|
|
5
5
|
export { noNestedMacros } from './noNestedMacros';
|
|
6
|
+
export { noUndeclaredMacros } from './noUndeclaredMacros';
|
|
7
|
+
export { noUnusedMacros } from './noUnusedMacros';
|
|
6
8
|
export { strictMacroDefinition } from './strictMacroDefinition';
|
|
7
9
|
export { hasRequiredMacroOptions } from './hasRequiredMacroOptions';
|
|
10
|
+
export { noSingleAsteriskComments } from './noSingleAsteriskComments';
|
package/rules/file/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.hasRequiredMacroOptions = exports.strictMacroDefinition = exports.noNestedMacros = exports.lineEndings = exports.hasMacroParentheses = exports.hasMacroNameInMend = exports.hasDoxygenHeader = void 0;
|
|
3
|
+
exports.noSingleAsteriskComments = exports.hasRequiredMacroOptions = exports.strictMacroDefinition = exports.noUnusedMacros = exports.noUndeclaredMacros = exports.noNestedMacros = exports.lineEndings = exports.hasMacroParentheses = exports.hasMacroNameInMend = exports.hasDoxygenHeader = void 0;
|
|
4
4
|
var hasDoxygenHeader_1 = require("./hasDoxygenHeader");
|
|
5
5
|
Object.defineProperty(exports, "hasDoxygenHeader", { enumerable: true, get: function () { return hasDoxygenHeader_1.hasDoxygenHeader; } });
|
|
6
6
|
var hasMacroNameInMend_1 = require("./hasMacroNameInMend");
|
|
@@ -11,8 +11,14 @@ var lineEndings_1 = require("./lineEndings");
|
|
|
11
11
|
Object.defineProperty(exports, "lineEndings", { enumerable: true, get: function () { return lineEndings_1.lineEndings; } });
|
|
12
12
|
var noNestedMacros_1 = require("./noNestedMacros");
|
|
13
13
|
Object.defineProperty(exports, "noNestedMacros", { enumerable: true, get: function () { return noNestedMacros_1.noNestedMacros; } });
|
|
14
|
+
var noUndeclaredMacros_1 = require("./noUndeclaredMacros");
|
|
15
|
+
Object.defineProperty(exports, "noUndeclaredMacros", { enumerable: true, get: function () { return noUndeclaredMacros_1.noUndeclaredMacros; } });
|
|
16
|
+
var noUnusedMacros_1 = require("./noUnusedMacros");
|
|
17
|
+
Object.defineProperty(exports, "noUnusedMacros", { enumerable: true, get: function () { return noUnusedMacros_1.noUnusedMacros; } });
|
|
14
18
|
var strictMacroDefinition_1 = require("./strictMacroDefinition");
|
|
15
19
|
Object.defineProperty(exports, "strictMacroDefinition", { enumerable: true, get: function () { return strictMacroDefinition_1.strictMacroDefinition; } });
|
|
16
20
|
var hasRequiredMacroOptions_1 = require("./hasRequiredMacroOptions");
|
|
17
21
|
Object.defineProperty(exports, "hasRequiredMacroOptions", { enumerable: true, get: function () { return hasRequiredMacroOptions_1.hasRequiredMacroOptions; } });
|
|
22
|
+
var noSingleAsteriskComments_1 = require("./noSingleAsteriskComments");
|
|
23
|
+
Object.defineProperty(exports, "noSingleAsteriskComments", { enumerable: true, get: function () { return noSingleAsteriskComments_1.noSingleAsteriskComments; } });
|
|
18
24
|
//# sourceMappingURL=index.js.map
|
package/rules/file/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/rules/file/index.ts"],"names":[],"mappings":";;;AAAA,uDAAqD;AAA5C,oHAAA,gBAAgB,OAAA;AACzB,2DAAyD;AAAhD,wHAAA,kBAAkB,OAAA;AAC3B,6DAA2D;AAAlD,0HAAA,mBAAmB,OAAA;AAC5B,6CAA2C;AAAlC,0GAAA,WAAW,OAAA;AACpB,mDAAiD;AAAxC,gHAAA,cAAc,OAAA;AACvB,iEAA+D;AAAtD,8HAAA,qBAAqB,OAAA;AAC9B,qEAAmE;AAA1D,kIAAA,uBAAuB,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/rules/file/index.ts"],"names":[],"mappings":";;;AAAA,uDAAqD;AAA5C,oHAAA,gBAAgB,OAAA;AACzB,2DAAyD;AAAhD,wHAAA,kBAAkB,OAAA;AAC3B,6DAA2D;AAAlD,0HAAA,mBAAmB,OAAA;AAC5B,6CAA2C;AAAlC,0GAAA,WAAW,OAAA;AACpB,mDAAiD;AAAxC,gHAAA,cAAc,OAAA;AACvB,2DAAyD;AAAhD,wHAAA,kBAAkB,OAAA;AAC3B,mDAAiD;AAAxC,gHAAA,cAAc,OAAA;AACvB,iEAA+D;AAAtD,8HAAA,qBAAqB,OAAA;AAC9B,qEAAmE;AAA1D,kIAAA,uBAAuB,OAAA;AAChC,uEAAqE;AAA5D,oIAAA,wBAAwB,OAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FileLintRule } from '../../types/LintRule';
|
|
2
|
+
/**
|
|
3
|
+
* Lint rule that checks for SAS comment statements that begin with a single
|
|
4
|
+
* asterisk, such as `* some text;`. These are easy to mistype and should be
|
|
5
|
+
* written as block comments instead.
|
|
6
|
+
*/
|
|
7
|
+
export declare const noSingleAsteriskComments: FileLintRule;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noSingleAsteriskComments = void 0;
|
|
4
|
+
const LintRuleType_1 = require("../../types/LintRuleType");
|
|
5
|
+
const Severity_1 = require("../../types/Severity");
|
|
6
|
+
const name = 'noSingleAsteriskComments';
|
|
7
|
+
const description = 'Disallow comment statements that begin with a single asterisk (*).';
|
|
8
|
+
const message = 'Line contains a single asterisk comment';
|
|
9
|
+
/**
|
|
10
|
+
* Matches the statements that open a data section. The `4` variants are
|
|
11
|
+
* terminated by `;;;;`, the others by a semicolon.
|
|
12
|
+
*/
|
|
13
|
+
const dataSectionStart = /^(datalines4?|cards4?|parmcards4?)\s*;/i;
|
|
14
|
+
/**
|
|
15
|
+
* Matches the functions whose argument is data rather than code.
|
|
16
|
+
*/
|
|
17
|
+
const maskingFunctionStart = /^%(nrstr|str)\s*\(/i;
|
|
18
|
+
/**
|
|
19
|
+
* Matches the procedures whose `submit` block holds another language, so SAS
|
|
20
|
+
* statement rules do not apply inside it.
|
|
21
|
+
*/
|
|
22
|
+
const embeddedLanguageProc = /^proc\s+(lua|groovy)\b/i;
|
|
23
|
+
/**
|
|
24
|
+
* Matches the statement that closes an embedded-language block.
|
|
25
|
+
*/
|
|
26
|
+
const embeddedLanguageEnd = /^endsubmit\s*;/i;
|
|
27
|
+
const embeddedLanguageEndSearch = /endsubmit/i;
|
|
28
|
+
const test = (value, config) => {
|
|
29
|
+
const severity = (config === null || config === void 0 ? void 0 : config.severityLevel[name]) || Severity_1.Severity.Warning;
|
|
30
|
+
const diagnostics = [];
|
|
31
|
+
const length = value.length;
|
|
32
|
+
let index = 0;
|
|
33
|
+
let lineNumber = 1;
|
|
34
|
+
let columnNumber = 1;
|
|
35
|
+
let atStatementStart = true;
|
|
36
|
+
let embeddedProc = false;
|
|
37
|
+
/**
|
|
38
|
+
* Advances the cursor to `target`, keeping the line and column counters in
|
|
39
|
+
* step. Every branch that consumes a non-code region moves through here.
|
|
40
|
+
*/
|
|
41
|
+
const advanceTo = (target) => {
|
|
42
|
+
const limit = Math.min(target, length);
|
|
43
|
+
while (index < limit) {
|
|
44
|
+
if (value[index] === '\n') {
|
|
45
|
+
lineNumber++;
|
|
46
|
+
columnNumber = 1;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
columnNumber++;
|
|
50
|
+
}
|
|
51
|
+
index++;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
while (index < length) {
|
|
55
|
+
const char = value[index];
|
|
56
|
+
if (char === '\n') {
|
|
57
|
+
lineNumber++;
|
|
58
|
+
columnNumber = 1;
|
|
59
|
+
index++;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (char === '\r' || char === ' ' || char === '\t') {
|
|
63
|
+
columnNumber++;
|
|
64
|
+
index++;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (char === ';') {
|
|
68
|
+
atStatementStart = true;
|
|
69
|
+
columnNumber++;
|
|
70
|
+
index++;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
// Block comment - /* ... */
|
|
74
|
+
if (char === '/' && value[index + 1] === '*') {
|
|
75
|
+
let end = index + 2;
|
|
76
|
+
while (end < length && !(value[end] === '*' && value[end + 1] === '/')) {
|
|
77
|
+
end++;
|
|
78
|
+
}
|
|
79
|
+
advanceTo(Math.min(end + 2, length));
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
// Macro comment - %* ... ;
|
|
83
|
+
if (char === '%' && value[index + 1] === '*') {
|
|
84
|
+
let end = index + 2;
|
|
85
|
+
while (end < length && value[end] !== ';')
|
|
86
|
+
end++;
|
|
87
|
+
advanceTo(Math.min(end + 1, length));
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
// Masking functions - the argument is data, not code. `%(` and `%)` are
|
|
91
|
+
// the escapes for literal parentheses inside the argument, so they do not
|
|
92
|
+
// affect the nesting depth.
|
|
93
|
+
const maskingFunction = maskingFunctionStart.exec(value.slice(index));
|
|
94
|
+
if (maskingFunction) {
|
|
95
|
+
let depth = 0;
|
|
96
|
+
let end = index + maskingFunction[0].length - 1;
|
|
97
|
+
while (end < length) {
|
|
98
|
+
if (value[end] === '%' &&
|
|
99
|
+
(value[end + 1] === '(' || value[end + 1] === ')')) {
|
|
100
|
+
end += 2;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (value[end] === '(')
|
|
104
|
+
depth++;
|
|
105
|
+
else if (value[end] === ')') {
|
|
106
|
+
depth--;
|
|
107
|
+
if (depth === 0) {
|
|
108
|
+
end++;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
end++;
|
|
113
|
+
}
|
|
114
|
+
advanceTo(end);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
// Quoted string literal - '' and "" are escaped quote characters.
|
|
118
|
+
if (char === "'" || char === '"') {
|
|
119
|
+
let end = index + 1;
|
|
120
|
+
while (end < length) {
|
|
121
|
+
if (value[end] === char) {
|
|
122
|
+
if (value[end + 1] === char) {
|
|
123
|
+
end += 2;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
end++;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
if (value[end] === '\n')
|
|
130
|
+
break;
|
|
131
|
+
end++;
|
|
132
|
+
}
|
|
133
|
+
advanceTo(end);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (atStatementStart) {
|
|
137
|
+
// Inside a proc lua / proc groovy submit block - the code is another
|
|
138
|
+
// language, so SAS statement rules do not apply to it.
|
|
139
|
+
if (embeddedProc) {
|
|
140
|
+
const endSubmit = embeddedLanguageEnd.exec(value.slice(index));
|
|
141
|
+
if (endSubmit) {
|
|
142
|
+
embeddedProc = false;
|
|
143
|
+
advanceTo(index + endSubmit[0].length);
|
|
144
|
+
atStatementStart = true;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
advanceTo(index + value.slice(index).search(embeddedLanguageEndSearch));
|
|
148
|
+
atStatementStart = true;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
// proc lua / proc groovy opens an embedded-language block. Only enter
|
|
152
|
+
// the block when it is closed, so a stray `proc lua;` cannot swallow
|
|
153
|
+
// the rest of the file.
|
|
154
|
+
const embeddedProcStart = embeddedLanguageProc.exec(value.slice(index));
|
|
155
|
+
if (embeddedProcStart &&
|
|
156
|
+
embeddedLanguageEndSearch.test(value.slice(index))) {
|
|
157
|
+
embeddedProc = true;
|
|
158
|
+
advanceTo(index + embeddedProcStart[0].length);
|
|
159
|
+
atStatementStart = false;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
// A data section - everything up to its terminating line is data, not
|
|
163
|
+
// code. The terminator is a line containing only the `;` or `;;;;`.
|
|
164
|
+
const dataSection = dataSectionStart.exec(value.slice(index));
|
|
165
|
+
if (dataSection) {
|
|
166
|
+
const terminator = /4/.test(dataSection[1]) ? ';;;;' : ';';
|
|
167
|
+
let lineStart = index + dataSection[0].length;
|
|
168
|
+
let end = length;
|
|
169
|
+
while (lineStart <= length) {
|
|
170
|
+
const lineEnd = value.indexOf('\n', lineStart);
|
|
171
|
+
const actualEnd = lineEnd === -1 ? length : lineEnd;
|
|
172
|
+
if (value.slice(lineStart, actualEnd).trim() === terminator) {
|
|
173
|
+
end = actualEnd;
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
if (lineEnd === -1)
|
|
177
|
+
break;
|
|
178
|
+
lineStart = lineEnd + 1;
|
|
179
|
+
}
|
|
180
|
+
advanceTo(end);
|
|
181
|
+
atStatementStart = true;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
// A comment statement - its body is opaque up to the terminating `;`.
|
|
185
|
+
if (char === '*') {
|
|
186
|
+
diagnostics.push({
|
|
187
|
+
message,
|
|
188
|
+
lineNumber,
|
|
189
|
+
startColumnNumber: columnNumber,
|
|
190
|
+
endColumnNumber: columnNumber + 1,
|
|
191
|
+
severity
|
|
192
|
+
});
|
|
193
|
+
let end = index + 1;
|
|
194
|
+
while (end < length && value[end] !== ';')
|
|
195
|
+
end++;
|
|
196
|
+
advanceTo(end + 1);
|
|
197
|
+
atStatementStart = true;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
atStatementStart = false;
|
|
202
|
+
columnNumber++;
|
|
203
|
+
index++;
|
|
204
|
+
}
|
|
205
|
+
return diagnostics;
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Lint rule that checks for SAS comment statements that begin with a single
|
|
209
|
+
* asterisk, such as `* some text;`. These are easy to mistype and should be
|
|
210
|
+
* written as block comments instead.
|
|
211
|
+
*/
|
|
212
|
+
exports.noSingleAsteriskComments = {
|
|
213
|
+
type: LintRuleType_1.LintRuleType.File,
|
|
214
|
+
name,
|
|
215
|
+
description,
|
|
216
|
+
message,
|
|
217
|
+
test
|
|
218
|
+
};
|
|
219
|
+
//# sourceMappingURL=noSingleAsteriskComments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"noSingleAsteriskComments.js","sourceRoot":"","sources":["../../../src/rules/file/noSingleAsteriskComments.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FileLintRule } from '../../types/LintRule';
|
|
2
|
+
/**
|
|
3
|
+
* Lint rule that reports each macro a file uses without declaring it in the
|
|
4
|
+
* header. The formatter resolves the warning by adding the macro names to the
|
|
5
|
+
* `<h4> SAS Macros </h4>` section.
|
|
6
|
+
*/
|
|
7
|
+
export declare const noUndeclaredMacros: FileLintRule;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noUndeclaredMacros = void 0;
|
|
4
|
+
const types_1 = require("../../types");
|
|
5
|
+
const LintRuleType_1 = require("../../types/LintRuleType");
|
|
6
|
+
const Severity_1 = require("../../types/Severity");
|
|
7
|
+
const headerMacros_1 = require("../../utils/headerMacros");
|
|
8
|
+
const parseMacroCalls_1 = require("../../utils/parseMacroCalls");
|
|
9
|
+
const parseMacros_1 = require("../../utils/parseMacros");
|
|
10
|
+
const sasMacros_1 = require("../../utils/sasMacros");
|
|
11
|
+
const name = 'noUndeclaredMacros';
|
|
12
|
+
const description = 'Enforce that every macro used in a file is declared.';
|
|
13
|
+
const message = `Macro '{macro}' is not declared - add it to the <h4> SAS Macros </h4> or <h4> Other Macros </h4> section of the header`;
|
|
14
|
+
/**
|
|
15
|
+
* Collects the first invocation of each macro that the file uses but does not
|
|
16
|
+
* declare. A macro is declared when it is defined in the file, or listed in the
|
|
17
|
+
* `<h4> SAS Macros </h4>` or `<h4> Other Macros </h4>` section of its header.
|
|
18
|
+
* Macros that ship with SAS are always considered declared.
|
|
19
|
+
*/
|
|
20
|
+
const getUndeclaredCalls = (value, config) => {
|
|
21
|
+
if (!value)
|
|
22
|
+
return [];
|
|
23
|
+
const declared = new Set();
|
|
24
|
+
(0, parseMacros_1.parseMacros)(value, config).forEach((macro) => {
|
|
25
|
+
if (macro.name)
|
|
26
|
+
declared.add(macro.name.toLowerCase());
|
|
27
|
+
});
|
|
28
|
+
(0, headerMacros_1.getHeaderMacros)(value, config).forEach((macro) => {
|
|
29
|
+
declared.add(macro.name.toLowerCase());
|
|
30
|
+
});
|
|
31
|
+
const undeclared = [];
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
(0, parseMacroCalls_1.parseMacroCalls)(value, config).forEach((call) => {
|
|
34
|
+
const key = call.name.toLowerCase();
|
|
35
|
+
if (seen.has(key))
|
|
36
|
+
return;
|
|
37
|
+
seen.add(key);
|
|
38
|
+
if ((0, sasMacros_1.isSasMacro)(call.name))
|
|
39
|
+
return;
|
|
40
|
+
if (declared.has(key))
|
|
41
|
+
return;
|
|
42
|
+
undeclared.push(call);
|
|
43
|
+
});
|
|
44
|
+
return undeclared;
|
|
45
|
+
};
|
|
46
|
+
const test = (value, config) => {
|
|
47
|
+
const cfg = config || new types_1.LintConfig();
|
|
48
|
+
const severity = cfg.severityLevel[name] || Severity_1.Severity.Warning;
|
|
49
|
+
return getUndeclaredCalls(value, cfg).map((call) => ({
|
|
50
|
+
message: message.replace('{macro}', call.name),
|
|
51
|
+
lineNumber: call.lineNumber,
|
|
52
|
+
startColumnNumber: call.startColumnNumber,
|
|
53
|
+
endColumnNumber: call.endColumnNumber,
|
|
54
|
+
severity
|
|
55
|
+
}));
|
|
56
|
+
};
|
|
57
|
+
const fix = (value, config) => {
|
|
58
|
+
const cfg = config || new types_1.LintConfig();
|
|
59
|
+
const macros = getUndeclaredCalls(value, cfg).map((call) => call.name);
|
|
60
|
+
if (!macros.length)
|
|
61
|
+
return value;
|
|
62
|
+
return (0, headerMacros_1.addMacrosToHeader)(value, cfg, macros);
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Lint rule that reports each macro a file uses without declaring it in the
|
|
66
|
+
* header. The formatter resolves the warning by adding the macro names to the
|
|
67
|
+
* `<h4> SAS Macros </h4>` section.
|
|
68
|
+
*/
|
|
69
|
+
exports.noUndeclaredMacros = {
|
|
70
|
+
type: LintRuleType_1.LintRuleType.File,
|
|
71
|
+
name,
|
|
72
|
+
description,
|
|
73
|
+
message,
|
|
74
|
+
test,
|
|
75
|
+
fix
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=noUndeclaredMacros.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"noUndeclaredMacros.js","sourceRoot":"","sources":["../../../src/rules/file/noUndeclaredMacros.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FileLintRule } from '../../types/LintRule';
|
|
2
|
+
/**
|
|
3
|
+
* Lint rule that reports macros declared in the `<h4> SAS Macros </h4>` section
|
|
4
|
+
* of a file header that the file itself never invokes. The formatter removes
|
|
5
|
+
* them from the header.
|
|
6
|
+
*/
|
|
7
|
+
export declare const noUnusedMacros: FileLintRule;
|