@atlisp/lint 0.2.17 → 0.2.19

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 (2) hide show
  1. package/dist/formatter.js +141 -25
  2. package/package.json +1 -1
package/dist/formatter.js CHANGED
@@ -2,13 +2,108 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.formatCode = formatCode;
4
4
  const utils_1 = require("./utils");
5
+ const parser_1 = require("@atlisp/parser");
5
6
  function isCommentLine(line) {
6
7
  const trimmed = line.trim();
7
8
  return trimmed.startsWith(';') || trimmed.startsWith(';|');
8
9
  }
10
+ function findMultiPairSetq(content) {
11
+ try {
12
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
13
+ if (!ast)
14
+ return [];
15
+ const ranges = [];
16
+ for (const node of (0, parser_1.astWalk)(ast)) {
17
+ if (!(0, parser_1.astIsList)(node))
18
+ continue;
19
+ const children = node.children;
20
+ if (children.length < 6)
21
+ continue;
22
+ const first = children[0];
23
+ if (!(0, parser_1.astIsSymbol)(first) || first.name.toLowerCase() !== 'setq')
24
+ continue;
25
+ const start = node.pos.offset;
26
+ const end = node.end?.offset ?? start;
27
+ const startLine = content.substring(0, start).split('\n').length - 1;
28
+ const endLine = content.substring(0, end).split('\n').length - 1;
29
+ ranges.push({ startLine, endLine, startOffset: start, endOffset: end });
30
+ }
31
+ return ranges;
32
+ }
33
+ catch {
34
+ return [];
35
+ }
36
+ }
37
+ function formatSetqFromNode(node, depth, indentSize, content) {
38
+ const children = node.children;
39
+ const pairs = [];
40
+ for (let i = 1; i + 1 < children.length; i += 2) {
41
+ const varNode = children[i];
42
+ const valNode = children[i + 1];
43
+ let varText;
44
+ if ((0, parser_1.astIsSymbol)(varNode)) {
45
+ varText = varNode.name;
46
+ }
47
+ else {
48
+ const vs = varNode.pos.offset;
49
+ const ve = varNode.end?.offset ?? vs;
50
+ varText = content.slice(vs, ve);
51
+ }
52
+ const vs = valNode.pos.offset;
53
+ const ve = valNode.end?.offset ?? vs;
54
+ const valText = content.slice(vs, ve);
55
+ pairs.push({ varText, valText });
56
+ }
57
+ if (pairs.length < 2) {
58
+ const indent = ' '.repeat(depth * indentSize);
59
+ return [indent + '(setq ' + pairs.map(p => p.varText + ' ' + p.valText).join(' ') + ')'];
60
+ }
61
+ const maxVarLen = Math.max(...pairs.map(p => p.varText.length));
62
+ const baseIndent = ' '.repeat(depth * indentSize);
63
+ const setqPrefix = '(setq ';
64
+ const varColumn = depth * indentSize + setqPrefix.length;
65
+ const lines = [];
66
+ lines.push(baseIndent + setqPrefix + pairs[0].varText + ' ' + pairs[0].valText);
67
+ const continuationIndent = ' '.repeat(varColumn);
68
+ for (let i = 1; i < pairs.length; i++) {
69
+ const padding = maxVarLen - pairs[i].varText.length;
70
+ const isLast = i === pairs.length - 1;
71
+ lines.push(continuationIndent + pairs[i].varText + ' '.repeat(padding + 1) + pairs[i].valText + (isLast ? ')' : ''));
72
+ }
73
+ return lines;
74
+ }
75
+ function runStandardFormat(lines, lineIdx, depth, _indentSize, rawLine, trimmed) {
76
+ const isComment = isCommentLine(trimmed);
77
+ const stripped = (0, utils_1.stripLine)(trimmed);
78
+ const trimmedStripped = stripped.trim();
79
+ if (trimmedStripped === '') {
80
+ return {
81
+ formattedLines: [' '.repeat(depth * _indentSize) + trimmed.trimStart()],
82
+ newDepth: depth
83
+ };
84
+ }
85
+ const opens = (trimmedStripped.match(/\(/g) || []).length;
86
+ const closes = (trimmedStripped.match(/\)/g) || []).length;
87
+ let leadingCloseCount = 0;
88
+ for (const ch of trimmedStripped) {
89
+ if (ch === ')')
90
+ leadingCloseCount++;
91
+ else
92
+ break;
93
+ }
94
+ let effectiveDepth = depth;
95
+ if (leadingCloseCount > 0) {
96
+ effectiveDepth = Math.max(0, depth - leadingCloseCount);
97
+ }
98
+ return {
99
+ formattedLines: [' '.repeat(effectiveDepth * _indentSize) + trimmed.trimStart()],
100
+ newDepth: depth + opens - closes
101
+ };
102
+ }
9
103
  function formatCode(content, indentSize = 2) {
10
104
  if (content === '' || content === '\n')
11
105
  return content;
106
+ const setqRanges = findMultiPairSetq(content);
12
107
  const lines = content.split('\n');
13
108
  const result = [];
14
109
  let depth = 0;
@@ -19,33 +114,54 @@ function formatCode(content, indentSize = 2) {
19
114
  result.push('');
20
115
  continue;
21
116
  }
22
- const isComment = isCommentLine(trimmed);
23
- const stripped = (0, utils_1.stripLine)(trimmed);
24
- const trimmedStripped = stripped.trim();
25
- if (trimmedStripped === '' && isComment) {
26
- result.push(' '.repeat(depth * indentSize) + trimmed.trimStart());
117
+ const sqRange = setqRanges.find(sq => sq.startLine === lineIdx);
118
+ if (sqRange) {
119
+ const setqText = content.slice(sqRange.startOffset, sqRange.endOffset);
120
+ const ast = (0, parser_1.parseAst)(setqText, { errorRecovery: true });
121
+ let sqNode;
122
+ if (ast) {
123
+ for (const n of (0, parser_1.astWalk)(ast)) {
124
+ if ((0, parser_1.astIsList)(n)) {
125
+ const f = n.children[0];
126
+ if ((0, parser_1.astIsSymbol)(f) && f.name.toLowerCase() === 'setq') {
127
+ sqNode = n;
128
+ break;
129
+ }
130
+ }
131
+ }
132
+ }
133
+ if (sqNode && (0, parser_1.astIsList)(sqNode)) {
134
+ const formattedLines = formatSetqFromNode(sqNode, depth, indentSize, setqText);
135
+ result.push(...formattedLines);
136
+ }
137
+ else {
138
+ const indent = ' '.repeat(depth * indentSize);
139
+ result.push(indent + trimmed.trimStart());
140
+ }
141
+ // Emit any trailing content after setq's end on the same line (e.g., extra ))
142
+ const afterSetqText = content.slice(sqRange.endOffset);
143
+ const afterSetqLine = afterSetqText.split('\n')[0];
144
+ if (afterSetqLine.trim()) {
145
+ const sAfter = (0, utils_1.stripLine)(afterSetqLine);
146
+ const tAfter = sAfter.trim();
147
+ if (tAfter) {
148
+ const opensAfter = (tAfter.match(/\(/g) || []).length;
149
+ const closesAfter = (tAfter.match(/\)/g) || []).length;
150
+ depth += opensAfter - closesAfter;
151
+ if (depth < 0)
152
+ depth = 0;
153
+ const leadingCloseCount = (tAfter.match(/^\)+/g) || [''])[0].length;
154
+ const outdent = depth - leadingCloseCount + (opensAfter - closesAfter);
155
+ const effectiveDepth = Math.max(0, depth - leadingCloseCount + (opensAfter - closesAfter));
156
+ result.push(' '.repeat(effectiveDepth * indentSize) + afterSetqLine.trim());
157
+ }
158
+ }
159
+ lineIdx = sqRange.endLine;
27
160
  continue;
28
161
  }
29
- if (trimmedStripped === '') {
30
- result.push(' '.repeat(depth * indentSize) + trimmed.trimStart());
31
- continue;
32
- }
33
- const opens = (trimmedStripped.match(/\(/g) || []).length;
34
- const closes = (trimmedStripped.match(/\)/g) || []).length;
35
- let leadingCloseCount = 0;
36
- for (const ch of trimmedStripped) {
37
- if (ch === ')')
38
- leadingCloseCount++;
39
- else
40
- break;
41
- }
42
- let effectiveDepth = depth;
43
- if (leadingCloseCount > 0) {
44
- effectiveDepth = Math.max(0, depth - leadingCloseCount);
45
- }
46
- const indent = ' '.repeat(effectiveDepth * indentSize);
47
- result.push(indent + trimmed.trimStart());
48
- depth += opens - closes;
162
+ const { formattedLines, newDepth } = runStandardFormat(lines, lineIdx, depth, indentSize, rawLine, trimmed);
163
+ result.push(...formattedLines);
164
+ depth = newDepth;
49
165
  if (depth < 0)
50
166
  depth = 0;
51
167
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlisp/lint",
3
- "version": "0.2.17",
3
+ "version": "0.2.19",
4
4
  "description": "AutoLISP static analysis tool — parens, security, conventions, SBCL syntax validation",
5
5
  "keywords": [
6
6
  "CAD",