@sasjs/lint 5.3.1 → 5.5.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 (53) hide show
  1. package/README.md +52 -0
  2. package/format/adjustLineLength.d.ts +34 -0
  3. package/format/adjustLineLength.js +159 -0
  4. package/format/adjustLineLength.js.map +1 -0
  5. package/format/formatFile.d.ts +3 -1
  6. package/format/formatFile.js +4 -4
  7. package/format/formatFile.js.map +1 -1
  8. package/format/formatFolder.d.ts +3 -1
  9. package/format/formatFolder.js +38 -7
  10. package/format/formatFolder.js.map +1 -1
  11. package/format/formatProject.d.ts +2 -1
  12. package/format/formatProject.js +2 -2
  13. package/format/formatProject.js.map +1 -1
  14. package/format/formatText.d.ts +19 -1
  15. package/format/formatText.js +23 -3
  16. package/format/formatText.js.map +1 -1
  17. package/format/longLines.d.ts +60 -0
  18. package/format/longLines.js +90 -0
  19. package/format/longLines.js.map +1 -0
  20. package/format/reflowComments.d.ts +28 -0
  21. package/format/reflowComments.js +305 -0
  22. package/format/reflowComments.js.map +1 -0
  23. package/format/rewrapText.d.ts +38 -0
  24. package/format/rewrapText.js +389 -0
  25. package/format/rewrapText.js.map +1 -0
  26. package/format/shared.js +34 -5
  27. package/format/shared.js.map +1 -1
  28. package/format/splitTrailingComments.d.ts +22 -0
  29. package/format/splitTrailingComments.js +68 -0
  30. package/format/splitTrailingComments.js.map +1 -0
  31. package/package.json +11 -24
  32. package/sasjslint-schema.json +10 -1
  33. package/types/FormatOptions.d.ts +29 -0
  34. package/types/FormatOptions.js +3 -0
  35. package/types/FormatOptions.js.map +1 -0
  36. package/types/KeywordCase.d.ts +10 -0
  37. package/types/KeywordCase.js +13 -0
  38. package/types/KeywordCase.js.map +1 -0
  39. package/types/LintConfig.d.ts +10 -0
  40. package/types/LintConfig.js +17 -0
  41. package/types/LintConfig.js.map +1 -1
  42. package/types/index.d.ts +2 -0
  43. package/types/index.js +2 -0
  44. package/types/index.js.map +1 -1
  45. package/utils/getDataSectionsDetail.d.ts +14 -0
  46. package/utils/getDataSectionsDetail.js +29 -13
  47. package/utils/getDataSectionsDetail.js.map +1 -1
  48. package/utils/getLintConfig.d.ts +1 -0
  49. package/utils/getLintConfig.js +1 -0
  50. package/utils/getLintConfig.js.map +1 -1
  51. package/utils/scanLine.d.ts +59 -0
  52. package/utils/scanLine.js +115 -0
  53. package/utils/scanLine.js.map +1 -0
package/README.md CHANGED
@@ -29,6 +29,7 @@ Configuration is via a `.sasjslint` file with the following structure (these are
29
29
  "ignoredLibnames": [],
30
30
  "ignoredMacros": ["webout"],
31
31
  "indentationMultiple": 2,
32
+ "keywordCase": "preserve",
32
33
  "lineEndings": "off",
33
34
  "lowerCaseFileNames": true,
34
35
  "lowerCaseMacroNames": true,
@@ -162,6 +163,14 @@ If creating a new value, use `{lineEnding}` instead of `\n`, eg as follows:
162
163
  }
