@atlisp/lint 0.2.16 → 0.2.17

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 (80) hide show
  1. package/README.md +1 -1
  2. package/atlisp-lint.default.json +1 -1
  3. package/dist/checks/arg-count.d.ts +2 -1
  4. package/dist/checks/arg-count.js +37 -19
  5. package/dist/checks/builtin-arg-count.d.ts +2 -1
  6. package/dist/checks/builtin-arg-count.js +3 -2
  7. package/dist/checks/cond-duplicate.js +5 -6
  8. package/dist/checks/constant-condition.js +9 -9
  9. package/dist/checks/dangerous-calls.js +1 -1
  10. package/dist/checks/dangling-defun.d.ts +0 -7
  11. package/dist/checks/dangling-defun.js +0 -63
  12. package/dist/checks/double-not.js +1 -1
  13. package/dist/checks/empty-branch.js +1 -1
  14. package/dist/checks/empty-catch.d.ts +2 -1
  15. package/dist/checks/empty-catch.js +8 -7
  16. package/dist/checks/function-complexity.js +5 -18
  17. package/dist/checks/global-naming.js +1 -1
  18. package/dist/checks/identical-branches.js +3 -3
  19. package/dist/checks/if-without-else.js +1 -1
  20. package/dist/checks/misplaced-else.js +2 -2
  21. package/dist/checks/missing-export.js +3 -3
  22. package/dist/checks/missing-princ.js +1 -1
  23. package/dist/checks/multiple-setq.js +1 -1
  24. package/dist/checks/no-return.d.ts +2 -1
  25. package/dist/checks/no-return.js +4 -3
  26. package/dist/checks/nth-usage.js +1 -1
  27. package/dist/checks/parameter-naming.js +2 -2
  28. package/dist/checks/quote-vs-function.js +1 -1
  29. package/dist/checks/recursive-call.js +1 -1
  30. package/dist/checks/redundant-let.js +1 -1
  31. package/dist/checks/redundant-nil-else.js +1 -1
  32. package/dist/checks/redundant-progn.js +1 -1
  33. package/dist/checks/redundant-setq.js +1 -1
  34. package/dist/checks/self-compare.js +1 -1
  35. package/dist/checks/setq-single-arg.js +3 -2
  36. package/dist/checks/single-arg-and-or.js +1 -1
  37. package/dist/checks/undeclared-setq.d.ts +2 -1
  38. package/dist/checks/undeclared-setq.js +6 -5
  39. package/dist/checks/unused-let.js +3 -3
  40. package/dist/checks/unused-local-fun.js +2 -2
  41. package/dist/checks/unused-package-dep.d.ts +0 -1
  42. package/dist/checks/unused-package-dep.js +6 -93
  43. package/dist/checks/unused-param.js +2 -2
  44. package/dist/checks/unused-variable.js +2 -2
  45. package/dist/checks/variable-shadow.js +3 -3
  46. package/dist/checks/while-constant.js +1 -1
  47. package/dist/cli/collector.js +26 -19
  48. package/dist/config.d.ts +1 -1
  49. package/dist/config.js +18 -9
  50. package/dist/fixes/index.d.ts +4 -0
  51. package/dist/fixes/index.js +843 -140
  52. package/dist/formatter.d.ts +0 -1
  53. package/dist/formatter.js +0 -6
  54. package/dist/index.js +59 -51
  55. package/dist/project.d.ts +1 -1
  56. package/dist/project.js +98 -112
  57. package/dist/runner.d.ts +1 -1
  58. package/dist/runner.js +17 -8
  59. package/dist/utils.js +21 -30
  60. package/dist/validate.js +3 -3
  61. package/dist/visitor-runner.d.ts +0 -28
  62. package/dist/visitor-runner.js +3 -809
  63. package/dist/visitors/equal-nil.d.ts +2 -2
  64. package/dist/visitors/equal-nil.js +5 -4
  65. package/dist/visitors/index.d.ts +1 -1
  66. package/dist/visitors/progn-in-cond.d.ts +2 -2
  67. package/dist/visitors/progn-in-cond.js +5 -4
  68. package/dist/visitors/redundant-and-or.d.ts +2 -2
  69. package/dist/visitors/redundant-and-or.js +3 -3
  70. package/dist/visitors/redundant-car-cdr.d.ts +2 -2
  71. package/dist/visitors/redundant-car-cdr.js +3 -3
  72. package/dist/visitors/register.d.ts +6 -0
  73. package/dist/visitors/register.js +625 -0
  74. package/dist/visitors/shared.d.ts +40 -0
  75. package/dist/visitors/shared.js +129 -0
  76. package/dist/visitors/types.d.ts +2 -7
  77. package/dist/visitors/types.js +0 -69
  78. package/dist/watch.js +33 -12
  79. package/dist/worker.js +1 -1
  80. package/package.json +2 -2
@@ -2,184 +2,888 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FIXABLE_RULES = void 0;
4
4
  exports.applyFixesFromProviders = applyFixesFromProviders;
5
- const utils_1 = require("../utils");
6
5
  const nth_usage_1 = require("../checks/nth-usage");
