@sasjs/lint 5.3.1 → 5.4.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.
@@ -3,6 +3,20 @@ interface DataSectionsDetail {
3
3
  start: number;
4
4
  end: number;
5
5
  }
6
+ /**
7
+ * A data section: the statement that opens it, and the record list it holds.
8
+ *
9
+ * The opener is matched against the line's CODE, with literals and comments
10
+ * blanked, so a `;cards;` inside a quoted string or a comment opens nothing -
11
+ * and against statement boundaries rather than the line start, so
12
+ * `input a b; cards;` on one line still opens one. The records themselves are
13
+ * not code: a quote in a record is data, so the scan state is reset at the
14
+ * opener and the records are matched as plain lines.
15
+ *
16
+ * @param {string} text - the text to read.
17
+ * @param {LintConfig} config - the configuration in force.
18
+ * @returns {DataSectionsDetail[]} the line ranges each section covers.
19
+ */
6
20
  export declare const getDataSectionsDetail: (text: string, config: LintConfig) => DataSectionsDetail[];
7
21
  export declare const checkIsDataLine: (dataSections: DataSectionsDetail[], lineIndex: number) => boolean;
8
22
  export {};
@@ -1,14 +1,29 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.checkIsDataLine = exports.getDataSectionsDetail = void 0;
4
+ const scanLine_1 = require("./scanLine");
4
5
  const splitText_1 = require("./splitText");
6
+ /**
7
+ * A data section: the statement that opens it, and the record list it holds.
8
+ *
9
+ * The opener is matched against the line's CODE, with literals and comments
10
+ * blanked, so a `;cards;` inside a quoted string or a comment opens nothing -
11
+ * and against statement boundaries rather than the line start, so
12
+ * `input a b; cards;` on one line still opens one. The records themselves are
13
+ * not code: a quote in a record is data, so the scan state is reset at the
14
+ * opener and the records are matched as plain lines.
15
+ *
16
+ * @param {string} text - the text to read.
17
+ * @param {LintConfig} config - the configuration in force.
18
+ * @returns {DataSectionsDetail[]} the line ranges each section covers.
19
+ */
5
20
  const getDataSectionsDetail = (text, config) => {
6
21
  const dataSections = [];
7
22
  const lines = (0, splitText_1.splitText)(text, config);
8
- const dataSectionStartRegex1 = new RegExp('^(datalines;)|(cards;)|(parmcards;)');
23
+ const opener = /(?:^|;)\s*(datalines|cards|parmcards)(4?);/i;
9
24
  const dataSectionEndRegex1 = new RegExp(';');
10
- const dataSectionStartRegex2 = new RegExp('^(datalines4;)|(cards4;)|(parmcards4;)');
11
25
  const dataSectionEndRegex2 = new RegExp(';;;;');
26
+ let state = (0, scanLine_1.cleanState)();
12
27
  let dataSectionStarted = false;
13
28
  let dataSectionStartIndex = -1;
14
29
  let dataSectionEndRegex = dataSectionEndRegex1;
@@ -17,19 +32,20 @@ const getDataSectionsDetail = (text, config) => {
17
32
  if (dataSectionEndRegex.test(line)) {
18
33
  dataSections.push({ start: dataSectionStartIndex, end: index });
19
34
  dataSectionStarted = false;
35
+ state = (0, scanLine_1.cleanState)();
20
36
  }
37
+ return;
21
38
  }
22
- else {
23
- if (dataSectionStartRegex1.test(line)) {
24
- dataSectionStarted = true;
25
- dataSectionStartIndex = index;
26
- dataSectionEndRegex = dataSectionEndRegex1;
27
- }
28
- else if (dataSectionStartRegex2.test(line)) {
29
- dataSectionStarted = true;
30
- dataSectionStartIndex = index;
31
- dataSectionEndRegex = dataSectionEndRegex2;
32
- }
39
+ const scanned = (0, scanLine_1.scanLine)(line, state);
40
+ state = scanned.endState;
41
+ const match = opener.exec(scanned.code);
42
+ if (match) {
43
+ dataSectionStarted = true;
44
+ dataSectionStartIndex = index;
45
+ dataSectionEndRegex = match[2]
46
+ ? dataSectionEndRegex2
47
+ : dataSectionEndRegex1;
48
+ state = (0, scanLine_1.cleanState)();
33
49
  }
34
50
  });
