@sasjs/lint 4.1.3 → 4.2.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/.github/CONTRIBUTING.md +111 -36
- package/.github/workflows/audit-watch.yml +68 -0
- package/.github/workflows/build.yml +20 -5
- package/.github/workflows/publish.yml +71 -5
- package/README.md +11 -13
- package/package.json +3 -3
- package/rules/file/hasDoxygenHeader.js +1 -3
- package/rules/file/hasDoxygenHeader.js.map +1 -1
- package/rules/file/hasMacroNameInMend.js +3 -3
- package/rules/file/hasMacroNameInMend.js.map +1 -1
- package/rules/file/lineEndings.d.ts +2 -1
- package/rules/file/lineEndings.js +26 -3
- package/rules/file/lineEndings.js.map +1 -1
- package/rules/file/noNestedMacros.js +2 -3
- package/rules/file/noNestedMacros.js.map +1 -1
- package/sasjslint-schema.json +1 -1
- package/scripts/audit-watch.mjs +249 -0
- package/utils/index.d.ts +1 -0
- package/utils/index.js +1 -0
- package/utils/index.js.map +1 -1
- package/utils/isIgnored.js +3 -3
- package/utils/isIgnored.js.map +1 -1
- package/utils/parseMacros.js +2 -3
- package/utils/parseMacros.js.map +1 -1
- package/utils/splitLines.d.ts +16 -0
- package/utils/splitLines.js +21 -0
- package/utils/splitLines.js.map +1 -0
package/.github/CONTRIBUTING.md
CHANGED
|
@@ -1,61 +1,108 @@
|
|
|
1
1
|
# Contributing
|
|
2
2
|
|
|
3
|
-
Contributions to `@sasjs/lint` are very welcome
|
|
4
|
-
|
|
3
|
+
Contributions to `@sasjs/lint` are very welcome. This repo is a TypeScript library with no SAS dependency - it reads `.sas` files as text - so you can work on every rule, the formatter and the JSON schema with Node alone.
|
|
4
|
+
|
|
5
|
+
Please fill in the pull request template and make sure your changes are covered by tests.
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
Node 22 or later (the `engines` field), and no lifecycle scripts:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
git clone https://github.com/sasjs/lint.git
|
|
13
|
+
cd lint
|
|
14
|
+
npm ci # .npmrc sets ignore-scripts=true, so nothing runs on install
|
|
15
|
+
git config core.hooksPath ./.git-hooks # the prepare script is one of those scripts
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Then:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm test # jest WITH the coverage gate - this is the one CI runs
|
|
22
|
+
npm run lint # prettier --check over src and test
|
|
23
|
+
npx tsc --noEmit # type check
|
|
24
|
+
npm run build # emit ./build, needed by anything that requires the built library
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`npx jest` alone does not apply the coverage threshold, so a change can look green locally and fail CI. Use `npm test`.
|
|
28
|
+
|
|
29
|
+
## Where things live
|
|
30
|
+
|
|
31
|
+
- `src/rules/file/`, `src/rules/line/`, `src/rules/path/` - one module per rule, `<name>.ts` plus `<name>.spec.ts`, each re-exported from its folder's `index.ts`.
|
|
32
|
+
- `src/lint/` - `lintText`, `lintFile`, `lintFolder`, `lintProject`, and `shared.ts` (`processText`, `processFile`, `processLine`).
|
|
33
|
+
- `src/format/` - `formatText`, `formatFile`, `formatFolder`, `formatProject`, and `shared.ts`.
|
|
34
|
+
- `src/types/` - `LintConfig.ts` (the rule registry and defaults), `LintRule.ts` (the File/Line/Path interfaces), `Diagnostic.ts`, `Severity.ts`, `LineEndings.ts`.
|
|
35
|
+
- `src/utils/` - shared helpers: `splitText`, `splitLines`, `getHeaderLinesCount`, `parseMacros`, `parseMacroCalls`, `headerMacros`, `mergeLintConfig`, `parseLintOverride`, `sasMacros`.
|
|
36
|
+
- `sasjslint-schema.json` and `README.md` - the public contract for every setting.
|
|
5
37
|
|
|
6
38
|
## Architecture
|
|
7
39
|
|
|
8
|
-
|
|
40
|
+
There are three types of rule:
|
|
9
41
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
42
|
+
- File rules - run once against the whole file
|
|
43
|
+
- Line rules - run once per line
|
|
44
|
+
- Path rules - run against a file path or name
|
|
13
45
|
|
|
14
|
-
|
|
15
|
-
Please also make sure to export it from the `index.ts` file in that folder.
|
|
46
|
+
A rule exports an object conforming to the `LintRule` interface: `type`, `name`, `description` and `message` at minimum. File, line and path rules also have `test`, a function returning `Diagnostic[]` for the consuming application to display. Rules that can repair what they find also have `fix`, which takes the original content - a line or the whole file - and returns it transformed. Path rules have no `fix`.
|
|
16
47
|
|
|
17
|
-
|
|
18
|
-
This means it will have a `type`, `name`, `description` and `message` at a minimum.
|
|
48
|
+
`test` reports positions as `lineNumber`, `startColumnNumber` and `endColumnNumber`. Severity comes from `config.severityLevel[name] || Severity.Warning`, so a rule must read its own name key.
|
|
19
49
|
|
|
20
|
-
|
|
21
|
-
This is a function that will run a piece of logic against the supplied item and produce an array of `Diagnostic` objects.
|
|
22
|
-
These objects can be used in the consuming application to display the problems in the code.
|
|
50
|
+
## Adding a rule
|
|
23
51
|
|
|
24
|
-
|
|
25
|
-
These rules will also have a `fix` property, which is a function that takes the original content -
|
|
26
|
-
either a line or the entire contents of a file, and returns the transformed content with the fix applied.
|
|
52
|
+
Moving the check into an existing rule versus adding a new one is a design call. A rule that must be separately switchable cannot be folded into another, because `severityLevel` is per rule NAME - one name means one opt-out. Check what the neighbouring rule's registration condition is first: a rule that only registers when a key is an explicit non-`off` value drags its whole remit along with any check folded in.
|
|
27
53
|
|
|
28
|
-
|
|
54
|
+
When a new rule is the right answer:
|
|
55
|
+
|
|
56
|
+
1. Add `src/rules/<type>/X.ts` and `X.spec.ts`.
|
|
57
|
+
2. Export it from `src/rules/<type>/index.ts`.
|
|
58
|
+
3. Register it in `src/types/LintConfig.ts`, choosing the default posture:
|
|
59
|
+
- `if (json?.X !== false)` pushes a rule that is ON by default;
|
|
60
|
+
- `if (json?.X)` pushes one that is OFF by default.
|
|
61
|
+
A rule whose `fix` must run during autoformat has to be ON, because the fix only runs for rules present in the config.
|
|
62
|
+
4. Add the key to the README config block and a `### X` section in alphabetical position, with `Default:` and `Severity:`.
|
|
63
|
+
5. Add the schema entries in `sasjslint-schema.json`: the `default` object, the `examples` object, a `properties.X` block, and a `severityLevel.properties.X` entry.
|
|
64
|
+
6. Add specs for both the default-on and the default-off path.
|
|
29
65
|
|
|
30
|
-
|
|
31
|
-
It helps us ensure that our lint rules do what they are intended to do.
|
|
66
|
+
There is no "warn but do not fix" state. A rule with a `fix` either has both warning and repairing active, or is absent and does neither. If your `fix` rewrites files, defaulting it ON changes the output of `sasjs lint fix` and format-on-save for every existing user, so say so in the PR and mark the commit accordingly.
|
|
32
67
|
|
|
33
|
-
|
|
34
|
-
This makes `@sasjs/lint` very easy to unit test, and so there is no excuse for not testing a new rule. :)
|
|
68
|
+
## The three documents that must agree
|
|
35
69
|
|
|
36
|
-
|
|
37
|
-
When modifying an existing rule, ensure that your changes haven't affected existing functionality by running the tests on your machine.
|
|
70
|
+
`DefaultLintConfiguration` (`src/utils/getLintConfig.ts`), the fenced config block in `README.md`, and the `default` object in `sasjslint-schema.json` state the same thing, and they drift. Treat them as one artifact: complete the default object first, then mirror it into both documents. `src/utils/getLintConfig.spec.ts` fails if any of the three disagree, and also enumerates the settings the constructor reads, so a setting added to `LintConfig` but not to the defaults is caught. An absent key must mean the documented default - a project with no `.sasjslint` and a project whose file omits the key must get the same rule set.
|
|
38
71
|
|
|
39
|
-
|
|
72
|
+
## Reading a file: one split for line endings
|
|
40
73
|
|
|
41
|
-
|
|
74
|
+
Readers must not split content on the ending CONFIGURED in `lineEndings`. That value says what the project wants, not what the file contains, and the lint path does not normalise - so a `crlf` split over an LF file returns one string holding the whole file, and column lookups then throw. Use `splitLines` (`src/utils/splitLines.ts`), which splits on `\r\n` or `\n` and drops the carriage returns. Writers still join with the configured ending.
|
|
42
75
|
|
|
43
|
-
|
|
44
|
-
If you are using VS Code for development, you can automatically fix your code to match the style as follows:
|
|
76
|
+
The formatter currently survives the mismatch because `lineEndings.fix` runs before the later rules, but that is not a licence for a reader to assume it: `formatFile` lints before it formats.
|
|
45
77
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
78
|
+
## Formatter rules
|
|
79
|
+
|
|
80
|
+
Only file and line rules that carry a `fix` run during formatting, in the order they were pushed onto the config. `hasDoxygenHeader` is pushed first, so with the default config a `fix` may assume a Doxygen header exists - but it must still return the text unchanged when there is none.
|
|
81
|
+
|
|
82
|
+
A `fix` must be idempotent: format the same file twice and the second pass must change nothing. Formatting a corpus twice and comparing is the quickest way to prove it. A non-idempotent fix shows up as churn and an inflated `fixedDiagnosticsCount`.
|
|
83
|
+
|
|
84
|
+
Two rules can reconcile the same header from opposite sides - `noUndeclaredMacros` adds used-but-undeclared names and `noUnusedMacros` removes declared-but-unused ones - so a removal helper must keep the `<h4> SAS Macros </h4>` heading when the list empties, or the add side loses its place to write.
|
|
85
|
+
|
|
86
|
+
## Testing
|
|
53
87
|
|
|
54
|
-
|
|
88
|
+
Most of the code is pure functions, so there is little to mock and no excuse for not testing a rule. When adding a rule, test the positive and negative scenarios in separate cases; when changing one, run the suite to prove nothing else moved.
|
|
89
|
+
|
|
90
|
+
Two habits catch the failures that plain coverage does not:
|
|
91
|
+
|
|
92
|
+
- **Mutation-check your assertions.** Revert the behaviour one piece at a time and confirm a test fails. An assertion that passes on the broken code is documenting nothing. Watch for vacuous cases: for example, a "does not throw" test over a mis-split file passes while the mis-split still contains the string being searched for, so the fixture has to be shaped to fail.
|
|
93
|
+
- **A negative test must fail for the reason it names.** Assert on the specific message or error, never a bare `toThrow()`.
|
|
94
|
+
|
|
95
|
+
## Regenerating the shipped macro list
|
|
96
|
+
|
|
97
|
+
The macros SAS ships are language data, owned by `@sasjs/sas-language`. Do not hand-write them here. `npm run generate:macros` reads its `macroStatements` and `macroFunctions` groups and writes `src/utils/sasMacros.generated.ts`, which is committed so the published package carries no runtime dependency. `sasMacros.spec.ts` reads the same data independently and fails when the committed list differs, which is the drift guard. Extending that vocabulary is a PR to `@sasjs/sas-language`.
|
|
98
|
+
|
|
99
|
+
## Code style
|
|
100
|
+
|
|
101
|
+
The repo uses `Prettier`. In VS Code, install the Prettier extension and set `editor.formatOnSave` (and `editor.formatOnPaste`) to `true`. Otherwise run `npm run lint:fix` before committing, and `npm run lint` to check.
|
|
55
102
|
|
|
56
103
|
## Git hooks
|
|
57
104
|
|
|
58
|
-
The repo ships its own
|
|
105
|
+
The repo ships its own hooks in [`.git-hooks/`](../.git-hooks):
|
|
59
106
|
|
|
60
107
|
- `pre-commit` - scans staged changes for secrets with gitleaks, and requires the `@nogoo9/gitleaks` devDependency to be installed
|
|
61
108
|
- `commit-msg` - verifies the commit message follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) standard
|
|
@@ -70,6 +117,20 @@ The pre-commit hook runs the gitleaks binary provided by the `@nogoo9/gitleaks`
|
|
|
70
117
|
|
|
71
118
|
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
119
|
|
|
120
|
+
## Commit messages and releases
|
|
121
|
+
|
|
122
|
+
Merging to `main` runs semantic-release: the commit type decides the version, and a release publishes to npm. Only `feat:`, `fix:` and `perf:` cut a release - a `chore:` or `docs:` PR publishes nothing, which is correct rather than a failure.
|
|
123
|
+
|
|
124
|
+
For a squash merge the PR TITLE drives the version, so keep it conventional. Judge the type by what users see: a new diagnostic message, or a formatter that now rewrites files it used to leave alone, is new behaviour and belongs in `feat:` even when it also fixes a defect. Warnings do not move the exit code of `sasjs lint` - only `noEncodedPasswords` ships as an error - so a new warning does not break CI for consumers; a rewritten file might surprise them, and the release notes are where to say so.
|
|
125
|
+
|
|
126
|
+
Mark a genuinely breaking change with `BREAKING CHANGE:` in the commit body, and name the config key that restores the old behaviour.
|
|
127
|
+
|
|
128
|
+
## The audit gate
|
|
129
|
+
|
|
130
|
+
`build.yml` runs `npm audit --omit=dev --audit-level=low`, scoped to production dependencies because the dev tree carries one advisory nothing here can fix (`sprintf-js` through the jest transform chain, with no patched release). A weekly `audit-watch.yml` runs the FULL audit and fails when that exemption stops matching reality - a new dev-tree advisory, the exempted one disappearing, or a watched package publishing a newer version.
|
|
131
|
+
|
|
132
|
+
Do not add a dev-tree advisory to the exemption to get a build green. Prefer removing the dependency; if an exemption is genuinely unavoidable, it needs the reachability argument written down and a guard that closes it, not an allowlist.
|
|
133
|
+
|
|
73
134
|
## CI is the enforcement point
|
|
74
135
|
|
|
75
136
|
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.
|
|
@@ -79,3 +140,17 @@ The [build workflow](./workflows/build.yml) scans the full history for secrets w
|
|
|
79
140
|
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
141
|
|
|
81
142
|
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.
|
|
143
|
+
|
|
144
|
+
## What to work on
|
|
145
|
+
|
|
146
|
+
The formatter applies these fixes today: `%mend` macro names, a Doxygen header template, adding undeclared macros to the `<h4> SAS Macros </h4>` section, removing unused macros from it, trailing spaces, and line endings (including a missing final newline) when [`lineEndings`](../README.md#lineendings) is configured.
|
|
147
|
+
|
|
148
|
+
Fixes not yet implemented, roughly in order of usefulness:
|
|
149
|
+
|
|
150
|
+
- Change tabs to spaces
|
|
151
|
+
- Remove gremlin characters
|
|
152
|
+
- Convert single asterisk comments to block comments
|
|
153
|
+
|
|
154
|
+
Harder, and still under investigation: automatic indentation and code layout.
|
|
155
|
+
|
|
156
|
+
Rules without a fix are equally welcome - a rule that reports something real is worth more than one that silently repairs it wrong. Open an issue describing the SAS construct and what a false positive would look like before you build it, so the heuristic can be argued about cheaply.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
name: Audit Watch
|
|
2
|
+
|
|
3
|
+
# The build gate runs `npm audit --omit=dev`, because the dev tree carries one
|
|
4
|
+
# advisory that cannot be fixed from here (GHSA-hp3w-g68c-fv3c, sprintf-js - see
|
|
5
|
+
# the Security Audit step in build.yml). Nothing in build.yml re-checks the dev
|
|
6
|
+
# tree, so that exemption would otherwise be open-ended. This runs the full audit
|
|
7
|
+
# weekly and fails when the exemption stops matching reality: a new dev-tree
|
|
8
|
+
# advisory appears, an exempted one disappears, or a package named in the
|
|
9
|
+
# exemption publishes a newer release. Any of those files an issue - and only
|
|
10
|
+
# those, because a scheduled job that is red every week trains people to ignore
|
|
11
|
+
# red.
|
|
12
|
+
|
|
13
|
+
on:
|
|
14
|
+
schedule:
|
|
15
|
+
- cron: '0 8 * * 1' # Mondays, 08:00 UTC
|
|
16
|
+
workflow_dispatch:
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
watch:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
issues: write
|
|
24
|
+
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
|
|
28
|
+
- name: Use Node.js
|
|
29
|
+
uses: actions/setup-node@v4
|
|
30
|
+
with:
|
|
31
|
+
node-version: 22
|
|
32
|
+
|
|
33
|
+
- name: Install Dependencies
|
|
34
|
+
run: npm ci
|
|
35
|
+
|
|
36
|
+
- name: Re-check the dev-tree audit exemption
|
|
37
|
+
id: watch
|
|
38
|
+
env:
|
|
39
|
+
AUDIT_WATCH_REPORT: ${{ runner.temp }}/audit-watch-report.md
|
|
40
|
+
run: node scripts/audit-watch.mjs
|
|
41
|
+
|
|
42
|
+
- name: File an issue
|
|
43
|
+
# `always()` because the watched step has already failed, which would
|
|
44
|
+
# otherwise skip this one.
|
|
45
|
+
if: always() && steps.watch.outcome == 'failure'
|
|
46
|
+
env:
|
|
47
|
+
GH_TOKEN: ${{ github.token }}
|
|
48
|
+
REPORT: ${{ runner.temp }}/audit-watch-report.md
|
|
49
|
+
run: |
|
|
50
|
+
title='Audit watch: the dev-tree exemption needs re-evaluating'
|
|
51
|
+
existing=$(gh issue list --state open --search "\"$title\" in:title" \
|
|
52
|
+
--json number --jq '.[0].number // empty')
|
|
53
|
+
|
|
54
|
+
if [ -n "$existing" ]; then
|
|
55
|
+
echo "already tracked in #$existing"
|
|
56
|
+
exit 0
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
if [ -f "$REPORT" ]; then
|
|
60
|
+
gh issue create --title "$title" --body-file "$REPORT"
|
|
61
|
+
else
|
|
62
|
+
# The script writes a report even when the watch itself breaks, so
|
|
63
|
+
# this means it never started. File the issue anyway rather than
|
|
64
|
+
# failing on a missing path and filing nothing.
|
|
65
|
+
gh issue create --title "$title" --body \
|
|
66
|
+
"The audit watch step failed before producing a report. Check the run:
|
|
67
|
+
$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
|
|
68
|
+
fi
|
|
@@ -36,11 +36,26 @@ jobs:
|
|
|
36
36
|
# gitleaks-action requires a license key for organization repos.
|
|
37
37
|
run: node_modules/.bin/gitleaks detect --source . --redact --no-banner -v
|
|
38
38
|
- name: Security Audit
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
|
|
39
|
+
# Prod-scoped on purpose: this gate covers what actually ships. The
|
|
40
|
+
# release toolchain is not a project dependency (see publish.yml), so
|
|
41
|
+
# the `npm` package bundled by @semantic-release/npm never reaches this
|
|
42
|
+
# tree - which is what made the old exemption-based audit script
|
|
43
|
+
# necessary. The dev tree carries one advisory that cannot be fixed
|
|
44
|
+
# from here: GHSA-hp3w-g68c-fv3c, sprintf-js reached through
|
|
45
|
+
# @jest/transform -> babel-plugin-istanbul ->
|
|
46
|
+
# @istanbuljs/load-nyc-config -> js-yaml 3 -> argparse. Every package in
|
|
47
|
+
# that chain is at its latest published version, and the advisory lists
|
|
48
|
+
# no patched release at all (sprintf-js 1.1.3 is the newest and is
|
|
49
|
+
# still in range), so gating on it would mean a permanently red build.
|
|
50
|
+
# `npm audit fix --force` does not fix it either - it downgrades jest
|
|
51
|
+
# 30 -> 25 and takes the tree from 21 advisories to 45.
|
|
52
|
+
#
|
|
53
|
+
# The exemption is not open-ended: audit-watch.yml runs the full audit
|
|
54
|
+
# weekly and fails when this comment stops matching the tree - a new
|
|
55
|
+
# dev-tree advisory, this one disappearing, or sprintf-js publishing a
|
|
56
|
+
# newer release. Keep the GHSA id named here and in the EXEMPTIONS list
|
|
57
|
+
# of scripts/audit-watch.mjs.
|
|
58
|
+
run: npm audit --omit=dev --audit-level=low
|
|
44
59
|
- name: Check Code Style
|
|
45
60
|
run: npm run lint
|
|
46
61
|
- name: Run Unit Tests
|
|
@@ -9,6 +9,18 @@ on:
|
|
|
9
9
|
- main
|
|
10
10
|
workflow_dispatch:
|
|
11
11
|
|
|
12
|
+
# One release at a time. Both triggers publish, and the push trigger fires on
|
|
13
|
+
# every release commit @semantic-release/git pushes back to main, so a
|
|
14
|
+
# dispatched recovery run can interleave with a push-triggered one. Two
|
|
15
|
+
# semantic-release processes racing the same tag and the same npm publish is a
|
|
16
|
+
# new way to strand a release - the state the retry below exists to recover.
|
|
17
|
+
# `cancel-in-progress: false` queues the second run rather than cancelling the
|
|
18
|
+
# first, because cancelling a run midway through a release is itself a way to
|
|
19
|
+
# leave a tag pushed and the release missing.
|
|
20
|
+
concurrency:
|
|
21
|
+
group: publish
|
|
22
|
+
cancel-in-progress: false
|
|
23
|
+
|
|
12
24
|
jobs:
|
|
13
25
|
build:
|
|
14
26
|
runs-on: ubuntu-latest
|
|
@@ -70,12 +82,66 @@ jobs:
|
|
|
70
82
|
# - and that bundled npm carries advisories nothing here can fix.
|
|
71
83
|
# Installing it here, pinned, keeps the dependency tree clean. Keep
|
|
72
84
|
# these versions in step with `release.plugins` in package.json.
|
|
85
|
+
#
|
|
86
|
+
# semantic-release pushes twice: @semantic-release/git pushes the release
|
|
87
|
+
# commit to the branch, then core pushes the tag in a second, tags-only
|
|
88
|
+
# push. When that second push is rejected the branch keeps the release
|
|
89
|
+
# commit - package.json at the new version - with no tag: no GitHub
|
|
90
|
+
# release, no npm publish, and every later run dying in the exec plugin's
|
|
91
|
+
# `npm version` with "Version not changed", a state only a hand repair
|
|
92
|
+
# clears. sasjs/adapter hit exactly that on 2026-10-17.
|
|
93
|
+
#
|
|
94
|
+
# The tag push runs after the tag exists locally and after the branch is
|
|
95
|
+
# updated, so one more attempt has only the tag to push. Retry exactly
|
|
96
|
+
# that case: a local tag alone does not identify it, because the tag is
|
|
97
|
+
# also there when the failure came later (the GitHub release), and
|
|
98
|
+
# retrying that finds the tag restored by semantic-release's own fetch,
|
|
99
|
+
# decides there is nothing to release, exits 0 and leaves the step green
|
|
100
|
+
# with the release still missing - so require the tag to be absent from
|
|
101
|
+
# the remote as well.
|
|
102
|
+
#
|
|
103
|
+
# Only a definitive answer counts. `git ls-remote --exit-code` exits 2 for
|
|
104
|
+
# an absent ref but 128 when the query itself fails (network, auth,
|
|
105
|
+
# unreachable remote), and treating every non-zero exit as "absent" would
|
|
106
|
+
# retry on the transient GitHub-side failures that are the very class of
|
|
107
|
+
# incident this retry repairs. So 2 retries, 0 and anything else fail the
|
|
108
|
+
# step.
|
|
73
109
|
run: |
|
|
74
|
-
|
|
75
|
-
--
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
110
|
+
run_release() {
|
|
111
|
+
npx --yes \
|
|
112
|
+
--package semantic-release@25.0.9 \
|
|
113
|
+
--package @semantic-release/exec@7.1.0 \
|
|
114
|
+
--package @semantic-release/git@11.0.1 \
|
|
115
|
+
semantic-release
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if run_release; then
|
|
119
|
+
exit 0
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
version=$(node -p "require('./package.json').version")
|
|
123
|
+
|
|
124
|
+
if ! git rev-parse -q --verify "refs/tags/v$version" >/dev/null; then
|
|
125
|
+
echo "No local v$version tag, so the failure came before the tag was created - not retrying." >&2
|
|
126
|
+
exit 1
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
ls_rc=0
|
|
130
|
+
git ls-remote --exit-code --tags origin "refs/tags/v$version" >/dev/null 2>&1 || ls_rc=$?
|
|
131
|
+
|
|
132
|
+
if [ "$ls_rc" -eq 0 ]; then
|
|
133
|
+
echo "v$version is already on the remote, so the failure came after the tag push - not retrying." >&2
|
|
134
|
+
exit 1
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
if [ "$ls_rc" -ne 2 ]; then
|
|
138
|
+
echo "Could not establish whether v$version is on the remote (git ls-remote exited $ls_rc) - not retrying." >&2
|
|
139
|
+
exit 1
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
echo "Retrying: v$version exists locally but not on the remote, so the tag push is what failed."
|
|
143
|
+
git tag -d "v$version"
|
|
144
|
+
run_release
|
|
79
145
|
env:
|
|
80
146
|
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} # <-- app token, not secrets.GITHUB_TOKEN
|
|
81
147
|
|
package/README.md
CHANGED
|
@@ -191,7 +191,9 @@ This will check each line to ensure that the count of leading spaces can be divi
|
|
|
191
191
|
|
|
192
192
|
### lineEndings
|
|
193
193
|
|
|
194
|
-
This setting ensures the line endings in a file to
|
|
194
|
+
This setting ensures the line endings in a file conform to the configured type. Possible values are `lf`, `crlf` and `off` (off means rule is set to be off). If the value is missing, null or undefined then the check would also be switched off (no default applied).
|
|
195
|
+
|
|
196
|
+
The last line is a line like any other: when this setting is `lf` or `crlf`, a file whose final line has no terminator is reported, and the formatter appends the missing terminator (`\r\n` when the setting is `crlf`, `\n` when it is `lf`). A project that wants the file end checked therefore sets this setting to `lf` or `crlf`; leaving it `off` means no line ending checks at all.
|
|
195
197
|
|
|
196
198
|
- Default: "off"
|
|
197
199
|
- Severity: WARNING
|
|
@@ -449,15 +451,17 @@ We've already implemented the following rules:
|
|
|
449
451
|
- Add undeclared macros to the `<h4> SAS Macros </h4>` section
|
|
450
452
|
- Remove unused macros from the `<h4> SAS Macros </h4>` section
|
|
451
453
|
- Remove trailing spaces
|
|
454
|
+
- Fix line endings, including adding a missing final newline when [`lineEndings`](#lineendings) is configured
|
|
455
|
+
|
|
456
|
+
The formatter only fixes rules that are in your configuration, so a rule that is off neither warns nor is repaired.
|
|
452
457
|
|
|
453
|
-
|
|
458
|
+
# Contributing
|
|
454
459
|
|
|
455
|
-
|
|
456
|
-
- zap gremlins
|
|
457
|
-
- fix line endings
|
|
458
|
-
- convert single asterisk comments to block comments
|
|
460
|
+
Development instructions, the checklist for adding a rule, and the formatter fixes still to be built live in [CONTRIBUTING.md](.github/CONTRIBUTING.md).
|
|
459
461
|
|
|
460
|
-
|
|
462
|
+
SASjs is an open source framework! Contributions are welcomed. If you would like to see a feature, because it would be useful in your project, but you don't have the requisite (Typescript) experience - then how about you engage us on a short project and we build it for you?
|
|
463
|
+
|
|
464
|
+
Contact [Allan Bowe](https://www.linkedin.com/in/allanbowe/) for further details.
|
|
461
465
|
|
|
462
466
|
# Further resources
|
|
463
467
|
|
|
@@ -465,12 +469,6 @@ We are also investigating some harder stuff, such as automatic indentation and c
|
|
|
465
469
|
* Longer intro to sasjs lint: https://vid.4gl.io/w/nDtkQFV1E8rtaa2BuM6U5s
|
|
466
470
|
* CLI docs: https://cli.sasjs.io/lint
|
|
467
471
|
|
|
468
|
-
# Sponsorship & Contributions
|
|
469
|
-
|
|
470
|
-
SASjs is an open source framework! Contributions are welcomed. If you would like to see a feature, because it would be useful in your project, but you don't have the requisite (Typescript) experience - then how about you engage us on a short project and we build it for you?
|
|
471
|
-
|
|
472
|
-
Contact [Allan Bowe](https://www.linkedin.com/in/allanbowe/) for further details.
|
|
473
|
-
|
|
474
472
|
# Contributors ✨
|
|
475
473
|
|
|
476
474
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
package/package.json
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
[
|
|
28
28
|
"@semantic-release/exec",
|
|
29
29
|
{
|
|
30
|
-
"prepareCmd": "npm version ${nextRelease.version} --no-git-tag-version"
|
|
30
|
+
"prepareCmd": "npm version ${nextRelease.version} --no-git-tag-version --allow-same-version"
|
|
31
31
|
}
|
|
32
32
|
],
|
|
33
33
|
[
|
|
@@ -70,8 +70,8 @@
|
|
|
70
70
|
"typescript": "6.0.3"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@sasjs/utils": "
|
|
73
|
+
"@sasjs/utils": "4.0.0",
|
|
74
74
|
"ignore": "7.0.11"
|
|
75
75
|
},
|
|
76
|
-
"version": "4.
|
|
76
|
+
"version": "4.2.0"
|
|
77
77
|
}
|
|
@@ -12,7 +12,6 @@ const message = 'File missing Doxygen header';
|
|
|
12
12
|
const messageForSingleAsterisk = 'File not following Doxygen header style, use double asterisks';
|
|
13
13
|
const test = (value, config) => {
|
|
14
14
|
var _a;
|
|
15
|
-
const lineEnding = (config === null || config === void 0 ? void 0 : config.lineEndings) === LineEndings_1.LineEndings.CRLF ? '\r\n' : '\n';
|
|
16
15
|
const severity = (config === null || config === void 0 ? void 0 : config.severityLevel[name]) || Severity_1.Severity.Warning;
|
|
17
16
|
try {
|
|
18
17
|
const hasFileHeader = value.trimStart().startsWith('/**');
|
|
@@ -23,8 +22,7 @@ const test = (value, config) => {
|
|
|
23
22
|
return [
|
|
24
23
|
{
|
|
25
24
|
message: messageForSingleAsterisk,
|
|
26
|
-
lineNumber: ((_a = value.split('/*')[0].match(
|
|
27
|
-
.length + 1,
|
|
25
|
+
lineNumber: ((_a = value.split('/*')[0].match(/\r\n|\n/g)) !== null && _a !== void 0 ? _a : []).length + 1,
|
|
28
26
|
startColumnNumber: 1,
|
|
29
27
|
endColumnNumber: 1,
|
|
30
28
|
severity
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hasDoxygenHeader.js","sourceRoot":"","sources":["../../../src/rules/file/hasDoxygenHeader.ts"],"names":[],"mappings":";;;AAAA,uCAAwC;AACxC,yDAAqD;AAErD,2DAAuD;AACvD,mDAA+C;AAC/C,6DAAoE;AAEpE,MAAM,IAAI,GAAG,kBAAkB,CAAA;AAC/B,MAAM,WAAW,GACf,qEAAqE,CAAA;AACvE,MAAM,OAAO,GAAG,6BAA6B,CAAA;AAC7C,MAAM,wBAAwB,GAC5B,+DAA+D,CAAA;AAEjE,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,MAAmB,EAAE,EAAE;;IAClD,MAAM,
|
|
1
|
+
{"version":3,"file":"hasDoxygenHeader.js","sourceRoot":"","sources":["../../../src/rules/file/hasDoxygenHeader.ts"],"names":[],"mappings":";;;AAAA,uCAAwC;AACxC,yDAAqD;AAErD,2DAAuD;AACvD,mDAA+C;AAC/C,6DAAoE;AAEpE,MAAM,IAAI,GAAG,kBAAkB,CAAA;AAC/B,MAAM,WAAW,GACf,qEAAqE,CAAA;AACvE,MAAM,OAAO,GAAG,6BAA6B,CAAA;AAC7C,MAAM,wBAAwB,GAC5B,+DAA+D,CAAA;AAEjE,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,MAAmB,EAAE,EAAE;;IAClD,MAAM,QAAQ,GAAG,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,aAAa,CAAC,IAAI,CAAC,KAAI,mBAAQ,CAAC,OAAO,CAAA;IAEhE,IAAI,CAAC;QACH,MAAM,aAAa,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;QACzD,IAAI,aAAa;YAAE,OAAO,EAAE,CAAA;QAE5B,MAAM,+BAA+B,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QAC1E,IAAI,+BAA+B;YACjC,OAAO;gBACL;oBACE,OAAO,EAAE,wBAAwB;oBACjC,UAAU,EACR,CAAC,MAAA,KAAK,CAAC,KAAK,CAAC,IAAI,CAAE,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,UAAU,CAAC,mCAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;oBAC7D,iBAAiB,EAAE,CAAC;oBACpB,eAAe,EAAE,CAAC;oBAClB,QAAQ;iBACT;aACF,CAAA;QAEH,OAAO;YACL;gBACE,OAAO;gBACP,UAAU,EAAE,CAAC;gBACb,iBAAiB,EAAE,CAAC;gBACpB,eAAe,EAAE,CAAC;gBAClB,QAAQ;aACT;SACF,CAAA;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO;YACL;gBACE,OAAO;gBACP,UAAU,EAAE,CAAC;gBACb,iBAAiB,EAAE,CAAC;gBACpB,eAAe,EAAE,CAAC;gBAClB,QAAQ;aACT;SACF,CAAA;IACH,CAAC;AACH,CAAC,CAAA;AAED,MAAM,GAAG,GAAG,CAAC,KAAa,EAAE,MAAmB,EAAU,EAAE;IACzD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IAClC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,KAAK,CAAA;IACd,CAAC;SAAM,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,wBAAwB;QACtD,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAEnC,MAAM,GAAG,MAAM,IAAI,IAAI,kBAAU,CAAC,wCAAwB,CAAC,CAAA;IAC3D,MAAM,gBAAgB,GAAG,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,WAAW,KAAI,yBAAW,CAAC,EAAE,CAAA;IAC9D,MAAM,UAAU,GAAG,gBAAgB,KAAK,yBAAW,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAA;IAEtE,OAAO,GAAG,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,aAAa,CAAC,OAAO,CACrC,eAAe,EACf,UAAU,CACX,GAAG,UAAU,GAAG,KAAK,EAAE,CAAA;AAC1B,CAAC,CAAA;AAED;;GAEG;AACU,QAAA,gBAAgB,GAAiB;IAC5C,IAAI,EAAE,2BAAY,CAAC,IAAI;IACvB,IAAI;IACJ,WAAW;IACX,OAAO;IACP,IAAI;IACJ,GAAG;CACJ,CAAA"}
|
|
@@ -6,12 +6,12 @@ const Severity_1 = require("../../types/Severity");
|
|
|
6
6
|
const getColumnNumber_1 = require("../../utils/getColumnNumber");
|
|
7
7
|
const LineEndings_1 = require("../../types/LineEndings");
|
|
8
8
|
const parseMacros_1 = require("../../utils/parseMacros");
|
|
9
|
+
const splitLines_1 = require("../../utils/splitLines");
|
|
9
10
|
const name = 'hasMacroNameInMend';
|
|
10
11
|
const description = 'Enforces the presence of the macro name in each %mend statement.';
|
|
11
12
|
const message = '%mend statement has missing or incorrect macro name';
|
|
12
13
|
const test = (value, config) => {
|
|
13
|
-
const
|
|
14
|
-
const lines = value ? value.split(lineEnding) : [];
|
|
14
|
+
const lines = (0, splitLines_1.splitLines)(value);
|
|
15
15
|
const macros = (0, parseMacros_1.parseMacros)(value, config);
|
|
16
16
|
const severity = (config === null || config === void 0 ? void 0 : config.severityLevel[name]) || Severity_1.Severity.Warning;
|
|
17
17
|
const diagnostics = [];
|
|
@@ -63,7 +63,7 @@ const test = (value, config) => {
|
|
|
63
63
|
};
|
|
64
64
|
const fix = (value, config) => {
|
|
65
65
|
const lineEnding = (config === null || config === void 0 ? void 0 : config.lineEndings) === LineEndings_1.LineEndings.CRLF ? '\r\n' : '\n';
|
|
66
|
-
const lines =
|
|
66
|
+
const lines = (0, splitLines_1.splitLines)(value);
|
|
67
67
|
const macros = (0, parseMacros_1.parseMacros)(value, config);
|
|
68
68
|
macros.forEach((macro) => {
|
|
69
69
|
if (macro.startLineNumbers.length === 0 && macro.endLineNumber !== null) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hasMacroNameInMend.js","sourceRoot":"","sources":["../../../src/rules/file/hasMacroNameInMend.ts"],"names":[],"mappings":";;;AAEA,2DAAuD;AACvD,mDAA+C;AAC/C,iEAA6D;AAE7D,yDAAqD;AACrD,yDAAqD;
|
|
1
|
+
{"version":3,"file":"hasMacroNameInMend.js","sourceRoot":"","sources":["../../../src/rules/file/hasMacroNameInMend.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FileLintRule } from '../../types/LintRule';
|
|
2
2
|
/**
|
|
3
|
-
* Lint rule that checks if line endings in a file match the configured type
|
|
3
|
+
* Lint rule that checks if line endings in a file match the configured type,
|
|
4
|
+
* including the line ending at the end of the file.
|
|
4
5
|
*/
|
|
5
6
|
export declare const lineEndings: FileLintRule;
|
|
@@ -5,8 +5,9 @@ const LineEndings_1 = require("../../types/LineEndings");
|
|
|
5
5
|
const LintRuleType_1 = require("../../types/LintRuleType");
|
|
6
6
|
const Severity_1 = require("../../types/Severity");
|
|
7
7
|
const name = 'lineEndings';
|
|
8
|
-
const description = 'Ensures line
|
|
8
|
+
const description = 'Ensures every line, including the last, ends with the configured line ending.';
|
|
9
9
|
const message = 'Incorrect line ending - {actual} instead of {expected}';
|
|
10
|
+
const missingMessage = 'File does not end with a {expected} line ending';
|
|
10
11
|
const test = (value, config) => {
|
|
11
12
|
const lineEndingConfig = (config === null || config === void 0 ? void 0 : config.lineEndings) || LineEndings_1.LineEndings.LF;
|
|
12
13
|
const expectedLineEnding = lineEndingConfig === LineEndings_1.LineEndings.LF ? '{lf}' : '{crlf}';
|
|
@@ -50,18 +51,40 @@ const test = (value, config) => {
|
|
|
50
51
|
});
|
|
51
52
|
}
|
|
52
53
|
});
|
|
54
|
+
// The end of the file is a line boundary too, so a last line with no
|
|
55
|
+
// terminator at all is a violation of this rule, not a separate concern.
|
|
56
|
+
// Gate on ANY terminator being present: a wrong-style terminator at the end
|
|
57
|
+
// is already reported by the loop above, and reporting it here as well would
|
|
58
|
+
// give one byte two diagnostics, at a line past the end of the file.
|
|
59
|
+
if (value.length > 0 && !value.endsWith('\n')) {
|
|
60
|
+
const lastLines = value.split(/\r\n|\n/);
|
|
61
|
+
const lastLine = lastLines[lastLines.length - 1];
|
|
62
|
+
diagnostics.push({
|
|
63
|
+
message: missingMessage.replace('{expected}', expectedLineEnding === '{lf}' ? 'LF' : 'CRLF'),
|
|
64
|
+
lineNumber: lastLines.length,
|
|
65
|
+
startColumnNumber: lastLine.length + 1,
|
|
66
|
+
endColumnNumber: lastLine.length + 2,
|
|
67
|
+
severity
|
|
68
|
+
});
|
|
69
|
+
}
|
|
53
70
|
return diagnostics;
|
|
54
71
|
};
|
|
55
72
|
const fix = (value, config) => {
|
|
56
73
|
const lineEndingConfig = (config === null || config === void 0 ? void 0 : config.lineEndings) || LineEndings_1.LineEndings.LF;
|
|
57
|
-
|
|
74
|
+
const converted = value
|
|
58
75
|
.replace(/\r\n/g, '{crlf}')
|
|
59
76
|
.replace(/\n/g, '{lf}')
|
|
60
77
|
.replace(/{crlf}/g, lineEndingConfig === LineEndings_1.LineEndings.LF ? '\n' : '\r\n')
|
|
61
78
|
.replace(/{lf}/g, lineEndingConfig === LineEndings_1.LineEndings.LF ? '\n' : '\r\n');
|
|
79
|
+
const expectedCharacter = lineEndingConfig === LineEndings_1.LineEndings.LF ? '\n' : '\r\n';
|
|
80
|
+
if (converted.length === 0 || converted.endsWith(expectedCharacter)) {
|
|
81
|
+
return converted;
|
|
82
|
+
}
|
|
83
|
+
return converted + expectedCharacter;
|
|
62
84
|
};
|
|
63
85
|
/**
|
|
64
|
-
* Lint rule that checks if line endings in a file match the configured type
|
|
86
|
+
* Lint rule that checks if line endings in a file match the configured type,
|
|
87
|
+
* including the line ending at the end of the file.
|
|
65
88
|
*/
|
|
66
89
|
exports.lineEndings = {
|
|
67
90
|
type: LintRuleType_1.LintRuleType.File,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lineEndings.js","sourceRoot":"","sources":["../../../src/rules/file/lineEndings.ts"],"names":[],"mappings":";;;AACA,yDAAqD;AAErD,2DAAuD;AACvD,mDAA+C;AAE/C,MAAM,IAAI,GAAG,aAAa,CAAA;AAC1B,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"lineEndings.js","sourceRoot":"","sources":["../../../src/rules/file/lineEndings.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,13 +5,12 @@ const LintRuleType_1 = require("../../types/LintRuleType");
|
|
|
5
5
|
const Severity_1 = require("../../types/Severity");
|
|
6
6
|
const getColumnNumber_1 = require("../../utils/getColumnNumber");
|
|
7
7
|
const parseMacros_1 = require("../../utils/parseMacros");
|
|
8
|
-
const
|
|
8
|
+
const splitLines_1 = require("../../utils/splitLines");
|
|
9
9
|
const name = 'noNestedMacros';
|
|
10
10
|
const description = 'Enfoces the absence of nested macro definitions.';
|
|
11
11
|
const message = `Macro definition for '{macro}' present in macro '{parent}'`;
|
|
12
12
|
const test = (value, config) => {
|
|
13
|
-
const
|
|
14
|
-
const lines = value ? value.split(lineEnding) : [];
|
|
13
|
+
const lines = (0, splitLines_1.splitLines)(value);
|
|
15
14
|
const diagnostics = [];
|
|
16
15
|
const macros = (0, parseMacros_1.parseMacros)(value, config);
|
|
17
16
|
const severity = (config === null || config === void 0 ? void 0 : config.severityLevel[name]) || Severity_1.Severity.Warning;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"noNestedMacros.js","sourceRoot":"","sources":["../../../src/rules/file/noNestedMacros.ts"],"names":[],"mappings":";;;AAEA,2DAAuD;AACvD,mDAA+C;AAC/C,iEAA6D;AAC7D,yDAAqD;
|
|
1
|
+
{"version":3,"file":"noNestedMacros.js","sourceRoot":"","sources":["../../../src/rules/file/noNestedMacros.ts"],"names":[],"mappings":";;;AAEA,2DAAuD;AACvD,mDAA+C;AAC/C,iEAA6D;AAC7D,yDAAqD;AACrD,uDAAmD;AAGnD,MAAM,IAAI,GAAG,gBAAgB,CAAA;AAC7B,MAAM,WAAW,GAAG,kDAAkD,CAAA;AACtE,MAAM,OAAO,GAAG,4DAA4D,CAAA;AAE5E,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,MAAmB,EAAE,EAAE;IAClD,MAAM,KAAK,GAAG,IAAA,uBAAU,EAAC,KAAK,CAAC,CAAA;IAC/B,MAAM,WAAW,GAAiB,EAAE,CAAA;IACpC,MAAM,MAAM,GAAG,IAAA,yBAAW,EAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IACzC,MAAM,QAAQ,GAAG,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,aAAa,CAAC,IAAI,CAAC,KAAI,mBAAQ,CAAC,OAAO,CAAA;IAEhE,MAAM;SACH,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;SAC9B,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;QACjB,WAAW,CAAC,IAAI,CAAC;YACf,OAAO,EAAE,OAAO;iBACb,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC;iBAC9B,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,WAAW,CAAC;YACzC,UAAU,EAAE,KAAK,CAAC,gBAAiB,CAAC,CAAC,CAAW;YAChD,iBAAiB,EAAE,IAAA,iCAAe,EAChC,KAAK,CAAE,KAAK,CAAC,gBAAiB,CAAC,CAAC,CAAY,GAAG,CAAC,CAAC,EACjD,QAAQ,CACT;YACD,eAAe,EACb,IAAA,iCAAe,EACb,KAAK,CAAE,KAAK,CAAC,gBAAiB,CAAC,CAAC,CAAY,GAAG,CAAC,CAAC,EACjD,QAAQ,CACT;gBACD,KAAK,CAAE,KAAK,CAAC,gBAAiB,CAAC,CAAC,CAAY,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM;gBAC/D,CAAC;YACH,QAAQ;SACT,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IACJ,OAAO,WAAW,CAAA;AACpB,CAAC,CAAA;AAED;;GAEG;AACU,QAAA,cAAc,GAAiB;IAC1C,IAAI,EAAE,2BAAY,CAAC,IAAI;IACvB,IAAI;IACJ,WAAW;IACX,OAAO;IACP,IAAI;CACL,CAAA"}
|
package/sasjslint-schema.json
CHANGED
|
@@ -230,7 +230,7 @@
|
|
|
230
230
|
"type": "string",
|
|
231
231
|
"enum": ["lf", "crlf", "off"],
|
|
232
232
|
"title": "lineEndings",
|
|
233
|
-
"description": "Enforces the configured terminating character for each line. Shows a warning when incorrect line endings are present.",
|
|
233
|
+
"description": "Enforces the configured terminating character for each line, including the last one. Shows a warning when incorrect line endings are present, and a warning for a file whose final line has no terminator; the formatter appends the missing terminator.",
|
|
234
234
|
"default": "off",
|
|
235
235
|
"examples": ["lf", "crlf"]
|
|
236
236
|
},
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watches the dev-tree audit exemption declared in `.github/workflows/build.yml`.
|
|
3
|
+
*
|
|
4
|
+
* That workflow gates on `npm audit --omit=dev` because the dev tree carries one
|
|
5
|
+
* advisory nothing in this repo can fix: GHSA-hp3w-g68c-fv3c (sprintf-js,
|
|
6
|
+
* reached through @jest/transform -> babel-plugin-istanbul ->
|
|
7
|
+
* @istanbuljs/load-nyc-config -> js-yaml 3 -> argparse). Every package in that
|
|
8
|
+
* chain is at its latest published version and the advisory lists no patched
|
|
9
|
+
* release, so gating on it would mean a permanently red build.
|
|
10
|
+
*
|
|
11
|
+
* An exemption with no expiry is how a scoped gate becomes a permanent one, so
|
|
12
|
+
* this script re-checks it weekly and reports when it is out of date:
|
|
13
|
+
*
|
|
14
|
+
* - a dev-tree advisory outside EXEMPTIONS appears - the exemption is too narrow
|
|
15
|
+
* - an EXEMPTIONS advisory no longer appears in the tree - the exemption is dead
|
|
16
|
+
* - a WATCHED_FOR_RELEASE package publishes a version newer than the one
|
|
17
|
+
* recorded here - a fix may now exist, so re-evaluate and drop the exemption
|
|
18
|
+
* - an EXEMPTIONS id is missing from the build.yml comment - the gate and this
|
|
19
|
+
* script disagree about what is exempt
|
|
20
|
+
*
|
|
21
|
+
* Any of those exits 1, failing the scheduled run and making the workflow file an
|
|
22
|
+
* issue. The known advisory on its own does NOT exit 1: it is expected, and a job
|
|
23
|
+
* that is red every week trains people to ignore red.
|
|
24
|
+
*
|
|
25
|
+
* Run with `node scripts/audit-watch.mjs`.
|
|
26
|
+
*/
|
|
27
|
+
import { execFileSync } from 'node:child_process'
|
|
28
|
+
import { appendFileSync, readFileSync, writeFileSync } from 'node:fs'
|
|
29
|
+
import path from 'node:path'
|
|
30
|
+
import { fileURLToPath } from 'node:url'
|
|
31
|
+
|
|
32
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
33
|
+
const BUILD_WORKFLOW = path.join('.github', 'workflows', 'build.yml')
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Advisories the dev-scoped gate in build.yml is allowed to leave unaddressed.
|
|
37
|
+
* Each one must also be named in that workflow's comment - the check below
|
|
38
|
+
* enforces it, so the gate and this list cannot drift apart.
|
|
39
|
+
*/
|
|
40
|
+
const EXEMPTIONS = ['GHSA-hp3w-g68c-fv3c']
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Packages whose published version decides whether an exemption is still
|
|
44
|
+
* justified. `newestKnown` is the newest release at the time the exemption was
|
|
45
|
+
* written; a newer one on the registry means a fix may exist and the exemption
|
|
46
|
+
* deserves a fresh look rather than another year of silence.
|
|
47
|
+
*/
|
|
48
|
+
const WATCHED_FOR_RELEASE = [{ name: 'sprintf-js', newestKnown: '1.1.3' }]
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* `npm audit --json` exits 1 whenever it reports anything, so the exit code says
|
|
52
|
+
* nothing about whether the command worked - parse the report instead.
|
|
53
|
+
*/
|
|
54
|
+
const runAudit = () => {
|
|
55
|
+
let stdout
|
|
56
|
+
try {
|
|
57
|
+
stdout = execFileSync('npm', ['audit', '--json'], {
|
|
58
|
+
encoding: 'utf8',
|
|
59
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
60
|
+
})
|
|
61
|
+
} catch (error) {
|
|
62
|
+
stdout = error.stdout
|
|
63
|
+
}
|
|
64
|
+
if (!stdout || !stdout.trim()) {
|
|
65
|
+
throw new Error('npm audit produced no JSON - run `npm ci` first')
|
|
66
|
+
}
|
|
67
|
+
return JSON.parse(stdout)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The advisories the report names, keyed by GHSA id. A `via` entry is a bare
|
|
72
|
+
* package name when the advisory is inherited from a dependency, and an object
|
|
73
|
+
* carrying the GHSA url when it is the root of the chain - only the latter are
|
|
74
|
+
* advisories in their own right.
|
|
75
|
+
*/
|
|
76
|
+
const collectAdvisories = (report) => {
|
|
77
|
+
const advisories = new Map()
|
|
78
|
+
|
|
79
|
+
for (const [packageName, info] of Object.entries(report?.vulnerabilities ?? {})) {
|
|
80
|
+
for (const via of info?.via ?? []) {
|
|
81
|
+
if (typeof via !== 'object' || via === null) continue
|
|
82
|
+
const id = String(via.url ?? '').split('/').pop()
|
|
83
|
+
if (!id?.startsWith('GHSA-')) continue
|
|
84
|
+
|
|
85
|
+
const entry = advisories.get(id) ?? {
|
|
86
|
+
severity: via.severity,
|
|
87
|
+
title: via.title,
|
|
88
|
+
packages: []
|
|
89
|
+
}
|
|
90
|
+
entry.packages.push(packageName)
|
|
91
|
+
advisories.set(id, entry)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return advisories
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The version the registry currently resolves `latest` to, or null if offline. */
|
|
99
|
+
const latestPublished = (name) => {
|
|
100
|
+
try {
|
|
101
|
+
return execFileSync('npm', ['view', name, 'version'], {
|
|
102
|
+
encoding: 'utf8',
|
|
103
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
104
|
+
}).trim()
|
|
105
|
+
} catch {
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Numeric dotted comparison, so `1.1.10` sorts above `1.1.9`. A prerelease
|
|
112
|
+
* suffix is dropped from both sides first: `1.1.3-beta.1` is not newer than
|
|
113
|
+
* `1.1.3`, and a prerelease that happens to be tagged `latest` must not raise
|
|
114
|
+
* the alarm by itself.
|
|
115
|
+
*/
|
|
116
|
+
const isNewer = (candidate, baseline) => {
|
|
117
|
+
const a = candidate.split('-')[0].split('.')
|
|
118
|
+
const b = baseline.split('-')[0].split('.')
|
|
119
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
120
|
+
const left = a[i] ?? '0'
|
|
121
|
+
const right = b[i] ?? '0'
|
|
122
|
+
if (left === right) continue
|
|
123
|
+
const leftNumber = Number(left)
|
|
124
|
+
const rightNumber = Number(right)
|
|
125
|
+
if (Number.isNaN(leftNumber) || Number.isNaN(rightNumber)) {
|
|
126
|
+
return left > right
|
|
127
|
+
}
|
|
128
|
+
return leftNumber > rightNumber
|
|
129
|
+
}
|
|
130
|
+
return false
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Write the report to the step summary and to the file the workflow reads.
|
|
135
|
+
* Wrapped so a failure to report cannot mask the failure being reported - and
|
|
136
|
+
* so the workflow's `gh issue create --body-file` always has a file, including
|
|
137
|
+
* on the path below where the watch itself broke.
|
|
138
|
+
*/
|
|
139
|
+
const writeReport = (summary) => {
|
|
140
|
+
try {
|
|
141
|
+
if (process.env.GITHUB_STEP_SUMMARY) {
|
|
142
|
+
appendFileSync(process.env.GITHUB_STEP_SUMMARY, summary)
|
|
143
|
+
}
|
|
144
|
+
if (process.env.AUDIT_WATCH_REPORT) {
|
|
145
|
+
writeFileSync(process.env.AUDIT_WATCH_REPORT, summary)
|
|
146
|
+
}
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error(`could not write the report: ${error.message}`)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const main = () => {
|
|
153
|
+
const report = runAudit()
|
|
154
|
+
const advisories = collectAdvisories(report)
|
|
155
|
+
const problems = []
|
|
156
|
+
|
|
157
|
+
for (const [id, advisory] of advisories) {
|
|
158
|
+
if (EXEMPTIONS.includes(id)) continue
|
|
159
|
+
problems.push(
|
|
160
|
+
`new dev-tree advisory ${id} (${advisory.severity}): ${advisory.title} - ` +
|
|
161
|
+
`packages: ${[...new Set(advisory.packages)].sort().join(', ')}. ` +
|
|
162
|
+
'Fix it, or add it to EXEMPTIONS and name it in the build.yml comment.'
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
for (const id of EXEMPTIONS) {
|
|
167
|
+
if (advisories.has(id)) continue
|
|
168
|
+
problems.push(
|
|
169
|
+
`exempted advisory ${id} no longer appears in the tree - the exemption is ` +
|
|
170
|
+
'dead weight. Remove it from EXEMPTIONS and from the build.yml comment, ' +
|
|
171
|
+
'and restore the full `npm audit` gate.'
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (const { name, newestKnown } of WATCHED_FOR_RELEASE) {
|
|
176
|
+
const latest = latestPublished(name)
|
|
177
|
+
if (latest === null) {
|
|
178
|
+
console.log(`skipped the registry check for ${name} - npm view failed`)
|
|
179
|
+
continue
|
|
180
|
+
}
|
|
181
|
+
if (isNewer(latest, newestKnown)) {
|
|
182
|
+
problems.push(
|
|
183
|
+
`${name} has published ${latest}, newer than the ${newestKnown} recorded ` +
|
|
184
|
+
'with the exemption - a fix may exist. Re-evaluate the exemption, bump ' +
|
|
185
|
+
'the chain if it clears, and drop the exemption when it does.'
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const buildWorkflow = readFileSync(path.join(ROOT, BUILD_WORKFLOW), 'utf8')
|
|
191
|
+
for (const id of EXEMPTIONS) {
|
|
192
|
+
if (buildWorkflow.includes(id)) continue
|
|
193
|
+
problems.push(
|
|
194
|
+
`exemption ${id} is not named in ${BUILD_WORKFLOW} - the gate and this ` +
|
|
195
|
+
'script disagree about what is exempt.'
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const { moderate = 0, high = 0, critical = 0, total = 0 } =
|
|
200
|
+
report?.metadata?.vulnerabilities ?? {}
|
|
201
|
+
|
|
202
|
+
const summary = [
|
|
203
|
+
'## Dev-tree audit watch',
|
|
204
|
+
'',
|
|
205
|
+
`Full audit: ${total} advisories (${critical} critical, ${high} high, ${moderate} moderate).`,
|
|
206
|
+
'',
|
|
207
|
+
'| advisory | severity | packages | exempt |',
|
|
208
|
+
'| --- | --- | --- | --- |',
|
|
209
|
+
...[...advisories].map(
|
|
210
|
+
([id, advisory]) =>
|
|
211
|
+
`| ${id} | ${advisory.severity} | ${[...new Set(advisory.packages)].sort().join(', ')} | ` +
|
|
212
|
+
`${EXEMPTIONS.includes(id) ? 'yes' : '**no**'} |`
|
|
213
|
+
),
|
|
214
|
+
'',
|
|
215
|
+
problems.length
|
|
216
|
+
? ['### The exemption needs attention', '', ...problems.map((p) => `- ${p}`), ''].join('\n')
|
|
217
|
+
: 'The exemption still matches the tree - nothing to do.',
|
|
218
|
+
''
|
|
219
|
+
].join('\n')
|
|
220
|
+
|
|
221
|
+
console.log(summary)
|
|
222
|
+
writeReport(summary)
|
|
223
|
+
|
|
224
|
+
process.exitCode = problems.length ? 1 : 0
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
main()
|
|
229
|
+
} catch (error) {
|
|
230
|
+
// The watch itself broke, so the exemption went unchecked. Say so in the
|
|
231
|
+
// report rather than leaving the workflow to fail on a missing file.
|
|
232
|
+
const summary = [
|
|
233
|
+
'## Dev-tree audit watch',
|
|
234
|
+
'',
|
|
235
|
+
'The watch did not complete, so the dev-tree audit exemption was **not**',
|
|
236
|
+
'checked against the tree. Nothing is known about the advisories right now.',
|
|
237
|
+
'',
|
|
238
|
+
'```',
|
|
239
|
+
error.message,
|
|
240
|
+
'```',
|
|
241
|
+
'',
|
|
242
|
+
'Investigate the failing step, then re-run the workflow.',
|
|
243
|
+
''
|
|
244
|
+
].join('\n')
|
|
245
|
+
|
|
246
|
+
console.error(`audit watch failed: ${error.message}`)
|
|
247
|
+
writeReport(summary)
|
|
248
|
+
process.exit(1)
|
|
249
|
+
}
|
package/utils/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from './gremlinCharacters';
|
|
|
5
5
|
export * from './isIgnored';
|
|
6
6
|
export * from './listSasFiles';
|
|
7
7
|
export * from './splitText';
|
|
8
|
+
export * from './splitLines';
|
|
8
9
|
export * from './getIndicesOf';
|
|
9
10
|
export * from './getHeaderLinesCount';
|
|
10
11
|
export * from './getDataSectionsDetail';
|
package/utils/index.js
CHANGED
|
@@ -21,6 +21,7 @@ __exportStar(require("./gremlinCharacters"), exports);
|
|
|
21
21
|
__exportStar(require("./isIgnored"), exports);
|
|
22
22
|
__exportStar(require("./listSasFiles"), exports);
|
|
23
23
|
__exportStar(require("./splitText"), exports);
|
|
24
|
+
__exportStar(require("./splitLines"), exports);
|
|
24
25
|
__exportStar(require("./getIndicesOf"), exports);
|
|
25
26
|
__exportStar(require("./getHeaderLinesCount"), exports);
|
|
26
27
|
__exportStar(require("./getDataSectionsDetail"), exports);
|
package/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iDAA8B;AAC9B,kDAA+B;AAC/B,mDAAgC;AAChC,sDAAmC;AACnC,8CAA2B;AAC3B,iDAA8B;AAC9B,8CAA2B;AAC3B,iDAA8B;AAC9B,wDAAqC;AACrC,0DAAuC;AACvC,iDAA8B;AAC9B,oDAAiC;AACjC,kDAA+B;AAC/B,sDAAmC;AACnC,oDAAiC;AACjC,8CAA2B"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iDAA8B;AAC9B,kDAA+B;AAC/B,mDAAgC;AAChC,sDAAmC;AACnC,8CAA2B;AAC3B,iDAA8B;AAC9B,8CAA2B;AAC3B,+CAA4B;AAC5B,iDAA8B;AAC9B,wDAAqC;AACrC,0DAAuC;AACvC,iDAA8B;AAC9B,oDAAiC;AACjC,kDAA+B;AAC/B,sDAAmC;AACnC,oDAAiC;AACjC,8CAA2B"}
|
package/utils/isIgnored.js
CHANGED
|
@@ -13,7 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.isIgnored = void 0;
|
|
16
|
-
const
|
|
16
|
+
const file_1 = require("@sasjs/utils/file");
|
|
17
17
|
const path_1 = __importDefault(require("path"));
|
|
18
18
|
const ignore_1 = __importDefault(require("ignore"));
|
|
19
19
|
const _1 = require(".");
|
|
@@ -32,8 +32,8 @@ const isIgnored = (fPath, configuration) => __awaiter(void 0, void 0, void 0, fu
|
|
|
32
32
|
if (fPath === projectRoot)
|
|
33
33
|
return false;
|
|
34
34
|
let gitIgnoreFileContent = '';
|
|
35
|
-
if (yield (0,
|
|
36
|
-
gitIgnoreFileContent = yield (0,
|
|
35
|
+
if (yield (0, file_1.fileExists)(gitIgnoreFilePath))
|
|
36
|
+
gitIgnoreFileContent = yield (0, file_1.readFile)(gitIgnoreFilePath);
|
|
37
37
|
return (0, ignore_1.default)()
|
|
38
38
|
.add(gitIgnoreFileContent)
|
|
39
39
|
.add(config.ignoreList)
|
package/utils/isIgnored.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"isIgnored.js","sourceRoot":"","sources":["../../src/utils/isIgnored.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,
|
|
1
|
+
{"version":3,"file":"isIgnored.js","sourceRoot":"","sources":["../../src/utils/isIgnored.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,4CAAwD;AACxD,gDAAuB;AACvB,oDAA2B;AAC3B,wBAAiD;AAGjD;;;;;GAKG;AACI,MAAM,SAAS,GAAG,CACvB,KAAa,EACb,aAA0B,EACR,EAAE;IACpB,MAAM,MAAM,GAAG,aAAa,IAAI,CAAC,MAAM,IAAA,gBAAa,GAAE,CAAC,CAAA;IACvD,MAAM,WAAW,GAAG,MAAM,IAAA,iBAAc,GAAE,CAAA;IAC1C,MAAM,iBAAiB,GAAG,cAAI,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAA;IAC9D,MAAM,QAAQ,GAAG,WAAW,GAAG,cAAI,CAAC,GAAG,CAAA;IACvC,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;IAEhD,IAAI,KAAK,KAAK,WAAW;QAAE,OAAO,KAAK,CAAA;IAEvC,IAAI,oBAAoB,GAAG,EAAE,CAAA;IAE7B,IAAI,MAAM,IAAA,iBAAU,EAAC,iBAAiB,CAAC;QACrC,oBAAoB,GAAG,MAAM,IAAA,eAAQ,EAAC,iBAAiB,CAAC,CAAA;IAE1D,OAAO,IAAA,gBAAM,GAAE;SACZ,GAAG,CAAC,oBAAoB,CAAC;SACzB,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC;SACtB,OAAO,CAAC,YAAY,CAAC,CAAA;AAC1B,CAAC,CAAA,CAAA;AArBY,QAAA,SAAS,aAqBrB"}
|
package/utils/parseMacros.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.parseMacros = void 0;
|
|
4
|
-
const LineEndings_1 = require("../types/LineEndings");
|
|
5
4
|
const trimComments_1 = require("./trimComments");
|
|
5
|
+
const splitLines_1 = require("./splitLines");
|
|
6
6
|
const parseMacros = (text, config) => {
|
|
7
|
-
const
|
|
8
|
-
const lines = text ? text.split(lineEnding) : [];
|
|
7
|
+
const lines = (0, splitLines_1.splitLines)(text);
|
|
9
8
|
const macros = [];
|
|
10
9
|
let isCommentStarted = false;
|
|
11
10
|
let macroStack = [];
|
package/utils/parseMacros.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parseMacros.js","sourceRoot":"","sources":["../../src/utils/parseMacros.ts"],"names":[],"mappings":";;;AACA,
|
|
1
|
+
{"version":3,"file":"parseMacros.js","sourceRoot":"","sources":["../../src/utils/parseMacros.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splits text into lines on either terminator, so a reader works whatever the
|
|
3
|
+
* file's own endings are.
|
|
4
|
+
*
|
|
5
|
+
* Readers must NOT split on the line ending configured in `lineEndings`. That
|
|
6
|
+
* value describes what the project wants, not what the file contains, and the
|
|
7
|
+
* lint path has no normalisation step - unlike the formatter, where
|
|
8
|
+
* `lineEndings.fix` converts every terminator before the later rules run. A
|
|
9
|
+
* configured-ending split over a file of the other style yields one string
|
|
10
|
+
* holding the whole file, so `lines[n - 1]` is not line n and column lookups
|
|
11
|
+
* throw.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} text - the text to split into lines.
|
|
14
|
+
* @returns {string[]} the lines, without their terminators.
|
|
15
|
+
*/
|
|
16
|
+
export declare const splitLines: (text: string) => string[];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.splitLines = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Splits text into lines on either terminator, so a reader works whatever the
|
|
6
|
+
* file's own endings are.
|
|
7
|
+
*
|
|
8
|
+
* Readers must NOT split on the line ending configured in `lineEndings`. That
|
|
9
|
+
* value describes what the project wants, not what the file contains, and the
|
|
10
|
+
* lint path has no normalisation step - unlike the formatter, where
|
|
11
|
+
* `lineEndings.fix` converts every terminator before the later rules run. A
|
|
12
|
+
* configured-ending split over a file of the other style yields one string
|
|
13
|
+
* holding the whole file, so `lines[n - 1]` is not line n and column lookups
|
|
14
|
+
* throw.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} text - the text to split into lines.
|
|
17
|
+
* @returns {string[]} the lines, without their terminators.
|
|
18
|
+
*/
|
|
19
|
+
const splitLines = (text) => text ? text.split(/\r\n|\n/) : [];
|
|
20
|
+
exports.splitLines = splitLines;
|
|
21
|
+
//# sourceMappingURL=splitLines.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"splitLines.js","sourceRoot":"","sources":["../../src/utils/splitLines.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;GAcG;AACI,MAAM,UAAU,GAAG,CAAC,IAAY,EAAY,EAAE,CACnD,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AADtB,QAAA,UAAU,cACY"}
|