@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
@@ -0,0 +1,309 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.removeMacrosFromHeader = exports.addMacrosToHeader = exports.getHeaderMacros = void 0;
4
+ const types_1 = require("../types");
5
+ const LineEndings_1 = require("../types/LineEndings");
6
+ const getHeaderLinesCount_1 = require("./getHeaderLinesCount");
7
+ const splitText_1 = require("./splitText");
8
+ const sasMacrosHeader = /<h4>\s*SAS Macros\s*<\/h4>/i;
9
+ const otherMacrosHeader = /<h4>\s*Other Macros\s*<\/h4>/i;
10
+ const anyHeader = /<h4>/i;
11
+ const headerTag = '</h4>';
12
+ const listItem = /^(\s*@li\s+)(.+?)(\s*)$/;
13
+ /**
14
+ * Reads an `@li` entry, returning the macro name and the column at which it
15
+ * starts. The column is derived from the entry text rather than from a search
16
+ * for the name, so an entry such as `@li li.sas` reports the `li` after the
17
+ * tag, not the `li` inside it.
18
+ *
19
+ * @param {string} text - the line, or the tail of a line, to read.
20
+ * @param {number} offset - the 0-based column at which `text` starts in the
21
+ * line, so a trailing entry on a `<h4>` line still reports its real position.
22
+ */
23
+ const parseListItem = (text, offset = 0) => {
24
+ const match = listItem.exec(text);
25
+ if (!match)
26
+ return null;
27
+ const entry = match[2];
28
+ const firstToken = entry.trim().split(/\s+/)[0] || '';
29
+ const normalised = firstToken.replace(/\\/g, '/');
30
+ const fileName = normalised.split('/').pop() || '';
31
+ const name = fileName.replace(/\.sas$/i, '');
32
+ if (!name)
33
+ return null;
34
+ const tokenOffset = entry.indexOf(firstToken);
35
+ const fileOffset = normalised.lastIndexOf('/') + 1;
36
+ const column = offset + match[1].length + tokenOffset + fileOffset + 1;
37
+ return { name, column };
38
+ };
39
+ /**
40
+ * Reads the `@li` entries of a section that starts at `sectionIndex`.
41
+ *
42
+ * A blank line ends the list unless the next non-blank line is another entry,
43
+ * so a list broken up by a stray blank line is still read in full - the
44
+ * formatter rewrites exactly the lines it read, and an entry it did not read
45
+ * would be left behind and duplicated.
46
+ *
47
+ * @returns the entries, and the index of the first line after the last one.
48
+ */
49
+ const readSectionList = (lines, sectionIndex, limit) => {
50
+ const entries = [];
51
+ let end = sectionIndex + 1;
52
+ let index = sectionIndex + 1;
53
+ while (index < limit && index < lines.length) {
54
+ const line = lines[index];
55
+ if (line.trim() === '') {
56
+ let next = index;
57
+ while (next < limit && next < lines.length && lines[next].trim() === '') {
58
+ next++;
59
+ }
60
+ if (next < limit && next < lines.length && listItem.test(lines[next])) {
61
+ index = next;
62
+ continue;
63
+ }
64
+ break;
65
+ }
66
+ const parsed = parseListItem(line);
67
+ if (!parsed)
68
+ break;
69
+ entries.push({
70
+ name: parsed.name,
71
+ line,
72
+ lineNumber: index + 1,
73
+ column: parsed.column
74
+ });
75
+ index++;
76
+ end = index;
77
+ }
78
+ return { entries, end };
79
+ };
80
+ /**
81
+ * Reads the macros declared in the header of a file - both the
82
+ * `<h4> SAS Macros </h4>` and the `<h4> Other Macros </h4>` sections.
83
+ * @param {string} text - the text content of the file.
84
+ * @param {LintConfig} config - the lint configuration, used for line endings.
85
+ * @returns {HeaderMacro[]} the declared macros, in the order they appear.
86
+ */
87
+ const getHeaderMacros = (text, config) => {
88
+ if (!text)
89
+ return [];
90
+ const cfg = config || new types_1.LintConfig();
91
+ const headerLinesCount = (0, getHeaderLinesCount_1.getHeaderLinesCount)(text, cfg);
92
+ if (!headerLinesCount)
93
+ return [];
94
+ const lines = (0, splitText_1.splitText)(text, cfg);
95
+ const macros = [];
96
+ for (let index = 0; index < headerLinesCount && index < lines.length; index++) {
97
+ const line = lines[index];
98
+ const section = sasMacrosHeader.test(line)
99
+ ? 'sasMacros'
100
+ : otherMacrosHeader.test(line)
101
+ ? 'otherMacros'
102
+ : null;
103
+ if (!section)
104
+ continue;
105
+ // An entry can share the line with the section tag.
106
+ const tagEnd = line.indexOf(headerTag) + headerTag.length;
107
+ const trailing = parseListItem(line.slice(tagEnd), tagEnd);
108
+ if (trailing) {
109
+ macros.push({
110
+ name: trailing.name,
111
+ lineNumber: index + 1,
112
+ startColumnNumber: trailing.column,
113
+ endColumnNumber: trailing.column + trailing.name.length,
114
+ section
115
+ });
116
+ }
117
+ readSectionList(lines, index, headerLinesCount).entries.forEach((entry) => {
118
+ macros.push({
119
+ name: entry.name,
120
+ lineNumber: entry.lineNumber,
121
+ startColumnNumber: entry.column,
122
+ endColumnNumber: entry.column + entry.name.length,
123
+ section
124
+ });
125
+ });
126
+ }
127
+ return macros;
128
+ };
129
+ exports.getHeaderMacros = getHeaderMacros;
130
+ /**
131
+ * Derives the indentation used by the content lines of a header. Falls back to
132
+ * two spaces, which is the indentation of the default header.
133
+ */
134
+ const getHeaderIndent = (lines, headerLinesCount) => {
135
+ for (let index = 1; index < headerLinesCount && index < lines.length; index++) {
136
+ const match = /^(\s+)\S/.exec(lines[index]);
137
+ if (match)
138
+ return match[1];
139
+ }
140
+ return ' ';
141
+ };
142
+ /**
143
+ * Ensures a blank line separates a header section from its neighbours. The
144
+ * section header sits at `sectionIndex` and `listEnd` is the index of the first
145
+ * line after the section's `@li` entries.
146
+ */
147
+ const withSectionSpacing = (lines, sectionIndex, listEnd) => {
148
+ const result = [...lines];
149
+ if (listEnd < result.length && result[listEnd].trim() !== '') {
150
+ result.splice(listEnd, 0, '');
151
+ }
152
+ if (sectionIndex > 0 && result[sectionIndex - 1].trim() !== '') {
153
+ result.splice(sectionIndex, 0, '');
154
+ }
155
+ return result;
156
+ };
157
+ /**
158
+ * Locates the `<h4> SAS Macros </h4>` section of a header.
159
+ *
160
+ * Returns `null` when the section is absent, and `null` when it cannot be
161
+ * rewritten without disturbing the surrounding text: when the section shares a
162
+ * line with the header's closing `**`, or when an `@li` entry shares the line
163
+ * with the section tag. In those cases the caller leaves the file alone, so a
164
+ * warning that cannot be resolved is preferable to a corrupted header.
165
+ */
166
+ const findSasMacrosSection = (lines, headerLinesCount) => {
167
+ let sectionIndex = -1;
168
+ for (let index = 0; index < headerLinesCount && index < lines.length; index++) {
169
+ if (sasMacrosHeader.test(lines[index])) {
170
+ sectionIndex = index;
171
+ break;
172
+ }
173
+ }
174
+ if (sectionIndex === -1)
175
+ return null;
176
+ if (sectionIndex === headerLinesCount - 1)
177
+ return null;
178
+ const tagEnd = lines[sectionIndex].indexOf(headerTag) + headerTag.length;
179
+ if (parseListItem(lines[sectionIndex].slice(tagEnd)))
180
+ return null;
181
+ const { entries, end } = readSectionList(lines, sectionIndex, headerLinesCount);
182
+ return { sectionIndex, entries, end };
183
+ };
184
+ /**
185
+ * Adds macro names to the `<h4> SAS Macros </h4>` section of a file header,
186
+ * merging them with the entries that are already listed, de-duplicating by
187
+ * name, and sorting the result alphabetically. A new section is added when the
188
+ * header has none.
189
+ *
190
+ * A blank line separates the section from the header content before it and from
191
+ * whatever follows the list, so the section reads as its own block.
192
+ *
193
+ * @param {string} text - the text content of the file.
194
+ * @param {LintConfig} config - the lint configuration, used for line endings.
195
+ * @param {string[]} macros - the macro names to add.
196
+ * @returns {string} the updated text content.
197
+ */
198
+ const addMacrosToHeader = (text, config, macros) => {
199
+ if (!text || !macros.length)
200
+ return text;
201
+ const headerLinesCount = (0, getHeaderLinesCount_1.getHeaderLinesCount)(text, config);
202
+ if (!headerLinesCount)
203
+ return text;
204
+ const lines = (0, splitText_1.splitText)(text, config);
205
+ const lineEnding = config.lineEndings === LineEndings_1.LineEndings.CRLF ? '\r\n' : '\n';
206
+ const headerEndIndex = headerLinesCount - 1;
207
+ const section = findSasMacrosSection(lines, headerLinesCount);
208
+ if (!section) {
209
+ // No section, or one that cannot be rewritten. Only a genuinely absent
210
+ // section is safe to create, so check for the tag first.
211
+ const hasSection = lines
212
+ .slice(0, headerLinesCount)
213
+ .some((line) => sasMacrosHeader.test(line));
214
+ if (hasSection || headerEndIndex === 0)
215
+ return text;
216
+ const indent = getHeaderIndent(lines, headerLinesCount);
217
+ const sectionLines = [
218
+ `${indent}<h4> SAS Macros </h4>`,
219
+ ...macros.map((macro) => `${indent}@li ${macro}.sas`)
220
+ ];
221
+ const result = [
222
+ ...lines.slice(0, headerEndIndex),
223
+ ...sectionLines,
224
+ ...lines.slice(headerEndIndex)
225
+ ];
226
+ return withSectionSpacing(result, headerEndIndex, headerEndIndex + sectionLines.length).join(lineEnding);
227
+ }
228
+ const { sectionIndex, entries, end } = section;
229
+ const indent = lines[sectionIndex].match(/^\s*/)[0];
230
+ const byName = new Map();
231
+ entries.forEach((entry) => {
232
+ const key = entry.name.toLowerCase();
233
+ if (!byName.has(key))
234
+ byName.set(key, { name: entry.name, line: entry.line });
235
+ });
236
+ macros.forEach((macro) => {
237
+ const key = macro.toLowerCase();
238
+ if (byName.has(key))
239
+ return;
240
+ byName.set(key, { name: macro, line: `${indent}@li ${macro}.sas` });
241
+ });
242
+ const sortedLines = [...byName.values()]
243
+ .sort((a, b) => compareMacroNames(a.name, b.name))
244
+ .map((entry) => entry.line);
245
+ const result = [
246
+ ...lines.slice(0, sectionIndex + 1),
247
+ ...sortedLines,
248
+ ...lines.slice(end)
249
+ ];
250
+ return withSectionSpacing(result, sectionIndex, sectionIndex + 1 + sortedLines.length).join(lineEnding);
251
+ };
252
+ exports.addMacrosToHeader = addMacrosToHeader;
253
+ /**
254
+ * Removes macro names from the `<h4> SAS Macros </h4>` section of a file
255
+ * header. The section header itself is kept, so an empty list remains a valid
256
+ * place for the formatter to add entries. The blank line before the section and
257
+ * after the list is preserved.
258
+ *
259
+ * The file is returned unchanged when it has no Doxygen header, when the header
260
+ * has no `SAS Macros` section, when the section cannot be rewritten safely, or
261
+ * when none of the named macros are listed.
262
+ *
263
+ * @param {string} text - the text content of the file.
264
+ * @param {LintConfig} config - the lint configuration, used for line endings.
265
+ * @param {string[]} macros - the macro names to remove.
266
+ * @returns {string} the updated text content.
267
+ */
268
+ const removeMacrosFromHeader = (text, config, macros) => {
269
+ if (!text || !macros.length)
270
+ return text;
271
+ const headerLinesCount = (0, getHeaderLinesCount_1.getHeaderLinesCount)(text, config);
272
+ if (!headerLinesCount)
273
+ return text;
274
+ const lines = (0, splitText_1.splitText)(text, config);
275
+ const lineEnding = config.lineEndings === LineEndings_1.LineEndings.CRLF ? '\r\n' : '\n';
276
+ const section = findSasMacrosSection(lines, headerLinesCount);
277
+ if (!section)
278
+ return text;
279
+ const { sectionIndex, entries, end } = section;
280
+ const remove = new Set(macros.map((macro) => macro.toLowerCase()));
281
+ const kept = [];
282
+ const seen = new Set();
283
+ let changed = false;
284
+ entries.forEach((entry) => {
285
+ const key = entry.name.toLowerCase();
286
+ if (remove.has(key)) {
287
+ changed = true;
288
+ return;
289
+ }
290
+ if (seen.has(key)) {
291
+ // A duplicate entry for a macro that is kept.
292
+ changed = true;
293
+ return;
294
+ }
295
+ seen.add(key);
296
+ kept.push(entry.line);
297
+ });
298
+ if (!changed)
299
+ return text;
300
+ const result = [
301
+ ...lines.slice(0, sectionIndex + 1),
302
+ ...kept,
303
+ ...lines.slice(end)
304
+ ];
305
+ return withSectionSpacing(result, sectionIndex, sectionIndex + 1 + kept.length).join(lineEnding);
306
+ };
307
+ exports.removeMacrosFromHeader = removeMacrosFromHeader;
308
+ const compareMacroNames = (a, b) => a.toLowerCase().localeCompare(b.toLowerCase());
309
+ //# sourceMappingURL=headerMacros.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headerMacros.js","sourceRoot":"","sources":["../../src/utils/headerMacros.ts"],"names":[],"mappings":"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"}
package/utils/index.d.ts CHANGED
@@ -8,3 +8,8 @@ export * from './splitText';
8
8
  export * from './getIndicesOf';
