@sasjs/lint 2.7.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +60 -5
  2. package/format/formatFile.js +2 -1
  3. package/format/formatFile.js.map +1 -1
  4. package/format/formatText.js +1 -1
  5. package/format/formatText.js.map +1 -1
  6. package/lint/lintFile.js +3 -2
  7. package/lint/lintFile.js.map +1 -1
  8. package/lint/lintText.js +1 -1
  9. package/lint/lintText.js.map +1 -1
  10. package/package.json +3 -1
  11. package/rules/file/index.d.ts +2 -0
  12. package/rules/file/index.js +5 -1
  13. package/rules/file/index.js.map +1 -1
  14. package/rules/file/noUndeclaredMacros.d.ts +7 -0
  15. package/rules/file/noUndeclaredMacros.js +77 -0
  16. package/rules/file/noUndeclaredMacros.js.map +1 -0
  17. package/rules/file/noUnusedMacros.d.ts +7 -0
  18. package/rules/file/noUnusedMacros.js +67 -0
  19. package/rules/file/noUnusedMacros.js.map +1 -0
  20. package/sasjslint-schema.json +51 -15
  21. package/scripts/generate-macro-names.mjs +101 -0
  22. package/types/LintConfig.d.ts +13 -0
  23. package/types/LintConfig.js +37 -15
  24. package/types/LintConfig.js.map +1 -1
  25. package/utils/getLintConfig.d.ts +26 -13
  26. package/utils/getLintConfig.js +28 -15
  27. package/utils/getLintConfig.js.map +1 -1
  28. package/utils/headerMacros.d.ts +51 -0
  29. package/utils/headerMacros.js +309 -0
  30. package/utils/headerMacros.js.map +1 -0
  31. package/utils/index.d.ts +5 -0
  32. package/utils/index.js +5 -0
  33. package/utils/index.js.map +1 -1
  34. package/utils/mergeLintConfig.d.ts +10 -0
  35. package/utils/mergeLintConfig.js +67 -0
  36. package/utils/mergeLintConfig.js.map +1 -0
  37. package/utils/parseLintOverride.d.ts +36 -0
  38. package/utils/parseLintOverride.js +109 -0
  39. package/utils/parseLintOverride.js.map +1 -0
  40. package/utils/parseMacroCalls.d.ts +28 -0
  41. package/utils/parseMacroCalls.js +230 -0
  42. package/utils/parseMacroCalls.js.map +1 -0
  43. package/utils/sasMacros.d.ts +16 -0
  44. package/utils/sasMacros.generated.d.ts +6 -0
  45. package/utils/sasMacros.generated.js +136 -0
  46. package/utils/sasMacros.generated.js.map +1 -0
  47. package/utils/sasMacros.js +22 -0
  48. package/utils/sasMacros.js.map +1 -0