6
+ const parser_1 = require("@atlisp/parser");
7
+ function buildPosToOffset(_content) {
8
+ return pos => pos.offset;
9
+ }
10
+ function findCloseParen(s, start) {
11
+ if (s[start] !== '(')
12
+ return -1;
13
+ let depth = 0;
14
+ let inStr = false;
15
+ for (let i = start; i < s.length; i++) {
16
+ if (inStr) {
17
+ if (s[i] === '\\') {
18
+ i++;
19
+ continue;
20
+ }
21
+ if (s[i] === '"')
22
+ inStr = false;
23
+ continue;
24
+ }
25
+ if (s[i] === ';') {
26
+ while (i < s.length && s[i] !== '\n')
27
+ i++;
28
+ continue;
29
+ }
30
+ if (s[i] === '"') {
31
+ inStr = true;
32
+ continue;
33
+ }
34
+ if (s[i] === '(')
35
+ depth++;
36
+ else if (s[i] === ')') {
37
+ depth--;
38
+ if (depth === 0)
39
+ return i;
40
+ }
41
+ }
42
+ return -1;
43
+ }
44
+ function buildLineOffsets(lines) {
45
+ const offsets = [];
46
+ let off = 0;
47
+ for (let i = 0; i < lines.length; i++) {
48
+ offsets.push(off);
49
+ off += lines[i].length + 1;
50
+ }
51
+ return offsets;
52
+ }
53
+ function extractNextForm(s, start) {
54
+ let i = start;
55
+ while (i < s.length && s[i] === ' ')
56
+ i++;
57
+ if (i >= s.length)
58
+ return ['', i];
59
+ if (s[i] === '(') {
60
+ const close = findCloseParen(s, i);
61
+ if (close > i)
62
+ return [s.slice(i, close + 1), close + 1];
63
+ return ['', i];
64
+ }
65
+ const atomMatch = s.slice(i).match(/^\S+/);
66
+ if (atomMatch)
67
+ return [atomMatch[0], i + atomMatch[0].length];
68
+ return ['', i];
69
+ }
70
+ function stripPrognWrapper(issues, content) {
71
+ const lines = content.split('\n');
72
+ const lineOffsets = buildLineOffsets(lines);
73
+ const ops = [];
74
+ for (const issue of issues) {
75
+ const lineIdx = issue.line - 1;
76
+ if (lineIdx < 0 || lineIdx >= lines.length)
77
+ continue;
78
+ const line = lines[lineIdx];
79
+ const pMatch = line.match(/\(progn(?=\s|$)/);
80
+ if (!pMatch)
81
+ continue;
82
+ const absP = lineOffsets[lineIdx] + pMatch.index;
83
+ const close = findCloseParen(content, absP);
84
+ if (close <= absP)
85
+ continue;
86
+ let argStart = absP + 6;
87
+ if (content[argStart] === ' ')
88
+ argStart++;
89
+ ops.push({ start: absP, end: close + 1, text: content.slice(argStart, close) });
90
+ }
91
+ ops.sort((a, b) => b.start - a.start);
92
+ for (const op of ops) {
93
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
94
+ }
95
+ return content;
96
+ }
97
+ function quote_style_fallback(issues, content) {
98
+ const lines = content.split('\n');
99
+ const lineOffsets = buildLineOffsets(lines);
100
+ const ops = [];
101
+ for (const issue of issues) {
102
+ const lineIdx = issue.line - 1;
103
+ if (lineIdx < 0 || lineIdx >= lines.length)
104
+ continue;
105
+ const line = lines[lineIdx];
106
+ const qMatch = line.match(/\(quote(?=\s|$)/);
107
+ if (!qMatch)
108
+ continue;
109
+ const absQ = lineOffsets[lineIdx] + qMatch.index;
110
+ const close = findCloseParen(content, absQ);
111
+ if (close <= absQ)
112
+ continue;
113
+ let argStart = absQ + 6;
114
+ if (content[argStart] === ' ')
115
+ argStart++;
116
+ ops.push({ start: absQ, end: close + 1, text: "'" + content.slice(argStart, close) });
117
+ }
118
+ ops.sort((a, b) => b.start - a.start);
119
+ for (const op of ops) {
120
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
121
+ }
122
+ return content;
123
+ }
124
+ function misplaced_else_fallback(issues, content) {
125
+ const lines = content.split('\n');
126
+ const lineOffsets = buildLineOffsets(lines);
127
+ const ops = [];
128
+ for (const issue of issues) {
129
+ const lineIdx = issue.line - 1;
130
+ if (lineIdx < 0 || lineIdx >= lines.length)
131
+ continue;
132
+ const line = lines[lineIdx];
133
+ const ifMatch = line.match(/\(if\s+\(not(?=\s|$)/);
134
+ if (!ifMatch)
135
+ continue;
136
+ const ifStart = ifMatch.index;
137
+ const absIf = lineOffsets[lineIdx] + ifStart;
138
+ const notStart = ifStart + ifMatch[0].length - 4;
139
+ const absNot = lineOffsets[lineIdx] + notStart;
140
+ const notClose = findCloseParen(content, absNot);
141
+ if (notClose <= absNot)
142
+ continue;
143
+ const ifClose = findCloseParen(content, absIf);
144
+ if (ifClose <= absIf)
145
+ continue;
146
+ const indent = line.slice(0, ifStart);
147
+ const condVar = content.slice(absNot + 5, notClose).trim();
148
+ const branchRaw = content.slice(notClose + 1, ifClose).trim();
149
+ const [thenForm, thenEnd] = extractNextForm(branchRaw, 0);
150
+ const elseForm = branchRaw.slice(thenEnd).trim();
151
+ if (!thenForm || !elseForm)
152
+ continue;
153
+ ops.push({
154
+ start: absIf,
155
+ end: ifClose + 1,
156
+ text: indent + '(if ' + condVar + ' ' + elseForm + ' ' + thenForm + ')',
157
+ });
158
+ }
159
+ ops.sort((a, b) => b.start - a.start);
160
+ for (const op of ops) {
161
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
162
+ }
163
+ return content;
164
+ }
165
+ function setq_multiple_fallback(issues, content) {
166
+ const lines = content.split('\n');
167
+ const lineOffsets = buildLineOffsets(lines);
168
+ const ops = [];
169
+ for (const issue of issues) {
170
+ const lineIdx = issue.line - 1;
171
+ if (lineIdx < 0 || lineIdx >= lines.length)
172
+ continue;
173
+ const line = lines[lineIdx];
174
+ const setqMatch = line.match(/\(setq(?=\s|$)/);
175
+ if (!setqMatch)
176
+ continue;
177
+ const absSetq = lineOffsets[lineIdx] + setqMatch.index;
178
+ const close = findCloseParen(content, absSetq);
179
+ if (close <= absSetq)
180
+ continue;
181
+ let scan = close + 1;
182
+ while (scan < content.length && (content[scan] === ' ' || content[scan] === '\t' || content[scan] === '\n'))
183
+ scan++;
184
+ if (scan >= content.length || content[scan] !== '(')
185
+ continue;
186
+ const nextClose = findCloseParen(content, scan);
187
+ if (nextClose <= scan)
188
+ continue;
189
+ const nextForm = content.slice(scan, nextClose + 1);
190
+ const nextMatch = nextForm.match(/^\(setq\s+(.*)\)\s*$/);
191
+ if (!nextMatch)
192
+ continue;
193
+ ops.push({ start: close, end: nextClose + 1, text: ' ' + nextMatch[1] + ')' });
194
+ }
195
+ ops.sort((a, b) => b.start - a.start);
196
+ for (const op of ops) {
197
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
198
+ }
199
+ return content;
200
+ }
201
+ function append_single_fallback(issues, content) {
202
+ const lines = content.split('\n');
203
+ const lineOffsets = buildLineOffsets(lines);
204
+ const ops = [];
205
+ for (const issue of issues) {
206
+ const lineIdx = issue.line - 1;
207
+ if (lineIdx < 0 || lineIdx >= lines.length)
208
+ continue;
209
+ const line = lines[lineIdx];
210
+ const aMatch = line.match(/\(append(?=\s|$)/);
211
+ if (!aMatch)
212
+ continue;
213
+ const absA = lineOffsets[lineIdx] + aMatch.index;
214
+ const close = findCloseParen(content, absA);
215
+ if (close <= absA)
216
+ continue;
217
+ const inner = content.slice(absA + 8, close).trim();
218
+ const listMatch = inner.match(/^\(list\s+(.*)\)\s*$/);
219
+ if (!listMatch)
220
+ continue;
221
+ ops.push({ start: absA, end: close + 1, text: '(cons ' + listMatch[1] + ')' });
222
+ }
223
+ ops.sort((a, b) => b.start - a.start);
224
+ for (const op of ops) {
225
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
226
+ }
227
+ return content;
228
+ }
229
+ function cond_simplify_fallback(issues, content) {
230
+ const lines = content.split('\n');
231
+ const lineOffsets = buildLineOffsets(lines);
232
+ const ops = [];
233
+ for (const issue of issues) {
234
+ const lineIdx = issue.line - 1;
235
+ if (lineIdx < 0 || lineIdx >= lines.length)
236
+ continue;
237
+ const line = lines[lineIdx];
238
+ const condMatch = line.match(/\(cond(?=\s|$)/);
239
+ if (!condMatch)
240
+ continue;
241
+ const condIdx = condMatch.index;
242
+ const absCond = lineOffsets[lineIdx] + condIdx;
243
+ const condEnd = findCloseParen(content, absCond);
244
+ if (condEnd <= absCond)
245
+ continue;
246
+ const branchOpen = content.indexOf('(', absCond + 6);
247
+ if (branchOpen === -1 || branchOpen >= condEnd)
248
+ continue;
249
+ const branchEnd = findCloseParen(content, branchOpen);
250
+ if (branchEnd <= branchOpen)
251
+ continue;
252
+ const branchContent = content.slice(branchOpen + 1, branchEnd).trim();
253
+ const [testExpr, testEnd] = extractNextForm(branchContent, 0);
254
+ const bodyExpr = branchContent.slice(testEnd).trim();
255
+ if (!testExpr || !bodyExpr)
256
+ continue;
257
+ const indent = line.slice(0, condIdx);
258
+ ops.push({ start: absCond, end: condEnd + 1, text: indent + '(if ' + testExpr + ' ' + bodyExpr + ')' });
259
+ }
260
+ ops.sort((a, b) => b.start - a.start);
261
+ for (const op of ops) {
262
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
263
+ }
264
+ return content;
265
+ }
266
+ function while_constant_fallback(issues, content) {
267
+ const lines = content.split('\n');
268
+ const lineOffsets = buildLineOffsets(lines);
269
+ const ops = [];
270
+ for (const issue of issues) {
271
+ const lineIdx = issue.line - 1;
272
+ if (lineIdx < 0 || lineIdx >= lines.length)
273
+ continue;
274
+ const line = lines[lineIdx];
275
+ const trimmed = line.trim();
276
+ const nilMatch = trimmed.match(/^\(\s*while\s+nil\s+(.*)\)\s*$/);
277
+ if (!nilMatch) {
278
+ const wMatch = line.match(/\(while(?=\s|$)/);
279
+ if (!wMatch)
280
+ continue;
281
+ const absW = lineOffsets[lineIdx] + wMatch.index;
282
+ const close = findCloseParen(content, absW);
283
+ if (close <= absW)
284
+ continue;
285
+ const afterKw = absW + 6;
286
+ const kwEnd = afterKw + (content[afterKw] === ' ' || content[afterKw] === '\t' ? 1 : 0);
287
+ const nilIdx = content.indexOf('nil', kwEnd);
288
+ if (nilIdx === -1 || nilIdx >= close)
289
+ continue;
290
+ ops.push({ start: absW, end: close + 1, text: 'nil' });
291
+ continue;
292
+ }
293
+ const absLineStart = lineOffsets[lineIdx];
294
+ const absLineEnd = absLineStart + line.length;
295
+ ops.push({ start: absLineStart, end: absLineEnd, text: 'nil' });
296
+ }
297
+ ops.sort((a, b) => b.start - a.start);
298
+ for (const op of ops) {
299
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
300
+ }
301
+ return content;
302
+ }
303
+ function redundant_cond_fallback(issues, content) {
304
+ const lines = content.split('\n');
305
+ const lineOffsets = buildLineOffsets(lines);
306
+ const ops = [];
307
+ for (const issue of issues) {
308
+ const lineIdx = issue.line - 1;
309
+ if (lineIdx < 0 || lineIdx >= lines.length)
310
+ continue;
311
+ const line = lines[lineIdx];
312
+ const m = line.match(/^(\s*)\(cond\s+\(\(([^)]+)\)\s+\(([^)]*)\)\s*\)\s*\)$/);
313
+ if (m) {
314
+ ops.push({
315
+ start: lineOffsets[lineIdx],
316
+ end: lineOffsets[lineIdx] + line.length,
317
+ text: m[1] + '(if (' + m[2] + ') (' + m[3] + '))',
318
+ });
319
+ continue;
320
+ }
321
+ const condMatch = line.match(/\(cond(?=\s|$)/);
322
+ if (!condMatch)
323
+ continue;
324
+ const condIdx = condMatch.index;
325
+ const absCond = lineOffsets[lineIdx] + condIdx;
326
+ const condEnd = findCloseParen(content, absCond);
327
+ if (condEnd <= absCond)
328
+ continue;
329
+ const branchOpen = content.indexOf('(', absCond + 6);
330
+ if (branchOpen === -1 || branchOpen >= condEnd)
331
+ continue;
332
+ const branchEnd = findCloseParen(content, branchOpen);
333
+ if (branchEnd <= branchOpen)
334
+ continue;
335
+ const indent = line.slice(0, condIdx);
336
+ const branchInner = content.slice(branchOpen + 1, branchEnd).trim();
337
+ const [testExpr, testEnd] = extractNextForm(branchInner, 0);
338
+ const bodyExpr = branchInner.slice(testEnd).trim();
339
+ if (!testExpr || !bodyExpr)
340
+ continue;
341
+ ops.push({ start: absCond, end: condEnd + 1, text: indent + '(if ' + testExpr + ' ' + bodyExpr + ')' });
342
+ }
343
+ ops.sort((a, b) => b.start - a.start);
344
+ for (const op of ops) {
345
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
346
+ }
347
+ return content;
348
+ }
349
+ function quote_vs_function_fallback(issues, content) {
350
+ const lines = content.split('\n');
351
+ const lineOffsets = buildLineOffsets(lines);
352
+ const ops = [];
353
+ for (const issue of issues) {
354
+ const lineIdx = issue.line - 1;
355
+ if (lineIdx < 0 || lineIdx >= lines.length)
356
+ continue;
357
+ const line = lines[lineIdx];
358
+ const qIdx = line.indexOf("'(lambda");
359
+ if (qIdx !== -1) {
360
+ const absQuotePos = lineOffsets[lineIdx] + qIdx;
361
+ const absLambdaOpen = absQuotePos + 1;
362
+ const closePos = findCloseParen(content, absLambdaOpen);
363
+ if (closePos !== -1) {
364
+ ops.push({ start: absQuotePos, end: absQuotePos + 1, text: '(function ' });
365
+ ops.push({ start: closePos + 1, end: closePos + 1, text: ')' });
366
+ }
367
+ continue;
368
+ }
369
+ const explicitQuote = line.match(/\(quote\s+\(lambda/);
370
+ if (explicitQuote) {
371
+ const absQuotePos = lineOffsets[lineIdx] + explicitQuote.index;
372
+ const beforeLambda = line.indexOf('(lambda', explicitQuote.index);
373
+ const quoteEnd = lineOffsets[lineIdx] + beforeLambda;
374
+ ops.push({ start: absQuotePos, end: quoteEnd, text: '(function ' });
375
+ }
376
+ }
377
+ ops.sort((a, b) => b.start - a.start);
378
+ for (const op of ops) {
379
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
380
+ }
381
+ return content;
382
+ }
7
383
  const FIX_PROVIDERS = {
8
384
  redundant_quotes(line) {
9
385
  return line.replace(/''(\S)/g, "'$1");
10
386
  },
11
- double_not(line) {
12
- return line.replace(/\(not\s+\(not\s+/g, '(not ');
13
- },
14
- redundant_nil_else(line) {
15
- return line.replace(/\s+nil\s*\)$/, ')');
16
- },
17
- single_arg_and_or(line) {
18
- const m = line.match(/\((and|or)\s+([^)\s]+)\s*\)/);
19
- return m ? m[2] : line;
20
- },
21
- redundant_progn(line) {
22
- const proIdx = line.indexOf('(progn ');
23
- if (proIdx === -1)
24
- return line;
25
- const beforePro = line.slice(0, proIdx);
26
- const close = (0, utils_1.findMatchingParen)(line, proIdx);
27
- if (close <= proIdx)
28
- return line;
29
- const inner = line.slice(proIdx + 7, close);
30
- const rest = line.slice(close + 1);
31
- return beforePro + inner + rest;
32
- },
33
387
  redundant_let(line) {
34
388
  return line.replace(/\(let\s+\(\)\s*/, '(progn ');
35
389
  },
36
- quote_style(line) {
37
- const qIdx = line.indexOf('(quote ');
38
- if (qIdx === -1)
39
- return line;
40
- const beforeQ = line.slice(0, qIdx);
41
- const close = (0, utils_1.findMatchingParen)(line, qIdx);
42
- if (close <= qIdx)
43
- return line;
44
- const quoted = line.slice(qIdx + 7, close);
45
- const rest = line.slice(close + 1);
46
- return beforeQ + "'" + quoted + rest;
47
- },
48
- redundant_if(line) {
49
- const pIdx = line.indexOf('(progn ');
50
- if (pIdx === -1)
51
- return line;
52
- const beforeP = line.slice(0, pIdx);
53
- const close = (0, utils_1.findMatchingParen)(line, pIdx);
54
- if (close <= pIdx)
55
- return line;
56
- const inner = line.slice(pIdx + 7, close);
57
- const rest = line.slice(close + 1);
58
- return beforeP + inner + rest;
59
- },
60
- misplaced_else(line) {
61
- const ifStart = line.indexOf('(if (not ');
62
- if (ifStart === -1)
63
- return line;
64
- const notIdx = line.indexOf('(not ', ifStart);
65
- if (notIdx === -1)
66
- return line;
67
- const notClose = (0, utils_1.findMatchingParen)(line, notIdx);
68
- if (notClose <= notIdx)
69
- return line;
70
- const ifClose = (0, utils_1.findMatchingParen)(line, ifStart);
71
- if (ifClose <= ifStart)
72
- return line;
73
- const indent = line.slice(0, ifStart);
74
- const condVar = line.slice(notIdx + 5, notClose);
75
- const branchContent = line.slice(notClose + 1, ifClose).trim();
76
- const [thenForm, thenEnd] = (0, utils_1.extractForm)(branchContent, 0);
77
- const elseForm = branchContent.slice(thenEnd).trim();
78
- if (!thenForm || !elseForm)
79
- return line;
80
- const rest = line.slice(ifClose + 1);
81
- return indent + '(if ' + condVar + ' ' + elseForm + ' ' + thenForm + ')' + rest;
82
- },
83
- setq_multiple(line) {
84
- const fixed = line.replace(/\)\(setq\s+/g, ' ');
85
- if (fixed === line)
86
- return line;
87
- const merged = line.replace(/^\(\s*setq\s+/, '(setq ');
88
- if (merged.includes('setq') && !merged.match(/\)\(/))
89
- return merged;
90
- return line.replace(/\)\(setq\s+/g, '\n');
91
- },
92
- append_single(line) {
93
- const aIdx = line.indexOf('(append ');
94
- if (aIdx === -1)
95
- return line;
96
- const close = (0, utils_1.findMatchingParen)(line, aIdx);
97
- if (close <= aIdx)
98
- return line;
99
- const inner = line.slice(aIdx + 8, close);
100
- const listMatch = inner.match(/^\(list\s+(.*)\)\s*$/);
101
- if (!listMatch)
102
- return line;
103
- return line.slice(0, aIdx) + '(cons ' + listMatch[1] + ')' + line.slice(close + 1);
104
- },
105
390
  nth_usage(line) {
106
391
  return (0, nth_usage_1.replaceNthUsage)(line) || line;
107
392
  },
108
393
  setq_single_arg(line) {
109
394
  return line;
110
395
  },
111
- extra_parens(line) {
112
- return line.replace(/\){2,}/g, ')');
113
- },
114
- empty_branch(line) {
115
- return line.replace(/\(if\s+\S+\s*\)/g, m => m.slice(0, -1) + ' nil)');
116
- },
117
396
  comment_style(line) {
118
397
  return line.replace(/^(\s*;)([^ ;|].*)$/gm, '$1 $2');
119
398
  },
120
399
  eq_usage(line) {
121
400
  return line.replace(/\(eq\s+(\S+)\s+(\d+)\s*\)/g, '(= $1 $2)');
122
401
  },
123
- cond_simplify(line) {
124
- const condIdx = line.indexOf('(cond ');
125
- if (condIdx === -1)
126
- return line;
127
- const condEnd = (0, utils_1.findMatchingParen)(line, condIdx);
128
- if (condEnd <= condIdx)
129
- return line;
130
- const inner = line.slice(condIdx + 6, condEnd).trim();
131
- const branchOpen = inner.indexOf('(');
132
- if (branchOpen === -1)
133
- return line;
134
- const branchEnd = (0, utils_1.findMatchingParen)(inner, branchOpen);
135
- if (branchEnd <= branchOpen)
136
- return line;
137
- const indent = line.slice(0, condIdx);
138
- const branchContent = inner.slice(branchOpen + 1, branchEnd).trim();
139
- const space = branchContent.search(/\s+/);
140
- if (space === -1)
141
- return line;
142
- const testExpr = branchContent.slice(0, space);
143
- const bodyExpr = branchContent.slice(space).trim();
144
- const rest = line.slice(condEnd + 1);
145
- return indent + '(if ' + testExpr + ' ' + bodyExpr + ')' + rest;
146
- },
147
402
  self_compare(line) {
148
403
  return line.replace(/\((?:[=/<>]+|eq|equal)\s+(\S+)\s+\1\s*\)/g, 'T');
149
404
  },
150
- while_constant(line) {
151
- const trimmed = line.trim();
152
- const nilMatch = trimmed.match(/^\(\s*while\s+nil\s+(.*)\)\s*$/);
153
- if (!nilMatch)
154
- return line;
155
- return line.replace(trimmed, 'nil');
156
- },
157
405
  redundant_list(line) {
158
406
  return line.replace(/\(\s*list\s*\)/g, 'nil');
159
407
  },
160
408
  zerop_usage(line) {
161
409
  return line.replace(/\(=\s+(\S+)\s+0\s*\)/g, '(zerop $1)').replace(/\(=\s+0\s+(\S+)\s*\)/g, '(zerop $1)');
162
410
  },
163
- redundant_cond(line) {
164
- const m = line.match(/^(\s*)\(cond\s+\(\(([^)]+)\)\s+\(([^)]*)\)\s*\)\s*\)$/);
165
- if (!m)
166
- return line;
167
- return `${m[1]}(if (${m[2]}) (${m[3]}))`;
411
+ };
412
+ const CONTENT_FIX_PROVIDERS = {
413
+ double_not(_issues, content) {
414
+ return content.replace(/\(not\s+\(not\s+/g, '(not ');
415
+ },
416
+ redundant_nil_else(issues, content) {
417
+ const ops = [];
418
+ const lines = content.split('\n');
419
+ const lineOffsets = buildLineOffsets(lines);
420
+ for (const issue of issues) {
421
+ const lineIdx = issue.line - 1;
422
+ if (lineIdx < 0 || lineIdx >= lines.length)
423
+ continue;
424
+ const abs = lineOffsets[lineIdx];
425
+ const m = lines[lineIdx].match(/(\s+)nil\s*\)\s*$/);
426
+ if (m) {
427
+ const idx = abs + m.index + m[1].length;
428
+ ops.push({ start: idx, end: idx + 3, text: '' });
429
+ }
430
+ }
431
+ ops.sort((a, b) => b.start - a.start);
432
+ for (const op of ops) {
433
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
434
+ }
435
+ return content;
436
+ },
437
+ single_arg_and_or(issues, content) {
438
+ const ops = [];
439
+ const lines = content.split('\n');
440
+ const lineOffsets = buildLineOffsets(lines);
441
+ for (const issue of issues) {
442
+ const lineIdx = issue.line - 1;
443
+ if (lineIdx < 0 || lineIdx >= lines.length)
444
+ continue;
445
+ const line = lines[lineIdx];
446
+ const m = line.match(/\((and|or)\s+([^)\s]+)\s*\)/);
447
+ if (m) {
448
+ const start = lineOffsets[lineIdx] + m.index;
449
+ const end = lineOffsets[lineIdx] + m.index + m[0].length;
450
+ ops.push({ start, end, text: m[2] });
451
+ }
452
+ }
453
+ ops.sort((a, b) => b.start - a.start);
454
+ for (const op of ops) {
455
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
456
+ }
457
+ return content;
458
+ },
459
+ redundant_progn(issues, content, ast, posToOffset) {
460
+ if (!ast || !posToOffset)
461
+ return stripPrognWrapper(issues, content);
462
+ const prognNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'progn'));
463
+ if (prognNodes.length === 0)
464
+ return content;
465
+ const issueLines = new Set(issues.map(i => i.line));
466
+ const ops = [];
467
+ for (const node of prognNodes) {
468
+ if (!node.end || !issueLines.has(node.pos.line))
469
+ continue;
470
+ const start = posToOffset(node.pos);
471
+ const end = posToOffset(node.end);
472
+ let argStart = start + 6;
473
+ if (content[argStart] === ' ')
474
+ argStart++;
475
+ const inner = content.slice(argStart, end - 1);
476
+ ops.push({ start, end, text: inner });
477
+ }
478
+ if (ops.length === 0)
479
+ return stripPrognWrapper(issues, content);
480
+ ops.sort((a, b) => b.start - a.start);
481
+ for (const op of ops) {
482
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
483
+ }
484
+ return content;
485
+ },
486
+ quote_style(issues, content, ast, posToOffset) {
487
+ if (!ast || !posToOffset)
488
+ return quote_style_fallback(issues, content);
489
+ const quoteNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'quote'));
490
+ if (quoteNodes.length === 0)
491
+ return content;
492
+ const issueLines = new Set(issues.map(i => i.line));
493
+ const ops = [];
494
+ for (const node of quoteNodes) {
495
+ if (!node.end || !issueLines.has(node.pos.line))
496
+ continue;
497
+ if (content[posToOffset(node.pos)] !== '(')
498
+ continue;
499
+ const start = posToOffset(node.pos);
500
+ const end = posToOffset(node.end);
501
+ let afterQuote = start + 6;
502
+ if (content[afterQuote] === ' ')
503
+ afterQuote++;
504
+ const quoted = content.slice(afterQuote, end - 1);
505
+ ops.push({ start, end, text: "'" + quoted });
506
+ }
507
+ if (ops.length === 0)
508
+ return quote_style_fallback(issues, content);
509
+ ops.sort((a, b) => b.start - a.start);
510
+ for (const op of ops) {
511
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
512
+ }
513
+ return content;
514
+ },
515
+ redundant_if(issues, content, ast, posToOffset) {
516
+ if (!ast || !posToOffset)
517
+ return stripPrognWrapper(issues, content);
518
+ const prognNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'progn') && n.parent !== undefined && (0, parser_1.astIsList)(n.parent, 'if'));
519
+ if (prognNodes.length === 0)
520
+ return content;
521
+ const issueLines = new Set(issues.map(i => i.line));
522
+ const ops = [];
523
+ for (const node of prognNodes) {
524
+ if (!node.end || !issueLines.has(node.pos.line))
525
+ continue;
526
+ const start = posToOffset(node.pos);
527
+ const end = posToOffset(node.end);
528
+ let argStart = start + 6;
529
+ if (content[argStart] === ' ')
530
+ argStart++;
531
+ const inner = content.slice(argStart, end - 1);
532
+ ops.push({ start, end, text: inner });
533
+ }
534
+ if (ops.length === 0)
535
+ return stripPrognWrapper(issues, content);
536
+ ops.sort((a, b) => b.start - a.start);
537
+ for (const op of ops) {
538
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
539
+ }
540
+ return content;
541
+ },
542
+ misplaced_else(issues, content, ast, posToOffset) {
543
+ if (!ast || !posToOffset)
544
+ return misplaced_else_fallback(issues, content);
545
+ const ifNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'if') && n.children && n.children.length >= 4 && (0, parser_1.astIsList)(n.children[1], 'not'));
546
+ if (ifNodes.length === 0)
547
+ return content;
548
+ const issueLines = new Set(issues.map(i => i.line));
549
+ const ops = [];
550
+ for (const node of ifNodes) {
551
+ if (!node.end || !issueLines.has(node.pos.line))
552
+ continue;
553
+ if (!(0, parser_1.astIsList)(node))
554
+ continue;
555
+ const start = posToOffset(node.pos);
556
+ const end = posToOffset(node.end);
557
+ const indent = ' '.repeat(node.pos.col - 1);
558
+ const notForm = node.children[1];
559
+ if (!(0, parser_1.astIsList)(notForm))
560
+ continue;
561
+ const condForm = notForm.children[1];
562
+ const condStart = posToOffset(condForm.pos);
563
+ const condEnd = posToOffset(condForm.end);
564
+ const condRaw = content.slice(condStart, condEnd);
565
+ const thenRaw = content.slice(posToOffset(node.children[2].pos), posToOffset(node.children[2].end));
566
+ const elseRaw = content.slice(posToOffset(node.children[3].pos), posToOffset(node.children[3].end));
567
+ ops.push({ start, end, text: indent + '(if ' + condRaw + ' ' + elseRaw + ' ' + thenRaw + ')' });
568
+ }
569
+ if (ops.length === 0)
570
+ return misplaced_else_fallback(issues, content);
571
+ ops.sort((a, b) => b.start - a.start);
572
+ for (const op of ops) {
573
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
574
+ }
575
+ return content;
576
+ },
577
+ setq_multiple(issues, content, ast, posToOffset) {
578
+ if (!ast || !posToOffset)
579
+ return setq_multiple_fallback(issues, content);
580
+ const setqNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'setq'));
581
+ if (setqNodes.length < 2)
582
+ return content;
583
+ const issueLines = new Set(issues.map(i => i.line));
584
+ const ops = [];
585
+ for (let i = 0; i < setqNodes.length - 1; i++) {
586
+ const a = setqNodes[i];
587
+ const b = setqNodes[i + 1];
588
+ if (!a.end || !b.end)
589
+ continue;
590
+ if (a.parent !== b.parent)
591
+ continue;
592
+ if (!issueLines.has(a.pos.line))
593
+ continue;
594
+ const parent = a.parent;
595
+ if (!parent || !(0, parser_1.astIsList)(parent))
596
+ continue;
597
+ const aIdx = parent.children.indexOf(a);
598
+ if (aIdx === -1 || parent.children[aIdx + 1] !== b)
599
+ continue;
600
+ const bStart = posToOffset(b.pos);
601
+ const bEnd = posToOffset(b.end);
602
+ let bodyStart = bStart + 5;
603
+ if (content[bodyStart] === ' ')
604
+ bodyStart++;
605
+ const body2 = content.slice(bodyStart, bEnd - 1);
606
+ const aEndPos = posToOffset(a.end) - 1;
607
+ ops.push({ start: aEndPos, end: bEnd, text: ' ' + body2 + ')' });
608
+ }
609
+ if (ops.length === 0)
610
+ return setq_multiple_fallback(issues, content);
611
+ ops.sort((a, b) => b.start - a.start);
612
+ for (const op of ops) {
613
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
614
+ }
615
+ return content;
616
+ },
617
+ append_single(issues, content, ast, posToOffset) {
618
+ if (!ast || !posToOffset)
619
+ return append_single_fallback(issues, content);
620
+ const appendNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'append'));
621
+ if (appendNodes.length === 0)
622
+ return content;
623
+ const issueLines = new Set(issues.map(i => i.line));
624
+ const ops = [];
625
+ for (const node of appendNodes) {
626
+ if (!node.end || !issueLines.has(node.pos.line))
627
+ continue;
628
+ if (!(0, parser_1.astIsList)(node) || node.children.length < 2)
629
+ continue;
630
+ const arg = node.children[1];
631
+ if (!(0, parser_1.astIsList)(arg, 'list'))
632
+ continue;
633
+ const start = posToOffset(node.pos);
634
+ const end = posToOffset(node.end);
635
+ const listStart = posToOffset(arg.children[1].pos);
636
+ const listEnd = posToOffset(arg.children[1].end);
637
+ const listBody = content.slice(listStart, listEnd);
638
+ ops.push({ start, end, text: '(cons ' + listBody + ')' });
639
+ }
640
+ if (ops.length === 0)
641
+ return append_single_fallback(issues, content);
642
+ ops.sort((a, b) => b.start - a.start);
643
+ for (const op of ops) {
644
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
645
+ }
646
+ return content;
647
+ },
648
+ extra_parens(issues, content) {
649
+ const lines = content.split('\n');
650
+ const ops = [];
651
+ for (const issue of issues) {
652
+ const lineIdx = issue.line - 1;
653
+ if (lineIdx < 0 || lineIdx >= lines.length)
654
+ continue;
655
+ const line = lines[lineIdx];
656
+ const dep = line.match(/\){2,}/);
657
+ if (!dep)
658
+ continue;
659
+ const abs = buildLineOffsets(lines)[lineIdx] + dep.index;
660
+ const count = dep[0].length;
661
+ let depth = 0;
662
+ for (let i = 0; i < content.length; i++) {
663
+ const c = content[i];
664
+ if (c === ';') {
665
+ while (i < content.length && content[i] !== '\n')
666
+ i++;
667
+ continue;
668
+ }
669
+ if (c === '(')
670
+ depth++;
671
+ else if (c === ')') {
672
+ depth--;
673
+ if (depth < 0 && i >= abs && i < abs + count) {
674
+ ops.push({ start: i, end: i + 1, text: '' });
675
+ }
676
+ }
677
+ }
678
+ }
679
+ ops.sort((a, b) => b.start - a.start);
680
+ for (const op of ops) {
681
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
682
+ }
683
+ return content;
684
+ },
685
+ empty_branch(issues, content) {
686
+ const ops = [];
687
+ const lines = content.split('\n');
688
+ const lineOffsets = buildLineOffsets(lines);
689
+ for (const issue of issues) {
690
+ const lineIdx = issue.line - 1;
691
+ if (lineIdx < 0 || lineIdx >= lines.length)
692
+ continue;
693
+ const line = lines[lineIdx];
694
+ const m = line.match(/\(if\s+(\S+)\s*\)/);
695
+ if (!m)
696
+ continue;
697
+ const start = lineOffsets[lineIdx] + m.index;
698
+ const end = start + m[0].length;
699
+ ops.push({ start: end - 1, end, text: ' nil)' });
700
+ }
701
+ ops.sort((a, b) => b.start - a.start);
702
+ for (const op of ops) {
703
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
704
+ }
705
+ return content;
706
+ },
707
+ cond_simplify(issues, content, ast, posToOffset) {
708
+ if (!ast || !posToOffset)
709
+ return cond_simplify_fallback(issues, content);
710
+ const condNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'cond') && n.children && n.children.length >= 2 && (0, parser_1.astIsList)(n.children[1]));
711
+ if (condNodes.length === 0)
712
+ return content;
713
+ const issueLines = new Set(issues.map(i => i.line));
714
+ const ops = [];
715
+ for (const node of condNodes) {
716
+ if (!node.end || !issueLines.has(node.pos.line))
717
+ continue;
718
+ if (!(0, parser_1.astIsList)(node) || node.children.length !== 2)
719
+ continue;
720
+ const branch = node.children[1];
721
+ if (!(0, parser_1.astIsList)(branch) || branch.children.length < 2)
722
+ continue;
723
+ const start = posToOffset(node.pos);
724
+ const end = posToOffset(node.end);
725
+ const indent = ' '.repeat(node.pos.col - 1);
726
+ const testStart = posToOffset(branch.children[0].pos);
727
+ const testEnd = posToOffset(branch.children[0].end);
728
+ const testExpr = content.slice(testStart, testEnd);
729
+ const bodyStart = posToOffset(branch.children[1].pos);
730
+ const bodyEnd = posToOffset(branch.children[1].end);
731
+ const bodyExpr = content.slice(bodyStart, bodyEnd);
732
+ ops.push({ start, end, text: indent + '(if ' + testExpr + ' ' + bodyExpr + ')' });
733
+ }
734
+ if (ops.length === 0)
735
+ return cond_simplify_fallback(issues, content);
736
+ ops.sort((a, b) => b.start - a.start);
737
+ for (const op of ops) {
738
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
739
+ }
740
+ return content;
741
+ },
742
+ while_constant(issues, content, ast, posToOffset) {
743
+ if (!ast || !posToOffset)
744
+ return while_constant_fallback(issues, content);
745
+ const whileNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'while'));
746
+ if (whileNodes.length === 0)
747
+ return content;
748
+ const issueLines = new Set(issues.map(i => i.line));
749
+ const ops = [];
750
+ for (const node of whileNodes) {
751
+ if (!node.end || !issueLines.has(node.pos.line))
752
+ continue;
753
+ if (!(0, parser_1.astIsList)(node) || node.children.length < 2)
754
+ continue;
755
+ const test = node.children[1];
756
+ if (test.type !== 'symbol' || test.name !== 'nil')
757
+ continue;
758
+ const start = posToOffset(node.pos);
759
+ const end = posToOffset(node.end);
760
+ ops.push({ start, end, text: 'nil' });
761
+ }
762
+ if (ops.length === 0)
763
+ return while_constant_fallback(issues, content);
764
+ ops.sort((a, b) => b.start - a.start);
765
+ for (const op of ops) {
766
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
767
+ }
768
+ return content;
769
+ },
770
+ redundant_cond(issues, content, ast, posToOffset) {
771
+ if (!ast || !posToOffset)
772
+ return redundant_cond_fallback(issues, content);
773
+ const condNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'cond') && n.children && n.children.length === 2 && (0, parser_1.astIsList)(n.children[1]));
774
+ if (condNodes.length === 0)
775
+ return content;
776
+ const issueLines = new Set(issues.map(i => i.line));
777
+ const ops = [];
778
+ for (const node of condNodes) {
779
+ if (!node.end || !issueLines.has(node.pos.line))
780
+ continue;
781
+ if (!(0, parser_1.astIsList)(node))
782
+ continue;
783
+ const branch = node.children[1];
784
+ if (!(0, parser_1.astIsList)(branch) || branch.children.length < 2)
785
+ continue;
786
+ const start = posToOffset(node.pos);
787
+ const end = posToOffset(node.end);
788
+ const indent = ' '.repeat(node.pos.col - 1);
789
+ const testStart = posToOffset(branch.children[0].pos);
790
+ const testEnd = posToOffset(branch.children[0].end);
791
+ const bodyStart = posToOffset(branch.children[1].pos);
792
+ const bodyEnd = posToOffset(branch.children[1].end);
793
+ ops.push({
794
+ start,
795
+ end,
796
+ text: indent + '(if ' + content.slice(testStart, testEnd) + ' ' + content.slice(bodyStart, bodyEnd) + ')',
797
+ });
798
+ }
799
+ if (ops.length === 0)
800
+ return redundant_cond_fallback(issues, content);
801
+ ops.sort((a, b) => b.start - a.start);
802
+ for (const op of ops) {
803
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
804
+ }
805
+ return content;
806
+ },
807
+ quote_vs_function(issues, content, ast, posToOffset) {
808
+ if (!ast || !posToOffset)
809
+ return quote_vs_function_fallback(issues, content);
810
+ const quoteNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'quote'));
811
+ if (quoteNodes.length === 0)
812
+ return content;
813
+ const issueLines = new Set(issues.map(i => i.line));
814
+ const ops = [];
815
+ for (const node of quoteNodes) {
816
+ if (!node.end || !issueLines.has(node.pos.line))
817
+ continue;
818
+ if (!(0, parser_1.astIsList)(node) || node.children.length < 2)
819
+ continue;
820
+ const quoted = node.children[1];
821
+ if (!(0, parser_1.astIsList)(quoted, 'lambda'))
822
+ continue;
823
+ const isShorthand = content[posToOffset(node.pos)] === "'";
824
+ if (isShorthand) {
825
+ ops.push({ start: posToOffset(node.pos), end: posToOffset(node.pos) + 1, text: '(function ' });
826
+ ops.push({ start: posToOffset(node.end), end: posToOffset(node.end), text: ')' });
827
+ }
828
+ else {
829
+ const lambdaStart = posToOffset(node.children[1].pos);
830
+ ops.push({ start: posToOffset(node.pos), end: lambdaStart, text: '(function ' });
831
+ }
832
+ }
833
+ if (ops.length === 0)
834
+ return quote_vs_function_fallback(issues, content);
835
+ ops.sort((a, b) => b.start - a.start);
836
+ for (const op of ops) {
837
+ content = content.slice(0, op.start) + op.text + content.slice(op.end);
838
+ }
839
+ return content;
168
840
  },