9
9
  export * from './getHeaderLinesCount';
10
10
  export * from './getDataSectionsDetail';
11
+ export * from './headerMacros';
12
+ export * from './mergeLintConfig';
13
+ export * from './parseLintOverride';
14
+ export * from './parseMacroCalls';
15
+ export * from './sasMacros';
package/utils/index.js CHANGED
@@ -24,4 +24,9 @@ __exportStar(require("./splitText"), exports);
24
24
  __exportStar(require("./getIndicesOf"), exports);
25
25
  __exportStar(require("./getHeaderLinesCount"), exports);
26
26
  __exportStar(require("./getDataSectionsDetail"), exports);
27
+ __exportStar(require("./headerMacros"), exports);
28
+ __exportStar(require("./mergeLintConfig"), exports);
29
+ __exportStar(require("./parseLintOverride"), exports);
30
+ __exportStar(require("./parseMacroCalls"), exports);
31
+ __exportStar(require("./sasMacros"), exports);
27
32
  //# sourceMappingURL=index.js.map
@@ -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"}
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,sDAAmC;AACnC,oDAAiC;AACjC,8CAA2B"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Merges a per-file configuration override over the project configuration.
3
+ *
4
+ * Scalars replace, arrays are additive, and objects merge key by key. A file
5
+ * therefore adds an allowed gremlin, a required macro option or a severity
6
+ * level without having to restate the rest of the project configuration, and
7
+ * it can still switch a rule off or raise a limit for itself. Deprecated
8
+ * setting names are folded onto their canonical key first.
9
+ */
10
+ export declare const mergeLintConfig: (base: any, override: any) => any;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mergeLintConfig = void 0;
4
+ /**
5
+ * Deprecated setting names, mapped to the canonical key. An alias is folded onto
6
+ * the canonical key before the merge, so a project that sets `noTabIndentation`
7
+ * and a file that sets `noTabs` cannot end up carrying both - which would let
8
+ * the alias win in the constructor and defeat the file.
9
+ */
10
+ const aliases = {
11
+ noTabIndentation: 'noTabs'
12
+ };
13
+ const normaliseAliases = (json) => {
14
+ if (!isPlainObject(json))
15
+ return json;
16
+ const normalised = Object.assign({}, json);
17
+ for (const [alias, canonical] of Object.entries(aliases)) {
18
+ if (alias in normalised) {
19
+ if (!(canonical in normalised)) {
20
+ normalised[canonical] = normalised[alias];
21
+ }
22
+ delete normalised[alias];
23
+ }
24
+ }
25
+ return normalised;
26
+ };
27
+ /**
28
+ * Merges a per-file configuration override over the project configuration.
29
+ *
30
+ * Scalars replace, arrays are additive, and objects merge key by key. A file
31
+ * therefore adds an allowed gremlin, a required macro option or a severity
32
+ * level without having to restate the rest of the project configuration, and
33
+ * it can still switch a rule off or raise a limit for itself. Deprecated
34
+ * setting names are folded onto their canonical key first.
35
+ */
36
+ const mergeLintConfig = (base, override) => {
37
+ const normalisedOverride = normaliseAliases(override);
38
+ if (!isPlainObject(base) || !isPlainObject(normalisedOverride)) {
39
+ return normalisedOverride;
40
+ }
41
+ const merged = normaliseAliases(base);
42
+ for (const [key, value] of Object.entries(normalisedOverride)) {
43
+ const current = merged[key];
44
+ if (Array.isArray(current) && Array.isArray(value)) {
45
+ merged[key] = mergeArrays(current, value);
46
+ }
47
+ else if (isPlainObject(current) && isPlainObject(value)) {
48
+ merged[key] = Object.assign(Object.assign({}, current), value);
49
+ }
50
+ else {
51
+ merged[key] = value;
52
+ }
53
+ }
54
+ return merged;
55
+ };
56
+ exports.mergeLintConfig = mergeLintConfig;
57
+ const isPlainObject = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
58
+ const isPrimitiveArray = (value) => value.every((item) => item === null || typeof item !== 'object');
59
+ /**
60
+ * Concatenates two arrays. Arrays of primitives are de-duplicated, so an
61
+ * override that repeats an entry the project already lists does not add it
62
+ * twice.
63
+ */
64
+ const mergeArrays = (base, override) => isPrimitiveArray(base) && isPrimitiveArray(override)
65
+ ? [...new Set([...base, ...override])]
66
+ : [...base, ...override];
67
+ //# sourceMappingURL=mergeLintConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mergeLintConfig.js","sourceRoot":"","sources":["../../src/utils/mergeLintConfig.ts"],"names":[],"mappings":";;;AAAA;;;;;GAKG;AACH,MAAM,OAAO,GAA2B;IACtC,gBAAgB,EAAE,QAAQ;CAC3B,CAAA;AAED,MAAM,gBAAgB,GAAG,CAAC,IAAS,EAAO,EAAE;IAC1C,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAErC,MAAM,UAAU,qBAAQ,IAAI,CAAE,CAAA;IAE9B,KAAK,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACzD,IAAI,KAAK,IAAI,UAAU,EAAE,CAAC;YACxB,IAAI,CAAC,CAAC,SAAS,IAAI,UAAU,CAAC,EAAE,CAAC;gBAC/B,UAAU,CAAC,SAAS,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;YAC3C,CAAC;YAED,OAAO,UAAU,CAAC,KAAK,CAAC,CAAA;QAC1B,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAA;AACnB,CAAC,CAAA;AAED;;;;;;;;GAQG;AACI,MAAM,eAAe,GAAG,CAAC,IAAS,EAAE,QAAa,EAAO,EAAE;IAC/D,MAAM,kBAAkB,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IAErD,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAC/D,OAAO,kBAAkB,CAAA;IAC3B,CAAC;IAED,MAAM,MAAM,GAAQ,gBAAgB,CAAC,IAAI,CAAC,CAAA;IAE1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAC9D,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;QAE3B,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACnD,MAAM,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QAC3C,CAAC;aAAM,IAAI,aAAa,CAAC,OAAO,CAAC,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1D,MAAM,CAAC,GAAG,CAAC,mCAAS,OAAkB,GAAM,KAAgB,CAAE,CAAA;QAChE,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;QACrB,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AAtBY,QAAA,eAAe,mBAsB3B;AAED,MAAM,aAAa,GAAG,CAAC,KAAU,EAAW,EAAE,CAC5C,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAE/D,MAAM,gBAAgB,GAAG,CAAC,KAAY,EAAW,EAAE,CACjD,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAA;AAElE;;;;GAIG;AACH,MAAM,WAAW,GAAG,CAAC,IAAW,EAAE,QAAe,EAAS,EAAE,CAC1D,gBAAgB,CAAC,IAAI,CAAC,IAAI,gBAAgB,CAAC,QAAQ,CAAC;IAClD,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,QAAQ,CAAC,CAAA"}
@@ -0,0 +1,36 @@
1
+ import type { LintConfig } from '../types/LintConfig';
2
+ /**
3
+ * Reads the per-file override from the header of a file.
4
+ *
5
+ * The override is a `@sasjslint` tag followed by a JSON object, anywhere in the
6
+ * Doxygen header:
7
+ *
8
+ * ```
9
+ * /**
10
+ * @file
11
+ * @brief Calls the settlement API
12
+ *
13
+ * @sasjslint {"maxHeaderLineLength": 200, "noUndeclaredMacros": false}
14
+ * **\/
15
+ * ```
16
+ *
17
+ * Only the header is read, so an override cannot be buried in the body of a
18
+ * file. A tag that is missing, unterminated, malformed or not an object is
19
+ * ignored rather than raised: the linter must not fail on bad input.
20
+ *
21
+ * @param {string} text - the text content of the file.
22
+ * @param {LintConfig} config - the resolved configuration, used to find the
23
+ * header and its line endings.
24
+ * @returns {object | null} the override, or null when there is none.
25
+ */
26
+ export declare const getLintOverride: (text: string, config: LintConfig) => any | null;
27
+ /**
28
+ * Returns the configuration to lint a file with: the given configuration, with
29
+ * the file's own `@sasjslint` override merged over it. The header wins, since
30
+ * it is the more specific statement.
31
+ *
32
+ * @param {string} text - the text content of the file.
33
+ * @param {LintConfig} config - the resolved configuration.
34
+ * @returns {LintConfig} the effective configuration.
35
+ */
36
+ export declare const applyLintOverride: (text: string, config: LintConfig) => LintConfig;
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyLintOverride = exports.getLintOverride = void 0;
4
+ const getHeaderLinesCount_1 = require("./getHeaderLinesCount");
5
+ const splitText_1 = require("./splitText");
6
+ /**
7
+ * The tag that introduces a per-file override, with the object that must follow
8
+ * it. The tag has to start a header line, and the `{` has to come immediately
9
+ * after it, so a `@brief` that mentions the syntax in prose, a longer tag such
10
+ * as `@sasjslintFoo`, and a tag with something else after it are all left alone
11
+ * rather than read as an override.
12
+ */
13
+ const marker = /^[^\w]*@sasjslint\s*\{/m;
14
+ /**
15
+ * Reads a balanced JSON object from `start`, honouring strings and escapes so
16
+ * a brace inside a value does not end the scan early.
17
+ */
18
+ const readObject = (text, start) => {
19
+ let depth = 0;
20
+ let inString = false;
21
+ let escaped = false;
22
+ for (let index = start; index < text.length; index++) {
23
+ const char = text[index];
24
+ if (inString) {
25
+ if (escaped)
26
+ escaped = false;
27
+ else if (char === '\\')
28
+ escaped = true;
29
+ else if (char === '"')
30
+ inString = false;
31
+ continue;
32
+ }
33
+ if (char === '"') {
34
+ inString = true;
35
+ }
36
+ else if (char === '{') {
37
+ depth++;
38
+ }
39
+ else if (char === '}') {
40
+ depth--;
41
+ if (depth === 0)
42
+ return text.slice(start, index + 1);
43
+ }
44
+ }
45
+ return null;
46
+ };
47
+ /**
48
+ * Reads the per-file override from the header of a file.
49
+ *
50
+ * The override is a `@sasjslint` tag followed by a JSON object, anywhere in the
51
+ * Doxygen header:
52
+ *
53
+ * ```
54
+ * /**
55
+ * @file
56
+ * @brief Calls the settlement API
57
+ *
58
+ * @sasjslint {"maxHeaderLineLength": 200, "noUndeclaredMacros": false}
59
+ * **\/
60
+ * ```
61
+ *
62
+ * Only the header is read, so an override cannot be buried in the body of a
63
+ * file. A tag that is missing, unterminated, malformed or not an object is
64
+ * ignored rather than raised: the linter must not fail on bad input.
65
+ *
66
+ * @param {string} text - the text content of the file.
67
+ * @param {LintConfig} config - the resolved configuration, used to find the
68
+ * header and its line endings.
69
+ * @returns {object | null} the override, or null when there is none.
70
+ */
71
+ const getLintOverride = (text, config) => {
72
+ if (!text)
73
+ return null;
74
+ const headerLinesCount = (0, getHeaderLinesCount_1.getHeaderLinesCount)(text, config);
75
+ if (!headerLinesCount)
76
+ return null;
77
+ const header = (0, splitText_1.splitText)(text, config).slice(0, headerLinesCount).join('\n');
78
+ const match = marker.exec(header);
79
+ if (!match)
80
+ return null;
81
+ // The match ends with the opening brace.
82
+ const brace = match.index + match[0].length - 1;
83
+ const body = readObject(header, brace);
84
+ if (!body)
85
+ return null;
86
+ try {
87
+ // The body always starts with `{`, so a successful parse is an object.
88
+ return JSON.parse(body);
89
+ }
90
+ catch (_a) {
91
+ return null;
92
+ }
93
+ };
94
+ exports.getLintOverride = getLintOverride;
95
+ /**
96
+ * Returns the configuration to lint a file with: the given configuration, with
97
+ * the file's own `@sasjslint` override merged over it. The header wins, since
98
+ * it is the more specific statement.
99
+ *
100
+ * @param {string} text - the text content of the file.
101
+ * @param {LintConfig} config - the resolved configuration.
102
+ * @returns {LintConfig} the effective configuration.
103
+ */
104
+ const applyLintOverride = (text, config) => {
105
+ const override = (0, exports.getLintOverride)(text, config);
106
+ return override ? config.override(override) : config;
107
+ };
108
+ exports.applyLintOverride = applyLintOverride;
109
+ //# sourceMappingURL=parseLintOverride.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseLintOverride.js","sourceRoot":"","sources":["../../src/utils/parseLintOverride.ts"],"names":[],"mappings":";;;AACA,+DAA2D;AAC3D,2CAAuC;AAEvC;;;;;;GAMG;AACH,MAAM,MAAM,GAAG,yBAAyB,CAAA;AAExC;;;GAGG;AACH,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,KAAa,EAAiB,EAAE;IAChE,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,IAAI,QAAQ,GAAG,KAAK,CAAA;IACpB,IAAI,OAAO,GAAG,KAAK,CAAA;IAEnB,KAAK,IAAI,KAAK,GAAG,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAA;QAExB,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,OAAO;gBAAE,OAAO,GAAG,KAAK,CAAA;iBACvB,IAAI,IAAI,KAAK,IAAI;gBAAE,OAAO,GAAG,IAAI,CAAA;iBACjC,IAAI,IAAI,KAAK,GAAG;gBAAE,QAAQ,GAAG,KAAK,CAAA;YACvC,SAAQ;QACV,CAAC;QAED,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,QAAQ,GAAG,IAAI,CAAA;QACjB,CAAC;aAAM,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACxB,KAAK,EAAE,CAAA;QACT,CAAC;aAAM,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACxB,KAAK,EAAE,CAAA;YACP,IAAI,KAAK,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAA;QACtD,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACI,MAAM,eAAe,GAAG,CAC7B,IAAY,EACZ,MAAkB,EACN,EAAE;IACd,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IAEtB,MAAM,gBAAgB,GAAG,IAAA,yCAAmB,EAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAC1D,IAAI,CAAC,gBAAgB;QAAE,OAAO,IAAI,CAAA;IAElC,MAAM,MAAM,GAAG,IAAA,qBAAS,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAE5E,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACjC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAA;IAEvB,yCAAyC;IACzC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAA;IAE/C,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IACtC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IAEtB,IAAI,CAAC;QACH,uEAAuE;QACvE,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IAAC,WAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC,CAAA;AA1BY,QAAA,eAAe,mBA0B3B;AAED;;;;;;;;GAQG;AACI,MAAM,iBAAiB,GAAG,CAC/B,IAAY,EACZ,MAAkB,EACN,EAAE;IACd,MAAM,QAAQ,GAAG,IAAA,uBAAe,EAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAC9C,OAAO,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;AACtD,CAAC,CAAA;AANY,QAAA,iBAAiB,qBAM7B"}
@@ -0,0 +1,28 @@
1
+ import { LintConfig } from '../types';
2
+ /**
3
+ * A macro invocation found in a file, with the position of its `%` character.
4
+ */
5
+ export interface MacroCall {
6
+ name: string;
7
+ lineNumber: number;
8
+ startColumnNumber: number;
9
+ endColumnNumber: number;
10
+ }
11
+ /**
12
+ * Finds the macro invocations in a file.
13
+ *
14
+ * SAS resolves macro triggers inside double-quoted strings but not inside
15
+ * single-quoted strings, so a double-quoted string is scanned as code while a
16
+ * single-quoted string is opaque. Invocations inside block comments, macro
17
+ * comments, comment statements, masking functions (`%str` / `%nrstr`), data
18
+ * sections and embedded-language `submit` blocks are excluded.
19
+ *
20
+ * Every `%name` token is reported, including the macro language keywords and
21
+ * the macros that ship with SAS. Callers decide which names are interesting.
22
+ *
23
+ * @param {string} text - the text content to scan.
24
+ * @param {LintConfig} config - the lint configuration. It is accepted for
25
+ * symmetry with the other utilities and is not used by the scan.
26
+ * @returns {MacroCall[]} the invocations, in the order they appear.
27
+ */
28
+ export declare const parseMacroCalls: (text: string, config?: LintConfig) => MacroCall[];