mirror of
https://github.com/sasjs/lint.git
synced 2025-12-10 17:34:36 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ccb122744 | ||
|
|
884480d3df | ||
|
|
1b940497aa | ||
|
|
94d9d246eb | ||
|
|
95502647e8 | ||
|
|
be9d5b8e68 | ||
|
|
c2d368327b | ||
|
|
94a693e57d | ||
|
|
fec3372f92 | ||
|
|
d5b38373d4 | ||
|
|
21114e0a6f | ||
|
|
b52b3ac42f | ||
|
|
7f4c389468 | ||
|
|
1fd4cd7ddc |
23
README.md
23
README.md
@@ -44,7 +44,7 @@ The SASjs framework recommends the use of Doxygen headers for describing all typ
|
||||
* Severity: WARNING
|
||||
|
||||
#### hasMacroNameInMend
|
||||
The addition of the macro name in the `%mend` statement is optional, but can approve readability in large programs. A discussion on this topic can be found [here](https://www.linkedin.com/posts/allanbowe_sas-sasapps-sasjs-activity-6783413360781266945-1-7m). The default setting will be the result of a popular vote by around 300 people.
|
||||
The addition of the macro name in the `%mend` statement is optional, but can approve readability in large programs. A discussion on this topic can be found [here](https://www.linkedin.com/posts/allanbowe_sas-sasapps-sasjs-activity-6783413360781266945-1-7m). The default setting was the result of a poll with over 300 votes.
|
||||
|
||||
* Default: true
|
||||
* Severity: WARNING
|
||||
@@ -72,13 +72,13 @@ Code becomes far more readable when line lengths are short. The most compelling
|
||||
|
||||
In batch mode, long SAS code lines may also be truncated, causing hard-to-detect errors.
|
||||
|
||||
For this reason we strongly recommend a line length limit, and we set the bar at 80. To turn this feature off, set the value to 0.
|
||||
We strongly recommend a line length limit, and set the bar at 80. To turn this feature off, set the value to 0.
|
||||
|
||||
* Default: 80
|
||||
* Severity: WARNING
|
||||
|
||||
#### noNestedMacros
|
||||
Where macros are defined inside other macros, they are recompiled every time the outer maro is invoked. Hence, it is widely considered inefficient, and bad practice, to nest macro definitions.
|
||||
Where macros are defined inside other macros, they are recompiled every time the outer macro is invoked. Hence, it is widely considered inefficient, and bad practice, to nest macro definitions.
|
||||
|
||||
* Default: true
|
||||
* Severity: WARNING
|
||||
@@ -95,7 +95,7 @@ In addition, when such files are used in URLs, they are often padded with a mess
|
||||
* Severity: WARNING
|
||||
|
||||
#### noTabIndentation
|
||||
Whilst there are some arguments for using tabs to indent (such as the ability to set your own indentation width, and to save on characters) there are many, many, many developers who think otherwise. We're in that camp. Sorry (not sorry).
|
||||
Whilst there are some arguments for using tabs to indent (such as the ability to set your own indentation width, and to reduce character count) there are many, many, many developers who think otherwise. We're in that camp. Sorry (not sorry).
|
||||
|
||||
* Default: true
|
||||
* Severity: WARNING
|
||||
@@ -116,14 +116,19 @@ This will highlight lines with trailing spaces. Trailing spaces serve no useful
|
||||
|
||||
A formatter will automatically apply rules when you hit SAVE, which can save a LOT of time.
|
||||
|
||||
We're looking to implement the following rules:
|
||||
We've already implemented the following rules:
|
||||
|
||||
* Remove trailing spaces
|
||||
* Change tabs to spaces
|
||||
* Add the macro name to the %mend statement
|
||||
* Add a doxygen header template if none exists
|
||||
* Remove trailing spaces
|
||||
|
||||
Later we will investigate some harder stuff, such as automatic indentation and code layout
|
||||
We're looking to implement the following rules:
|
||||
|
||||
* Change tabs to spaces
|
||||
* zap gremlins
|
||||
* fix line endings
|
||||
|
||||
We are also investigating some harder stuff, such as automatic indentation and code layout
|
||||
|
||||
## Sponsorship & Contributions
|
||||
|
||||
@@ -133,7 +138,7 @@ Contact [Allan Bowe](https://www.linkedin.com/in/allanbowe/) for further details
|
||||
|
||||
## SAS 9 Health check
|
||||
|
||||
The SASjs Linter (and formatter) is a great way to de-risk and accelerate the delivery of SAS code into production environments. However, code is just one part of a SAS estate. If you are running SAS 9, you may be interested to know what 'gremlins' are lurking in your system. Maybe you are preparing for a migration. Maybe you are preparing to hand over the control of your environment. Either way, an assessment of your existing system would put minds at rest and pro-actively identify trouble spots.
|
||||
The SASjs Linter (and formatter) is a great way to de-risk and accelerate the delivery of SAS code into production environments. However, code is just one part of a SAS estate. If you are running SAS 9, you may be interested to know what 'gremlins' are lurking in your SAS 9 system. Maybe you are preparing for a migration. Maybe you are preparing to hand over the control of your environment. Either way, an assessment of your existing system would put minds at rest and pro-actively identify trouble spots.
|
||||
|
||||
The SAS 9 Health Check is a 'plug & play' product, that uses the [SAS 9 REST API](https://sas9api.io) to run hundreds of metadata and system checks to identify common problems. The checks are non-invasive, and becuase it is a client app, there is NOTHING TO INSTALL on your SAS server. We offer this assessment for a low fixed fee, and if you engage our (competitively priced) services to address the issues we highlight, then the assessment is free.
|
||||
|
||||
|
||||
65
package-lock.json
generated
65
package-lock.json
generated
@@ -648,13 +648,15 @@
|
||||
}
|
||||
},
|
||||
"@sasjs/utils": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.12.0.tgz",
|
||||
"integrity": "sha512-OnC/7R+nGI8tlSPCcI7fPyD7T97B+McnkXT0IuAYDNGbfwRPuseWq0I1h+kbAWThGT67H4hnp61N0qr8LkpHZQ==",
|
||||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.19.0.tgz",
|
||||
"integrity": "sha512-b/NlIvTaISIFsllucetBwJjFUiM13I+bS06WtK3WN0G1EXzVrjJt+eXqgkQZbIZfoaeKo5oRigOtZUXth65duQ==",
|
||||
"requires": {
|
||||
"@types/prompts": "^2.0.13",
|
||||
"chalk": "^4.1.1",
|
||||
"cli-table": "^0.3.6",
|
||||
"consola": "^2.15.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"prompts": "^2.4.1",
|
||||
"valid-url": "^1.0.9"
|
||||
},
|
||||
@@ -782,10 +784,9 @@
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "15.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz",
|
||||
"integrity": "sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA==",
|
||||
"dev": true
|
||||
"version": "15.12.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz",
|
||||
"integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww=="
|
||||
},
|
||||
"@types/normalize-package-data": {
|
||||
"version": "2.4.0",
|
||||
@@ -799,6 +800,14 @@
|
||||
"integrity": "sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/prompts": {
|
||||
"version": "2.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.0.13.tgz",
|
||||
"integrity": "sha512-jwMOIGy49VruR/gYehhJYgpVzB+EVpEE7t7j9m1oTo4HMpOe7KmsyqdBuoxAzA5B4caUgx0cKrWr7wUEqMXJ7Q==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/stack-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz",
|
||||
@@ -1938,6 +1947,23 @@
|
||||
"map-cache": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz",
|
||||
"integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
@@ -2022,8 +2048,7 @@
|
||||
"graceful-fs": {
|
||||
"version": "4.2.6",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
|
||||
"integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ=="
|
||||
},
|
||||
"growly": {
|
||||
"version": "1.3.0",
|
||||
@@ -3055,6 +3080,22 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsprim": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
||||
@@ -4620,9 +4661,9 @@
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
|
||||
"integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==",
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz",
|
||||
"integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==",
|
||||
"dev": true
|
||||
},
|
||||
"union-value": {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"package:lib": "npm run build && cp ./package.json build && cp README.md build && cd build && npm version \"5.0.0\" && npm pack",
|
||||
"lint:fix": "npx prettier --write '{src,test}/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'",
|
||||
"lint": "npx prettier --check '{src,test}/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'",
|
||||
"postinstall": "[ -d .git ] && git config core.hooksPath ./.git-hooks || true"
|
||||
"prepare": "git rev-parse --git-dir && git config core.hooksPath ./.git-hooks || true"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -38,13 +38,13 @@
|
||||
"homepage": "https://github.com/sasjs/lint#readme",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/node": "^15.0.2",
|
||||
"@types/node": "^15.12.2",
|
||||
"jest": "^26.6.3",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-jest": "^26.5.6",
|
||||
"typescript": "^4.2.4"
|
||||
"typescript": "^4.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sasjs/utils": "^2.12.0"
|
||||
"@sasjs/utils": "^2.19.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,32 @@ describe('hasDoxygenHeader - test', () => {
|
||||
expect(hasDoxygenHeader.test(content)).toEqual([])
|
||||
})
|
||||
|
||||
it('should return an empty array when the file starts with a doxygen header', () => {
|
||||
const content = `
|
||||
|
||||
|
||||
/*
|
||||
@file
|
||||
@brief Returns an unused libref
|
||||
*/
|
||||
|
||||
%macro mf_getuniquelibref(prefix=mclib,maxtries=1000);
|
||||
%local x libref;
|
||||
%let x={SAS002};
|
||||
%do x=0 %to &maxtries;`
|
||||
|
||||
expect(hasDoxygenHeader.test(content)).toEqual([
|
||||
{
|
||||
message:
|
||||
'File not following Doxygen header style, use double asterisks',
|
||||
lineNumber: 4,
|
||||
startColumnNumber: 1,
|
||||
endColumnNumber: 1,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when the file has no header', () => {
|
||||
const content = `
|
||||
%macro mf_getuniquelibref(prefix=mclib,maxtries=1000);
|
||||
@@ -86,7 +112,33 @@ describe('hasDoxygenHeader - fix', () => {
|
||||
expect(hasDoxygenHeader.fix!(content)).toEqual(content)
|
||||
})
|
||||
|
||||
it('should should add a doxygen header if not present', () => {
|
||||
it('should update single asterisks to double if a doxygen header is already present', () => {
|
||||
const contentOriginal = `
|
||||
/*
|
||||
@file
|
||||
@brief Returns an unused libref
|
||||
*/
|
||||
|
||||
%macro mf_getuniquelibref(prefix=mclib,maxtries=1000);
|
||||
%local x libref;
|
||||
%let x={SAS002};
|
||||
%do x=0 %to &maxtries;`
|
||||
|
||||
const contentExpected = `
|
||||
/**
|
||||
@file
|
||||
@brief Returns an unused libref
|
||||
*/
|
||||
|
||||
%macro mf_getuniquelibref(prefix=mclib,maxtries=1000);
|
||||
%local x libref;
|
||||
%let x={SAS002};
|
||||
%do x=0 %to &maxtries;`
|
||||
|
||||
expect(hasDoxygenHeader.fix!(contentOriginal)).toEqual(contentExpected)
|
||||
})
|
||||
|
||||
it('should add a doxygen header if not present', () => {
|
||||
const content = `%macro mf_getuniquelibref(prefix=mclib,maxtries=1000);
|
||||
%local x libref;
|
||||
%let x={SAS002};
|
||||
|
||||
@@ -10,10 +10,28 @@ const name = 'hasDoxygenHeader'
|
||||
const description =
|
||||
'Enforce the presence of a Doxygen header at the start of each file.'
|
||||
const message = 'File missing Doxygen header'
|
||||
const test = (value: string) => {
|
||||
const messageForSingleAsterisk =
|
||||
'File not following Doxygen header style, use double asterisks'
|
||||
const test = (value: string, config?: LintConfig) => {
|
||||
const lineEnding = config?.lineEndings === LineEndings.CRLF ? '\r\n' : '\n'
|
||||
try {
|
||||
const hasFileHeader = value.trimStart().startsWith('/*')
|
||||
const hasFileHeader = value.trimStart().startsWith('/**')
|
||||
if (hasFileHeader) return []
|
||||
|
||||
const hasFileHeaderWithSingleAsterisk = value.trimStart().startsWith('/*')
|
||||
if (hasFileHeaderWithSingleAsterisk)
|
||||
return [
|
||||
{
|
||||
message: messageForSingleAsterisk,
|
||||
lineNumber:
|
||||
(value.split('/*')![0]!.match(new RegExp(lineEnding, 'g')) ?? [])
|
||||
.length + 1,
|
||||
startColumnNumber: 1,
|
||||
endColumnNumber: 1,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
]
|
||||
|
||||
return [
|
||||
{
|
||||
message,
|
||||
@@ -37,9 +55,12 @@ const test = (value: string) => {
|
||||
}
|
||||
|
||||
const fix = (value: string, config?: LintConfig): string => {
|
||||
if (test(value).length === 0) {
|
||||
const result = test(value, config)
|
||||
if (result.length === 0) {
|
||||
return value
|
||||
}
|
||||
} else if (result[0].message == messageForSingleAsterisk)
|
||||
return value.replace('/*', '/**')
|
||||
|
||||
const lineEndingConfig = config?.lineEndings || LineEndings.LF
|
||||
const lineEnding = lineEndingConfig === LineEndings.LF ? '\n' : '\r\n'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user