163
164
  ```
164
165
 
166
+ ### keywordCase
167
+
168
+ The case the formatter gives SAS keywords. `preserve` leaves them as written, which is what the printer does when nothing is asked of it; `upper` and `lower` rewrite them.
169
+
170
+ It is a formatting setting rather than a rule - casing is a house style, and reporting it on every line would bury the rules that describe defects - and it takes effect when the formatter is given a layout engine. Every caller with a SAS printer supplies one (`@sasjs/sas-language` is where ours comes from), because the words worth changing are the ones that engine's lexer identifies. Strings and comments are left alone; a name the lexer cannot tell from a keyword - a DATA step variable called `label` - is cased with it.
171
+
172
+ - Default: preserve
173
+
165
174
  ### hasDoxygenHeader
166
175
 
167
176
  The SASjs framework recommends the use of Doxygen headers for describing all types of SAS program. This check identifies files where a Doxygen header does not begin on the first line, where it opens with a single asterisk, where it closes with one, where the close shares its line with the header's content, and where the block is never closed. A header is located by its close: the line that holds nothing but `**/`, which is the form `sasjs compile` and this rule set read. A block opened with `/**` whose close sits at the end of a content line, or which has no close at all, is therefore not yet a header.
@@ -333,6 +342,46 @@ In batch mode, long SAS code lines may also be truncated, causing hard-to-detect
333
342
 
334
343
  We strongly recommend a line length limit, and set the bar at 80. To turn this feature off, set the value to 0.
335
344
 
345
+ Saving with the formatter does four things for this rule, in this order.
346
+
347
+ **It lifts a trailing comment onto its own line** when a line is over the limit and carries code before the comment. The code keeps the line it was on and the comment moves below it at the same indentation, which hands the code to the wrap below and the prose to the reflow - so `, var /* variable name from which to return the format */` becomes two lines, and each is handled by the pass that owns it. The lift happens even when the code alone overruns the limit: each half then reaches the pass that owns it, instead of the line being refused by both.
348
+
349
+ **It breaks a long line of code** at the last space that still fits, and indents the continuation by the next multiple of [`indentationMultiple`](#indentationmultiple), so a wrap never introduces an indentation warning of its own. A space that follows a `;` is preferred over one inside a statement: the semicolon ends the statement, so a newline there is inert and the line reads as SAS rather than as a slice of one, and for a `put` or `%let` it needs none of the `%trim(` machinery below. It also prefers a space that does not leave a macro keyword such as `%let` or `%then` at the end of the line, and a line is rewritten only when every line it becomes fits the limit - so the pass never trades one warning for another, which also keeps `fixedDiagnosticsCount` from going backwards. The shapes where a break would change what SAS reads, or what another rule parses, are left alone:
350
+
351
+ - `datalines` / `cards` / `parmcards` records, and the statement that opens them, where a newline ends the record
352
+ - a `%*` comment statement, which is prose
353
+ - any space inside `%str`-family masking or a `%sysfunc` / `%qsysfunc` call, whose argument text is carried as data: measured on a live engine, breaking inside `%sysfunc(fput(...))` wrote `Content-Type: application/csv` where the macro meant one space
354
+
355
+ A `put`, `putlog` or `%put` statement (including the `%&var.put` form), the window statements, and a `%let` assignment are broken differently, because a plain newline there is data rather than a delimiter: it is a line of the log for the `put` family, and part of the value for `%let`. The break ends the line with `%trim(` and opens the next with `)`, so the newline and the indentation become the argument of `%trim` - which resolves to nothing - and the blank the break replaced sits after the closing marker:
356
+
357
+ ```sas
358
+ %put NOTE: the macro could not find the requested%trim(
359
+ ) table and stopped;
360
+ %let exp8=MPE_DERIVED:CALLER:VAR_LOGIC may not call%trim(
361
+ ) a function that;
362
+ ```
363
+
364
+ Leaving the blank at the end of the upper line instead would lose it: measured on a live engine, `requested %trim(` printed `requestedtable`, because SAS drops a blank that sits immediately before a macro call - and a trailing blank is what `noTrailingSpaces` reports anyway. The construct is verified end to end for both forms: a macro holding these statements writes byte-identical log lines, and the `%let` variables hold byte-identical values, before and after a format at a limit of 40 and at 60, several breaks per statement included. A `%let` value built over two statements, and one whose text already carries a `%trim(` of its own, are covered by the same check.
365
+
366
+ A `%macro` declaration is broken before its parameter commas, and the continuation line starts with the comma, so the parameters read as a list and the declaration stays the one statement the macro rules parse:
367
+
368
+ ```sas
369
+ %macro mf_writefile(fpath
370
+ ,mode=O,l1=,l2=,l3=,l4=,l5=,l6=,l7=,l8=,l9=,l10=);
371
+ ```
372
+
373
+ **It reflows the prose in a block comment**, paragraph by paragraph: the lines of one paragraph are joined and rewrapped, so the block comes back even rather than ragged. A paragraph breaks at a blank line and before anything line-oriented - an `@li` entry, a `@sasjslint` tag, an `<h4>` heading, a list item, a table row or a macro trigger - and a `@brief` or `@param` line carries the lines under it. Blocks holding a `@example` / `@code` / `@verbatim` body, the `<h4> SAS Macros </h4>` section, a comment that shares a line with code, and a comment inside a `datalines` record are left byte-identical. Prose written with a `*` at the start of every line - the JavaDoc style - is read as a list and left as it is. A reflow normalises the runs of spaces inside the paragraph it rewrites, so two spaces after a full stop become one.
374
+
375
+ **It raises the file's own exception** for a line neither pass could shorten - a URL in a header, a long quoted literal that generates code, an indented data record - by writing or merging a [`@sasjslint`](#per-file-overrides) block at the end of the header:
376
+
377
+ ```sas
378
+ @sasjslint {"maxHeaderLineLength": 123}
379
+ ```
380
+
381
+ The value is the exact length the longest such line needs and no more: a header line raises `maxHeaderLineLength`, a data record raises `maxDataLineLength`, and a body line raises `maxLineLength`. An existing exception keeps every key it had, and a value is never lowered.
382
+
383
+ Nothing is raised for a line whose author can still act on it: a `%*` comment, a `put` whose text holds a word longer than the limit, a `%macro` declaration whose single parameter is too long, or a comment line that a reflow cannot fit. Excusing one of those would relax `maxLineLength` for every line of code in the file, and each is a line the author can rewrite.
384
+
336
385
  - Default: 80
337
386
  - Severity: WARNING
338
387
 
@@ -690,6 +739,9 @@ We've already implemented the following rules:
690
739
  - Remove unused macros from the `<h4> SAS Macros </h4>` section
691
740
  - Remove trailing spaces
692
741
  - Fix line endings, including adding a missing final newline when [`lineEndings`](#lineendings) is configured
742
+ - Break a line that exceeds [`maxLineLength`](#maxlinelength) at whitespace, where doing so cannot change what SAS reads
743
+ - Reflow the prose in a block comment, paragraph by paragraph
744
+ - Raise the file's own `@sasjslint` exception for a line neither of the above can shorten
693
745
 
694
746
  The formatter only fixes rules that are in your configuration, so a rule that is off neither warns nor is repaired.
695
747
 
@@ -0,0 +1,34 @@
1
+ import { LintConfig } from '../types';
2
+ /**
3
+ * Raises the file's own `@sasjslint` exception so that the lines the passes
4
+ * could not shorten stop being reported.
5
+ *
6
+ * The formatter can break most long lines, and `reflowComments` can reflow
7
+ * prose, but a single token wider than the limit - a URL in a header, a long
8
+ * quoted literal that generates code - cannot be shortened at all, and a data
9
+ * record is not code. The documented way to accept those is the per-file
10
+ * override, and projects do it by hand today (sasjs/core carries
11
+ * `{"maxLineLength": 123}` for one documentation URL). This writes it for them,
12
+ * at the exact length the longest such line needs and no more:
13
+ *
14
+ * - a header line raises `maxHeaderLineLength`, which affects header lines only
15
+ * - a data record raises `maxDataLineLength`, which affects records only
16
+ * - a body line raises `maxLineLength`, which affects the whole file
17
+ *
18
+ * Lines the wrapper declines on purpose - `put`-family statements, where a
19
+ * break is an extra line of output, and `%macro` / `%mend` declarations - do NOT
20
+ * raise the limit: they are fixable by hand, and excusing them would hide the
21
+ * finding for good.
22
+ *
23
+ * The value is never lowered, an existing exception keeps every key it had, and
24
+ * no header means nowhere to write, in which case the text is returned
25
+ * untouched.
26
+ *
27
+ * @param {string} text - the text to adjust.
28
+ * @param {LintConfig} configuration - the effective configuration, the file's
29
+ * own override included.
30
+ * @returns {string} the text, with its exception raised when one is needed.
31
+ */
32
+ export declare const adjustLineLength: (text: string, configuration: LintConfig) => string;
33
+ /** The configured line ending, for the rare case a terminator has to be invented. */
34
+ export declare const lineEndingOf: (configuration: LintConfig) => string;
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.lineEndingOf = exports.adjustLineLength = void 0;
4
+ const LineEndings_1 = require("../types/LineEndings");
5
+ const getDataSectionsDetail_1 = require("../utils/getDataSectionsDetail");
6
+ const getHeaderLinesCount_1 = require("../utils/getHeaderLinesCount");
7
+ const parseLintOverride_1 = require("../utils/parseLintOverride");
8
+ const scanLine_1 = require("../utils/scanLine");
9
+ const longLines_1 = require("./longLines");
10
+ const rewrapText_1 = require("./rewrapText");
11
+ /** The line that carries a file's own lint configuration. */
12
+ const overrideTag = /^(\s*)@sasjslint\s*(\{.*\})\s*$/;
13
+ /**
14
+ * Raises the file's own `@sasjslint` exception so that the lines the passes
15
+ * could not shorten stop being reported.
16
+ *
17
+ * The formatter can break most long lines, and `reflowComments` can reflow
18
+ * prose, but a single token wider than the limit - a URL in a header, a long
19
+ * quoted literal that generates code - cannot be shortened at all, and a data
20
+ * record is not code. The documented way to accept those is the per-file
21
+ * override, and projects do it by hand today (sasjs/core carries
22
+ * `{"maxLineLength": 123}` for one documentation URL). This writes it for them,
23
+ * at the exact length the longest such line needs and no more:
24
+ *
25
+ * - a header line raises `maxHeaderLineLength`, which affects header lines only
26
+ * - a data record raises `maxDataLineLength`, which affects records only
27
+ * - a body line raises `maxLineLength`, which affects the whole file
28
+ *
29
+ * Lines the wrapper declines on purpose - `put`-family statements, where a
30
+ * break is an extra line of output, and `%macro` / `%mend` declarations - do NOT
31
+ * raise the limit: they are fixable by hand, and excusing them would hide the
32
+ * finding for good.
33
+ *
34
+ * The value is never lowered, an existing exception keeps every key it had, and
35
+ * no header means nowhere to write, in which case the text is returned
36
+ * untouched.
37
+ *
38
+ * @param {string} text - the text to adjust.
39
+ * @param {LintConfig} configuration - the effective configuration, the file's
40
+ * own override included.
41
+ * @returns {string} the text, with its exception raised when one is needed.
42
+ */
43
+ const adjustLineLength = (text, configuration) => {
44
+ if (!text)
45
+ return text;
46
+ if (!configuration.lineLintRules.some((rule) => rule.name === 'maxLineLength'))
47
+ return text;
48
+ const headerLinesCount = (0, getHeaderLinesCount_1.getHeaderLinesCount)(text, configuration);
49
+ if (!headerLinesCount)
50
+ return text;
51
+ const entries = (0, rewrapText_1.splitWithTerminators)(text);
52
+ const lines = entries.map((entry) => entry.line);
53
+ const dataSections = (0, getDataSectionsDetail_1.getDataSectionsDetail)(text, configuration);
54
+ const headerLimit = Math.max(configuration.maxLineLength, configuration.maxHeaderLineLength);
55
+ const dataLimit = Math.max(configuration.maxLineLength, configuration.maxDataLineLength);
56
+ const needed = {
57
+ maxLineLength: 0,
58
+ maxHeaderLineLength: 0,
59
+ maxDataLineLength: 0
60
+ };
61
+ let state = (0, scanLine_1.cleanState)();
62
+ let breakAwareOpen = false;
63
+ lines.forEach((line, index) => {
64
+ const isHeader = index < headerLinesCount;
65
+ const isData = (0, getDataSectionsDetail_1.checkIsDataLine)(dataSections, index);
66
+ // A data record is data, so it is not scanned: a comment marker or an
67
+ // apostrophe in one would otherwise stay open for the rest of the file, and
68
+ // every later line would read as a comment that no pass may touch.
69
+ const scanned = isData ? (0, scanLine_1.noScan)() : (0, scanLine_1.scanLine)(line, state);
70
+ state = scanned.endState;
71
+ const breakAware = isData
72
+ ? { line: false, open: false }
73
+ : (0, longLines_1.breakAwareState)(scanned.code, breakAwareOpen);
74
+ breakAwareOpen = breakAware.open;
75
+ const limit = isData
76
+ ? dataLimit
77
+ : isHeader
78
+ ? headerLimit
79
+ : configuration.maxLineLength;
80
+ if (line.length <= limit)
81
+ return;
82
+ if (breakAware.line ||
83
+ longLines_1.macroComment.test(line) ||
84
+ longLines_1.macroDeclaration.test(line))
85
+ return;
86
+ // A comment carries no key of its own, so excusing one would raise the
87
+ // limit for every line of code in the file: `reflowComments` has already
88
+ // reflowed the prose, and a paragraph holding a word it cannot fit - a URL -
89
+ // is left to its author. A header line is the exception, because
90
+ // `maxHeaderLineLength` scopes the relaxation to the header.
91
+ //
92
+ // The guard turns on what makes the line long, not on the comment being
93
+ // there: a line of code that overruns the limit on its own, with a comment
94
+ // after it, is excused like any other, because no pass may break it either.
95
+ const codeEnd = scanned.code.trimEnd().length;
96
+ if (!isHeader && !isData && scanned.hasComment && codeEnd <= limit)
97
+ return;
98
+ const key = isData
99
+ ? 'maxDataLineLength'
100
+ : isHeader
101
+ ? 'maxHeaderLineLength'
102
+ : 'maxLineLength';
103
+ needed[key] = Math.max(needed[key], line.length);
104
+ });
105
+ const raised = Object.keys(needed).filter((key) => needed[key] > configuration[key]);
106
+ if (!raised.length)
107
+ return text;
108
+ const override = (0, parseLintOverride_1.getLintOverride)(text, configuration);
109
+ const existing = lines
110
+ .slice(0, headerLinesCount)
111
+ .find((line) => overrideTag.test(line));
112
+ // A tag that does not parse is reported by the linter and belongs to its
113
+ // author: rewriting it would discard whatever they were trying to say.
114
+ if (existing && !override)
115
+ return text;
116
+ const merged = Object.assign({}, (override !== null && override !== void 0 ? override : {}));
117
+ raised.forEach((key) => {
118
+ var _a;
119
+ merged[key] = Math.max((_a = merged[key]) !== null && _a !== void 0 ? _a : 0, needed[key]);
120
+ });
121
+ const json = '{' +
122
+ Object.keys(merged)
123
+ .map((key) => `"${key}": ${JSON.stringify(merged[key])}`)
124
+ .join(', ') +
125
+ '}';
126
+ if (existing) {
127
+ const match = overrideTag.exec(existing);
128
+ if (match[2] === json)
129
+ return text;
130
+ const at = lines.indexOf(existing);
131
+ const rewritten = [...lines];
132
+ rewritten[at] = `${match[1]}@sasjslint ${json}`;
133
+ return rewritten.map((line, index) => line + entries[index].ending).join('');
134
+ }
135
+ // No exception yet: write one as the last content line of the header, which
136
+ // is where sasjs/core keeps them, so the header's own layout is untouched.
137
+ const closingIndex = headerLinesCount - 1;
138
+ const closing = lines[closingIndex];
139
+ if (!/^\s*[*]{2}\/\s*$/.test(closing))
140
+ return text;
141
+ const previous = lines[closingIndex - 1];
142
+ const indent = /^\s*/.exec(previous)[0] || ' ';
143
+ const ending = entries[closingIndex].ending || (0, exports.lineEndingOf)(configuration);
144
+ const separator = previous.trim() === '' ? [] : [''];
145
+ const inserted = [...separator, `${indent}@sasjslint ${json}`];
146
+ const out = lines
147
+ .slice(0, closingIndex)
148
+ .map((line, index) => line + entries[index].ending);
149
+ inserted.forEach((line) => out.push(line + ending));
150
+ lines.slice(closingIndex).forEach((line, index) => {
151
+ out.push(line + entries[closingIndex + index].ending);
152
+ });
153
+ return out.join('');
154
+ };
155
+ exports.adjustLineLength = adjustLineLength;
156
+ /** The configured line ending, for the rare case a terminator has to be invented. */
157
+ const lineEndingOf = (configuration) => configuration.lineEndings === LineEndings_1.LineEndings.CRLF ? '\r\n' : '\n';
158
+ exports.lineEndingOf = lineEndingOf;
159
+ //# sourceMappingURL=adjustLineLength.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adjustLineLength.js","sourceRoot":"","sources":["../../src/format/adjustLineLength.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,11 @@
1
1
  import { FormatResult } from '../types';
2
2
  import { LintConfig } from '../types/LintConfig';
3
+ import { FormatOptions } from '../types/FormatOptions';
3
4
  /**
4
5
  * Applies automatic formatting to the file at the given path.
5
6
  * @param {string} filePath - the path to the file to be formatted.
6
7
  * @param {LintConfig} configuration - an optional configuration. When not passed in, this is read from the .sasjslint file.
8
+ * @param {FormatOptions} options - an optional layout engine, used for the reprint pass.
7
9
  * @returns {Promise<FormatResult>} Resolves successfully when the file has been formatted.
8
10
  */
9
- export declare const formatFile: (filePath: string, configuration?: LintConfig) => Promise<FormatResult>;
11
+ export declare const formatFile: (filePath: string, configuration?: LintConfig, options?: FormatOptions) => Promise<FormatResult>;
@@ -12,21 +12,21 @@ 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");
16
15
  const getLintConfig_1 = require("../utils/getLintConfig");
17
- const shared_1 = require("./shared");
16
+ const formatText_1 = require("./formatText");
18
17
  /**
19
18
  * Applies automatic formatting to the file at the given path.
20
19
  * @param {string} filePath - the path to the file to be formatted.
21
20
  * @param {LintConfig} configuration - an optional configuration. When not passed in, this is read from the .sasjslint file.
21
+ * @param {FormatOptions} options - an optional layout engine, used for the reprint pass.
22
22
  * @returns {Promise<FormatResult>} Resolves successfully when the file has been formatted.
23
23
  */
24
- const formatFile = (filePath, configuration) => __awaiter(void 0, void 0, void 0, function* () {
24
+ const formatFile = (filePath_1, configuration_1, ...args_1) => __awaiter(void 0, [filePath_1, configuration_1, ...args_1], void 0, function* (filePath, configuration, options = {}) {
25
25
  const config = configuration || (yield (0, getLintConfig_1.getLintConfig)());
26
26
  const diagnosticsBeforeFormat = yield (0, lint_1.lintFile)(filePath, config);
27
27
  const diagnosticsCountBeforeFormat = diagnosticsBeforeFormat.length;
28
28
  const text = yield (0, file_1.readFile)(filePath);
29
- const formattedText = (0, shared_1.processText)(text, (0, parseLintOverride_1.applyLintOverride)(text, config));
29
+ const formattedText = yield (0, formatText_1.formatText)(text, config, options);
30
30
  yield (0, file_1.createFile)(filePath, formattedText);
31
31
  const diagnosticsAfterFormat = yield (0, lint_1.lintFile)(filePath, config);
32
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,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"}
1
+ {"version":3,"file":"formatFile.js","sourceRoot":"","sources":["../../src/format/formatFile.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,4CAAwD;AACxD,kCAAkC;AAGlC,0DAAsD;AAEtD,6CAAyC;AAEzC;;;;;;GAMG;AACI,MAAM,UAAU,GAAG,yCAID,EAAE,gFAHzB,QAAgB,EAChB,aAA0B,EAC1B,UAAyB,EAAE;IAE3B,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,MAAM,IAAA,uBAAU,EAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;IAE7D,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;AAhCY,QAAA,UAAU,cAgCtB"}
@@ -1,9 +1,11 @@
1
1
  import { FormatResult } from '../types';
2
2
  import { LintConfig } from '../types/LintConfig';
3
+ import { FormatOptions } from '../types/FormatOptions';
3
4
  /**
4
5
  * Automatically formats all SAS files in the folder at the given path.
5
6
  * @param {string} folderPath - the path to the folder to be formatted.
6
7
  * @param {LintConfig} configuration - an optional configuration. When not passed in, this is read from the .sasjslint file.
8
+ * @param {FormatOptions} options - an optional layout engine, passed to every file.
7
9
  * @returns {Promise<FormatResult>} Resolves successfully when all SAS files in the given folder have been formatted.
8
10
  */
9
- export declare const formatFolder: (folderPath: string, configuration?: LintConfig) => Promise<FormatResult>;
11
+ export declare const formatFolder: (folderPath: string, configuration?: LintConfig, options?: FormatOptions) => Promise<FormatResult>;
@@ -20,6 +20,14 @@ const asyncForEach_1 = require("../utils/asyncForEach");
20
20
  const getLintConfig_1 = require("../utils/getLintConfig");
21
21
  const listSasFiles_1 = require("../utils/listSasFiles");
22
22
  const formatFile_1 = require("./formatFile");
23
+ /**
24
+ * One line of the aggregate raised when a walk finishes with failures.
25
+ *
26
+ * The engine is the caller's own function, so what it throws is not necessarily an
27
+ * Error - a string is enough - and `error.message` on one of those reads
28
+ * `undefined`, dropping the reason the rejection was passed on for.
29
+ */
30
+ const describeFailure = (where, error) => `${where}: ${error instanceof Error ? error.message : String(error)}`;
23
31
  const excludeFolders = [
24
32
  '.git',
25
33
  '.github',
@@ -32,32 +40,55 @@ const excludeFolders = [
32
40
  * Automatically formats all SAS files in the folder at the given path.
33
41
  * @param {string} folderPath - the path to the folder to be formatted.
34
42
  * @param {LintConfig} configuration - an optional configuration. When not passed in, this is read from the .sasjslint file.
43
+ * @param {FormatOptions} options - an optional layout engine, passed to every file.
35
44
  * @returns {Promise<FormatResult>} Resolves successfully when all SAS files in the given folder have been formatted.
36
45
  */
37
- const formatFolder = (folderPath, configuration) => __awaiter(void 0, void 0, void 0, function* () {
46
+ const formatFolder = (folderPath_1, configuration_1, ...args_1) => __awaiter(void 0, [folderPath_1, configuration_1, ...args_1], void 0, function* (folderPath, configuration, options = {}) {
38
47
  const config = configuration || (yield (0, getLintConfig_1.getLintConfig)());
39
- const diagnosticsBeforeFormat = yield (0, lint_1.lintFolder)(folderPath);
48
+ // The counts have to be measured with the same configuration the files are
49
+ // formatted by, or a rule the caller switched off is still counted as unfixed.
50
+ const diagnosticsBeforeFormat = yield (0, lint_1.lintFolder)(folderPath, config);
40
51
  const diagnosticsCountBeforeFormat = Array.from(diagnosticsBeforeFormat.values()).reduce((a, b) => a + b.length, 0);
41
52
  const fileNames = yield (0, listSasFiles_1.listSasFiles)(folderPath);
53
+ // A file the engine refuses must not decide what happens to the rest of the
54
+ // tree: the walk continues and the failures are raised together at the end, so
55
+ // one unformattable buffer cannot leave a project half written.
56
+ const failures = [];
42
57
  yield (0, asyncForEach_1.asyncForEach)(fileNames, (fileName) => __awaiter(void 0, void 0, void 0, function* () {
43
58
  const filePath = path_1.default.join(folderPath, fileName);
44
- yield (0, formatFile_1.formatFile)(filePath);
59
+ try {
60
+ yield (0, formatFile_1.formatFile)(filePath, config, options);
61
+ }
62
+ catch (error) {
63
+ failures.push(describeFailure(filePath, error));
64
+ }
45
65
  }));
46
66
  const subFolders = (yield (0, file_1.listSubFoldersInFolder)(folderPath)).filter((f) => !excludeFolders.includes(f));
47
67
  yield (0, asyncForEach_1.asyncForEach)(subFolders, (subFolder) => __awaiter(void 0, void 0, void 0, function* () {
48
- yield (0, exports.formatFolder)(path_1.default.join(folderPath, subFolder), config);
68
+ try {
69
+ yield (0, exports.formatFolder)(path_1.default.join(folderPath, subFolder), config, options);
70
+ }
71
+ catch (error) {
72
+ failures.push(describeFailure(path_1.default.join(folderPath, subFolder), error));
73
+ }
49
74
  }));
50
- const diagnosticsAfterFormat = yield (0, lint_1.lintFolder)(folderPath);
75
+ const diagnosticsAfterFormat = yield (0, lint_1.lintFolder)(folderPath, config);
51
76
  const diagnosticsCountAfterFormat = Array.from(diagnosticsAfterFormat.values()).reduce((a, b) => a + b.length, 0);
52
77
  const fixedDiagnosticsCount = diagnosticsCountBeforeFormat - diagnosticsCountAfterFormat;
53
78
  const updatedFilePaths = [];
79
+ // Both maps come from lintFolder over the same file list, so every path in
80
+ // before is in after as well - a fallback there would be a branch no run reaches.
54
81
  Array.from(diagnosticsBeforeFormat.keys()).forEach((filePath) => {
55
- const diagnosticsBefore = diagnosticsBeforeFormat.get(filePath) || [];
56
- const diagnosticsAfter = diagnosticsAfterFormat.get(filePath) || [];
82
+ const diagnosticsBefore = diagnosticsBeforeFormat.get(filePath);
83
+ const diagnosticsAfter = diagnosticsAfterFormat.get(filePath);
57
84
  if (diagnosticsBefore.length !== diagnosticsAfter.length) {
58
85
  updatedFilePaths.push(filePath);
59
86
  }
60
87
  });
88
+ if (failures.length) {
89
+ throw new Error(`Could not format ${failures.length} file(s). Everything else was formatted.\n` +
90
+ failures.map((failure) => ` ${failure}`).join('\n'));
91
+ }
61
92
  return {
62
93
  updatedFilePaths,
63
94
  fixedDiagnosticsCount,
@@ -1 +1 @@
1
- {"version":3,"file":"formatFolder.js","sourceRoot":"","sources":["../../src/format/formatFolder.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,4CAA0D;AAC1D,gDAAuB;AACvB,kCAAoC;AAGpC,wDAAoD;AACpD,0DAAsD;AACtD,wDAAoD;AACpD,6CAAyC;AAEzC,MAAM,cAAc,GAAG;IACrB,MAAM;IACN,SAAS;IACT,SAAS;IACT,cAAc;IACd,YAAY;IACZ,cAAc;CACf,CAAA;AAED;;;;;GAKG;AACI,MAAM,YAAY,GAAG,CAC1B,UAAkB,EAClB,aAA0B,EACH,EAAE;IACzB,MAAM,MAAM,GAAG,aAAa,IAAI,CAAC,MAAM,IAAA,6BAAa,GAAE,CAAC,CAAA;IACvD,MAAM,uBAAuB,GAAG,MAAM,IAAA,iBAAU,EAAC,UAAU,CAAC,CAAA;IAC5D,MAAM,4BAA4B,GAAG,KAAK,CAAC,IAAI,CAC7C,uBAAuB,CAAC,MAAM,EAAE,CACjC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IAEnC,MAAM,SAAS,GAAG,MAAM,IAAA,2BAAY,EAAC,UAAU,CAAC,CAAA;IAChD,MAAM,IAAA,2BAAY,EAAC,SAAS,EAAE,CAAO,QAAQ,EAAE,EAAE;QAC/C,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;QAChD,MAAM,IAAA,uBAAU,EAAC,QAAQ,CAAC,CAAA;IAC5B,CAAC,CAAA,CAAC,CAAA;IAEF,MAAM,UAAU,GAAG,CAAC,MAAM,IAAA,6BAAsB,EAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAClE,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,CAC3C,CAAA;IAED,MAAM,IAAA,2BAAY,EAAC,UAAU,EAAE,CAAO,SAAS,EAAE,EAAE;QACjD,MAAM,IAAA,oBAAY,EAAC,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,CAAA;IAC9D,CAAC,CAAA,CAAC,CAAA;IAEF,MAAM,sBAAsB,GAAG,MAAM,IAAA,iBAAU,EAAC,UAAU,CAAC,CAAA;IAC3D,MAAM,2BAA2B,GAAG,KAAK,CAAC,IAAI,CAC5C,sBAAsB,CAAC,MAAM,EAAE,CAChC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IAEnC,MAAM,qBAAqB,GACzB,4BAA4B,GAAG,2BAA2B,CAAA;IAE5D,MAAM,gBAAgB,GAAa,EAAE,CAAA;IAErC,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;QAC9D,MAAM,iBAAiB,GAAG,uBAAuB,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;QACrE,MAAM,gBAAgB,GAAG,sBAAsB,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;QAEnE,IAAI,iBAAiB,CAAC,MAAM,KAAK,gBAAgB,CAAC,MAAM,EAAE,CAAC;YACzD,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACjC,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,OAAO;QACL,gBAAgB;QAChB,qBAAqB;QACrB,kBAAkB,EAAE,sBAAsB;KAC3C,CAAA;AACH,CAAC,CAAA,CAAA;AAhDY,QAAA,YAAY,gBAgDxB"}
1
+ {"version":3,"file":"formatFolder.js","sourceRoot":"","sources":["../../src/format/formatFolder.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { FormatResult } from '../types/FormatResult';
2
+ import { FormatOptions } from '../types/FormatOptions';
2
3
  /**
3
4
  * Automatically formats all SAS files in the current project.
4
5
  * @returns {Promise<FormatResult>} Resolves successfully when all SAS files in the current project have been formatted.
5
6
  */
6
- export declare const formatProject: () => Promise<FormatResult>;
7
+ export declare const formatProject: (options?: FormatOptions) => Promise<FormatResult>;
@@ -16,13 +16,13 @@ const formatFolder_1 = require("./formatFolder");
16
16
  * Automatically formats all SAS files in the current project.
17
17
  * @returns {Promise<FormatResult>} Resolves successfully when all SAS files in the current project have been formatted.
18
18
  */
19
- const formatProject = () => __awaiter(void 0, void 0, void 0, function* () {
19
+ const formatProject = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (options = {}) {
20
20
  const projectRoot = (yield (0, getProjectRoot_1.getProjectRoot)()) || process.currentDir;
21
21
  if (!projectRoot) {
22
22
  throw new Error('SASjs Project Root was not found.');
23
23
  }
24
24
  console.info(`Formatting all .sas files under ${projectRoot}`);
25
- return yield (0, formatFolder_1.formatFolder)(projectRoot);
25
+ return yield (0, formatFolder_1.formatFolder)(projectRoot, undefined, options);
26
26
  });
27
27
  exports.formatProject = formatProject;
28
28
  //# sourceMappingURL=formatProject.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"formatProject.js","sourceRoot":"","sources":["../../src/format/formatProject.ts"],"names":[],"mappings":";;;;;;;;;;;;AAEA,4DAAwD;AACxD,iDAA6C;AAE7C;;;GAGG;AACI,MAAM,aAAa,GAAG,GAAgC,EAAE;IAC7D,MAAM,WAAW,GAAG,CAAC,MAAM,IAAA,+BAAc,GAAE,CAAC,IAAI,OAAO,CAAC,UAAU,CAAA;IAClE,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAA;IACtD,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,mCAAmC,WAAW,EAAE,CAAC,CAAA;IAE9D,OAAO,MAAM,IAAA,2BAAY,EAAC,WAAW,CAAC,CAAA;AACxC,CAAC,CAAA,CAAA;AATY,QAAA,aAAa,iBASzB"}
1
+ {"version":3,"file":"formatProject.js","sourceRoot":"","sources":["../../src/format/formatProject.ts"],"names":[],"mappings":";;;;;;;;;;;;AAGA,4DAAwD;AACxD,iDAA6C;AAE7C;;;GAGG;AACI,MAAM,aAAa,GAAG,YAEJ,EAAE,mDADzB,UAAyB,EAAE;IAE3B,MAAM,WAAW,GAAG,CAAC,MAAM,IAAA,+BAAc,GAAE,CAAC,IAAI,OAAO,CAAC,UAAU,CAAA;IAClE,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAA;IACtD,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,mCAAmC,WAAW,EAAE,CAAC,CAAA;IAE9D,OAAO,MAAM,IAAA,2BAAY,EAAC,WAAW,EAAE,SAAS,EAAE,OAAO,CAAC,CAAA;AAC5D,CAAC,CAAA,CAAA;AAXY,QAAA,aAAa,iBAWzB"}
@@ -1,2 +1,20 @@
1
+ import { FormatOptions } from '../types/FormatOptions';
1
2
  import { LintConfig } from '../types';
2
- export declare const formatText: (text: string, configuration?: LintConfig) => Promise<string>;
3
+ /**
4
+ * Formats SAS text: the caller's reprint pass if one was supplied, then the
5
+ * configured rules.
6
+ *
7
+ * The order is deliberate. A reprint decides where lines break and how blocks are
8
+ * indented, so the rules that wrap and trim have to run over its output - applying
9
+ * them to the original text and reprinting afterwards would undo both. The lint
10
+ * override comment travels with the text through the reprint (it is a comment),
11
+ * so `keywordCase` from an `@sasjslint` line applies to the same buffer.
12
+ *
13
+ * A rejection from the engine is not caught: its message is the one that says why
14
+ * (`@sasjs/sas-language` names the data file it could not read, for example), and
15
+ * a formatter that swallowed it would return a buffer that looks formatted and is
16
+ * not. `formatFolder` is the one place that catches, because there a single
17
+ * refusal must not stop the walk - it collects them and raises them together once
18
+ * the tree is done.
19
+ */
20
+ export declare const formatText: (text: string, configuration?: LintConfig, options?: FormatOptions) => Promise<string>;
@@ -12,9 +12,29 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.formatText = void 0;
13
13
  const utils_1 = require("../utils");
14
14
  const shared_1 = require("./shared");
15
- const formatText = (text, configuration) => __awaiter(void 0, void 0, void 0, function* () {
16
- const config = configuration || (yield (0, utils_1.getLintConfig)());
17
- return (0, shared_1.processText)(text, (0, utils_1.applyLintOverride)(text, config));
15
+ /**
16
+ * Formats SAS text: the caller's reprint pass if one was supplied, then the
17
+ * configured rules.
18
+ *
19
+ * The order is deliberate. A reprint decides where lines break and how blocks are
20
+ * indented, so the rules that wrap and trim have to run over its output - applying
21
+ * them to the original text and reprinting afterwards would undo both. The lint
22
+ * override comment travels with the text through the reprint (it is a comment),
23
+ * so `keywordCase` from an `@sasjslint` line applies to the same buffer.
24
+ *
25
+ * A rejection from the engine is not caught: its message is the one that says why
26
+ * (`@sasjs/sas-language` names the data file it could not read, for example), and
27
+ * a formatter that swallowed it would return a buffer that looks formatted and is
28
+ * not. `formatFolder` is the one place that catches, because there a single
29
+ * refusal must not stop the walk - it collects them and raises them together once
30
+ * the tree is done.
31
+ */
32
+ const formatText = (text_1, configuration_1, ...args_1) => __awaiter(void 0, [text_1, configuration_1, ...args_1], void 0, function* (text, configuration, options = {}) {
33
+ const config = (0, utils_1.applyLintOverride)(text, configuration || (yield (0, utils_1.getLintConfig)()));
34
+ const formatted = options.reprint
35
+ ? yield options.reprint(text, { keywordCase: config.keywordCase })
36
+ : text;
37
+ return (0, shared_1.processText)(formatted, config);
18
38
  });
19
39
  exports.formatText = formatText;
20
40
  //# sourceMappingURL=formatText.js.map
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"formatText.js","sourceRoot":"","sources":["../../src/format/formatText.ts"],"names":[],"mappings":";;;;;;;;;;;;AAEA,oCAA2D;AAC3D,qCAAsC;AAEtC;;;;;;;;;;;;;;;;GAgBG;AACI,MAAM,UAAU,GAAG,qCAIxB,EAAE,4EAHF,IAAY,EACZ,aAA0B,EAC1B,UAAyB,EAAE;IAE3B,MAAM,MAAM,GAAG,IAAA,yBAAiB,EAC9B,IAAI,EACJ,aAAa,IAAI,CAAC,MAAM,IAAA,qBAAa,GAAE,CAAC,CACzC,CAAA;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO;QAC/B,CAAC,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC;QAClE,CAAC,CAAC,IAAI,CAAA;IAER,OAAO,IAAA,oBAAW,EAAC,SAAS,EAAE,MAAM,CAAC,CAAA;AACvC,CAAC,CAAA,CAAA;AAfY,QAAA,UAAU,cAetB"}
@@ -0,0 +1,60 @@
1
+ import { LintConfig } from '../types';
2
+ /** A macro declaration, which the macro rules read as one statement. */
3
+ export declare const macroDeclaration: RegExp;
4
+ /**
5
+ * A macro comment statement - `%* text ;` - which is prose that happens to start
6
+ * with a trigger. Breaking it would put the terminator on a line of its own, and
7
+ * like a block comment it belongs to its author.
8
+ */
9
+ export declare const macroComment: RegExp;
10
+ /**
11
+ * A statement whose line breaks are data rather than space between tokens.
12
+ *
13
+ * For the DATA step's `put` and `putlog`, and the macro and window statements
14
+ * that write text a reader sees, a break adds a line to the log or to the file
15
+ * being written. For `%let` a break lands inside the value, and the value keeps
16
+ * the newline and the continuation indent - measured on a live engine, where a
17
+ * wrapped `%let` message compared against a single-spaced one failed.
18
+ *
19
+ * Both are broken with the `%trim(` idiom rather than by a plain newline, and
20
+ * neither is left as it is: a break that carries `%trim(` adds no whitespace and
21
+ * no output line.
22
+ */
23
+ export declare const breakAwareStatement: RegExp;
24
+ /**
25
+ * The index where the text of a `put`-family statement begins, so a break is
26
+ * never taken before the keyword that names it.
27
+ *
28
+ * A continuation line carries no keyword of its own, and its whole content is
29
+ * text, so there the text begins at the first non-whitespace character.
30
+ *
31
+ * @param {string} line - the line to read.
32
+ * @returns {number} the index of the first character of the statement's text.
33
+ */
34
+ export declare const putTextStart: (line: string) => number;
35
+ /** The index of the first non-whitespace character at or after `from`. */
36
+ export declare const firstNonSpace: (line: string, from: number) => number;
37
+ /**
38
+ * The indentation a continuation line takes: the next multiple of
39
+ * `indentationMultiple` after the line's own, so a break can never introduce an
40
+ * `indentationMultiple` diagnostic of its own.
41
+ */
42
+ export declare const continuationIndentFor: (indent: string, multiple: number) => string;
43
+ /** The indentation multiple in force, with the rule's documented default. */
44
+ export declare const indentationMultipleOf: (configuration: LintConfig) => number;
45
+ /**
46
+ * Reads whether one line opens, or carries the continuation of, a statement
47
+ * whose line breaks SAS reads as output lines.
48
+ *
49
+ * Splitting the code view on `;` gives one entry per statement on the line, the
50
+ * last entry being the part that has not reached its own terminator.
51
+ *
52
+ * @param {string} code - the line with literals and comments blanked.
53
+ * @param {boolean} open - whether the previous line left such a statement open.
54
+ * @returns {{ line: boolean, open: boolean }} whether this line belongs to one,
55
+ * and whether the next line still does.
56
+ */
57
+ export declare const breakAwareState: (code: string, open: boolean) => {
58
+ line: boolean;
59
+ open: boolean;
60
+ };