fix(lint): make noUnusedMacros an error and clear its 54 findings
Build / Build-and-ng-test (pull_request) Successful in 5m45s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m2s
Build / Build-and-test-development (pull_request) Successful in 31m7s

sas/.sasjslint now promotes noUnusedMacros to "error", so the rule fails
the build rather than printing a warning. The 54 findings it reported are
gone:

- 46 header entries nothing calls are dropped. Each removal was checked
  against the compiled artefacts rather than the linter alone: the
  project was compiled before and after, and the two sasjsbuild trees
  compared. 37 macro definitions disappear from the generated programs -
  every one of them either a duplicate of a definition the file still
  reaches through another declared macro, or a macro no generated program
  calls. No macro that a generated program calls lost its definition.
- 8 entries are kept, in five files that carry
  @sasjslint {"noUnusedMacros": false} with the reason written above it:
  the three serviceInit programs and serviceterm.sas declare mpeinit and
  mpeterm without calling them, because that declaration is how a service
  body reaches those macros at all; loadfile.sas declares mp_binarycopy
  for the fallback that is commented out on SAS 9.4 and later.
- Four headers are left with no macros to declare, so their empty
  <h4> SAS Macros </h4> section is removed rather than kept as a stub.

@sasjs/cli is bumped 6.2.0 -> 6.5.0 in sas/package.json. The lint engine
ships inside the CLI and is not a direct dependency, so the pinned CLI is
what fixes the rule set: 6.2.0 resolves @sasjs/lint 4.3.0, 6.5.0 pins
4.9.0 exactly. Both versions were compiled against this tree and the
sasjsbuild output is byte-identical, so the bump changes the lint engine
and nothing else.

`sasjs lint` is also now reachable as `npm run lint:sas`, and a CI step in
build.yaml runs it. Until now nothing ran SAS lint automatically, and a
bare `sasjs` resolves whatever CLI is installed globally - a different
lint version reports a different rule set, which is why the counts in
issue #376 (taken with 6.2.5) do not match the pinned 6.2.0.

43 warnings remain under the rules not yet promoted: noUndeclaredMacros
(25), noBannedStrings (13), macro name length and case (4),
indentationMultiple (1). They are the next rungs of the ratchet.

The pre-commit Prettier gate could not run: it needs client/node_modules,
which this worktree does not install, and the fix it attempted touched
three client files that this change does not modify (reverted). No client
file is in this commit.
This commit is contained in:
dc committed 2026-11-01 00:36:05 +00:00
1 parent ea789c4977
commit c8c0858295
48 files changed
+62 -112

No files matched your search