35
51
  return dataSections;
@@ -1 +1 @@
1
- {"version":3,"file":"getDataSectionsDetail.js","sourceRoot":"","sources":["../../src/utils/getDataSectionsDetail.ts"],"names":[],"mappings":";;;AACA,2CAAuC;AAOhC,MAAM,qBAAqB,GAAG,CAAC,IAAY,EAAE,MAAkB,EAAE,EAAE;IACxE,MAAM,YAAY,GAAyB,EAAE,CAAA;IAC7C,MAAM,KAAK,GAAG,IAAA,qBAAS,EAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAErC,MAAM,sBAAsB,GAAG,IAAI,MAAM,CACvC,qCAAqC,CACtC,CAAA;IACD,MAAM,oBAAoB,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,CAAA;IAC5C,MAAM,sBAAsB,GAAG,IAAI,MAAM,CACvC,wCAAwC,CACzC,CAAA;IACD,MAAM,oBAAoB,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,CAAA;IAE/C,IAAI,kBAAkB,GAAG,KAAK,CAAA;IAC9B,IAAI,qBAAqB,GAAG,CAAC,CAAC,CAAA;IAC9B,IAAI,mBAAmB,GAAG,oBAAoB,CAAA;IAE9C,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC5B,IAAI,kBAAkB,EAAE,CAAC;YACvB,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnC,YAAY,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAA;gBAC/D,kBAAkB,GAAG,KAAK,CAAA;YAC5B,CAAC;QACH,CAAC;aAAM,CAAC;YACN,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACtC,kBAAkB,GAAG,IAAI,CAAA;gBACzB,qBAAqB,GAAG,KAAK,CAAA;gBAC7B,mBAAmB,GAAG,oBAAoB,CAAA;YAC5C,CAAC;iBAAM,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC7C,kBAAkB,GAAG,IAAI,CAAA;gBACzB,qBAAqB,GAAG,KAAK,CAAA;gBAC7B,mBAAmB,GAAG,oBAAoB,CAAA;YAC5C,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,OAAO,YAAY,CAAA;AACrB,CAAC,CAAA;AArCY,QAAA,qBAAqB,yBAqCjC;AAEM,MAAM,eAAe,GAAG,CAC7B,YAAkC,EAClC,SAAiB,EACjB,EAAE;IACF,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;QACvC,IAAI,SAAS,IAAI,WAAW,CAAC,KAAK,IAAI,SAAS,IAAI,WAAW,CAAC,GAAG;YAChE,OAAO,IAAI,CAAA;IACf,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC,CAAA;AAVY,QAAA,eAAe,mBAU3B"}
1
+ {"version":3,"file":"getDataSectionsDetail.js","sourceRoot":"","sources":["../../src/utils/getDataSectionsDetail.ts"],"names":[],"mappings":";;;AACA,yCAAiD;AACjD,2CAAuC;AAOvC;;;;;;;;;;;;;GAaG;AACI,MAAM,qBAAqB,GAAG,CAAC,IAAY,EAAE,MAAkB,EAAE,EAAE;IACxE,MAAM,YAAY,GAAyB,EAAE,CAAA;IAC7C,MAAM,KAAK,GAAG,IAAA,qBAAS,EAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAErC,MAAM,MAAM,GAAG,6CAA6C,CAAA;IAC5D,MAAM,oBAAoB,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,CAAA;IAC5C,MAAM,oBAAoB,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,CAAA;IAE/C,IAAI,KAAK,GAAG,IAAA,qBAAU,GAAE,CAAA;IACxB,IAAI,kBAAkB,GAAG,KAAK,CAAA;IAC9B,IAAI,qBAAqB,GAAG,CAAC,CAAC,CAAA;IAC9B,IAAI,mBAAmB,GAAG,oBAAoB,CAAA;IAE9C,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC5B,IAAI,kBAAkB,EAAE,CAAC;YACvB,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnC,YAAY,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAA;gBAC/D,kBAAkB,GAAG,KAAK,CAAA;gBAC1B,KAAK,GAAG,IAAA,qBAAU,GAAE,CAAA;YACtB,CAAC;YACD,OAAM;QACR,CAAC;QAED,MAAM,OAAO,GAAG,IAAA,mBAAQ,EAAC,IAAI,EAAE,KAAK,CAAC,CAAA;QACrC,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAA;QAExB,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QAEvC,IAAI,KAAK,EAAE,CAAC;YACV,kBAAkB,GAAG,IAAI,CAAA;YACzB,qBAAqB,GAAG,KAAK,CAAA;YAC7B,mBAAmB,GAAG,KAAK,CAAC,CAAC,CAAC;gBAC5B,CAAC,CAAC,oBAAoB;gBACtB,CAAC,CAAC,oBAAoB,CAAA;YACxB,KAAK,GAAG,IAAA,qBAAU,GAAE,CAAA;QACtB,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,OAAO,YAAY,CAAA;AACrB,CAAC,CAAA;AAvCY,QAAA,qBAAqB,yBAuCjC;AAEM,MAAM,eAAe,GAAG,CAC7B,YAAkC,EAClC,SAAiB,EACjB,EAAE;IACF,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;QACvC,IAAI,SAAS,IAAI,WAAW,CAAC,KAAK,IAAI,SAAS,IAAI,WAAW,CAAC,GAAG;YAChE,OAAO,IAAI,CAAA;IACf,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC,CAAA;AAVY,QAAA,eAAe,mBAU3B"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Reads one line of a SAS program, carrying the state a break decision needs
3
+ * from one physical line to the next.
4
+ *
5
+ * A quoted literal, a block comment and a masking macro's argument text all
6
+ * continue onto the next line, so a caller that decides where a line may be
7
+ * broken - or where a statement really ends - needs to know which of them is
8
+ * open. The scan is one pass with those three states, because each affects the
9
+ * reading of the others: `%str(%')` is an apostrophe, and a scanner that read it
10
+ * as the start of a literal would carry a phantom string into every line below.
11
+ */
12
+ export interface ScanState {
13
+ /** The quote character a literal is still open with, or `''`. */
14
+ quote: string;
15
+ /** Whether a block comment is still open. */
16
+ inComment: boolean;
17
+ /** Depth of the masking call the scan is inside. */
18
+ maskedDepth: number;
19
+ }
20
+ export declare const cleanState: () => ScanState;
21
+ /** A block comment's span on one line, as absolute column indices. */
22
+ export interface CommentSpan {
23
+ /** The column at which the comment opens. */
24
+ start: number;
25
+ /** The column of the closing marker's asterisk, or the line length. */
26
+ end: number;
27
+ }
28
+ /** A physical line, as the scanner reads it. */
29
+ export interface ScannedLine {
30
+ /**
31
+ * The columns at which a newline may be inserted: every space that is not
32
+ * inside a quoted literal, inside a masking macro's parentheses, or inside a
33
+ * block comment.
34
+ */
35
+ positions: number[];
36
+ /** The line with every literal and comment character blanked to a space. */
37
+ code: string;
38
+ /** Whether the line carries any block comment, open or closed. */
39
+ hasComment: boolean;
40
+ /** Each block comment the line opens, with the span it covers. */
41
+ comments: CommentSpan[];
42
+ endState: ScanState;
43
+ }
44
+ /**
45
+ * The scan of a line that is not code, and so opens and closes nothing.
46
+ *
47
+ * A `datalines` record is data: a comment marker or an apostrophe in one is a
48
+ * character in a value, not the start of a comment or a literal. Reading such a
49
+ * line with the code scanner would carry that state into every line below it.
50
+ */
51
+ export declare const noScan: () => ScannedLine;
52
+ /**
53
+ * Reads one line, starting from the state the previous line ended in.
54
+ *
55
+ * A break is never taken inside a literal, because a SAS literal really does
56
+ * continue onto the next line (`"ab` / `cd"` is one value) and an inserted
57
+ * newline would become part of the string.
58
+ */
59
+ export declare const scanLine: (line: string, state: ScanState) => ScannedLine;
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scanLine = exports.noScan = exports.cleanState = void 0;
4
+ const cleanState = () => ({
5
+ quote: '',
6
+ inComment: false,
7
+ maskedDepth: 0
8
+ });
9
+ exports.cleanState = cleanState;
10
+ /**
11
+ * The calls whose argument text is carried as DATA rather than as tokens.
12
+ *
13
+ * The masking macros - `%str(`, `%nrstr(`, `%bquote(`, `%nrbquote(`,
14
+ * `%superq(`, `%nrsuperq(`, `%quote(`, `%nrquote(`, `%unquote(` - return their
15
+ * argument as a value, so a newline inserted inside one becomes part of it.
16
+ * `%sysfunc(` and `%qsysfunc(` are the same problem one level down: the argument
17
+ * text is handed to a DATA step function, and for the text functions the
18
+ * whitespace is data. Measured on a live engine, breaking inside
19
+ * `%sysfunc(fput(&fid,%str(&name): %str(&value)))` wrote
20
+ * `Content-Type: application/csv` where the macro meant one space - so no
21
+ * space inside either form is a break point.
22
+ */
23
+ const maskStart = /%n?r?(?:str|bquote|superq|quote|unquote)\s*\(|%q?sysfunc\s*\(/iy;
24
+ /**
25
+ * The scan of a line that is not code, and so opens and closes nothing.
26
+ *
27
+ * A `datalines` record is data: a comment marker or an apostrophe in one is a
28
+ * character in a value, not the start of a comment or a literal. Reading such a
29
+ * line with the code scanner would carry that state into every line below it.
30
+ */
31
+ const noScan = () => ({
32
+ positions: [],
33
+ code: '',
34
+ hasComment: false,
35
+ comments: [],
36
+ endState: (0, exports.cleanState)()
37
+ });
38
+ exports.noScan = noScan;
39
+ /**
40
+ * Reads one line, starting from the state the previous line ended in.
41
+ *
42
+ * A break is never taken inside a literal, because a SAS literal really does
43
+ * continue onto the next line (`"ab` / `cd"` is one value) and an inserted
44
+ * newline would become part of the string.
45
+ */
46
+ const scanLine = (line, state) => {
47
+ const positions = [];
48
+ const code = new Array(line.length).fill(' ');
49
+ const comments = [];
50
+ let { quote, inComment, maskedDepth } = state;
51
+ let hasComment = inComment;
52
+ let commentStart = inComment ? 0 : -1;
53
+ for (let index = 0; index < line.length; index++) {
54
+ const character = line[index];
55
+ if (inComment) {
56
+ if (character === '*' && line[index + 1] === '/') {
57
+ inComment = false;
58
+ comments.push({ start: commentStart, end: index });
59
+ index++;
60
+ continue;
61
+ }
62
+ // A space inside a block comment is prose, not a break point: the
63
+ // contract above states it, and no caller wants one - the wrap refuses a
64
+ // comment-bearing line and `reflowComments` owns its text.
65
+ continue;
66
+ }
67
+ // Masked text is data, so it is read before anything that OPENS a literal
68
+ // or a comment: `%str(%')` is how a single quote is written, and reading
69
+ // that apostrophe as the start of a literal would carry a phantom string
70
+ // into every line below it.
71
+ if (maskedDepth) {
72
+ if (character === '(')
73
+ maskedDepth++;
74
+ else if (character === ')')
75
+ maskedDepth--;
76
+ continue;
77
+ }
78
+ if (quote) {
79
+ if (character === quote)
80
+ quote = '';
81
+ continue;
82
+ }
83
+ if (character === "'" || character === '"') {
84
+ quote = character;
85
+ continue;
86
+ }
87
+ if (character === '/' && line[index + 1] === '*') {
88
+ inComment = true;
89
+ hasComment = true;
90
+ commentStart = index;
91
+ index++;
92
+ continue;
93
+ }
94
+ maskStart.lastIndex = index;
95
+ if (maskStart.test(line)) {
96
+ maskedDepth = 1;
97
+ index = maskStart.lastIndex - 1;
98
+ continue;
99
+ }
100
+ if (character === ' ')
101
+ positions.push(index);
102
+ code[index] = character;
103
+ }
104
+ if (inComment)
105
+ comments.push({ start: commentStart, end: line.length });
106
+ return {
107
+ positions,
108
+ code: code.join(''),
109
+ hasComment,
110
+ comments,
111
+ endState: { quote, inComment, maskedDepth }
112
+ };
113
+ };
114
+ exports.scanLine = scanLine;
115
+ //# sourceMappingURL=scanLine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanLine.js","sourceRoot":"","sources":["../../src/utils/scanLine.ts"],"names":[],"mappings":"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"}