@sasjs/lint 5.1.1 → 5.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/README.md CHANGED
@@ -177,7 +177,9 @@ The addition of the macro name in the `%mend` statement is optional, but can app
177
177
 
178
178
  ### hasMacroParentheses
179
179
 
180
- As per the example [here](https://github.com/sasjs/lint/issues/20), macros defined without parentheses cause problems if that macro is ever extended (it's not possible to reliably extend that macro without potentially breaking some code that has used the macro). It's better to always define parentheses, even if they are not used. This check will also throw a warning if there are spaces between the macro name and the opening parenthesis.
180
+ As per the example [here](https://github.com/sasjs/lint/issues/20), macros defined without parentheses cause problems if that macro is ever extended (it's not possible to reliably extend that macro without potentially breaking some code that has used the macro). It's better to always define parentheses, even if they are not used. This check reports a definition whose parameter list is absent altogether, and a definition that carries no name.
181
+
182
+ Running `sasjs lint fix` (or saving in the SASjs VS Code extension with `formatOnSave`) adds the empty parameter list to a definition that takes no parameters. The case of the keyword and the name, the spacing before the `;` and any comment on that line are left exactly as they were. A declaration that carries parameter text without parentheses is left alone - an inserted `()` would land in front of the parameters - so that warning stays for the author to resolve.
181
183
 
182
184
  - Default: true
183
185
  - Severity: WARNING
package/package.json CHANGED
@@ -93,7 +93,7 @@
93
93
  "glob": "^14.0.2",
94
94
  "minimatch": "^11.0.1"
95
95
  },
96
- "version": "5.1.1",
96
+ "version": "5.2.0",
97
97
  "devEngines": {
98
98
  "runtime": {
99
99
  "name": "node",
@@ -1,5 +1,7 @@
1
1
  import { FileLintRule } from '../../types/LintRule';
2
2
  /**
3
- * Lint rule that enforces the presence of parentheses in macro definitions..
3
+ * Lint rule that enforces the presence of parentheses in macro definitions.. It
4
+ * reports a definition with no name or with no parentheses, and its fix adds
5
+ * the empty parameter list to a declaration that takes no parameters.
4
6
  */
5
7
  export declare const hasMacroParentheses: FileLintRule;
@@ -5,10 +5,34 @@ 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 utils_1 = require("../../utils");
8
9
  const getSeverity_1 = require("../../utils/getSeverity");
9
10
  const name = 'hasMacroParentheses';
10
11
  const description = 'Enforces the presence of parentheses in macro definitions.';
11
12
  const message = 'Macro definition missing parentheses';
13
+ /**
14
+ * The whole declaration line of a macro that takes no parameters: the keyword,
15
+ * the name, an optional comment, and the end of the statement. Nothing else may
16
+ * sit between the name and the `;`, which is what makes this the guard for the
17
+ * fix - appending `()` to `%macro foo param=1;` would put the parentheses in
18
+ * front of the parameters, so that shape is left to the author.
19
+ */
20
+ const parameterlessDeclaration = (macroName) => new RegExp(`(%macro\\s+${(0, utils_1.escapeRegExp)(macroName)})((?:\\s*/\\*.*?\\*/)?\\s*;)`, 'i');
21
+ /** The line of the declaration that carries the macro name. */
22
+ const nameLine = (macro) => macro.declarationLines.findIndex((declarationLine) => declarationLine.includes(macro.name));
23
+ /**
24
+ * The offset at which each line of the text starts, measured on the text as it
25
+ * stands. Used to edit one line without re-joining every other one, which would
26
+ * rewrite line endings this rule has no business touching.
27
+ */
28
+ const lineStarts = (text) => {
29
+ const starts = [0];
30
+ for (let index = 0; index < text.length; index++) {
31
+ if (text[index] === '\n')
32
+ starts.push(index + 1);
33
+ }
34
+ return starts;
35
+ };
12
36
  const test = (value, config) => {
13
37
  const diagnostics = [];
14
38
  const macros = (0, parseMacros_1.parseMacros)(value, config);
@@ -28,7 +52,7 @@ const test = (value, config) => {
28
52
  });
29
53
  }
30
54
  else if (!macro.declarationLines.find((dl) => dl.includes('('))) {
31
- const macroNameLineIndex = macro.declarationLines.findIndex((dl) => dl.includes(macro.name));
55
+ const macroNameLineIndex = nameLine(macro);
32
56
  diagnostics.push({
33
57
  message,
34
58
  lineNumber: macro.startLineNumbers[macroNameLineIndex],
@@ -43,13 +67,54 @@ const test = (value, config) => {
43
67
  return diagnostics;
44
68
  };
45
69
  /**
46
- * Lint rule that enforces the presence of parentheses in macro definitions..
70
+ * Adds the empty parameter list to every declaration that takes no parameters.
71
+ *
72
+ * The parentheses go immediately after the name, so any spacing that follows it
73
+ * (and a comment on the same line) survives untouched. The declaration is the
74
+ * guard: a line whose name is followed by parameter text does not match, so
75
+ * `%macro foo param=1;` keeps its warning and is left to the author, and a
76
+ * declaration that carries no name is skipped here for the same reason.
77
+ *
78
+ * Only the bytes of the declaration line are rewritten - the rest of the file,
79
+ * its line endings included, is copied through untouched.
80
+ */
81
+ const fix = (value, config) => {
82
+ if (!value)
83
+ return value;
84
+ // The name's line is resolved once, in the filter, so the loop has no
85
+ // unreachable branch for a declaration whose name the parser invented.
86
+ const declarations = (0, parseMacros_1.parseMacros)(value, config)
87
+ .filter((macro) => !!macro.name)
88
+ .map((macro) => ({ macro, index: nameLine(macro) }))
89
+ .filter(({ index }) => index !== -1);
90
+ return declarations.reduce((text, { macro, index }) => {
91
+ const lineNumber = macro.startLineNumbers[index];
92
+ const starts = lineStarts(text);
93
+ const start = starts[lineNumber - 1];
94
+ const end = lineNumber < starts.length ? starts[lineNumber] : text.length;
95
+ // The line is taken with its terminator and put back with it, so every byte
96
+ // of the file other than the two this fix inserts is left alone.
97
+ const line = text.slice(start, end);
98
+ // Anchored on the statement rather than the line, so a line carrying two
99
+ // declarations gets each of them in turn. `$2` keeps the spacing before the
100
+ // `;` and any comment that sits between the name and it.
101
+ const pattern = parameterlessDeclaration(macro.name);
102
+ if (!pattern.test(line))
103
+ return text;
104
+ return (text.slice(0, start) + line.replace(pattern, '$1()$2') + text.slice(end));
105
+ }, value);
106
+ };
107
+ /**
108
+ * Lint rule that enforces the presence of parentheses in macro definitions.. It
109
+ * reports a definition with no name or with no parentheses, and its fix adds
110
+ * the empty parameter list to a declaration that takes no parameters.
47
111
  */
48
112
  exports.hasMacroParentheses = {
49
113
  type: LintRuleType_1.LintRuleType.File,
50
114
  name,
51
115
  description,
52
116
  message,
53
- test
117
+ test,
118
+ fix
54
119
  };
55
120
  //# sourceMappingURL=hasMacroParentheses.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hasMacroParentheses.js","sourceRoot":"","sources":["../../../src/rules/file/hasMacroParentheses.ts"],"names":[],"mappings":";;;AAEA,2DAAuD;AACvD,mDAA+C;AAC/C,iEAA6D;AAC7D,yDAAqD;AAErD,yDAAqD;AAErD,MAAM,IAAI,GAAG,qBAAqB,CAAA;AAClC,MAAM,WAAW,GAAG,4DAA4D,CAAA;AAChF,MAAM,OAAO,GAAG,sCAAsC,CAAA;AAEtD,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,MAAmB,EAAE,EAAE;IAClD,MAAM,WAAW,GAAiB,EAAE,CAAA;IACpC,MAAM,MAAM,GAAG,IAAA,yBAAW,EAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IACzC,MAAM,QAAQ,GAAG,IAAA,yBAAW,EAAC,MAAM,EAAE,IAAI,EAAE,mBAAQ,CAAC,OAAO,CAAC,CAAA;IAE5D,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;QACvB,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YAChB,WAAW,CAAC,IAAI,CAAC;gBACf,OAAO,EAAE,+BAA+B;gBACxC,UAAU,EAAE,KAAK,CAAC,gBAAiB,CAAC,CAAC,CAAC;gBACtC,iBAAiB,EAAE,IAAA,iCAAe,EAChC,KAAK,CAAC,gBAAiB,CAAC,CAAC,CAAC,EAC1B,QAAQ,EACR,IAAI,CACL;gBACD,wEAAwE;gBACxE,yBAAyB;gBACzB,eAAe,EACb,IAAA,iCAAe,EAAC,KAAK,CAAC,gBAAiB,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC;oBAC3D,KAAK,CAAC,WAAW,CAAC,MAAM;oBACxB,CAAC;gBACH,QAAQ;aACT,CAAC,CAAA;QACJ,CAAC;aAAM,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAClE,MAAM,kBAAkB,GAAG,KAAK,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,CACjE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CACxB,CAAA;YACD,WAAW,CAAC,IAAI,CAAC;gBACf,OAAO;gBACP,UAAU,EAAE,KAAK,CAAC,gBAAiB,CAAC,kBAAkB,CAAC;gBACvD,iBAAiB,EAAE,IAAA,iCAAe,EAChC,KAAK,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,EAC1C,KAAK,CAAC,IAAI,CACX;gBACD,eAAe,EACb,IAAA,iCAAe,EACb,KAAK,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,EAC1C,KAAK,CAAC,IAAI,CACX;oBACD,KAAK,CAAC,IAAI,CAAC,MAAM;oBACjB,CAAC;gBACH,QAAQ;aACT,CAAC,CAAA;QACJ,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,OAAO,WAAW,CAAA;AACpB,CAAC,CAAA;AAED;;GAEG;AACU,QAAA,mBAAmB,GAAiB;IAC/C,IAAI,EAAE,2BAAY,CAAC,IAAI;IACvB,IAAI;IACJ,WAAW;IACX,OAAO;IACP,IAAI;CACL,CAAA"}
1
+ {"version":3,"file":"hasMacroParentheses.js","sourceRoot":"","sources":["../../../src/rules/file/hasMacroParentheses.ts"],"names":[],"mappings":"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"}
@@ -161,7 +161,7 @@
161
161
  "$id": "#/properties/hasMacroParentheses",
162
162
  "type": "boolean",
163
163
  "title": "hasMacroParentheses",
164
- "description": "Enforces the presence of parentheses in macro definitions. Shows a warning for each macro defined without parentheses, or with spaces between the macro name and the opening parenthesis.",
164
+ "description": "Enforces the presence of parentheses in macro definitions. Shows a warning for each macro defined without a parameter list, or without a name. The fix adds the empty parameter list to a definition that takes no parameters.",
165
165
  "default": true,
166
166
  "examples": [true, false]
167
167
  },