mirror of
https://github.com/sasjs/lint.git
synced 2025-12-12 10:24:36 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
482ecec150 | ||
|
|
b4ec32b72c | ||
|
|
dcfeb7a641 | ||
|
|
e5780cd69a | ||
|
|
5a358330c0 | ||
|
|
fa9e4136bc | ||
|
|
0c9b23c51b | ||
|
|
9daf8f8c82 | ||
|
|
7ed846e3aa | ||
|
|
f7f989fabd | ||
|
|
850cf85ef1 | ||
|
|
3dc304fffc | ||
|
|
e329529484 | ||
|
|
15190bfe88 | ||
|
|
bc011c4b47 | ||
|
|
a95c083b61 | ||
|
|
96fb384ec9 | ||
|
|
21fd4e8fcc | ||
|
|
ac595c65d0 | ||
|
|
e5763ce529 | ||
|
|
4729f04589 | ||
|
|
596d56c906 | ||
|
|
32956db8b2 | ||
|
|
7b58c455dc | ||
|
|
c86fd7dd1d | ||
|
|
34e9a7b139 | ||
|
|
5de3d33c1c | ||
|
|
3a6a5d30e3 | ||
|
|
0f629c4aca | ||
|
|
ae4c5e8347 | ||
|
|
3c700a97fc | ||
|
|
d113ef4ddd | ||
|
|
dce9453680 | ||
|
|
e76abc2db2 | ||
|
|
1e70b9debc |
18
.git-hooks/commit-msg
Executable file
18
.git-hooks/commit-msg
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
RED="\033[1;31m"
|
||||||
|
GREEN="\033[1;32m"
|
||||||
|
|
||||||
|
# Get the commit message (the parameter we're given is just the path to the
|
||||||
|
# temporary file which holds the message).
|
||||||
|
commit_message=$(cat "$1")
|
||||||
|
|
||||||
|
if (echo "$commit_message" | grep -Eq "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z \-]+\))?!?: .+$") then
|
||||||
|
echo "${GREEN} ✔ Commit message meets Conventional Commit standards"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "${RED}❌ Commit message does not meet the Conventional Commit standard!"
|
||||||
|
echo "An example of a valid message is:"
|
||||||
|
echo " feat(login): add the 'remember me' button"
|
||||||
|
echo "ℹ More details at: https://www.conventionalcommits.org/en/v1.0.0/#summary"
|
||||||
|
exit 1
|
||||||
7
.github/dependabot.yml
vendored
Normal file
7
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: npm
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
open-pull-requests-limit: 10
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
[](https://dependabot.com)
|
||||||
|
|
||||||
# SAS Code linting and formatting
|
# SAS Code linting and formatting
|
||||||
|
|
||||||
Our goal is to help SAS developers everywhere spend less time on code reviews, bug fixing and arguing about standards - and more time delivering extraordinary business value.
|
Our goal is to help SAS developers everywhere spend less time on code reviews, bug fixing and arguing about standards - and more time delivering extraordinary business value.
|
||||||
@@ -14,7 +16,7 @@ Configuration is via a `.sasjslint` file with the following structure (these are
|
|||||||
{
|
{
|
||||||
"noEncodedPasswords": true,
|
"noEncodedPasswords": true,
|
||||||
"hasDoxygenHeader": true,
|
"hasDoxygenHeader": true,
|
||||||
"hasMacroNameInMend": false,
|
"hasMacroNameInMend": true,
|
||||||
"hasMacroParentheses": true,
|
"hasMacroParentheses": true,
|
||||||
"indentationMultiple": 2,
|
"indentationMultiple": 2,
|
||||||
"lowerCaseFileNames": true,
|
"lowerCaseFileNames": true,
|
||||||
@@ -44,7 +46,7 @@ The SASjs framework recommends the use of Doxygen headers for describing all typ
|
|||||||
#### hasMacroNameInMend
|
#### 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 will be the result of a popular vote by around 300 people.
|
||||||
|
|
||||||
* Default: false (for now)
|
* Default: true
|
||||||
* Severity: WARNING
|
* Severity: WARNING
|
||||||
|
|
||||||
#### hasMacroParentheses
|
#### hasMacroParentheses
|
||||||
|
|||||||
93
package-lock.json
generated
93
package-lock.json
generated
@@ -648,14 +648,35 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@sasjs/utils": {
|
"@sasjs/utils": {
|
||||||
"version": "2.10.1",
|
"version": "2.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.12.0.tgz",
|
||||||
"integrity": "sha512-T54jx6NEMLu2+R/ux4qcb3dDJ7nFrKkPCkmPXEfZxPQBkbq4C0kmaZv6dC63RDH68wYhoXR2S5fION5fFh91iw==",
|
"integrity": "sha512-OnC/7R+nGI8tlSPCcI7fPyD7T97B+McnkXT0IuAYDNGbfwRPuseWq0I1h+kbAWThGT67H4hnp61N0qr8LkpHZQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/prompts": "^2.0.9",
|
"chalk": "^4.1.1",
|
||||||
|
"cli-table": "^0.3.6",
|
||||||
"consola": "^2.15.0",
|
"consola": "^2.15.0",
|
||||||
"prompts": "^2.4.0",
|
"prompts": "^2.4.1",
|
||||||
"valid-url": "^1.0.9"
|
"valid-url": "^1.0.9"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
|
||||||
|
"requires": {
|
||||||
|
"ansi-styles": "^4.1.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"prompts": {
|
||||||
|
"version": "2.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz",
|
||||||
|
"integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==",
|
||||||
|
"requires": {
|
||||||
|
"kleur": "^3.0.3",
|
||||||
|
"sisteransi": "^1.0.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@sinonjs/commons": {
|
"@sinonjs/commons": {
|
||||||
@@ -751,9 +772,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/jest": {
|
"@types/jest": {
|
||||||
"version": "26.0.21",
|
"version": "26.0.23",
|
||||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.21.tgz",
|
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.23.tgz",
|
||||||
"integrity": "sha512-ab9TyM/69yg7eew9eOwKMUmvIZAKEGZYlq/dhe5/0IMUd/QLJv5ldRMdddSn+u22N13FP3s5jYyktxuBwY0kDA==",
|
"integrity": "sha512-ZHLmWMJ9jJ9PTiT58juykZpL7KjwJywFN3Rr2pTSkyQfydf/rk22yS7W8p5DaVUMQ2BQC7oYiU3FjbTM/mYrOA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"jest-diff": "^26.0.0",
|
"jest-diff": "^26.0.0",
|
||||||
@@ -761,9 +782,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "14.14.35",
|
"version": "15.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.35.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz",
|
||||||
"integrity": "sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag=="
|
"integrity": "sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/normalize-package-data": {
|
"@types/normalize-package-data": {
|
||||||
"version": "2.4.0",
|
"version": "2.4.0",
|
||||||
@@ -777,14 +799,6 @@
|
|||||||
"integrity": "sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==",
|
"integrity": "sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/prompts": {
|
|
||||||
"version": "2.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.0.10.tgz",
|
|
||||||
"integrity": "sha512-W3PEl3l4vmxdgfY6LUG7ysh+mLJOTOFYmSpiLe6MCo1OdEm8b5s6ZJfuTQgEpYNwcMiiaRzJespPS5Py2tqLlQ==",
|
|
||||||
"requires": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@types/stack-utils": {
|
"@types/stack-utils": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz",
|
||||||
@@ -881,7 +895,6 @@
|
|||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"color-convert": "^2.0.1"
|
"color-convert": "^2.0.1"
|
||||||
}
|
}
|
||||||
@@ -1278,6 +1291,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cli-table": {
|
||||||
|
"version": "0.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.6.tgz",
|
||||||
|
"integrity": "sha512-ZkNZbnZjKERTY5NwC2SeMeLeifSPq/pubeRoTpdr3WchLlnZg6hEgvHkK5zL7KNFdd9PmHN8lxrENUwI3cE8vQ==",
|
||||||
|
"requires": {
|
||||||
|
"colors": "1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"cliui": {
|
"cliui": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||||
@@ -1315,7 +1336,6 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"color-name": "~1.1.4"
|
"color-name": "~1.1.4"
|
||||||
}
|
}
|
||||||
@@ -1323,8 +1343,7 @@
|
|||||||
"color-name": {
|
"color-name": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"colorette": {
|
"colorette": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
@@ -1332,6 +1351,11 @@
|
|||||||
"integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
|
"integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"colors": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
|
||||||
|
"integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs="
|
||||||
|
},
|
||||||
"combined-stream": {
|
"combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
@@ -2036,8 +2060,7 @@
|
|||||||
"has-flag": {
|
"has-flag": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"has-value": {
|
"has-value": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -3587,6 +3610,7 @@
|
|||||||
"version": "2.4.0",
|
"version": "2.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz",
|
||||||
"integrity": "sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==",
|
"integrity": "sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"kleur": "^3.0.3",
|
"kleur": "^3.0.3",
|
||||||
"sisteransi": "^1.0.5"
|
"sisteransi": "^1.0.5"
|
||||||
@@ -4395,7 +4419,6 @@
|
|||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"has-flag": "^4.0.0"
|
"has-flag": "^4.0.0"
|
||||||
}
|
}
|
||||||
@@ -4517,9 +4540,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ts-jest": {
|
"ts-jest": {
|
||||||
"version": "26.5.4",
|
"version": "26.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.5.tgz",
|
||||||
"integrity": "sha512-I5Qsddo+VTm94SukBJ4cPimOoFZsYTeElR2xy6H2TOVs+NsvgYglW8KuQgKoApOKuaU/Ix/vrF9ebFZlb5D2Pg==",
|
"integrity": "sha512-7tP4m+silwt1NHqzNRAPjW1BswnAhopTdc2K3HEkRZjF0ZG2F/e/ypVH0xiZIMfItFtD3CX0XFbwPzp9fIEUVg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"bs-logger": "0.x",
|
"bs-logger": "0.x",
|
||||||
@@ -4535,9 +4558,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "7.3.4",
|
"version": "7.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
|
||||||
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
|
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"lru-cache": "^6.0.0"
|
"lru-cache": "^6.0.0"
|
||||||
@@ -4597,9 +4620,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "4.2.3",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
|
||||||
"integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==",
|
"integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"union-value": {
|
"union-value": {
|
||||||
|
|||||||
13
package.json
13
package.json
@@ -8,7 +8,8 @@
|
|||||||
"postpublish": "git clean -fd",
|
"postpublish": "git clean -fd",
|
||||||
"package:lib": "npm run build && cp ./package.json build && cp README.md build && cd build && npm version \"5.0.0\" && npm pack",
|
"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: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}'"
|
"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"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
@@ -36,14 +37,14 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/sasjs/lint#readme",
|
"homepage": "https://github.com/sasjs/lint#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^26.0.21",
|
"@types/jest": "^26.0.23",
|
||||||
"@types/node": "^14.14.35",
|
"@types/node": "^15.0.2",
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"ts-jest": "^26.5.4",
|
"ts-jest": "^26.5.5",
|
||||||
"typescript": "^4.2.3"
|
"typescript": "^4.2.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/utils": "^2.10.1"
|
"@sasjs/utils": "^2.12.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
"noSpacesInFileNames": true,
|
"noSpacesInFileNames": true,
|
||||||
"noTabIndentation": true,
|
"noTabIndentation": true,
|
||||||
"noTrailingSpaces": true,
|
"noTrailingSpaces": true,
|
||||||
"lineEndings": "lf"
|
"lineEndings": "lf",
|
||||||
|
"strictMacroDefinition": true
|
||||||
},
|
},
|
||||||
"examples": [
|
"examples": [
|
||||||
{
|
{
|
||||||
@@ -31,7 +32,8 @@
|
|||||||
"hasMacroNameInMend": true,
|
"hasMacroNameInMend": true,
|
||||||
"noNestedMacros": true,
|
"noNestedMacros": true,
|
||||||
"hasMacroParentheses": true,
|
"hasMacroParentheses": true,
|
||||||
"lineEndings": "crlf"
|
"lineEndings": "crlf",
|
||||||
|
"strictMacroDefinition": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -130,6 +132,14 @@
|
|||||||
"description": "Enforces the configured terminating character for each line. Shows a warning when incorrect line endings are present.",
|
"description": "Enforces the configured terminating character for each line. Shows a warning when incorrect line endings are present.",
|
||||||
"default": "lf",
|
"default": "lf",
|
||||||
"examples": ["lf", "crlf"]
|
"examples": ["lf", "crlf"]
|
||||||
|
},
|
||||||
|
"strictMacroDefinition": {
|
||||||
|
"$id": "#/properties/strictMacroDefinition",
|
||||||
|
"type": "boolean",
|
||||||
|
"title": "strictMacroDefinition",
|
||||||
|
"description": "Enforces Macro Definition syntax. Shows a warning when incorrect syntax is used.",
|
||||||
|
"default": true,
|
||||||
|
"examples": [true, false]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,22 +8,76 @@ describe('formatFile', () => {
|
|||||||
const content = `%macro somemacro(); \n%put 'hello';\n%mend;`
|
const content = `%macro somemacro(); \n%put 'hello';\n%mend;`
|
||||||
const expectedContent = `/**\n @file\n @brief <Your brief here>\n <h4> SAS Macros </h4>\n**/\n%macro somemacro();\n%put 'hello';\n%mend somemacro;`
|
const expectedContent = `/**\n @file\n @brief <Your brief here>\n <h4> SAS Macros </h4>\n**/\n%macro somemacro();\n%put 'hello';\n%mend somemacro;`
|
||||||
await createFile(path.join(__dirname, 'format-file-test.sas'), content)
|
await createFile(path.join(__dirname, 'format-file-test.sas'), content)
|
||||||
|
const expectedResult = {
|
||||||
|
updatedFilePaths: [path.join(__dirname, 'format-file-test.sas')],
|
||||||
|
fixedDiagnosticsCount: 3,
|
||||||
|
unfixedDiagnostics: []
|
||||||
|
}
|
||||||
|
|
||||||
await formatFile(path.join(__dirname, 'format-file-test.sas'))
|
const result = await formatFile(
|
||||||
const result = await readFile(path.join(__dirname, 'format-file-test.sas'))
|
path.join(__dirname, 'format-file-test.sas')
|
||||||
|
)
|
||||||
|
const formattedContent = await readFile(
|
||||||
|
path.join(__dirname, 'format-file-test.sas')
|
||||||
|
)
|
||||||
|
|
||||||
expect(result).toEqual(expectedContent)
|
expect(result).toEqual(expectedResult)
|
||||||
|
expect(formattedContent).toEqual(expectedContent)
|
||||||
|
|
||||||
await deleteFile(path.join(__dirname, 'format-file-test.sas'))
|
await deleteFile(path.join(__dirname, 'format-file-test.sas'))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should use the provided config if available', async () => {
|
it('should use the provided config if available', async () => {
|
||||||
const content = `%macro somemacro(); \n%put 'hello';\n%mend;`
|
const content = `%macro somemacro(); \n%put 'hello';\n%mend;`
|
||||||
const expectedContent = `/**\r\n @file\r\n @brief <Your brief here>\r\n <h4> SAS Macros </h4>\r\n**/\r\n%macro somemacro();\r\n%put 'hello';\r\n%mend somemacro;`
|
const expectedContent = `/**\r\n @file\r\n @brief <Your brief here>\r\n <h4> SAS Macros </h4>\r\n**/\r\n%macro somemacro();\r\n%put 'hello';\r\n%mend;`
|
||||||
|
const expectedResult = {
|
||||||
|
updatedFilePaths: [path.join(__dirname, 'format-file-config.sas')],
|
||||||
|
fixedDiagnosticsCount: 2,
|
||||||
|
unfixedDiagnostics: [
|
||||||
|
{
|
||||||
|
endColumnNumber: 7,
|
||||||
|
lineNumber: 8,
|
||||||
|
message: '%mend statement is missing macro name - somemacro',
|
||||||
|
severity: 1,
|
||||||
|
startColumnNumber: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
await createFile(path.join(__dirname, 'format-file-config.sas'), content)
|
await createFile(path.join(__dirname, 'format-file-config.sas'), content)
|
||||||
|
|
||||||
await formatFile(
|
const result = await formatFile(
|
||||||
path.join(__dirname, 'format-file-config.sas'),
|
path.join(__dirname, 'format-file-config.sas'),
|
||||||
|
new LintConfig({
|
||||||
|
lineEndings: 'crlf',
|
||||||
|
hasMacroNameInMend: false,
|
||||||
|
hasDoxygenHeader: true,
|
||||||
|
noTrailingSpaces: true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const formattedContent = await readFile(
|
||||||
|
path.join(__dirname, 'format-file-config.sas')
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result).toEqual(expectedResult)
|
||||||
|
expect(formattedContent).toEqual(expectedContent)
|
||||||
|
|
||||||
|
await deleteFile(path.join(__dirname, 'format-file-config.sas'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not update any files if there are no formatting violations', async () => {
|
||||||
|
const content = `/**\r\n @file\r\n @brief <Your brief here>\r\n <h4> SAS Macros </h4>\r\n**/\r\n%macro somemacro();\r\n%put 'hello';\r\n%mend somemacro;`
|
||||||
|
const expectedResult = {
|
||||||
|
updatedFilePaths: [],
|
||||||
|
fixedDiagnosticsCount: 0,
|
||||||
|
unfixedDiagnostics: []
|
||||||
|
}
|
||||||
|
await createFile(
|
||||||
|
path.join(__dirname, 'format-file-no-violations.sas'),
|
||||||
|
content
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = await formatFile(
|
||||||
|
path.join(__dirname, 'format-file-no-violations.sas'),
|
||||||
new LintConfig({
|
new LintConfig({
|
||||||
lineEndings: 'crlf',
|
lineEndings: 'crlf',
|
||||||
hasMacroNameInMend: true,
|
hasMacroNameInMend: true,
|
||||||
@@ -31,12 +85,13 @@ describe('formatFile', () => {
|
|||||||
noTrailingSpaces: true
|
noTrailingSpaces: true
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
const result = await readFile(
|
const formattedContent = await readFile(
|
||||||
path.join(__dirname, 'format-file-config.sas')
|
path.join(__dirname, 'format-file-no-violations.sas')
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(result).toEqual(expectedContent)
|
expect(result).toEqual(expectedResult)
|
||||||
|
expect(formattedContent).toEqual(content)
|
||||||
|
|
||||||
await deleteFile(path.join(__dirname, 'format-file-config.sas'))
|
await deleteFile(path.join(__dirname, 'format-file-no-violations.sas'))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { createFile, readFile } from '@sasjs/utils/file'
|
import { createFile, readFile } from '@sasjs/utils/file'
|
||||||
|
import { lintFile } from '../lint'
|
||||||
|
import { FormatResult } from '../types'
|
||||||
import { LintConfig } from '../types/LintConfig'
|
import { LintConfig } from '../types/LintConfig'
|
||||||
import { getLintConfig } from '../utils/getLintConfig'
|
import { getLintConfig } from '../utils/getLintConfig'
|
||||||
import { processText } from './shared'
|
import { processText } from './shared'
|
||||||
@@ -7,16 +9,37 @@ import { processText } from './shared'
|
|||||||
* Applies automatic formatting to the file at the given path.
|
* Applies automatic formatting to the file at the given path.
|
||||||
* @param {string} filePath - the path to the file to be formatted.
|
* @param {string} filePath - the path to the file to be formatted.
|
||||||
* @param {LintConfig} configuration - an optional configuration. When not passed in, this is read from the .sasjslint file.
|
* @param {LintConfig} configuration - an optional configuration. When not passed in, this is read from the .sasjslint file.
|
||||||
* @returns {Promise<void>} Resolves successfully when the file has been formatted.
|
* @returns {Promise<FormatResult>} Resolves successfully when the file has been formatted.
|
||||||
*/
|
*/
|
||||||
export const formatFile = async (
|
export const formatFile = async (
|
||||||
filePath: string,
|
filePath: string,
|
||||||
configuration?: LintConfig
|
configuration?: LintConfig
|
||||||
) => {
|
): Promise<FormatResult> => {
|
||||||
const config = configuration || (await getLintConfig())
|
const config = configuration || (await getLintConfig())
|
||||||
|
const diagnosticsBeforeFormat = await lintFile(filePath)
|
||||||
|
const diagnosticsCountBeforeFormat = diagnosticsBeforeFormat.length
|
||||||
|
|
||||||
const text = await readFile(filePath)
|
const text = await readFile(filePath)
|
||||||
|
|
||||||
const formattedText = processText(text, config)
|
const formattedText = processText(text, config)
|
||||||
|
|
||||||
await createFile(filePath, formattedText)
|
await createFile(filePath, formattedText)
|
||||||
|
|
||||||
|
const diagnosticsAfterFormat = await lintFile(filePath)
|
||||||
|
const diagnosticsCountAfterFormat = diagnosticsAfterFormat.length
|
||||||
|
|
||||||
|
const fixedDiagnosticsCount =
|
||||||
|
diagnosticsCountBeforeFormat - diagnosticsCountAfterFormat
|
||||||
|
|
||||||
|
const updatedFilePaths: string[] = []
|
||||||
|
|
||||||
|
if (fixedDiagnosticsCount) {
|
||||||
|
updatedFilePaths.push(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
updatedFilePaths,
|
||||||
|
fixedDiagnosticsCount,
|
||||||
|
unfixedDiagnostics: diagnosticsAfterFormat
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,23 +6,39 @@ import {
|
|||||||
deleteFolder,
|
deleteFolder,
|
||||||
readFile
|
readFile
|
||||||
} from '@sasjs/utils/file'
|
} from '@sasjs/utils/file'
|
||||||
|
import { Diagnostic, LintConfig } from '../types'
|
||||||
|
|
||||||
describe('formatFolder', () => {
|
describe('formatFolder', () => {
|
||||||
it('should fix linting issues in a given folder', async () => {
|
it('should fix linting issues in a given folder', async () => {
|
||||||
const content = `%macro somemacro(); \n%put 'hello';\n%mend;`
|
const content = `%macro somemacro(); \n%put 'hello';\n%mend;`
|
||||||
const expectedContent = `/**\n @file\n @brief <Your brief here>\n <h4> SAS Macros </h4>\n**/\n%macro somemacro();\n%put 'hello';\n%mend somemacro;`
|
const expectedContent = `/**\n @file\n @brief <Your brief here>\n <h4> SAS Macros </h4>\n**/\n%macro somemacro();\n%put 'hello';\n%mend somemacro;`
|
||||||
|
const expectedResult = {
|
||||||
|
updatedFilePaths: [
|
||||||
|
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas')
|
||||||
|
],
|
||||||
|
fixedDiagnosticsCount: 3,
|
||||||
|
unfixedDiagnostics: new Map<string, Diagnostic[]>([
|
||||||
|
[
|
||||||
|
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas'),
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
])
|
||||||
|
}
|
||||||
await createFolder(path.join(__dirname, 'format-folder-test'))
|
await createFolder(path.join(__dirname, 'format-folder-test'))
|
||||||
await createFile(
|
await createFile(
|
||||||
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas'),
|
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas'),
|
||||||
content
|
content
|
||||||
)
|
)
|
||||||
|
|
||||||
await formatFolder(path.join(__dirname, 'format-folder-test'))
|
const result = await formatFolder(
|
||||||
const result = await readFile(
|
path.join(__dirname, 'format-folder-test')
|
||||||
|
)
|
||||||
|
const formattedContent = await readFile(
|
||||||
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas')
|
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas')
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(result).toEqual(expectedContent)
|
expect(formattedContent).toEqual(expectedContent)
|
||||||
|
expect(result).toEqual(expectedResult)
|
||||||
|
|
||||||
await deleteFolder(path.join(__dirname, 'format-folder-test'))
|
await deleteFolder(path.join(__dirname, 'format-folder-test'))
|
||||||
})
|
})
|
||||||
@@ -30,6 +46,29 @@ describe('formatFolder', () => {
|
|||||||
it('should fix linting issues in subfolders of a given folder', async () => {
|
it('should fix linting issues in subfolders of a given folder', async () => {
|
||||||
const content = `%macro somemacro(); \n%put 'hello';\n%mend;`
|
const content = `%macro somemacro(); \n%put 'hello';\n%mend;`
|
||||||
const expectedContent = `/**\n @file\n @brief <Your brief here>\n <h4> SAS Macros </h4>\n**/\n%macro somemacro();\n%put 'hello';\n%mend somemacro;`
|
const expectedContent = `/**\n @file\n @brief <Your brief here>\n <h4> SAS Macros </h4>\n**/\n%macro somemacro();\n%put 'hello';\n%mend somemacro;`
|
||||||
|
const expectedResult = {
|
||||||
|
updatedFilePaths: [
|
||||||
|
path.join(
|
||||||
|
__dirname,
|
||||||
|
'format-folder-test',
|
||||||
|
'subfolder',
|
||||||
|
'format-folder-test.sas'
|
||||||
|
)
|
||||||
|
],
|
||||||
|
fixedDiagnosticsCount: 3,
|
||||||
|
unfixedDiagnostics: new Map<string, Diagnostic[]>([
|
||||||
|
[
|
||||||
|
path.join(
|
||||||
|
__dirname,
|
||||||
|
'format-folder-test',
|
||||||
|
'subfolder',
|
||||||
|
'format-folder-test.sas'
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
await createFolder(path.join(__dirname, 'format-folder-test'))
|
await createFolder(path.join(__dirname, 'format-folder-test'))
|
||||||
await createFolder(path.join(__dirname, 'subfolder'))
|
await createFolder(path.join(__dirname, 'subfolder'))
|
||||||
await createFile(
|
await createFile(
|
||||||
@@ -42,8 +81,10 @@ describe('formatFolder', () => {
|
|||||||
content
|
content
|
||||||
)
|
)
|
||||||
|
|
||||||
await formatFolder(path.join(__dirname, 'format-folder-test'))
|
const result = await formatFolder(
|
||||||
const result = await readFile(
|
path.join(__dirname, 'format-folder-test')
|
||||||
|
)
|
||||||
|
const formattedContent = await readFile(
|
||||||
path.join(
|
path.join(
|
||||||
__dirname,
|
__dirname,
|
||||||
'format-folder-test',
|
'format-folder-test',
|
||||||
@@ -52,7 +93,135 @@ describe('formatFolder', () => {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(result).toEqual(expectedContent)
|
expect(result).toEqual(expectedResult)
|
||||||
|
expect(formattedContent).toEqual(expectedContent)
|
||||||
|
|
||||||
|
await deleteFolder(path.join(__dirname, 'format-folder-test'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should use a custom configuration when provided', async () => {
|
||||||
|
const content = `%macro somemacro(); \n%put 'hello';\n%mend;`
|
||||||
|
const expectedContent = `/**\n @file\n @brief <Your brief here>\n <h4> SAS Macros </h4>\n**/\n%macro somemacro();\n%put 'hello';\n%mend somemacro;`
|
||||||
|
const expectedResult = {
|
||||||
|
updatedFilePaths: [
|
||||||
|
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas')
|
||||||
|
],
|
||||||
|
fixedDiagnosticsCount: 3,
|
||||||
|
unfixedDiagnostics: new Map<string, Diagnostic[]>([
|
||||||
|
[
|
||||||
|
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas'),
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
])
|
||||||
|
}
|
||||||
|
await createFolder(path.join(__dirname, 'format-folder-test'))
|
||||||
|
await createFile(
|
||||||
|
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas'),
|
||||||
|
content
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = await formatFolder(
|
||||||
|
path.join(__dirname, 'format-folder-test'),
|
||||||
|
new LintConfig({
|
||||||
|
lineEndings: 'crlf',
|
||||||
|
hasMacroNameInMend: false,
|
||||||
|
hasDoxygenHeader: true,
|
||||||
|
noTrailingSpaces: true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const formattedContent = await readFile(
|
||||||
|
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas')
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(formattedContent).toEqual(expectedContent)
|
||||||
|
expect(result).toEqual(expectedResult)
|
||||||
|
|
||||||
|
await deleteFolder(path.join(__dirname, 'format-folder-test'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fix linting issues in subfolders of a given folder', async () => {
|
||||||
|
const content = `%macro somemacro(); \n%put 'hello';\n%mend;`
|
||||||
|
const expectedContent = `/**\n @file\n @brief <Your brief here>\n <h4> SAS Macros </h4>\n**/\n%macro somemacro();\n%put 'hello';\n%mend somemacro;`
|
||||||
|
const expectedResult = {
|
||||||
|
updatedFilePaths: [
|
||||||
|
path.join(
|
||||||
|
__dirname,
|
||||||
|
'format-folder-test',
|
||||||
|
'subfolder',
|
||||||
|
'format-folder-test.sas'
|
||||||
|
)
|
||||||
|
],
|
||||||
|
fixedDiagnosticsCount: 3,
|
||||||
|
unfixedDiagnostics: new Map<string, Diagnostic[]>([
|
||||||
|
[
|
||||||
|
path.join(
|
||||||
|
__dirname,
|
||||||
|
'format-folder-test',
|
||||||
|
'subfolder',
|
||||||
|
'format-folder-test.sas'
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
await createFolder(path.join(__dirname, 'format-folder-test'))
|
||||||
|
await createFolder(path.join(__dirname, 'subfolder'))
|
||||||
|
await createFile(
|
||||||
|
path.join(
|
||||||
|
__dirname,
|
||||||
|
'format-folder-test',
|
||||||
|
'subfolder',
|
||||||
|
'format-folder-test.sas'
|
||||||
|
),
|
||||||
|
content
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = await formatFolder(
|
||||||
|
path.join(__dirname, 'format-folder-test')
|
||||||
|
)
|
||||||
|
const formattedContent = await readFile(
|
||||||
|
path.join(
|
||||||
|
__dirname,
|
||||||
|
'format-folder-test',
|
||||||
|
'subfolder',
|
||||||
|
'format-folder-test.sas'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result).toEqual(expectedResult)
|
||||||
|
expect(formattedContent).toEqual(expectedContent)
|
||||||
|
|
||||||
|
await deleteFolder(path.join(__dirname, 'format-folder-test'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not update any files when there are no violations', async () => {
|
||||||
|
const content = `/**\n @file\n @brief <Your brief here>\n <h4> SAS Macros </h4>\n**/\n%macro somemacro();\n%put 'hello';\n%mend somemacro;`
|
||||||
|
const expectedResult = {
|
||||||
|
updatedFilePaths: [],
|
||||||
|
fixedDiagnosticsCount: 0,
|
||||||
|
unfixedDiagnostics: new Map<string, Diagnostic[]>([
|
||||||
|
[
|
||||||
|
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas'),
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
])
|
||||||
|
}
|
||||||
|
await createFolder(path.join(__dirname, 'format-folder-test'))
|
||||||
|
await createFile(
|
||||||
|
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas'),
|
||||||
|
content
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = await formatFolder(
|
||||||
|
path.join(__dirname, 'format-folder-test')
|
||||||
|
)
|
||||||
|
const formattedContent = await readFile(
|
||||||
|
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas')
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(formattedContent).toEqual(content)
|
||||||
|
expect(result).toEqual(expectedResult)
|
||||||
|
|
||||||
await deleteFolder(path.join(__dirname, 'format-folder-test'))
|
await deleteFolder(path.join(__dirname, 'format-folder-test'))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { listSubFoldersInFolder } from '@sasjs/utils/file'
|
import { listSubFoldersInFolder } from '@sasjs/utils/file'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import { lintFolder } from '../lint'
|
||||||
|
import { FormatResult } from '../types'
|
||||||
import { LintConfig } from '../types/LintConfig'
|
import { LintConfig } from '../types/LintConfig'
|
||||||
import { asyncForEach } from '../utils/asyncForEach'
|
import { asyncForEach } from '../utils/asyncForEach'
|
||||||
import { getLintConfig } from '../utils/getLintConfig'
|
import { getLintConfig } from '../utils/getLintConfig'
|
||||||
@@ -19,13 +21,18 @@ const excludeFolders = [
|
|||||||
* Automatically formats all SAS files in the folder at the given path.
|
* Automatically formats all SAS files in the folder at the given path.
|
||||||
* @param {string} folderPath - the path to the folder to be formatted.
|
* @param {string} folderPath - the path to the folder to be formatted.
|
||||||
* @param {LintConfig} configuration - an optional configuration. When not passed in, this is read from the .sasjslint file.
|
* @param {LintConfig} configuration - an optional configuration. When not passed in, this is read from the .sasjslint file.
|
||||||
* @returns {Promise<void>} Resolves successfully when all SAS files in the given folder have been formatted.
|
* @returns {Promise<FormatResult>} Resolves successfully when all SAS files in the given folder have been formatted.
|
||||||
*/
|
*/
|
||||||
export const formatFolder = async (
|
export const formatFolder = async (
|
||||||
folderPath: string,
|
folderPath: string,
|
||||||
configuration?: LintConfig
|
configuration?: LintConfig
|
||||||
) => {
|
): Promise<FormatResult> => {
|
||||||
const config = configuration || (await getLintConfig())
|
const config = configuration || (await getLintConfig())
|
||||||
|
const diagnosticsBeforeFormat = await lintFolder(folderPath)
|
||||||
|
const diagnosticsCountBeforeFormat = Array.from(
|
||||||
|
diagnosticsBeforeFormat.values()
|
||||||
|
).reduce((a, b) => a + b.length, 0)
|
||||||
|
|
||||||
const fileNames = await listSasFiles(folderPath)
|
const fileNames = await listSasFiles(folderPath)
|
||||||
await asyncForEach(fileNames, async (fileName) => {
|
await asyncForEach(fileNames, async (fileName) => {
|
||||||
const filePath = path.join(folderPath, fileName)
|
const filePath = path.join(folderPath, fileName)
|
||||||
@@ -39,4 +46,29 @@ export const formatFolder = async (
|
|||||||
await asyncForEach(subFolders, async (subFolder) => {
|
await asyncForEach(subFolders, async (subFolder) => {
|
||||||
await formatFolder(path.join(folderPath, subFolder), config)
|
await formatFolder(path.join(folderPath, subFolder), config)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const diagnosticsAfterFormat = await lintFolder(folderPath)
|
||||||
|
const diagnosticsCountAfterFormat = Array.from(
|
||||||
|
diagnosticsAfterFormat.values()
|
||||||
|
).reduce((a, b) => a + b.length, 0)
|
||||||
|
|
||||||
|
const fixedDiagnosticsCount =
|
||||||
|
diagnosticsCountBeforeFormat - diagnosticsCountAfterFormat
|
||||||
|
|
||||||
|
const updatedFilePaths: string[] = []
|
||||||
|
|
||||||
|
Array.from(diagnosticsBeforeFormat.keys()).forEach((filePath) => {
|
||||||
|
const diagnosticsBefore = diagnosticsBeforeFormat.get(filePath) || []
|
||||||
|
const diagnosticsAfter = diagnosticsAfterFormat.get(filePath) || []
|
||||||
|
|
||||||
|
if (diagnosticsBefore.length !== diagnosticsAfter.length) {
|
||||||
|
updatedFilePaths.push(filePath)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
updatedFilePaths,
|
||||||
|
fixedDiagnosticsCount,
|
||||||
|
unfixedDiagnostics: diagnosticsAfterFormat
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
|
import { lintFolder } from '../lint/lintFolder'
|
||||||
|
import { FormatResult } from '../types/FormatResult'
|
||||||
import { getProjectRoot } from '../utils/getProjectRoot'
|
import { getProjectRoot } from '../utils/getProjectRoot'
|
||||||
import { formatFolder } from './formatFolder'
|
import { formatFolder } from './formatFolder'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Automatically formats all SAS files in the current project.
|
* Automatically formats all SAS files in the current project.
|
||||||
* @returns {Promise<void>} Resolves successfully when all SAS files in the current project have been formatted.
|
* @returns {Promise<FormatResult>} Resolves successfully when all SAS files in the current project have been formatted.
|
||||||
*/
|
*/
|
||||||
export const formatProject = async () => {
|
export const formatProject = async (): Promise<FormatResult> => {
|
||||||
const projectRoot =
|
const projectRoot =
|
||||||
(await getProjectRoot()) || process.projectDir || process.currentDir
|
(await getProjectRoot()) || process.projectDir || process.currentDir
|
||||||
if (!projectRoot) {
|
if (!projectRoot) {
|
||||||
throw new Error('SASjs Project Root was not found.')
|
throw new Error('SASjs Project Root was not found.')
|
||||||
}
|
}
|
||||||
|
|
||||||
return await formatFolder(projectRoot)
|
return await formatFolder(projectRoot)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ describe('hasDoxygenHeader - test', () => {
|
|||||||
it('should return an array with a single diagnostic when the file is undefined', () => {
|
it('should return an array with a single diagnostic when the file is undefined', () => {
|
||||||
const content = undefined
|
const content = undefined
|
||||||
|
|
||||||
expect(hasDoxygenHeader.test((content as unknown) as string)).toEqual([
|
expect(hasDoxygenHeader.test(content as unknown as string)).toEqual([
|
||||||
{
|
{
|
||||||
message: 'File missing Doxygen header',
|
message: 'File missing Doxygen header',
|
||||||
lineNumber: 1,
|
lineNumber: 1,
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ describe('hasMacroNameInMend - test', () => {
|
|||||||
it('should return an empty array when the file is undefined', () => {
|
it('should return an empty array when the file is undefined', () => {
|
||||||
const content = undefined
|
const content = undefined
|
||||||
|
|
||||||
expect(hasMacroNameInMend.test((content as unknown) as string)).toEqual([])
|
expect(hasMacroNameInMend.test(content as unknown as string)).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('nestedMacros', () => {
|
describe('nestedMacros', () => {
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ describe('hasMacroParentheses', () => {
|
|||||||
it('should return an empty array when the file is undefined', () => {
|
it('should return an empty array when the file is undefined', () => {
|
||||||
const content = undefined
|
const content = undefined
|
||||||
|
|
||||||
expect(hasMacroParentheses.test((content as unknown) as string)).toEqual([])
|
expect(hasMacroParentheses.test(content as unknown as string)).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('with extra spaces and comments', () => {
|
describe('with extra spaces and comments', () => {
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ describe('noNestedMacros', () => {
|
|||||||
it('should return an empty array when the file is undefined', () => {
|
it('should return an empty array when the file is undefined', () => {
|
||||||
const content = undefined
|
const content = undefined
|
||||||
|
|
||||||
expect(noNestedMacros.test((content as unknown) as string)).toEqual([])
|
expect(noNestedMacros.test(content as unknown as string)).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should use the configured line ending while testing content', () => {
|
it('should use the configured line ending while testing content', () => {
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ export { maxLineLength } from './maxLineLength'
|
|||||||
export { noEncodedPasswords } from './noEncodedPasswords'
|
export { noEncodedPasswords } from './noEncodedPasswords'
|
||||||
export { noTabIndentation } from './noTabIndentation'
|
export { noTabIndentation } from './noTabIndentation'
|
||||||
export { noTrailingSpaces } from './noTrailingSpaces'
|
export { noTrailingSpaces } from './noTrailingSpaces'
|
||||||
|
export { strictMacroDefinition } from './strictMacroDefinition'
|
||||||
|
|||||||
88
src/rules/line/strictMacroDefinition.spec.ts
Normal file
88
src/rules/line/strictMacroDefinition.spec.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { LintConfig, Severity } from '../../types'
|
||||||
|
import { strictMacroDefinition } from './strictMacroDefinition'
|
||||||
|
|
||||||
|
describe('strictMacroDefinition', () => {
|
||||||
|
it('should return an empty array when the line has correct macro definition syntax', () => {
|
||||||
|
const line = '%macro somemacro;'
|
||||||
|
expect(strictMacroDefinition.test(line, 1)).toEqual([])
|
||||||
|
|
||||||
|
const line2 = '%macro somemacro();'
|
||||||
|
expect(strictMacroDefinition.test(line2, 1)).toEqual([])
|
||||||
|
|
||||||
|
const line3 = '%macro somemacro(var1);'
|
||||||
|
expect(strictMacroDefinition.test(line3, 1)).toEqual([])
|
||||||
|
|
||||||
|
const line4 = '%macro somemacro/minoperator;'
|
||||||
|
expect(strictMacroDefinition.test(line4, 1)).toEqual([])
|
||||||
|
|
||||||
|
const line5 = '%macro somemacro /minoperator;'
|
||||||
|
expect(strictMacroDefinition.test(line5, 1)).toEqual([])
|
||||||
|
|
||||||
|
const line6 = '%macro somemacro(var1, var2)/minoperator;'
|
||||||
|
expect(strictMacroDefinition.test(line6, 1)).toEqual([])
|
||||||
|
|
||||||
|
const line7 =
|
||||||
|
' /* Some Comment */ %macro somemacro(var1, var2) /minoperator ; /* Some Comment */'
|
||||||
|
expect(strictMacroDefinition.test(line7, 1)).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return an array with a single diagnostic when Macro definition has space in param', () => {
|
||||||
|
const line = '%macro somemacro(va r1);'
|
||||||
|
expect(strictMacroDefinition.test(line, 1)).toEqual([
|
||||||
|
{
|
||||||
|
message: `Param 'va r1' cannot have space`,
|
||||||
|
lineNumber: 1,
|
||||||
|
startColumnNumber: 18,
|
||||||
|
endColumnNumber: 22,
|
||||||
|
severity: Severity.Warning
|
||||||
|
}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return an array with a two diagnostics when Macro definition has space in param', () => {
|
||||||
|
const line = '%macro somemacro(var1, var 2, v ar3, var4);'
|
||||||
|
expect(strictMacroDefinition.test(line, 1)).toEqual([
|
||||||
|
{
|
||||||
|
message: `Param 'var 2' cannot have space`,
|
||||||
|
lineNumber: 1,
|
||||||
|
startColumnNumber: 24,
|
||||||
|
endColumnNumber: 28,
|
||||||
|
severity: Severity.Warning
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: `Param 'v ar3' cannot have space`,
|
||||||
|
lineNumber: 1,
|
||||||
|
startColumnNumber: 31,
|
||||||
|
endColumnNumber: 35,
|
||||||
|
severity: Severity.Warning
|
||||||
|
}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return an array with a single diagnostic when Macro definition has invalid option', () => {
|
||||||
|
const line = '%macro somemacro(var1, var2)/minXoperator;'
|
||||||
|
expect(strictMacroDefinition.test(line, 1)).toEqual([
|
||||||
|
{
|
||||||
|
message: `Option 'minXoperator' is not valid`,
|
||||||
|
lineNumber: 1,
|
||||||
|
startColumnNumber: 30,
|
||||||
|
endColumnNumber: 41,
|
||||||
|
severity: Severity.Warning
|
||||||
|
}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return an array with a two diagnostics when Macro definition has invalid options', () => {
|
||||||
|
const line =
|
||||||
|
'%macro somemacro(var1, var2)/ store invalidoption secure ;'
|
||||||
|
expect(strictMacroDefinition.test(line, 1)).toEqual([
|
||||||
|
{
|
||||||
|
message: `Option 'invalidoption' is not valid`,
|
||||||
|
lineNumber: 1,
|
||||||
|
startColumnNumber: 39,
|
||||||
|
endColumnNumber: 51,
|
||||||
|
severity: Severity.Warning
|
||||||
|
}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
88
src/rules/line/strictMacroDefinition.ts
Normal file
88
src/rules/line/strictMacroDefinition.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { Diagnostic } from '../../types/Diagnostic'
|
||||||
|
import { LintConfig } from '../../types'
|
||||||
|
import { LineLintRule } from '../../types/LintRule'
|
||||||
|
import { LintRuleType } from '../../types/LintRuleType'
|
||||||
|
import { Severity } from '../../types/Severity'
|
||||||
|
import { parseMacros } from '../../utils/parseMacros'
|
||||||
|
|
||||||
|
const name = 'strictMacroDefinition'
|
||||||
|
const description = 'Enforce strictly rules of macro definition syntax.'
|
||||||
|
const message = 'Incorrent Macro Definition Syntax'
|
||||||
|
|
||||||
|
const validOptions = [
|
||||||
|
'CMD',
|
||||||
|
'DES',
|
||||||
|
'MINDELIMITER',
|
||||||
|
'MINOPERATOR',
|
||||||
|
'NOMINOPERATOR',
|
||||||
|
'PARMBUFF',
|
||||||
|
'SECURE',
|
||||||
|
'NOSECURE',
|
||||||
|
'STMT',
|
||||||
|
'SOURCE',
|
||||||
|
'SRC',
|
||||||
|
'STORE'
|
||||||
|
]
|
||||||
|
|
||||||
|
const test = (value: string, lineNumber: number) => {
|
||||||
|
const diagnostics: Diagnostic[] = []
|
||||||
|
|
||||||
|
const macros = parseMacros(value)
|
||||||
|
const declaration = macros[0]?.declaration
|
||||||
|
if (!declaration) return []
|
||||||
|
|
||||||
|
const regExpParams = new RegExp(/\((.*?)\)/)
|
||||||
|
const regExpParamsResult = regExpParams.exec(declaration)
|
||||||
|
|
||||||
|
let _declaration = declaration
|
||||||
|
if (regExpParamsResult) {
|
||||||
|
const paramsPresent = regExpParamsResult[1]
|
||||||
|
|
||||||
|
const paramsTrimmed = paramsPresent.trim()
|
||||||
|
const params = paramsTrimmed.split(',')
|
||||||
|
params.forEach((param) => {
|
||||||
|
const trimedParam = param.split('=')[0].trim()
|
||||||
|
if (trimedParam.includes(' ')) {
|
||||||
|
diagnostics.push({
|
||||||
|
message: `Param '${trimedParam}' cannot have space`,
|
||||||
|
lineNumber,
|
||||||
|
startColumnNumber: value.indexOf(trimedParam) + 1,
|
||||||
|
endColumnNumber: value.indexOf(trimedParam) + trimedParam.length,
|
||||||
|
severity: Severity.Warning
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
_declaration = declaration.split(`(${paramsPresent})`)[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
const optionsPresent = _declaration.split('/')?.[1]?.trim().split(' ')
|
||||||
|
|
||||||
|
optionsPresent
|
||||||
|
?.filter((o) => !!o)
|
||||||
|
.forEach((option) => {
|
||||||
|
const trimmedOption = option.trim()
|
||||||
|
if (!validOptions.includes(trimmedOption.toUpperCase())) {
|
||||||
|
diagnostics.push({
|
||||||
|
message: `Option '${trimmedOption}' is not valid`,
|
||||||
|
lineNumber,
|
||||||
|
startColumnNumber: value.indexOf(trimmedOption) + 1,
|
||||||
|
endColumnNumber: value.indexOf(trimmedOption) + trimmedOption.length,
|
||||||
|
severity: Severity.Warning
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return diagnostics
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lint rule that checks if a line has followed syntax for macro definition
|
||||||
|
*/
|
||||||
|
export const strictMacroDefinition: LineLintRule = {
|
||||||
|
type: LintRuleType.Line,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
message,
|
||||||
|
test
|
||||||
|
}
|
||||||
10
src/types/FormatResult.ts
Normal file
10
src/types/FormatResult.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Diagnostic } from './Diagnostic'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the result of a format operation on a file, folder or project.
|
||||||
|
*/
|
||||||
|
export interface FormatResult {
|
||||||
|
updatedFilePaths: string[]
|
||||||
|
fixedDiagnosticsCount: number
|
||||||
|
unfixedDiagnostics: Map<string, Diagnostic[]> | Diagnostic[]
|
||||||
|
}
|
||||||
@@ -10,7 +10,8 @@ import {
|
|||||||
maxLineLength,
|
maxLineLength,
|
||||||
noEncodedPasswords,
|
noEncodedPasswords,
|
||||||
noTabIndentation,
|
noTabIndentation,
|
||||||
noTrailingSpaces
|
noTrailingSpaces,
|
||||||
|
strictMacroDefinition
|
||||||
} from '../rules/line'
|
} from '../rules/line'
|
||||||
import { lowerCaseFileNames, noSpacesInFileNames } from '../rules/path'
|
import { lowerCaseFileNames, noSpacesInFileNames } from '../rules/path'
|
||||||
import { LineEndings } from './LineEndings'
|
import { LineEndings } from './LineEndings'
|
||||||
@@ -90,5 +91,9 @@ export class LintConfig {
|
|||||||
if (json?.hasMacroParentheses) {
|
if (json?.hasMacroParentheses) {
|
||||||
this.fileLintRules.push(hasMacroParentheses)
|
this.fileLintRules.push(hasMacroParentheses)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (json?.strictMacroDefinition) {
|
||||||
|
this.lineLintRules.push(strictMacroDefinition)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from './Diagnostic'
|
export * from './Diagnostic'
|
||||||
|
export * from './FormatResult'
|
||||||
export * from './LintConfig'
|
export * from './LintConfig'
|
||||||
export * from './LintRule'
|
export * from './LintRule'
|
||||||
export * from './LintRuleType'
|
export * from './LintRuleType'
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { LintConfig } from '../types/LintConfig'
|
|||||||
import { getLintConfig } from './getLintConfig'
|
import { getLintConfig } from './getLintConfig'
|
||||||
|
|
||||||
const expectedFileLintRulesCount = 4
|
const expectedFileLintRulesCount = 4
|
||||||
const expectedLineLintRulesCount = 5
|
const expectedLineLintRulesCount = 6
|
||||||
const expectedPathLintRulesCount = 2
|
const expectedPathLintRulesCount = 2
|
||||||
|
|
||||||
describe('getLintConfig', () => {
|
describe('getLintConfig', () => {
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ export const DefaultLintConfiguration = {
|
|||||||
indentationMultiple: 2,
|
indentationMultiple: 2,
|
||||||
hasMacroNameInMend: true,
|
hasMacroNameInMend: true,
|
||||||
noNestedMacros: true,
|
noNestedMacros: true,
|
||||||
hasMacroParentheses: true
|
hasMacroParentheses: true,
|
||||||
|
strictMacroDefinition: true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -25,6 +25,76 @@ describe('parseMacros', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should return an array with a single macro having parameters', () => {
|
||||||
|
const text = `%macro test(var,sum);
|
||||||
|
%put 'hello';
|
||||||
|
%mend`
|
||||||
|
|
||||||
|
const macros = parseMacros(text, new LintConfig())
|
||||||
|
|
||||||
|
expect(macros.length).toEqual(1)
|
||||||
|
expect(macros).toContainEqual({
|
||||||
|
name: 'test',
|
||||||
|
declarationLine: '%macro test(var,sum);',
|
||||||
|
terminationLine: '%mend',
|
||||||
|
declaration: '%macro test(var,sum)',
|
||||||
|
termination: '%mend',
|
||||||
|
startLineNumber: 1,
|
||||||
|
endLineNumber: 3,
|
||||||
|
parentMacro: '',
|
||||||
|
hasMacroNameInMend: false,
|
||||||
|
hasParentheses: false,
|
||||||
|
mismatchedMendMacroName: ''
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return an array with a single macro having PARMBUFF option', () => {
|
||||||
|
const text = `%macro test/parmbuff;
|
||||||
|
%put 'hello';
|
||||||
|
%mend`
|
||||||
|
|
||||||
|
const macros = parseMacros(text, new LintConfig())
|
||||||
|
|
||||||
|
expect(macros.length).toEqual(1)
|
||||||
|
expect(macros).toContainEqual({
|
||||||
|
name: 'test',
|
||||||
|
declarationLine: '%macro test/parmbuff;',
|
||||||
|
terminationLine: '%mend',
|
||||||
|
declaration: '%macro test/parmbuff',
|
||||||
|
termination: '%mend',
|
||||||
|
startLineNumber: 1,
|
||||||
|
endLineNumber: 3,
|
||||||
|
parentMacro: '',
|
||||||
|
hasMacroNameInMend: false,
|
||||||
|
hasParentheses: false,
|
||||||
|
mismatchedMendMacroName: ''
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return an array with a single macro having paramerter & SOURCE option', () => {
|
||||||
|
const text = `/* commentary */ %macro foobar(arg) /store source
|
||||||
|
des="This macro does not do much";
|
||||||
|
%put 'hello';
|
||||||
|
%mend`
|
||||||
|
|
||||||
|
const macros = parseMacros(text, new LintConfig())
|
||||||
|
|
||||||
|
expect(macros.length).toEqual(1)
|
||||||
|
expect(macros).toContainEqual({
|
||||||
|
name: 'foobar',
|
||||||
|
declarationLine: '/* commentary */ %macro foobar(arg) /store source',
|
||||||
|
terminationLine: '%mend',
|
||||||
|
declaration: '%macro foobar(arg) /store source',
|
||||||
|
termination: '%mend',
|
||||||
|
startLineNumber: 1,
|
||||||
|
endLineNumber: 4,
|
||||||
|
parentMacro: '',
|
||||||
|
hasMacroNameInMend: false,
|
||||||
|
hasParentheses: false,
|
||||||
|
mismatchedMendMacroName: ''
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('should return an array with multiple macros', () => {
|
it('should return an array with multiple macros', () => {
|
||||||
const text = `%macro foo;
|
const text = `%macro foo;
|
||||||
%put 'foo';
|
%put 'foo';
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export const parseMacros = (text: string, config?: LintConfig): Macro[] => {
|
|||||||
const name = trimmedStatement
|
const name = trimmedStatement
|
||||||
.slice(7, trimmedStatement.length)
|
.slice(7, trimmedStatement.length)
|
||||||
.trim()
|
.trim()
|
||||||
|
.split('/')[0]
|
||||||
.split('(')[0]
|
.split('(')[0]
|
||||||
macroStack.push({
|
macroStack.push({
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -6,11 +6,14 @@ export const trimComments = (
|
|||||||
|
|
||||||
if (commentStarted || trimmed.startsWith('/*')) {
|
if (commentStarted || trimmed.startsWith('/*')) {
|
||||||
const parts = trimmed.split('*/')
|
const parts = trimmed.split('*/')
|
||||||
if (parts.length > 1) {
|
if (parts.length === 2) {
|
||||||
return {
|
return {
|
||||||
statement: (parts.pop() as string).trim(),
|
statement: (parts.pop() as string).trim(),
|
||||||
commentStarted: false
|
commentStarted: false
|
||||||
}
|
}
|
||||||
|
} else if (parts.length > 2) {
|
||||||
|
parts.shift()
|
||||||
|
return trimComments(parts.join('*/'), false)
|
||||||
} else {
|
} else {
|
||||||
return { statement: '', commentStarted: true }
|
return { statement: '', commentStarted: true }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user