@atlisp/lint 0.2.21 → 0.2.22

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.
@@ -4,6 +4,7 @@ exports.checkMultipleSetq = checkMultipleSetq;
4
4
  exports.checkMultipleSetqAst = checkMultipleSetqAst;
5
5
  const locale_1 = require("../locale");
6
6
  const parser_1 = require("@atlisp/parser");
7
+ const shared_1 = require("../visitors/shared");
7
8
  function checkMultipleSetq(content, file) {
8
9
  const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
9
10
  return checkMultipleSetqAst(ast, file);
@@ -18,7 +19,7 @@ function checkMultipleSetqAst(ast, file) {
18
19
  parents.add(n.parent);
19
20
  }
20
21
  for (const parent of parents) {
21
- if (!(0, parser_1.astIsList)(parent))
22
+ if (!(0, parser_1.astIsList)(parent) || (0, shared_1.isNonSequentialParent)(parent))
22
23
  continue;
23
24
  let prevWasSetq = false;
24
25
  let prevSetqNode = null;
@@ -2,14 +2,40 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.checkSetqMultiple = checkSetqMultiple;
4
4
  const locale_1 = require("../locale");
5
- const utils_1 = require("../utils");
5
+ const parser_1 = require("@atlisp/parser");
6
+ const shared_1 = require("../visitors/shared");
6
7
  function checkSetqMultiple(content, file) {
7
8
  const issues = [];
8
- const lines = content.split('\n');
9
- for (let i = 0; i < lines.length; i++) {
10
- const stripped = (0, utils_1.stripLine)(lines[i]);
11
- if (/\(setq\s+\S+\s+[^\s)]+\)\s*\(setq\b/.test(stripped)) {
12
- issues.push({ file, line: i + 1, severity: 'warn', rule: 'setq_multiple', message: (0, locale_1.t)('setq_multiple') });
9
+ const ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
10
+ const parents = new Set();
11
+ const setqNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'setq'));
12
+ for (const n of setqNodes) {
13
+ if (n.parent)
14
+ parents.add(n.parent);
15
+ }
16
+ for (const parent of parents) {
17
+ if (!(0, parser_1.astIsList)(parent) || (0, shared_1.isNonSequentialParent)(parent))
18
+ continue;
19
+ let prevSetqNode = null;
20
+ for (const child of parent.children) {
21
+ if ((0, parser_1.astIsList)(child, 'setq')) {
22
+ if (prevSetqNode) {
23
+ issues.push({
24
+ file,
25
+ line: prevSetqNode.pos.line,
26
+ severity: 'warn',
27
+ rule: 'setq_multiple',
28
+ message: (0, locale_1.t)('setq_multiple'),
29
+ });
30
+ prevSetqNode = null;
31
+ }
32
+ else {
33
+ prevSetqNode = child;
34
+ }
35
+ }
36
+ else {
37
+ prevSetqNode = null;
38
+ }
13
39
  }
14
40
  }
15
41
  return issues;
@@ -4,6 +4,7 @@ exports.FIXABLE_RULES = void 0;
4
4
  exports.applyFixesFromProviders = applyFixesFromProviders;
5
5
  const nth_usage_1 = require("../checks/nth-usage");
6
6
  const parser_1 = require("@atlisp/parser");
7
+ const shared_1 = require("../visitors/shared");
7
8
  function buildPosToOffset(_content) {
8
9
  return pos => pos.offset;
9
10
  }
@@ -162,7 +163,24 @@ function misplaced_else_fallback(issues, content) {
162
163
  }
163
164
  return content;
164
165
  }
166
+ function isSetqInNonSequentialParent(ast, line) {
167
+ if (!ast)
168
+ return false;
169
+ const setqNodes = (0, parser_1.astFindAll)(ast, n => (0, parser_1.astIsList)(n, 'setq') && n.pos.line === line);
170
+ for (const n of setqNodes) {
171
+ if (n.parent && (0, shared_1.isNonSequentialParent)(n.parent))
172
+ return true;
173
+ }
174
+ return false;
175
+ }
165
176
  function setq_multiple_fallback(issues, content) {
177
+ let ast;
178
+ try {
179
+ ast = (0, parser_1.parseAst)(content, { errorRecovery: true });
180
+ }
181
+ catch {
182
+ /* ignore parse errors */
183
+ }
166
184
  const lines = content.split('\n');
167
185
  const lineOffsets = buildLineOffsets(lines);
168
186
  const ops = [];
@@ -170,6 +188,9 @@ function setq_multiple_fallback(issues, content) {
170
188
  const lineIdx = issue.line - 1;
171
189
  if (lineIdx < 0 || lineIdx >= lines.length)
172
190
  continue;
191
+ // Skip setq forms in non-sequential positions (if branches, and/or, ...)
192
+ if (isSetqInNonSequentialParent(ast, issue.line))
193
+ continue;
173
194
  const line = lines[lineIdx];
174
195
  const setqMatch = line.match(/\(setq(?=\s|$)/);
175
196
  if (!setqMatch)
@@ -594,6 +615,8 @@ const CONTENT_FIX_PROVIDERS = {
594
615
  const parent = a.parent;
595
616
  if (!parent || !(0, parser_1.astIsList)(parent))
596
617
  continue;
618
+ if ((0, shared_1.isNonSequentialParent)(parent))
619
+ continue;
597
620
  const aIdx = parent.children.indexOf(a);
598
621
  if (aIdx === -1 || parent.children[aIdx + 1] !== b)
599
622
  continue;
@@ -273,7 +273,11 @@ function registerVisitorRules(visitor, state, checks, addIssue, config, file) {
273
273
  dangerNames.add('eval');
274
274
  if (dangerNames.size > 0) {
275
275
  visitor.on('*', node => {
276
- if ((0, parser_1.astIsList)(node) && node.children.length > 0 && (0, parser_1.astIsSymbol)(node.children[0]) && node.children[0].name && dangerNames.has(node.children[0].name)) {
276
+ if ((0, parser_1.astIsList)(node) &&
277
+ node.children.length > 0 &&
278
+ (0, parser_1.astIsSymbol)(node.children[0]) &&
279
+ node.children[0].name &&
280
+ dangerNames.has(node.children[0].name)) {
277
281
  if (!(0, shared_1.isInQuote)(node)) {
278
282
  state.dangerousCalls.push({ name: node.children[0].name, node });
279
283
  }
@@ -284,6 +288,10 @@ function registerVisitorRules(visitor, state, checks, addIssue, config, file) {
284
288
  if (checks['multiple_setq'] !== 'off') {
285
289
  visitor.on('*', node => {
286
290
  if ((0, parser_1.astIsList)(node)) {
291
+ // Skip condition/branch positions (if/and/or/while/...) where setq
292
+ // children are mutually exclusive or short-circuited — merging would break them.
293
+ if ((0, shared_1.isNonSequentialParent)(node))
294
+ return;
287
295
  let prevSetq = false;
288
296
  for (const child of node.children) {
289
297
  if ((0, parser_1.astIsList)(child, 'setq')) {
@@ -605,12 +613,18 @@ function registerVisitorRules(visitor, state, checks, addIssue, config, file) {
605
613
  if ((0, shared_1.isInQuote)(node))
606
614
  return;
607
615
  const parent = node.parent;
608
- if (parent && (0, parser_1.astIsList)(parent) && parent.children.length > 0 && (0, parser_1.astIsSymbol)(parent.children[0], 'vl-catch-all-apply')) {
616
+ if (parent &&
617
+ (0, parser_1.astIsList)(parent) &&
618
+ parent.children.length > 0 &&
619
+ (0, parser_1.astIsSymbol)(parent.children[0], 'vl-catch-all-apply')) {
609
620
  return;
610
621
  }
611
622
  let hasCallback = false;
612
623
  for (const child of node.children.slice(1)) {
613
- if ((0, parser_1.astIsList)(child) && child.children.length > 0 && (0, parser_1.astIsSymbol)(child.children[0]) && (child.children[0].name === 'lambda' || child.children[0].name === 'function')) {
624
+ if ((0, parser_1.astIsList)(child) &&
625
+ child.children.length > 0 &&
626
+ (0, parser_1.astIsSymbol)(child.children[0]) &&
627
+ (child.children[0].name === 'lambda' || child.children[0].name === 'function')) {
614
628
  hasCallback = true;
615
629
  break;
616
630
  }
@@ -36,5 +36,6 @@ export declare function hasSelfCall(body: AstNode[], fnName: string): boolean;
36
36
  export declare function defunParams(defunNode: AstNode): string[];
37
37
  export declare function defunLocals(defunNode: AstNode): string[];
38
38
  export declare function collectSymbolRefs(node: AstNode, result: Set<string>): void;
39
+ export declare function isNonSequentialParent(node: AstNode): boolean;
39
40
  export declare const higherOrderFuncs: Set<string>;
40
41
  //# sourceMappingURL=shared.d.ts.map
@@ -10,6 +10,7 @@ exports.hasSelfCall = hasSelfCall;
10
10
  exports.defunParams = defunParams;
11
11
  exports.defunLocals = defunLocals;
12
12
  exports.collectSymbolRefs = collectSymbolRefs;
13
+ exports.isNonSequentialParent = isNonSequentialParent;
13
14
  const parser_1 = require("@atlisp/parser");
14
15
  function isInQuote(node, checkFunction = true) {
15
16
  for (const p of (0, parser_1.astAncestors)(node)) {
@@ -114,6 +115,19 @@ function collectSymbolRefs(node, result) {
114
115
  collectSymbolRefs(c, result);
115
116
  }
116
117
  }
118
+ // Forms whose child lists are not guaranteed to execute in sequence
119
+ // (conditions / branches / short-circuit value positions). Merging consecutive
120
+ // (setq ...) children of these heads would change program semantics.
121
+ const NON_SEQUENTIAL_HEADS = new Set(['if', 'and', 'or', 'while', 'repeat', 'foreach']);
122
+ // Returns true when the list's children occupy non-sequential positions
123
+ // (e.g. the then/else branches of an if), so consecutive setq children
124
+ // must NOT be reported as mergeable.
125
+ function isNonSequentialParent(node) {
126
+ if (!(0, parser_1.astIsList)(node) || node.children.length === 0)
127
+ return true;
128
+ const head = node.children[0];
129
+ return (0, parser_1.astIsSymbol)(head) && NON_SEQUENTIAL_HEADS.has(head.name);
130
+ }
117
131
  exports.higherOrderFuncs = new Set([
118
132
  'mapcar',
119
133
  'apply',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlisp/lint",
3
- "version": "0.2.21",
3
+ "version": "0.2.22",
4
4
  "description": "AutoLISP static analysis tool — parens, security, conventions, SBCL syntax validation",
5
5
  "keywords": [
6
6
  "CAD",