+10
View File
@@ -84,6 +84,16 @@ jobs:
- name: Lint check
run: npm run lint:check
- name: SAS lint check
# sas/.sasjslint promotes each rule to "error" as its findings are
# cleared, and only errors change the exit code - so this gate fails on
# a regression in a cleared rule and passes while other rules still
# carry warnings.
run: |
cd sas
npm ci --ignore-scripts
npm run lint:sas
- name: Licence checker
run: |
cd client
+2 -1
View File
@@ -31,7 +31,8 @@ Rationale: hard-wrapped prose produces noisy diffs when sentences are edited and
Never consider a change complete until the relevant linters pass on the files you touched — do not rely on the user's pre-commit hooks to catch it:
- **Client (TypeScript/HTML/etc.)**: run `npm run lint:check` from the `client/` directory (prettier). Fix any failures with `npm run lint:fix`.
- **SAS**: after creating or modifying any `.sas` files, run `sasjs lint` from the `sas/` directory and ensure the files you touched have no lint warnings (the repo currently has pre-existing warnings in other files, which can be ignored).
- **SAS**: after creating or modifying any `.sas` files, run `npm run lint:sas` from the `sas/` directory. That script runs the `@sasjs/cli` version pinned in `sas/package.json`, so it matches CI; a global or `npx` `sasjs` may resolve a different `@sasjs/lint` and report a different rule set.
Rules are promoted to `error` in `sas/.sasjslint` one at a time as their findings reach zero, and only errors change the exit code - so the whole project must stay clean of the promoted rules, not just the files you touched. Warnings under rules not yet promoted are the remaining backlog; do not add to them in files you touch, and do not clear other files' findings in the same change unless that rule is the point of the change.
`sas/.sasjslint` carries `ignoredMacros: ["webout", "mpeinit", "mpeterm"]`. `webout` is the linter's own default - `sasjs compile` injects `%webout` into every service - and an explicit list replaces that default, so it has to be restated there. `mpeinit` and `mpeterm` are listed because every service is compiled with a serviceInit program (`sasjs/utils/serviceinitsas9|viya|server.sas`) that declares both macros in its own header, so a service body may call them without declaring them. Add a macro to that list only when it reaches a file the same way - injected by the compiler, through serviceInit, testSetUp or another implicit provider - never to silence a genuinely missing declaration.
`noBannedStrings` flags the literal words `error ` and `warning ` anywhere in a file, comments included. Reword prose; where the text has to stay (a real `%put`, a doxygen `@warning` tag), keep the log output identical with `%put %str(WARN)ING:` rather than a per-file override, and use `@sasjslint {"noBannedStrings": false}` only when the word itself is the content.
+4 -1
View File
@@ -12,5 +12,8 @@
"hasMacroParentheses": true,
"strictMacroDefinition": true,
"ignoredMacros": ["webout", "mpeinit", "mpeterm"],
"ignoreList":["sasjs/scratch"]
"ignoreList":["sasjs/scratch"],
"severityLevel": {
"noUnusedMacros": "error"
}
}
+14 -55
View File
@@ -6,7 +6,7 @@
"": {
"name": "dc-sas",
"dependencies": {
"@sasjs/cli": "6.2.0",
"@sasjs/cli": "6.5.0",
"@sasjs/core": "7.0.3"
}
},
@@ -165,14 +165,15 @@
}
},
"node_modules/@sasjs/cli": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-6.2.0.tgz",
"integrity": "sha512-zovAOCmyPqcm+gooAZgwvgGH8n5cas3sY0lA80+hyFoFtmZ8VjW/bh19LM8OVwheT9dBCRCx4fnHwjy5XGpzvQ==",
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@sasjs/cli/-/cli-6.5.0.tgz",
"integrity": "sha512-1I7B0iKd+16ByIGPToGfODU3S1bxO/3VCGpmu5alql/HlXdxu75djdpuzCovbsSeWm+M4FrsrBKDZTc9hhBrLA==",
"license": "ISC",
"dependencies": {
"@sasjs/adapter": "^5.1.1",
"@sasjs/core": "^6.0.0",
"@sasjs/utils": "^4.1.0",
"@sasjs/core": "^6.1.1",
"@sasjs/lint": "4.9.0",
"@sasjs/utils": "^4.1.2",
"axios": "1.20.0",
"chalk": "4.1.2",
"dotenv": "18.0.8",
@@ -194,9 +195,6 @@
},
"engines": {
"node": ">=22"
},
"peerDependencies": {
"@sasjs/lint": "^4.3.0"
}
},
"node_modules/@sasjs/cli/node_modules/@sasjs/core": {
@@ -206,9 +204,9 @@
"license": "MIT"
},
"node_modules/@sasjs/cli/node_modules/@sasjs/utils": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-4.1.0.tgz",
"integrity": "sha512-aC5pOugg6/2jKQR7ZaYsLtbsFy/iVacsrN3/Kzh6BouJUszuVrr13HMrQUSKcWL+rJzxYof7ymmmCl+vE6YwAw==",
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-4.1.2.tgz",
"integrity": "sha512-5QLBVlCqiiDW/GoxiHUCyT+8GJjJA/S/TX0DS/t4O4HC1Im/QpQzxlS86QkjLAQBBHovYPyh+v4pgdh+IYwaEQ==",
"license": "ISC",
"dependencies": {
"@fast-csv/format": "5.0.7",
@@ -247,56 +245,18 @@
"license": "MIT"
},
"node_modules/@sasjs/lint": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@sasjs/lint/-/lint-4.3.0.tgz",
"integrity": "sha512-65cjy4IjA4PgSlicOruF0DI9N2HO3LJqtgphsZCg8g7N/StE2FWLJLJbD1N4OHxN6htrUfj5SEawJJiHo5T9Yw==",
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@sasjs/lint/-/lint-4.9.0.tgz",
"integrity": "sha512-QEBo19bWUdKIaE62Pk8eb35Q9EjHlifyOmbH+voItX6muCqHTX88SOzLD4MYtFVuagm/cnGplIVcKvUZzO+bSw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@sasjs/utils": "4.0.0",
"@sasjs/utils": "^4.0.0",
"ignore": "7.0.11"
},
"engines": {
"node": ">=22"
}
},
"node_modules/@sasjs/lint/node_modules/@sasjs/utils": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-4.0.0.tgz",
"integrity": "sha512-lARoCnRM4gSVf8ShoP5elHi7W28K/14MK037dQ0ABgMlP3+r6OuhAZYwK/a70tED47tKxyIoCAKLdP6jrG0ewQ==",
"license": "ISC",
"peer": true,
"dependencies": {
"@fast-csv/format": "5.0.7",
"@types/fs-extra": "11.0.4",
"@types/prompts": "2.4.9",
"chalk": "4.1.1",
"consola": "3.4.2",
"fs-extra": "11.4.1",
"jwt-decode": "4.0.0",
"prompts": "2.4.2"
},
"engines": {
"node": ">=22"
}
},
"node_modules/@sasjs/lint/node_modules/chalk": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"license": "MIT",
"peer": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/@sasjs/utils": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-4.0.1.tgz",
@@ -1112,7 +1072,6 @@
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.11.tgz",
"integrity": "sha512-YChdK5txDjwGUvgR7oCJcLGkwi3LDh8Zx8tS7ndYciLEg/oOCL26VUBSUEtgS7EjiybcBqFxh5j0rAdDX7/bbg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 4"
}
+2 -1
View File
@@ -3,6 +3,7 @@
"description": "dc-sas",
"scripts": {
"cpfavicon": "cp sasjs/utils/favicon.ico ../client/dist/favicon.ico",
"lint:sas": "sasjs lint",
"build": "cd ../client && npm run build",
"deploy:server": "rsync -avhe ssh ../client/dist/* --delete allbow@sas.4gl.io:/opt/sas/sas9/config/Lev1/Web/WebServer/htdocs/allbow/server",
"sas9": "rsync -avhe ssh ./dist/* --delete ${npm_config_account}@sas.4gl.io:/opt/sas/sas9/config/Lev1/Web/WebServer/htdocs/${npm_config_account}/dc",
@@ -33,7 +34,7 @@
},
"private": true,
"dependencies": {
"@sasjs/cli": "6.2.0",
"@sasjs/cli": "6.5.0",
"@sasjs/core": "7.0.3"
},
"overrides": {
@@ -5,9 +5,6 @@
BREAKING CHANGE - deprecate MPE_APPROVALS and MPE_REVIEW (now MPE_SUBMIT does
both)
<h4> SAS Macros </h4>
@li mm_assigndirectlib.sas
**/
%let dclib=YOURDCLIB;
@@ -5,7 +5,6 @@
<h4> SAS Macros </h4>
@li mf_getuniquename.sas
@li mf_getuser.sas
@li mp_assertdsobs.sas
@li mpe_getgroups.sas
-1
View File
@@ -10,7 +10,6 @@
<h4> SAS Macros </h4>
@li mf_getattrn.sas
@li mf_existds.sas
@li mf_getuser.sas
@version 9.4
-1
View File
@@ -31,7 +31,6 @@
@li mf_getuser.sas
@li mf_getuniquefileref.sas
@li mf_getuniquename.sas
@li mf_nobs.sas
@li mp_abort.sas
@li mp_filtergenerate.sas
@li mpe_getgroups.sas
-2
View File
@@ -20,8 +20,6 @@
labels still come from the target.
<h4> SAS Macros </h4>
@li mf_getuniquename.sas
@li mf_nobs.sas
@li mp_assert.sas
@li mp_assertdsobs.sas
@li mp_assertscope.sas
@@ -9,7 +9,6 @@
@li mp_assert.sas
@li mp_assertscope.sas
@li mpe_getversions.sas
@li mpe_targetloader.sas
@li mx_execute.sas
**/
-1
View File
@@ -8,7 +8,6 @@
<h4> SAS Macros </h4>
@li dc_getservicecode.sas
@li mf_getapploc.sas
@li mp_abort.sas
@li mp_include.sas
@author 4GL Apps Ltd
-1
View File
@@ -8,7 +8,6 @@
<h4> SAS Macros </h4>
@li dc_getsettings.sas
@li mf_fmtdttm.sas
@li mf_getuser.sas
@li mp_abort.sas
@li mp_init.sas
-1
View File
@@ -5,7 +5,6 @@
enables tracking and performance logging
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mf_getplatform.sas
@version 9.4
-3
View File
@@ -20,10 +20,7 @@
<h4> SAS Macros </h4>
@li mpeinit.sas
@li mf_getengine.sas
@li mf_getuser.sas
@li mf_increment.sas
@li mf_nobs.sas
@li mf_uid.sas
@li mp_abort.sas
@li mp_binarycopy.sas
@li mp_replace.sas
@@ -10,8 +10,6 @@
@li mf_getuser.sas
@li mf_nobs.sas
@li mp_abort.sas
@li mp_binarycopy.sas
@li mp_ds2cards.sas
@li mp_ds2csv.sas
@li mp_streamfile.sas
@li mp_validatecol.sas
@@ -4,7 +4,6 @@
<h4> SAS Macros </h4>
@li mp_assert.sas
@li mp_assertdsobs.sas
@li mx_execute.sas
**/
@@ -5,7 +5,6 @@
<h4> SAS Macros </h4>
@li mf_getuser.sas
@li mf_getvarlist.sas
@li mf_verifymacvars.sas
@li mp_abort.sas
@li mp_lockanytable.sas
@@ -11,7 +11,6 @@
@li mpe_accesscheck.sas
@li mp_abort.sas
@li mp_dirlist.sas
@li mp_binarycopy.sas
@li mf_getattrn.sas
@li mp_streamfile.sas
-1
View File
@@ -9,7 +9,6 @@
@li mf_getattrn.sas
@li mf_getuser.sas
@li mp_abort.sas
@li mp_binarycopy.sas
@li mp_streamfile.sas
@li mp_validatecol.sas
@@ -13,7 +13,6 @@
<h4> SAS Macros </h4>
@li dc_assignlib.sas
@li mf_existds.sas
@li mp_abort.sas
@li mp_applyformats.sas
@li mp_getcols.sas
@@ -5,7 +5,6 @@
<h4> SAS Macros </h4>
@li mx_execute.sas
@li mp_assert.sas
@li mp_assertdsobs.sas
**/
-3
View File
@@ -17,17 +17,14 @@
|SHOW_DIFFS|DC20220208T142124517_124703_1184|"Tue, 08 Feb 2022 14:23:05 GMT"|
<h4> SAS Macros </h4>
@li bitemporal_dataloader.sas
@li dc_assignlib.sas
@li dc_cassave.sas
@li mf_existds.sas
@li mf_existvar.sas
@li mf_getattrn.sas
@li mf_getengine.sas
@li mf_getquotedstr.sas
@li mf_getuniquelibref.sas
@li mf_getuser.sas
@li mf_getvarlist.sas
@li mf_nobs.sas
@li mf_verifymacvars.sas
@li mp_abort.sas
@@ -5,7 +5,6 @@
<h4> SAS Macros </h4>
@li mp_assertcols.sas
@li mp_assertcolvals.sas
@li mp_assertdsobs.sas
@li mf_getuniquefileref.sas
@li mx_execute.sas
-1
View File
@@ -8,7 +8,6 @@
@li mf_getuser.sas
@li mp_abort.sas
@li mp_dirlist.sas
@li mp_binarycopy.sas
@li mp_streamfile.sas
@version 9.2
+6
View File
@@ -3,6 +3,12 @@
@brief Loads a file
@details
mp_binarycopy is declared for the SAS 9.3 and earlier fallback in the copy
step below: the %mp_binarycopy call is commented out on 9.4 and later, so
nothing in the file calls it.
@sasjslint {"noUnusedMacros": false}
<h4> SAS Macros </h4>
@li mddl_sas_cntlout.sas
@li mp_abort.sas
@@ -3,7 +3,6 @@
@brief testing restore process
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mx_execute.sas
@li mp_assert.sas
@@ -5,7 +5,6 @@
<h4> SAS Macros </h4>
@li mx_execute.sas
@li mp_assert.sas
@li mp_assertcolvals.sas
@li mf_getuniquefileref.sas
**/
@@ -3,7 +3,6 @@
@brief testing getgroups service.
<h4> SAS Macros </h4>
@li mf_getuser.sas
@li mp_assertcols.sas
@li mx_execute.sas
-1
View File
@@ -17,7 +17,6 @@
The data for a particular version
<h4> SAS Macros </h4>
@li mf_getuser.sas
@li mpeinit.sas
@li mpe_getvars.sas
@@ -46,9 +46,6 @@
|2|CURRENCY_SYMBOL|C||"EUR"|.|
|2|CURRENCY_SYMBOL|C||"HKD"|1|
<h4> SAS Macros </h4>
@li dc_getlibs.sas
**/
@@ -48,9 +48,6 @@
|2|CURRENCY_SYMBOL|C||"HKD"|
<h4> SAS Macros </h4>
@li dc_getlibs.sas
**/
@@ -18,9 +18,6 @@
|---|
|44|
<h4> SAS Macros </h4>
@li dc_getlibs.sas
**/
@@ -20,7 +20,6 @@
@li mm_createstp.sas
@li mm_deletedocument.sas
@li mm_deletestp.sas
@li mm_getstpcode.sas
@li mm_getstpinfo.sas
@li mp_abort.sas
@li mp_init.sas
@@ -5,7 +5,6 @@
<h4> SAS Macros </h4>
@li mm_getcols.sas
@li mp_abort.sas
@version 9.2
@author 4GL Apps Ltd
@@ -5,7 +5,6 @@
<h4> SAS Macros </h4>
@li mm_gettables.sas
@li mp_abort.sas
@version 9.2
@author 4GL Apps Ltd
@@ -5,7 +5,6 @@
during configuration, when the settings service does not yet exist.
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mm_getusers.sas
@version 9.3
@@ -3,7 +3,6 @@
@brief List the members of a group
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mpeinit.sas
@version 9.3
@@ -3,7 +3,6 @@
@brief List the members of a role
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mpeinit.sas
@author 4GL Apps Ltd
@@ -5,8 +5,6 @@
during configuration, when the settings service does not yet exist.
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mpeinit.sas
@li ms_getgroups.sas
<h4> Data Inputs </h4>
@@ -3,7 +3,6 @@
@brief List the members of a group
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mpeinit.sas
@li ms_getusers.sas
@@ -3,7 +3,6 @@
@brief Get initial tokens using an auth code - DEPRECATED
<h4> SAS Macros </h4>
@li mpeinit.sas
@li mv_tokenrefresh.sas
@author 4GL Apps Ltd
@@ -3,7 +3,6 @@
@brief List the members of a group
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mpeinit.sas
@li dc_getgroupmembers.sas
-2
View File
@@ -4,9 +4,7 @@
<h4> SAS Macros </h4>
@li dc_getsettings.sas
@li mf_getplatform.sas
@li mpeinit2.sas
@li mp_abort.sas
@li mp_init.sas
**/
+6
View File
@@ -4,6 +4,12 @@
@details This file is included in *every* service, *after* the macros and
*before* the service code.
The macros are declared, not called: this program is compiled ahead of every
service body, so the declarations are how a service reaches %mpeinit() and
%mpeterm() without declaring them itself.
@sasjslint {"noUnusedMacros": false}
<h4> SAS Macros </h4>
@li mpeinit.sas
@li mpeterm.sas
+6
View File
@@ -4,6 +4,12 @@
@details This file is included in *every* service, *after* the macros and
*before* the service code.
The macros are declared, not called: this program is compiled ahead of every
service body, so the declarations are how a service reaches %mpeinit() and
%mpeterm() without declaring them itself.
@sasjslint {"noUnusedMacros": false}
<h4> SAS Macros </h4>
@li mpeinit.sas
@li mpeterm.sas
+6
View File
@@ -4,6 +4,12 @@
@details This file is included in *every* service, *after* the macros and
*before* the service code.
The macros are declared, not called: this program is compiled ahead of every
service body, so the declarations are how a service reaches %mpeinit() and
%mpeterm() without declaring them itself.
@sasjslint {"noUnusedMacros": false}
<h4> SAS Macros </h4>
@li mpeinit.sas
@li mpeterm.sas
+6
View File
@@ -3,6 +3,12 @@
@brief this file is called at the end of every service
@details This file is included at the *end* of every service.
mpeterm is declared, not called: this program is compiled after every service
body, so the declaration is how a service reaches %mpeterm() without
declaring it itself.
@sasjslint {"noUnusedMacros": false}
<h4> SAS Macros </h4>
@li mpeterm.sas