169
841
  };
170
- exports.FIXABLE_RULES = new Set(Object.keys(FIX_PROVIDERS));
842
+ exports.FIXABLE_RULES = new Set([...Object.keys(FIX_PROVIDERS), ...Object.keys(CONTENT_FIX_PROVIDERS)]);
171
843
  function applyFixesFromProviders(issues, content, filepath) {
844
+ const fileIssues = issues.filter(i => i.file === filepath);
845
+ if (fileIssues.length === 0)
846
+ return content;
847
+ const hasAstContentRules = [
848
+ 'redundant_progn',
849
+ 'quote_style',
850
+ 'redundant_if',
851
+ 'misplaced_else',
852
+ 'setq_multiple',
853
+ 'append_single',
854
+ 'cond_simplify',
855
+ 'while_constant',
856
+ 'redundant_cond',
857
+ 'quote_vs_function',
858
+ ];
859
+ const needsAst = fileIssues.some(i => hasAstContentRules.includes(i.rule));
860
+ let ast;
861
+ if (needsAst) {
862
+ try {
863
+ ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
864
+ }
865
+ catch {
866
+ /* ignore parse errors */
867
+ }
868
+ }
869
+ const posToOffset = buildPosToOffset(content);
870
+ for (const [rule, provider] of Object.entries(CONTENT_FIX_PROVIDERS)) {
871
+ const ruleIssues = fileIssues.filter(i => i.rule === rule);
872
+ if (ruleIssues.length > 0) {
873
+ content = provider(ruleIssues, content, ast, posToOffset);
874
+ }
875
+ }
876
+ // Re-split after content fix providers may have modified content
172
877
  const lines = content.split('\n');
173
- const fixedLines = new Set();
174
878
  const fixesByRule = new Map();
175
- for (const iss of issues) {
176
- if (iss.file !== filepath)
177
- continue;
879
+ for (const iss of fileIssues) {
178
880
  const set = fixesByRule.get(iss.rule) || new Set();
179
881
  set.add(iss.line);
180
882
  fixesByRule.set(iss.rule, set);
181
883
  }
182
884
  for (const [rule, lineSet] of fixesByRule) {
885
+ if (CONTENT_FIX_PROVIDERS[rule])
886
+ continue;
183
887
  const provider = FIX_PROVIDERS[rule];
184
888
  if (!provider)
185
889
  continue;
@@ -191,7 +895,6 @@ function applyFixesFromProviders(issues, content, filepath) {
191
895
  const fixed = provider(lines[idx]);
192
896
  if (fixed !== lines[idx]) {
193
897
  lines[idx] = fixed;
194
- fixedLines.add(idx);
195
898
  }
196
899
  }
197
900
  }