package/README.md CHANGED
@@ -17,16 +17,17 @@ Our goal is to help SAS developers everywhere spend less time on code reviews, b
17
17
  - [@sasjs/vscode-extension](https://github.com/sasjs/vscode-extension) - just download SASjs in the VSCode marketplace, and select view/problems in the menu bar.
18
18
  - [@sasjs/cli](https://cli.sasjs.io/lint) - run `sasjs lint` to get a list of all files with their problems, along with line and column indexes.
19
19
 
20
- Configuration is via a `.sasjslint` file with the following structure (these are also the defaults if no .sasjslint file is found):
20
+ Configuration is via a `.sasjslint` file with the following structure (these are also the defaults - a setting that is absent, and a project with no `.sasjslint` file at all, uses the value below):
21
21
 
22
22
  ```json
23
23
  {
24
+ "allowedGremlins": [],
25
+ "defaultHeader": "/**{lineEnding} @file{lineEnding} @brief <Your brief here>{lineEnding}{lineEnding} <h4> SAS Macros </h4>{lineEnding}{lineEnding}**/",
24
26
  "hasDoxygenHeader": true,
25
27
  "hasMacroNameInMend": true,
26
28
  "hasMacroParentheses": true,
27
29
  "hasRequiredMacroOptions": false,
28
- "requiredMacroOptions": ["SECURE", "SRC"],
29
- "ignoreList": ["sasjsbuild/", "sasjsresults/"],
30
+ "ignoreList": [],
30
31
  "indentationMultiple": 2,
31
32
  "lineEndings": "off",
32
33
  "lowerCaseFileNames": true,
@@ -34,13 +35,17 @@ Configuration is via a `.sasjslint` file with the following structure (these are
34
35
  "maxHeaderLineLength": 80,
35
36
  "maxLineLength": 80,
36
37
  "noEncodedPasswords": true,
38
+ "noGremlins": true,
37
39
  "noNestedMacros": true,
38
40
  "noSingleAsteriskComments": false,
39
- "noGremlins": true,
40
41
  "noSpacesInFileNames": true,
41
42
  "noTabs": true,
42
43
  "noTrailingSpaces": true,
43
- "defaultHeader": "/**{lineEnding} @file{lineEnding} @brief <Your brief here>{lineEnding} <h4> SAS Macros </h4>{lineEnding}**/"
44
+ "noUndeclaredMacros": true,
45
+ "noUnusedMacros": true,
46
+ "requiredMacroOptions": [],
47
+ "severityLevel": {},
48
+ "strictMacroDefinition": true
44
49
  }
45
50
  ```
46
51
 
@@ -56,6 +61,29 @@ For more details, and the default state, see the description of each rule below.
56
61
 
57
62
  Configuring a non-zero return code (ERROR) is helpful when running `sasjs lint` as part of a git pre-commit hook. An example is available [here](https://github.com/sasjs/template_jobs/blob/main/.git-hooks/pre-commit).
58
63
 
64
+ ## Per-file overrides
65
+
66
+ A file can adjust the configuration for itself, with a `@sasjslint` block in its Doxygen header:
67
+
68
+ ```sas
69
+ /**
70
+ @file
71
+ @brief Calls the settlement API
72
+
73
+ @sasjslint {"maxHeaderLineLength": 200, "noUndeclaredMacros": false}
74
+ **/
75
+ ```
76
+
77
+ The block holds a JSON object that is merged over the resolved `.sasjslint`:
78
+
79
+ - scalars replace, so a file can switch a rule off, switch one on, or raise a limit for itself;
80
+ - arrays are additive, so `allowedGremlins` and `requiredMacroOptions` gain entries rather than replacing the project's;
81
+ - objects merge key by key, so a file can set one `severityLevel` without dropping the others.
82
+
83
+ The override applies to the whole file, and to the formatter, so `sasjs lint fix` and format-on-save honour it - a file that switches `noUnusedMacros` off keeps the entries it would otherwise lose. Only the header is read, so an override cannot be hidden in the body of a file, and a block that is malformed is ignored rather than raised. Place it outside the `<h4> SAS Macros </h4>` section, which the formatter rewrites.
84
+
85
+ One exception: `ignoreList` is evaluated before a file is read, so an entry a header adds cannot exclude the file it sits in.
86
+
59
87
  ### allowedGremlins
60
88
 
61
89
  An array of hex codes that represents allowed gremlins (invisible / undesirable characters). To allow all gremlins, you can also set the `noGremlins` rule to `false`. The full gremlin list is [here](https://github.com/sasjs/lint/blob/main/src/utils/gremlinCharacters.ts).
@@ -79,10 +107,14 @@ The default header is as follows:
79
107
  /**
80
108
  @file
81
109
  @brief <Your brief here>
110
+
82
111
  <h4> SAS Macros </h4>
112
+
83
113
  **/
84
114
  ```
85
115
 
116
+ A blank line separates the `<h4> SAS Macros </h4>` section from the header content before it and from whatever follows the list. The same blank-line convention applies to the `<h4> Other Macros </h4>` section.
117
+
86
118
  If creating a new value, use `{lineEnding}` instead of `\n`, eg as follows:
87
119
 
88
120
  ```json
@@ -289,6 +321,27 @@ This will highlight lines with trailing spaces. Trailing spaces serve no useful
289
321
  - Default: true
290
322
  - severity: WARNING
291
323
 
324
+ ### noUndeclaredMacros
325
+
326
+ SASjs programs declare the macros they use in the `<h4> SAS Macros </h4>` section of the header, so that the compiler can assemble the job before it runs. This rule reports a warning for each macro that a file invokes without declaring it - either by defining it in the file, or by listing it under `<h4> SAS Macros </h4>` or `<h4> Other Macros </h4>`.
327
+
328
+ The macros that ship with SAS (`%scan`, `%index`, `%sysfunc` and the other macro functions, along with the macro language keywords such as `%if`, `%then` and `%do`) are always considered declared, so they are never reported. That list is generated from the `macroStatements` and `macroFunctions` groups of `@sasjs/sas-language` (`npm run generate:macros`), so it tracks the SAS language data rather than being maintained here. Macros made available through the `SASAUTOS` system option are not visible to the linter and will be reported.
329
+
330
+ The warning is resolved by adding the macro name to the header. Running `sasjs lint fix` (or saving in the SASjs VS Code extension with `formatOnSave`) adds the missing macros automatically, as a unique list sorted alphabetically.
331
+
332
+ - Default: true
333
+ - Severity: WARNING
334
+
335
+ ### noUnusedMacros
336
+
337
+ The counterpart of `noUndeclaredMacros`. This rule reports a warning for each macro listed under `<h4> SAS Macros </h4>` that the file itself never invokes, which usually means the header is out of date.
338
+
339
+ A macro can be referenced indirectly - for example by another macro in the dependency chain, or through a dynamic `%&macro` call - without appearing as a direct invocation in the file, so the warning is advisory.
340
+
341
+ Running `sasjs lint fix` (or saving in the SASjs VS Code extension with `formatOnSave`) removes the unused entries from the header. Together with `noUndeclaredMacros`, this leaves the `<h4> SAS Macros </h4>` section listing exactly the macros the file uses.
342
+
343
+ - Default: true
344
+ - Severity: WARNING
292
345
 
293
346
  ## severityLevel
294
347
 
@@ -318,6 +371,8 @@ We've already implemented the following rules:
318
371
 
319
372
  - Add the macro name to the %mend statement
320
373
  - Add a doxygen header template if none exists
374
+ - Add undeclared macros to the `<h4> SAS Macros </h4>` section
375
+ - Remove unused macros from the `<h4> SAS Macros </h4>` section
321
376
  - Remove trailing spaces
322
377
 
323
378
  We're looking to implement the following rules:
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.formatFile = void 0;
13
13
  const file_1 = require("@sasjs/utils/file");
14
14
  const lint_1 = require("../lint");
15
+ const parseLintOverride_1 = require("../utils/parseLintOverride");
15
16
  const getLintConfig_1 = require("../utils/getLintConfig");
16
17
  const shared_1 = require("./shared");
17
18
  /**
@@ -25,7 +26,7 @@ const formatFile = (filePath, configuration) => __awaiter(void 0, void 0, void 0
25
26
  const diagnosticsBeforeFormat = yield (0, lint_1.lintFile)(filePath, config);
26
27
  const diagnosticsCountBeforeFormat = diagnosticsBeforeFormat.length;
27
28
  const text = yield (0, file_1.readFile)(filePath);
28
- const formattedText = (0, shared_1.processText)(text, config);
29
+ const formattedText = (0, shared_1.processText)(text, (0, parseLintOverride_1.applyLintOverride)(text, config));
29
30
  yield (0, file_1.createFile)(filePath, formattedText);
30
31
  const diagnosticsAfterFormat = yield (0, lint_1.lintFile)(filePath, config);
31
32
  const diagnosticsCountAfterFormat = diagnosticsAfterFormat.length;
@@ -1 +1 @@
1
- {"version":3,"file":"formatFile.js","sourceRoot":"","sources":["../../src/format/formatFile.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,4CAAwD;AACxD,kCAAkC;AAGlC,0DAAsD;AACtD,qCAAsC;AAEtC;;;;;GAKG;AACI,MAAM,UAAU,GAAG,CACxB,QAAgB,EAChB,aAA0B,EACH,EAAE;IACzB,MAAM,MAAM,GAAG,aAAa,IAAI,CAAC,MAAM,IAAA,6BAAa,GAAE,CAAC,CAAA;IACvD,MAAM,uBAAuB,GAAG,MAAM,IAAA,eAAQ,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IAChE,MAAM,4BAA4B,GAAG,uBAAuB,CAAC,MAAM,CAAA;IAEnE,MAAM,IAAI,GAAG,MAAM,IAAA,eAAQ,EAAC,QAAQ,CAAC,CAAA;IAErC,MAAM,aAAa,GAAG,IAAA,oBAAW,EAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAE/C,MAAM,IAAA,iBAAU,EAAC,QAAQ,EAAE,aAAa,CAAC,CAAA;IAEzC,MAAM,sBAAsB,GAAG,MAAM,IAAA,eAAQ,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IAC/D,MAAM,2BAA2B,GAAG,sBAAsB,CAAC,MAAM,CAAA;IAEjE,MAAM,qBAAqB,GACzB,4BAA4B,GAAG,2BAA2B,CAAA;IAE5D,MAAM,gBAAgB,GAAa,EAAE,CAAA;IAErC,IAAI,qBAAqB,EAAE,CAAC;QAC1B,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACjC,CAAC;IAED,OAAO;QACL,gBAAgB;QAChB,qBAAqB;QACrB,kBAAkB,EAAE,sBAAsB;KAC3C,CAAA;AACH,CAAC,CAAA,CAAA;AA/BY,QAAA,UAAU,cA+BtB"}
1
+ {"version":3,"file":"formatFile.js","sourceRoot":"","sources":["../../src/format/formatFile.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,4CAAwD;AACxD,kCAAkC;AAGlC,kEAA8D;AAC9D,0DAAsD;AACtD,qCAAsC;AAEtC;;;;;GAKG;AACI,MAAM,UAAU,GAAG,CACxB,QAAgB,EAChB,aAA0B,EACH,EAAE;IACzB,MAAM,MAAM,GAAG,aAAa,IAAI,CAAC,MAAM,IAAA,6BAAa,GAAE,CAAC,CAAA;IACvD,MAAM,uBAAuB,GAAG,MAAM,IAAA,eAAQ,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IAChE,MAAM,4BAA4B,GAAG,uBAAuB,CAAC,MAAM,CAAA;IAEnE,MAAM,IAAI,GAAG,MAAM,IAAA,eAAQ,EAAC,QAAQ,CAAC,CAAA;IAErC,MAAM,aAAa,GAAG,IAAA,oBAAW,EAAC,IAAI,EAAE,IAAA,qCAAiB,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;IAExE,MAAM,IAAA,iBAAU,EAAC,QAAQ,EAAE,aAAa,CAAC,CAAA;IAEzC,MAAM,sBAAsB,GAAG,MAAM,IAAA,eAAQ,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IAC/D,MAAM,2BAA2B,GAAG,sBAAsB,CAAC,MAAM,CAAA;IAEjE,MAAM,qBAAqB,GACzB,4BAA4B,GAAG,2BAA2B,CAAA;IAE5D,MAAM,gBAAgB,GAAa,EAAE,CAAA;IAErC,IAAI,qBAAqB,EAAE,CAAC;QAC1B,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACjC,CAAC;IAED,OAAO;QACL,gBAAgB;QAChB,qBAAqB;QACrB,kBAAkB,EAAE,sBAAsB;KAC3C,CAAA;AACH,CAAC,CAAA,CAAA;AA/BY,QAAA,UAAU,cA+BtB"}
@@ -14,7 +14,7 @@ const utils_1 = require("../utils");
14
14
  const shared_1 = require("./shared");
15
15
  const formatText = (text, configuration) => __awaiter(void 0, void 0, void 0, function* () {
16
16
  const config = configuration || (yield (0, utils_1.getLintConfig)());
17
- return (0, shared_1.processText)(text, config);
17
+ return (0, shared_1.processText)(text, (0, utils_1.applyLintOverride)(text, config));
18
18
  });
19
19
  exports.formatText = formatText;
20
20
  //# sourceMappingURL=formatText.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"formatText.js","sourceRoot":"","sources":["../../src/format/formatText.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,oCAAwC;AACxC,qCAAsC;AAE/B,MAAM,UAAU,GAAG,CAAO,IAAY,EAAE,aAA0B,EAAE,EAAE;IAC3E,MAAM,MAAM,GAAG,aAAa,IAAI,CAAC,MAAM,IAAA,qBAAa,GAAE,CAAC,CAAA;IACvD,OAAO,IAAA,oBAAW,EAAC,IAAI,EAAE,MAAM,CAAC,CAAA;AAClC,CAAC,CAAA,CAAA;AAHY,QAAA,UAAU,cAGtB"}
1
+ {"version":3,"file":"formatText.js","sourceRoot":"","sources":["../../src/format/formatText.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,oCAA2D;AAC3D,qCAAsC;AAE/B,MAAM,UAAU,GAAG,CAAO,IAAY,EAAE,aAA0B,EAAE,EAAE;IAC3E,MAAM,MAAM,GAAG,aAAa,IAAI,CAAC,MAAM,IAAA,qBAAa,GAAE,CAAC,CAAA;IACvD,OAAO,IAAA,oBAAW,EAAC,IAAI,EAAE,IAAA,yBAAiB,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;AAC3D,CAAC,CAAA,CAAA;AAHY,QAAA,UAAU,cAGtB"}
package/lint/lintFile.js CHANGED
@@ -24,8 +24,9 @@ const lintFile = (filePath, configuration) => __awaiter(void 0, void 0, void 0,
24
24
  return [];
25
25
  const config = configuration || (yield (0, utils_1.getLintConfig)());
26
26
  const text = yield (0, file_1.readFile)(filePath);
27
- const fileDiagnostics = (0, shared_1.processFile)(filePath, config);
28
- const textDiagnostics = (0, shared_1.processText)(text, config);
27
+ const effectiveConfig = (0, utils_1.applyLintOverride)(text, config);
28
+ const fileDiagnostics = (0, shared_1.processFile)(filePath, effectiveConfig);
29
+ const textDiagnostics = (0, shared_1.processText)(text, effectiveConfig);
29
30
  return [...fileDiagnostics, ...textDiagnostics];
30
31
  });
31
32
  exports.lintFile = lintFile;
@@ -1 +1 @@
1
- {"version":3,"file":"lintFile.js","sourceRoot":"","sources":["../../src/lint/lintFile.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,4CAA4C;AAE5C,oCAAmD;AACnD,qCAAmD;AAEnD;;;;;GAKG;AACI,MAAM,QAAQ,GAAG,CACtB,QAAgB,EAChB,aAA0B,EACH,EAAE;IACzB,IAAI,MAAM,IAAA,iBAAS,EAAC,QAAQ,EAAE,aAAa,CAAC;QAAE,OAAO,EAAE,CAAA;IAEvD,MAAM,MAAM,GAAG,aAAa,IAAI,CAAC,MAAM,IAAA,qBAAa,GAAE,CAAC,CAAA;IACvD,MAAM,IAAI,GAAG,MAAM,IAAA,eAAQ,EAAC,QAAQ,CAAC,CAAA;IAErC,MAAM,eAAe,GAAG,IAAA,oBAAW,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IACrD,MAAM,eAAe,GAAG,IAAA,oBAAW,EAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAEjD,OAAO,CAAC,GAAG,eAAe,EAAE,GAAG,eAAe,CAAC,CAAA;AACjD,CAAC,CAAA,CAAA;AAbY,QAAA,QAAQ,YAapB"}
1
+ {"version":3,"file":"lintFile.js","sourceRoot":"","sources":["../../src/lint/lintFile.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,4CAA4C;AAE5C,oCAAsE;AACtE,qCAAmD;AAEnD;;;;;GAKG;AACI,MAAM,QAAQ,GAAG,CACtB,QAAgB,EAChB,aAA0B,EACH,EAAE;IACzB,IAAI,MAAM,IAAA,iBAAS,EAAC,QAAQ,EAAE,aAAa,CAAC;QAAE,OAAO,EAAE,CAAA;IAEvD,MAAM,MAAM,GAAG,aAAa,IAAI,CAAC,MAAM,IAAA,qBAAa,GAAE,CAAC,CAAA;IACvD,MAAM,IAAI,GAAG,MAAM,IAAA,eAAQ,EAAC,QAAQ,CAAC,CAAA;IACrC,MAAM,eAAe,GAAG,IAAA,yBAAiB,EAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAEvD,MAAM,eAAe,GAAG,IAAA,oBAAW,EAAC,QAAQ,EAAE,eAAe,CAAC,CAAA;IAC9D,MAAM,eAAe,GAAG,IAAA,oBAAW,EAAC,IAAI,EAAE,eAAe,CAAC,CAAA;IAE1D,OAAO,CAAC,GAAG,eAAe,EAAE,GAAG,eAAe,CAAC,CAAA;AACjD,CAAC,CAAA,CAAA;AAdY,QAAA,QAAQ,YAcpB"}
package/lint/lintText.js CHANGED
@@ -22,7 +22,7 @@ const shared_1 = require("./shared");
22
22
  */
23
23
  const lintText = (text, configuration) => __awaiter(void 0, void 0, void 0, function* () {
24
24
  const config = configuration || (yield (0, utils_1.getLintConfig)());
25
- return (0, shared_1.processText)(text, config);
25
+ return (0, shared_1.processText)(text, (0, utils_1.applyLintOverride)(text, config));
26
26
  });
27
27
  exports.lintText = lintText;
28
28
  //# sourceMappingURL=lintText.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"lintText.js","sourceRoot":"","sources":["../../src/lint/lintText.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,oCAAwC;AACxC,qCAAsC;AAEtC;;;;;;;GAOG;AACI,MAAM,QAAQ,GAAG,CACtB,IAAY,EACZ,aAA0B,EACH,EAAE;IACzB,MAAM,MAAM,GAAG,aAAa,IAAI,CAAC,MAAM,IAAA,qBAAa,GAAE,CAAC,CAAA;IACvD,OAAO,IAAA,oBAAW,EAAC,IAAI,EAAE,MAAM,CAAC,CAAA;AAClC,CAAC,CAAA,CAAA;AANY,QAAA,QAAQ,YAMpB"}
1
+ {"version":3,"file":"lintText.js","sourceRoot":"","sources":["../../src/lint/lintText.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,oCAA2D;AAC3D,qCAAsC;AAEtC;;;;;;;GAOG;AACI,MAAM,QAAQ,GAAG,CACtB,IAAY,EACZ,aAA0B,EACH,EAAE;IACzB,MAAM,MAAM,GAAG,aAAa,IAAI,CAAC,MAAM,IAAA,qBAAa,GAAE,CAAC,CAAA;IACvD,OAAO,IAAA,oBAAW,EAAC,IAAI,EAAE,IAAA,yBAAiB,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;AAC3D,CAAC,CAAA,CAAA;AANY,QAAA,QAAQ,YAMpB"}
package/package.json CHANGED
@@ -4,6 +4,7 @@
4
4
  "scripts": {
5
5
  "test": "jest --coverage",
6
6
  "build": "rimraf build && tsc",
7
+ "generate:macros": "node ./scripts/generate-macro-names.mjs",
7
8
  "preinstall": "node checkNodeVersion",
8
9
  "prebuild": "node checkNodeVersion",
9
10
  "publishInit": "cp -r ./build/* . && rm -rf ./build && rm -rf ./src && rm tsconfig.json",
@@ -59,6 +60,7 @@
59
60
  "homepage": "https://github.com/sasjs/lint#readme",
60
61
  "devDependencies": {
61
62
  "@nogoo9/gitleaks": "8.30.1-post.2",
63
+ "@sasjs/sas-language": "0.3.0",
62
64
  "@types/jest": "30.0.0",
63
65
  "@types/node": "26.6.3",
64
66
  "jest": "30.5.2",
@@ -70,5 +72,5 @@
70
72
  "@sasjs/utils": "3.6.2",
71
73
  "ignore": "7.0.11"
72
74
  },
73
- "version": "2.7.0"
75
+ "version": "4.0.0"
74
76
  }
@@ -3,6 +3,8 @@ export { hasMacroNameInMend } from './hasMacroNameInMend';
3
3
  export { hasMacroParentheses } from './hasMacroParentheses';
4
4
  export { lineEndings } from './lineEndings';
5
5
  export { noNestedMacros } from './noNestedMacros';
6
+ export { noUndeclaredMacros } from './noUndeclaredMacros';
7
+ export { noUnusedMacros } from './noUnusedMacros';
6
8
  export { strictMacroDefinition } from './strictMacroDefinition';
7
9
  export { hasRequiredMacroOptions } from './hasRequiredMacroOptions';
8
10
  export { noSingleAsteriskComments } from './noSingleAsteriskComments';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.noSingleAsteriskComments = exports.hasRequiredMacroOptions = exports.strictMacroDefinition = exports.noNestedMacros = exports.lineEndings = exports.hasMacroParentheses = exports.hasMacroNameInMend = exports.hasDoxygenHeader = void 0;
3
+ exports.noSingleAsteriskComments = exports.hasRequiredMacroOptions = exports.strictMacroDefinition = exports.noUnusedMacros = exports.noUndeclaredMacros = exports.noNestedMacros = exports.lineEndings = exports.hasMacroParentheses = exports.hasMacroNameInMend = exports.hasDoxygenHeader = void 0;
4
4
  var hasDoxygenHeader_1 = require("./hasDoxygenHeader");
5
5
  Object.defineProperty(exports, "hasDoxygenHeader", { enumerable: true, get: function () { return hasDoxygenHeader_1.hasDoxygenHeader; } });
6
6
  var hasMacroNameInMend_1 = require("./hasMacroNameInMend");
@@ -11,6 +11,10 @@ var lineEndings_1 = require("./lineEndings");
11
11
  Object.defineProperty(exports, "lineEndings", { enumerable: true, get: function () { return lineEndings_1.lineEndings; } });
12
12
  var noNestedMacros_1 = require("./noNestedMacros");
13
13
  Object.defineProperty(exports, "noNestedMacros", { enumerable: true, get: function () { return noNestedMacros_1.noNestedMacros; } });
14
+ var noUndeclaredMacros_1 = require("./noUndeclaredMacros");
15
+ Object.defineProperty(exports, "noUndeclaredMacros", { enumerable: true, get: function () { return noUndeclaredMacros_1.noUndeclaredMacros; } });
16
+ var noUnusedMacros_1 = require("./noUnusedMacros");
17
+ Object.defineProperty(exports, "noUnusedMacros", { enumerable: true, get: function () { return noUnusedMacros_1.noUnusedMacros; } });
14
18
  var strictMacroDefinition_1 = require("./strictMacroDefinition");
15
19
  Object.defineProperty(exports, "strictMacroDefinition", { enumerable: true, get: function () { return strictMacroDefinition_1.strictMacroDefinition; } });
16
20
  var hasRequiredMacroOptions_1 = require("./hasRequiredMacroOptions");
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/rules/file/index.ts"],"names":[],"mappings":";;;AAAA,uDAAqD;AAA5C,oHAAA,gBAAgB,OAAA;AACzB,2DAAyD;AAAhD,wHAAA,kBAAkB,OAAA;AAC3B,6DAA2D;AAAlD,0HAAA,mBAAmB,OAAA;AAC5B,6CAA2C;AAAlC,0GAAA,WAAW,OAAA;AACpB,mDAAiD;AAAxC,gHAAA,cAAc,OAAA;AACvB,iEAA+D;AAAtD,8HAAA,qBAAqB,OAAA;AAC9B,qEAAmE;AAA1D,kIAAA,uBAAuB,OAAA;AAChC,uEAAqE;AAA5D,oIAAA,wBAAwB,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/rules/file/index.ts"],"names":[],"mappings":";;;AAAA,uDAAqD;AAA5C,oHAAA,gBAAgB,OAAA;AACzB,2DAAyD;AAAhD,wHAAA,kBAAkB,OAAA;AAC3B,6DAA2D;AAAlD,0HAAA,mBAAmB,OAAA;AAC5B,6CAA2C;AAAlC,0GAAA,WAAW,OAAA;AACpB,mDAAiD;AAAxC,gHAAA,cAAc,OAAA;AACvB,2DAAyD;AAAhD,wHAAA,kBAAkB,OAAA;AAC3B,mDAAiD;AAAxC,gHAAA,cAAc,OAAA;AACvB,iEAA+D;AAAtD,8HAAA,qBAAqB,OAAA;AAC9B,qEAAmE;AAA1D,kIAAA,uBAAuB,OAAA;AAChC,uEAAqE;AAA5D,oIAAA,wBAAwB,OAAA"}
@@ -0,0 +1,7 @@
1
+ import { FileLintRule } from '../../types/LintRule';
2
+ /**
3
+ * Lint rule that reports each macro a file uses without declaring it in the
4
+ * header. The formatter resolves the warning by adding the macro names to the
5
+ * `<h4> SAS Macros </h4>` section.
6
+ */
7
+ export declare const noUndeclaredMacros: FileLintRule;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noUndeclaredMacros = void 0;
4
+ const types_1 = require("../../types");
5
+ const LintRuleType_1 = require("../../types/LintRuleType");
6
+ const Severity_1 = require("../../types/Severity");
7
+ const headerMacros_1 = require("../../utils/headerMacros");
8
+ const parseMacroCalls_1 = require("../../utils/parseMacroCalls");
9
+ const parseMacros_1 = require("../../utils/parseMacros");
10
+ const sasMacros_1 = require("../../utils/sasMacros");
11
+ const name = 'noUndeclaredMacros';
12
+ const description = 'Enforce that every macro used in a file is declared.';
13
+ const message = `Macro '{macro}' is not declared - add it to the <h4> SAS Macros </h4> or <h4> Other Macros </h4> section of the header`;
14
+ /**
15
+ * Collects the first invocation of each macro that the file uses but does not
16
+ * declare. A macro is declared when it is defined in the file, or listed in the
17
+ * `<h4> SAS Macros </h4>` or `<h4> Other Macros </h4>` section of its header.
18
+ * Macros that ship with SAS are always considered declared.
19
+ */
20
+ const getUndeclaredCalls = (value, config) => {
21
+ if (!value)
22
+ return [];
23
+ const declared = new Set();
24
+ (0, parseMacros_1.parseMacros)(value, config).forEach((macro) => {
25
+ if (macro.name)
26
+ declared.add(macro.name.toLowerCase());
27
+ });
28
+ (0, headerMacros_1.getHeaderMacros)(value, config).forEach((macro) => {
29
+ declared.add(macro.name.toLowerCase());
30
+ });
31
+ const undeclared = [];
32
+ const seen = new Set();
33
+ (0, parseMacroCalls_1.parseMacroCalls)(value, config).forEach((call) => {
34
+ const key = call.name.toLowerCase();
35
+ if (seen.has(key))
36
+ return;
37
+ seen.add(key);
38
+ if ((0, sasMacros_1.isSasMacro)(call.name))
39
+ return;
40
+ if (declared.has(key))
41
+ return;
42
+ undeclared.push(call);
43
+ });
44
+ return undeclared;
45
+ };
46
+ const test = (value, config) => {
47
+ const cfg = config || new types_1.LintConfig();
48
+ const severity = cfg.severityLevel[name] || Severity_1.Severity.Warning;
49
+ return getUndeclaredCalls(value, cfg).map((call) => ({
50
+ message: message.replace('{macro}', call.name),
51
+ lineNumber: call.lineNumber,
52
+ startColumnNumber: call.startColumnNumber,
53
+ endColumnNumber: call.endColumnNumber,
54
+ severity
55
+ }));
56
+ };
57
+ const fix = (value, config) => {
58
+ const cfg = config || new types_1.LintConfig();
59
+ const macros = getUndeclaredCalls(value, cfg).map((call) => call.name);
60
+ if (!macros.length)
61
+ return value;
62
+ return (0, headerMacros_1.addMacrosToHeader)(value, cfg, macros);
63
+ };
64
+ /**
65
+ * Lint rule that reports each macro a file uses without declaring it in the
66
+ * header. The formatter resolves the warning by adding the macro names to the
67
+ * `<h4> SAS Macros </h4>` section.
68
+ */
69
+ exports.noUndeclaredMacros = {
70
+ type: LintRuleType_1.LintRuleType.File,
71
+ name,
72
+ description,
73
+ message,
74
+ test,
75
+ fix
76
+ };
77
+ //# sourceMappingURL=noUndeclaredMacros.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"noUndeclaredMacros.js","sourceRoot":"","sources":["../../../src/rules/file/noUndeclaredMacros.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { FileLintRule } from '../../types/LintRule';
2
+ /**
3
+ * Lint rule that reports macros declared in the `<h4> SAS Macros </h4>` section
4
+ * of a file header that the file itself never invokes. The formatter removes
5
+ * them from the header.
6
+ */
7
+ export declare const noUnusedMacros: FileLintRule;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noUnusedMacros = void 0;
4
+ const types_1 = require("../../types");
5
+ const LintRuleType_1 = require("../../types/LintRuleType");
6
+ const Severity_1 = require("../../types/Severity");
7
+ const headerMacros_1 = require("../../utils/headerMacros");
8
+ const parseMacroCalls_1 = require("../../utils/parseMacroCalls");
9
+ const name = 'noUnusedMacros';
10
+ const description = 'Report macros declared in the <h4> SAS Macros </h4> section that the file does not use.';
11
+ const message = `Macro '{macro}' is declared in the <h4> SAS Macros </h4> section but not used in the file`;
12
+ /**
13
+ * Collects the macros declared in the `<h4> SAS Macros </h4>` section that the
14
+ * file itself never invokes. A macro that appears more than once is reported
15
+ * once, at its first entry.
16
+ */
17
+ const getUnusedMacros = (value, config) => {
18
+ if (!value)
19
+ return [];
20
+ const used = new Set((0, parseMacroCalls_1.parseMacroCalls)(value, config).map((call) => call.name.toLowerCase()));
21
+ const unused = [];
22
+ const reported = new Set();
23
+ (0, headerMacros_1.getHeaderMacros)(value, config)
24
+ .filter((macro) => macro.section === 'sasMacros')
25
+ .forEach((macro) => {
26
+ const key = macro.name.toLowerCase();
27
+ if (reported.has(key))
28
+ return;
29
+ reported.add(key);
30
+ if (used.has(key))
31
+ return;
32
+ unused.push(macro);
33
+ });
34
+ return unused;
35
+ };
36
+ const test = (value, config) => {
37
+ const cfg = config || new types_1.LintConfig();
38
+ const severity = cfg.severityLevel[name] || Severity_1.Severity.Warning;
39
+ return getUnusedMacros(value, cfg).map((macro) => ({
40
+ message: message.replace('{macro}', macro.name),
41
+ lineNumber: macro.lineNumber,
42
+ startColumnNumber: macro.startColumnNumber,
43
+ endColumnNumber: macro.endColumnNumber,
44
+ severity
45
+ }));
46
+ };
47
+ const fix = (value, config) => {
48
+ const cfg = config || new types_1.LintConfig();
49
+ const macros = getUnusedMacros(value, cfg).map((macro) => macro.name);
50
+ if (!macros.length)
51
+ return value;
52
+ return (0, headerMacros_1.removeMacrosFromHeader)(value, cfg, macros);
53
+ };
54
+ /**
55
+ * Lint rule that reports macros declared in the `<h4> SAS Macros </h4>` section
56
+ * of a file header that the file itself never invokes. The formatter removes
57
+ * them from the header.
58
+ */
59
+ exports.noUnusedMacros = {
60
+ type: LintRuleType_1.LintRuleType.File,
61
+ name,
62
+ description,
63
+ message,
64
+ test,
65
+ fix
66
+ };
67
+ //# sourceMappingURL=noUnusedMacros.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"noUnusedMacros.js","sourceRoot":"","sources":["../../../src/rules/file/noUnusedMacros.ts"],"names":[],"mappings":";;;AAAA,uCAAoD;AAEpD,2DAAuD;AACvD,mDAA+C;AAC/C,2DAIiC;AACjC,iEAA6D;AAE7D,MAAM,IAAI,GAAG,gBAAgB,CAAA;AAC7B,MAAM,WAAW,GACf,yFAAyF,CAAA;AAC3F,MAAM,OAAO,GAAG,2FAA2F,CAAA;AAE3G;;;;GAIG;AACH,MAAM,eAAe,GAAG,CAAC,KAAa,EAAE,MAAkB,EAAiB,EAAE;IAC3E,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAA;IAErB,MAAM,IAAI,GAAG,IAAI,GAAG,CAClB,IAAA,iCAAe,EAAC,KAAK,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CACtE,CAAA;IAED,MAAM,MAAM,GAAkB,EAAE,CAAA;IAChC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAA;IAElC,IAAA,8BAAe,EAAC,KAAK,EAAE,MAAM,CAAC;SAC3B,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,KAAK,WAAW,CAAC;SAChD,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;QACjB,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAA;QACpC,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAM;QAC7B,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACjB,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAM;QAEzB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACpB,CAAC,CAAC,CAAA;IAEJ,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AAED,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,MAAmB,EAAgB,EAAE;IAChE,MAAM,GAAG,GAAG,MAAM,IAAI,IAAI,kBAAU,EAAE,CAAA;IACtC,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,mBAAQ,CAAC,OAAO,CAAA;IAE5D,OAAO,eAAe,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACjD,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC;QAC/C,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;QAC1C,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,QAAQ;KACT,CAAC,CAAC,CAAA;AACL,CAAC,CAAA;AAED,MAAM,GAAG,GAAG,CAAC,KAAa,EAAE,MAAmB,EAAU,EAAE;IACzD,MAAM,GAAG,GAAG,MAAM,IAAI,IAAI,kBAAU,EAAE,CAAA;IACtC,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAErE,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAEhC,OAAO,IAAA,qCAAsB,EAAC,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,CAAA;AACnD,CAAC,CAAA;AAED;;;;GAIG;AACU,QAAA,cAAc,GAAiB;IAC1C,IAAI,EAAE,2BAAY,CAAC,IAAI;IACvB,IAAI;IACJ,WAAW;IACX,OAAO;IACP,IAAI;IACJ,GAAG;CACJ,CAAA"}
@@ -3,29 +3,33 @@
3
3
  "$id": "https://github.com/sasjs/lint/blob/main/sasjslint-schema.json",
4
4
  "type": "object",
5
5
  "title": "SASjs Lint Config File",
6
- "description": "The SASjs Lint Config file provides the settings for customising SAS code style in your project.",
6
+ "description": "The SASjs Lint Config file provides the settings for customising SAS code style in your project. A SAS file can override these settings for itself with a `@sasjslint` block in its Doxygen header.",
7
7
  "default": {
8
- "noEncodedPasswords": true,
8
+ "allowedGremlins": [],
9
+ "defaultHeader": "/**{lineEnding} @file{lineEnding} @brief <Your brief here>{lineEnding}{lineEnding} <h4> SAS Macros </h4>{lineEnding}{lineEnding}**/",
9
10
  "hasDoxygenHeader": true,
10
- "defaultHeader": "/**{lineEnding} @file{lineEnding} @brief <Your brief here>{lineEnding} <h4> SAS Macros </h4>{lineEnding}**/",
11
- "hasMacroNameInMend": false,
11
+ "hasMacroNameInMend": true,
12
12
  "hasMacroParentheses": true,
13
+ "hasRequiredMacroOptions": false,
14
+ "ignoreList": [],
13
15
  "indentationMultiple": 2,
16
+ "lineEndings": "off",
14
17
  "lowerCaseFileNames": true,
15
- "maxLineLength": 80,
16
- "maxHeaderLineLength": 80,
17
18
  "maxDataLineLength": 80,
19
+ "maxHeaderLineLength": 80,
20
+ "maxLineLength": 80,
21
+ "noEncodedPasswords": true,
18
22
  "noGremlins": true,
19
23
  "noNestedMacros": true,
20
24
  "noSingleAsteriskComments": false,
21
25
  "noSpacesInFileNames": true,
22
26
  "noTabs": true,
23
27
  "noTrailingSpaces": true,
24
- "lineEndings": "off",
25
- "strictMacroDefinition": true,
26
- "ignoreList": ["sajsbuild", "sasjsresults"],
27
- "hasRequiredMacroOptions": false,
28
- "requiredMacroOptions": []
28
+ "noUndeclaredMacros": true,
29
+ "noUnusedMacros": true,
30
+ "requiredMacroOptions": [],
31
+ "severityLevel": {},
32
+ "strictMacroDefinition": true
29
33
  },
30
34
  "examples": [
31
35
  {
@@ -44,6 +48,8 @@
44
48
  "hasMacroNameInMend": true,
45
49
  "noNestedMacros": true,
46
50
  "noSingleAsteriskComments": false,
51
+ "noUndeclaredMacros": true,
52
+ "noUnusedMacros": true,
47
53
  "hasMacroParentheses": true,
48
54
  "lineEndings": "crlf",
49
55
  "strictMacroDefinition": true,
@@ -74,7 +80,7 @@
74
80
  "type": "string",
75
81
  "title": "defaultHeader",
76
82
  "description": "This sets the default program header - applies when a SAS program does NOT begin with `/**`.",
77
- "default": "/**{lineEnding} @file{lineEnding} @brief <Your brief here>{lineEnding} <h4> SAS Macros </h4>{lineEnding}**/",
83
+ "default": "/**{lineEnding} @file{lineEnding} @brief <Your brief here>{lineEnding}{lineEnding} <h4> SAS Macros </h4>{lineEnding}{lineEnding}**/",
78
84
  "examples": []
79
85
  },
80
86
  "noGremlins": {
@@ -102,7 +108,7 @@
102
108
  "type": "boolean",
103
109
  "title": "hasMacroNameInMend",
104
110
  "description": "Enforces the presence of macro names in %mend statements. Shows a warning for %mend statements with missing or mismatched macro names.",
105
- "default": false,
111
+ "default": true,
106
112
  "examples": [true, false]
107
113
  },
108
114
  "hasMacroParentheses": {
@@ -193,6 +199,22 @@
193
199
  "default": true,
194
200
  "examples": [true, false]
195
201
  },
202
+ "noUndeclaredMacros": {
203
+ "$id": "#/properties/noUndeclaredMacros",
204
+ "type": "boolean",
205
+ "title": "noUndeclaredMacros",
206
+ "description": "Reports a warning for each macro a file invokes without declaring it in the `<h4> SAS Macros </h4>` or `<h4> Other Macros </h4>` section of the header. Macros that ship with SAS are always considered declared. The formatter resolves the warning by adding the macro names to the header, sorted alphabetically.",
207
+ "default": true,
208
+ "examples": [true, false]
209
+ },
210
+ "noUnusedMacros": {
211
+ "$id": "#/properties/noUnusedMacros",
212
+ "type": "boolean",
213
+ "title": "noUnusedMacros",
214
+ "description": "Reports a warning for each macro declared in the `<h4> SAS Macros </h4>` section of the header that the file itself never invokes. The formatter removes the unused entries. A macro can be referenced indirectly, so the warning is advisory.",
215
+ "default": true,
216
+ "examples": [true, false]
217
+ },
196
218
  "lineEndings": {
197
219
  "$id": "#/properties/lineEndings",
198
220
  "type": "string",
@@ -215,8 +237,8 @@
215
237
  "type": "array",
216
238
  "title": "ignoreList",
217
239
  "description": "An array of paths or path patterns to ignore when linting. Any files or matching patterns in the .gitignore file will also be ignored.",
218
- "default": ["sasjsbuild/", "sasjsresults/"],
219
- "examples": ["sasjs/tests", "tmp/scratch.sas"]
240
+ "default": [],
241
+ "examples": [["sasjsbuild/", "sasjsresults/"], ["sasjs/tests", "tmp/scratch.sas"]]
220
242
  },
221
243
  "hasRequiredMacroOptions": {
222
244
  "$id": "#/properties/hasRequiredMacroOptions",
@@ -344,6 +366,20 @@
344
366
  "enum": ["error", "warn"],
345
367
  "default": "warn"
346
368
  },
369
+ "noUndeclaredMacros": {
370
+ "$id": "#/properties/severityLevel/noUndeclaredMacros",
371
+ "title": "noUndeclaredMacros",
372
+ "type": "string",
373
+ "enum": ["error", "warn"],
374
+ "default": "warn"
375
+ },
376
+ "noUnusedMacros": {
377
+ "$id": "#/properties/severityLevel/noUnusedMacros",
378
+ "title": "noUnusedMacros",
379
+ "type": "string",
380
+ "enum": ["error", "warn"],
381
+ "default": "warn"
382
+ },
347
383
  "lineEndings": {
348
384
  "$id": "#/properties/severityLevel/lineEndings",
349
385
  "title": "lineEndings",