@atlisp/lint 0.2.23 → 0.2.25
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.
- package/README.md +0 -1
- package/atlisp-lint.default.json +0 -1
- package/atlisp-lint.schema.json +0 -1
- package/dist/checks/builtin-arg-count.js +7 -3
- package/dist/checks/commented-code.js +10 -1
- package/dist/checks/empty-comments.js +9 -2
- package/dist/checks/error-handling.js +6 -1
- package/dist/checks/extra-parens.js +6 -1
- package/dist/checks/function-order.js +6 -1
- package/dist/checks/missing-doc.js +22 -3
- package/dist/checks/parens.js +9 -2
- package/dist/checks/trailing-paren.js +17 -0
- package/dist/config.js +0 -1
- package/dist/disable.js +6 -1
- package/dist/formatter.js +2 -2
- package/dist/locale.js +0 -2
- package/dist/presets.js +0 -2
- package/dist/rules.js +0 -1
- package/dist/runner.js +0 -1
- package/dist/utils.d.ts +23 -0
- package/dist/utils.js +95 -0
- package/dist/validate.js +0 -1
- package/dist/visitors/register.js +0 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -102,7 +102,6 @@ npx @atlisp/lint --format-check
|
|
|
102
102
|
| `redundant_nil_else` | warn | 风格 | if 中冗余 nil 分支 |
|
|
103
103
|
| `single_arg_and_or` | warn | 风格 | 单参数 and/or 可简化 |
|
|
104
104
|
| `double_not` | warn | 风格 | 双重 (not (not ...)) |
|
|
105
|
-
| `multiple_setq` | warn | 风格 | 连续 setq 可合并 |
|
|
106
105
|
| `setq_multiple` | warn | 风格 | 连续 setq 可合并 |
|
|
107
106
|
| `setq_single_arg` | warn | 正确性 | setq 缺少值 |
|
|
108
107
|
| `assoc_without_cdr` | warn | 正确性 | assoc 结果未用 cdr 提取 |
|
package/atlisp-lint.default.json
CHANGED
package/atlisp-lint.schema.json
CHANGED
|
@@ -90,7 +90,6 @@
|
|
|
90
90
|
"mixed_indent": { "type": "string", "enum": ["off", "info", "warn", "error"], "default": "info" },
|
|
91
91
|
"module_cycle": { "type": "string", "enum": ["off", "info", "warn", "error"], "default": "warn" },
|
|
92
92
|
"module_registration": { "type": "string", "enum": ["off", "info", "warn", "error"], "default": "off" },
|
|
93
|
-
"multiple_setq": { "type": "string", "enum": ["off", "info", "warn", "error"], "default": "info" },
|
|
94
93
|
"namespace_header": { "type": "string", "enum": ["off", "info", "warn", "error"], "default": "off" },
|
|
95
94
|
"no_return": { "type": "string", "enum": ["off", "info", "warn", "error"], "default": "info" },
|
|
96
95
|
"nth_usage": { "type": "string", "enum": ["off", "info", "warn", "error"], "default": "info" },
|
|
@@ -179,7 +179,7 @@ const BUILTIN_ARITY = {
|
|
|
179
179
|
acad_colordlg: { min: 1, max: 3 },
|
|
180
180
|
// === ActiveX ===
|
|
181
181
|
'vlax-invoke-method': { min: 2, max: -1 },
|
|
182
|
-
'vlax-get-property': { min: 2, max:
|
|
182
|
+
'vlax-get-property': { min: 2, max: -1 },
|
|
183
183
|
'vlax-put-property': { min: 3, max: 3 },
|
|
184
184
|
'vlax-get-or-create-object': { min: 1, max: 2 },
|
|
185
185
|
'vlax-create-object': { min: 1, max: 1 },
|
|
@@ -284,8 +284,12 @@ function checkNode(node, content, file, issues) {
|
|
|
284
284
|
});
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
|
-
|
|
288
|
-
|
|
287
|
+
// Lambda lists may contain a bare `/` separator, which is not a function call
|
|
288
|
+
const lambdaListIdx = funcName === 'lambda' ? 1 : funcName === 'defun' || funcName === 'defun-q' ? 2 : -1;
|
|
289
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
290
|
+
if (i === lambdaListIdx)
|
|
291
|
+
continue;
|
|
292
|
+
checkNode(node.children[i], content, file, issues);
|
|
289
293
|
}
|
|
290
294
|
}
|
|
291
295
|
//# sourceMappingURL=builtin-arg-count.js.map
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.checkCommentedCode = checkCommentedCode;
|
|
4
4
|
const locale_1 = require("../locale");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
5
6
|
const CODE_PATTERNS = [
|
|
6
7
|
/^\s*\(defun\s/i,
|
|
7
8
|
/^\s*\(setq\s/i,
|
|
@@ -22,17 +23,25 @@ const CODE_PATTERNS = [
|
|
|
22
23
|
function checkCommentedCode(content, file) {
|
|
23
24
|
const issues = [];
|
|
24
25
|
const lines = content.split('\n');
|
|
26
|
+
const blockRanges = (0, utils_1.getBlockCommentRanges)(content);
|
|
25
27
|
for (let i = 0; i < lines.length; i++) {
|
|
28
|
+
const lineNum = i + 1;
|
|
29
|
+
// Skip lines inside block comments
|
|
30
|
+
if ((0, utils_1.isInsideBlockComment)(lineNum, blockRanges))
|
|
31
|
+
continue;
|
|
26
32
|
const line = lines[i];
|
|
27
33
|
const match = line.match(/^\s*;+([^;].*)$/);
|
|
28
34
|
if (!match)
|
|
29
35
|
continue;
|
|
36
|
+
// Skip block comment markers ;| and |;
|
|
30
37
|
const afterSemicolon = match[1];
|
|
38
|
+
if (afterSemicolon.startsWith('|'))
|
|
39
|
+
continue;
|
|
31
40
|
for (const pat of CODE_PATTERNS) {
|
|
32
41
|
if (pat.test(afterSemicolon)) {
|
|
33
42
|
issues.push({
|
|
34
43
|
file,
|
|
35
|
-
line:
|
|
44
|
+
line: lineNum,
|
|
36
45
|
severity: 'warn',
|
|
37
46
|
rule: 'commented_code',
|
|
38
47
|
message: (0, locale_1.t)('commented_code'),
|
|
@@ -2,18 +2,25 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.checkEmptyComments = checkEmptyComments;
|
|
4
4
|
const locale_1 = require("../locale");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
5
6
|
function checkEmptyComments(content, file) {
|
|
6
7
|
const issues = [];
|
|
7
8
|
const lines = content.split('\n');
|
|
9
|
+
const blockRanges = (0, utils_1.getBlockCommentRanges)(content);
|
|
8
10
|
for (let i = 0; i < lines.length; i++) {
|
|
11
|
+
const lineNum = i + 1;
|
|
12
|
+
// Skip lines inside block comments
|
|
13
|
+
if ((0, utils_1.isInsideBlockComment)(lineNum, blockRanges))
|
|
14
|
+
continue;
|
|
9
15
|
const line = lines[i].trim();
|
|
10
16
|
// Check: line is a comment with only whitespace after the ;
|
|
11
|
-
|
|
17
|
+
// But skip block comment markers ;| and |;
|
|
18
|
+
if (line.startsWith(';') && !line.startsWith(';|') && !line.startsWith('|;')) {
|
|
12
19
|
const afterSemicolon = line.slice(1);
|
|
13
20
|
if (afterSemicolon.trim() === '') {
|
|
14
21
|
issues.push({
|
|
15
22
|
file,
|
|
16
|
-
line:
|
|
23
|
+
line: lineNum,
|
|
17
24
|
severity: 'warn',
|
|
18
25
|
rule: 'empty_comment',
|
|
19
26
|
message: (0, locale_1.t)('empty_comment'),
|
|
@@ -11,15 +11,20 @@ function checkErrorHandling(content, file) {
|
|
|
11
11
|
let defunStart = 0;
|
|
12
12
|
let defunText = '';
|
|
13
13
|
let hasErrorHandler = false;
|
|
14
|
+
const blockRanges = (0, utils_1.getBlockCommentRanges)(content);
|
|
14
15
|
let runningDepth = 0;
|
|
15
16
|
for (let i = 0; i < lines.length; i++) {
|
|
17
|
+
const lineNum = i + 1;
|
|
18
|
+
// Skip lines inside block comments
|
|
19
|
+
if ((0, utils_1.isInsideBlockComment)(lineNum, blockRanges))
|
|
20
|
+
continue;
|
|
16
21
|
const stripped = (0, utils_1.stripLine)(lines[i]);
|
|
17
22
|
if (!inDefun) {
|
|
18
23
|
const m = stripped.match(/\(defun\s+(\S+)/);
|
|
19
24
|
if (m) {
|
|
20
25
|
inDefun = true;
|
|
21
26
|
defunName = m[1];
|
|
22
|
-
defunStart =
|
|
27
|
+
defunStart = lineNum;
|
|
23
28
|
defunText = '';
|
|
24
29
|
hasErrorHandler = false;
|
|
25
30
|
runningDepth = 0;
|
|
@@ -7,7 +7,12 @@ function checkExtraParens(content, file) {
|
|
|
7
7
|
const issues = [];
|
|
8
8
|
const lines = content.split('\n');
|
|
9
9
|
let runningDepth = 0;
|
|
10
|
+
const blockRanges = (0, utils_1.getBlockCommentRanges)(content);
|
|
10
11
|
for (let i = 0; i < lines.length; i++) {
|
|
12
|
+
const lineNum = i + 1;
|
|
13
|
+
// Skip lines inside block comments
|
|
14
|
+
if ((0, utils_1.isInsideBlockComment)(lineNum, blockRanges))
|
|
15
|
+
continue;
|
|
11
16
|
const stripped = (0, utils_1.stripLine)(lines[i]);
|
|
12
17
|
const closeOnly = stripped.trim();
|
|
13
18
|
// Empty line resets nothing; just continue tracking depth
|
|
@@ -29,7 +34,7 @@ function checkExtraParens(content, file) {
|
|
|
29
34
|
const extra = lineClose - runningDepth - lineOpen;
|
|
30
35
|
issues.push({
|
|
31
36
|
file,
|
|
32
|
-
line:
|
|
37
|
+
line: lineNum,
|
|
33
38
|
severity: 'warn',
|
|
34
39
|
rule: 'extra_parens',
|
|
35
40
|
message: (0, locale_1.t)('extra_parens', extra),
|
|
@@ -7,7 +7,12 @@ function checkFunctionOrder(content, file) {
|
|
|
7
7
|
const issues = [];
|
|
8
8
|
const lines = content.split('\n');
|
|
9
9
|
const defined = new Set();
|
|
10
|
+
const blockRanges = (0, utils_1.getBlockCommentRanges)(content);
|
|
10
11
|
for (let i = 0; i < lines.length; i++) {
|
|
12
|
+
const lineNum = i + 1;
|
|
13
|
+
// Skip lines inside block comments
|
|
14
|
+
if ((0, utils_1.isInsideBlockComment)(lineNum, blockRanges))
|
|
15
|
+
continue;
|
|
11
16
|
const stripped = (0, utils_1.stripLine)(lines[i]);
|
|
12
17
|
const defunM = stripped.match(/\(defun\s+(\S+)/);
|
|
13
18
|
if (defunM) {
|
|
@@ -24,7 +29,7 @@ function checkFunctionOrder(content, file) {
|
|
|
24
29
|
continue;
|
|
25
30
|
if (!defined.has(name) && name.length > 1 && /^[a-z]/.test(name)) {
|
|
26
31
|
defined.add(name);
|
|
27
|
-
issues.push({ file, line:
|
|
32
|
+
issues.push({ file, line: lineNum, severity: 'warn', rule: 'function_order', message: (0, locale_1.t)('function_order', name) });
|
|
28
33
|
}
|
|
29
34
|
}
|
|
30
35
|
}
|
|
@@ -6,7 +6,12 @@ const utils_1 = require("../utils");
|
|
|
6
6
|
function checkMissingDoc(content, file) {
|
|
7
7
|
const issues = [];
|
|
8
8
|
const lines = content.split('\n');
|
|
9
|
+
const blockRanges = (0, utils_1.getBlockCommentRanges)(content);
|
|
9
10
|
for (let i = 0; i < lines.length; i++) {
|
|
11
|
+
const lineNum = i + 1;
|
|
12
|
+
// Skip lines inside block comments
|
|
13
|
+
if ((0, utils_1.isInsideBlockComment)(lineNum, blockRanges))
|
|
14
|
+
continue;
|
|
10
15
|
const stripped = (0, utils_1.stripLine)(lines[i]);
|
|
11
16
|
const m = stripped.match(/\(defun\s+(\S+)/);
|
|
12
17
|
if (!m)
|
|
@@ -18,10 +23,10 @@ function checkMissingDoc(content, file) {
|
|
|
18
23
|
continue;
|
|
19
24
|
if (name === 'T' || name === 'nil')
|
|
20
25
|
continue;
|
|
21
|
-
if (!hasDocString(lines, i)) {
|
|
26
|
+
if (!hasDocString(lines, i, blockRanges)) {
|
|
22
27
|
issues.push({
|
|
23
28
|
file,
|
|
24
|
-
line:
|
|
29
|
+
line: lineNum,
|
|
25
30
|
severity: 'warn',
|
|
26
31
|
rule: 'missing_doc',
|
|
27
32
|
message: (0, locale_1.t)('missing_doc', name),
|
|
@@ -30,16 +35,30 @@ function checkMissingDoc(content, file) {
|
|
|
30
35
|
}
|
|
31
36
|
return issues;
|
|
32
37
|
}
|
|
33
|
-
function hasDocString(lines, defunLine) {
|
|
38
|
+
function hasDocString(lines, defunLine, blockRanges) {
|
|
34
39
|
let depth = 0;
|
|
35
40
|
let inArgs = false;
|
|
36
41
|
let argsDone = false;
|
|
37
42
|
let inStr = false;
|
|
38
43
|
let foundExpr = false;
|
|
44
|
+
let inBlockComment = false;
|
|
39
45
|
for (let i = defunLine; i < lines.length; i++) {
|
|
40
46
|
const line = lines[i];
|
|
41
47
|
for (let j = 0; j < line.length; j++) {
|
|
42
48
|
const ch = line[j];
|
|
49
|
+
// Handle block comments
|
|
50
|
+
if (!inStr && !inBlockComment && ch === ';' && line[j + 1] === '|') {
|
|
51
|
+
inBlockComment = true;
|
|
52
|
+
j++; // skip |
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (inBlockComment) {
|
|
56
|
+
if (ch === '|' && line[j + 1] === ';') {
|
|
57
|
+
inBlockComment = false;
|
|
58
|
+
j++; // skip ;
|
|
59
|
+
}
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
43
62
|
if (ch === ';' && !inStr)
|
|
44
63
|
break;
|
|
45
64
|
if (inStr) {
|
package/dist/checks/parens.js
CHANGED
|
@@ -2,11 +2,18 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.checkParens = checkParens;
|
|
4
4
|
const locale_1 = require("../locale");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
5
6
|
function checkParens(content, file) {
|
|
6
7
|
let openP = 0, closeP = 0;
|
|
7
8
|
let inStr = false;
|
|
8
|
-
const
|
|
9
|
-
|
|
9
|
+
const blockRanges = (0, utils_1.getBlockCommentRanges)(content);
|
|
10
|
+
const cleaned = (0, utils_1.skipBlockComments)(content, blockRanges);
|
|
11
|
+
const lines = cleaned.split('\n');
|
|
12
|
+
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
13
|
+
const line = lines[lineIdx];
|
|
14
|
+
// Skip lines that are entirely within a block comment
|
|
15
|
+
if ((0, utils_1.isInsideBlockComment)(lineIdx + 1, blockRanges))
|
|
16
|
+
continue;
|
|
10
17
|
let i = 0;
|
|
11
18
|
while (i < line.length) {
|
|
12
19
|
const ch = line[i];
|
|
@@ -2,14 +2,31 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.checkTrailingParen = checkTrailingParen;
|
|
4
4
|
const locale_1 = require("../locale");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
5
6
|
function checkTrailingParen(content, file) {
|
|
6
7
|
// Count total parens to detect excess closing parens
|
|
7
8
|
let openCount = 0;
|
|
8
9
|
let closeCount = 0;
|
|
9
10
|
let inStr = false;
|
|
11
|
+
const blockRanges = (0, utils_1.getBlockCommentRanges)(content);
|
|
10
12
|
// We must parse strings accurately to avoid counting parens inside strings
|
|
13
|
+
// Also skip block comments
|
|
14
|
+
let inBlockComment = false;
|
|
11
15
|
for (let i = 0; i < content.length; i++) {
|
|
12
16
|
const ch = content[i];
|
|
17
|
+
// Handle block comments
|
|
18
|
+
if (!inStr && !inBlockComment && ch === ';' && content[i + 1] === '|') {
|
|
19
|
+
inBlockComment = true;
|
|
20
|
+
i++; // skip |
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (inBlockComment) {
|
|
24
|
+
if (ch === '|' && content[i + 1] === ';') {
|
|
25
|
+
inBlockComment = false;
|
|
26
|
+
i++; // skip ;
|
|
27
|
+
}
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
13
30
|
if (ch === '"')
|
|
14
31
|
inStr = !inStr;
|
|
15
32
|
if (inStr) {
|
package/dist/config.js
CHANGED
package/dist/disable.js
CHANGED
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.parseDisableComments = parseDisableComments;
|
|
4
4
|
exports.isDisabled = isDisabled;
|
|
5
|
+
const utils_1 = require("./utils");
|
|
5
6
|
function parseDisableComments(content, fileLine) {
|
|
6
7
|
const map = new Map();
|
|
7
8
|
const lines = content.split('\n');
|
|
9
|
+
const blockRanges = (0, utils_1.getBlockCommentRanges)(content);
|
|
8
10
|
for (let i = 0; i < lines.length; i++) {
|
|
11
|
+
const lineNum = i + 1;
|
|
9
12
|
const line = lines[i];
|
|
13
|
+
// Disable comments inside block comments are also valid
|
|
14
|
+
// (this is a design choice - allows disabling rules within commented sections)
|
|
10
15
|
const disableFileM = line.match(/;\s*atlisp-lint:\s*disable-file=(.+)/i);
|
|
11
16
|
if (disableFileM) {
|
|
12
17
|
const rules = disableFileM[1]
|
|
@@ -26,7 +31,7 @@ function parseDisableComments(content, fileLine) {
|
|
|
26
31
|
.split(',')
|
|
27
32
|
.map(r => r.trim())
|
|
28
33
|
.filter(Boolean);
|
|
29
|
-
let targetLine =
|
|
34
|
+
let targetLine = lineNum;
|
|
30
35
|
if (fileLine !== undefined)
|
|
31
36
|
targetLine = fileLine;
|
|
32
37
|
for (const rule of rules) {
|
package/dist/formatter.js
CHANGED
|
@@ -79,7 +79,7 @@ function runStandardFormat(lines, lineIdx, depth, _indentSize, rawLine, trimmed)
|
|
|
79
79
|
if (trimmedStripped === '') {
|
|
80
80
|
return {
|
|
81
81
|
formattedLines: [' '.repeat(depth * _indentSize) + trimmed.trimStart()],
|
|
82
|
-
newDepth: depth
|
|
82
|
+
newDepth: depth,
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
85
|
const opens = (trimmedStripped.match(/\(/g) || []).length;
|
|
@@ -97,7 +97,7 @@ function runStandardFormat(lines, lineIdx, depth, _indentSize, rawLine, trimmed)
|
|
|
97
97
|
}
|
|
98
98
|
return {
|
|
99
99
|
formattedLines: [' '.repeat(effectiveDepth * _indentSize) + trimmed.trimStart()],
|
|
100
|
-
newDepth: depth + opens - closes
|
|
100
|
+
newDepth: depth + opens - closes,
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
103
|
function formatCode(content, indentSize = 2) {
|
package/dist/locale.js
CHANGED
|
@@ -42,7 +42,6 @@ const messages = {
|
|
|
42
42
|
recursive_call: "Function '{0}' calls itself — possible infinite recursion",
|
|
43
43
|
variable_shadow: "Variable '{0}' is set with setq inside a let that binds the same name",
|
|
44
44
|
unused_local_fun: "Local function/variable '{0}' is defined in defun with / but never used",
|
|
45
|
-
multiple_setq: 'Consecutive (setq ...) forms — consider merging into a single (setq ...)',
|
|
46
45
|
redundant_quotes: "Redundant double quote — ''x can be simplified to 'x",
|
|
47
46
|
trailing_paren: "Excess closing parenthesis: {0} extra ')' found",
|
|
48
47
|
empty_comment: 'Empty comment line — contains only a semicolon with no text',
|
|
@@ -186,7 +185,6 @@ Options:
|
|
|
186
185
|
recursive_call: "函数 '{0}' 调用了自身——可能存在无限递归",
|
|
187
186
|
variable_shadow: "变量 '{0}' 在 let 绑定了相同名称后被 setq 赋值",
|
|
188
187
|
unused_local_fun: "局部函数/变量 '{0}' 在 defun 的 / 后定义但从未使用",
|
|
189
|
-
multiple_setq: '连续多个 (setq ...) ——建议合并为一个 (setq ...)',
|
|
190
188
|
redundant_quotes: "冗余双引号 —— ''x 可以简化为 'x",
|
|
191
189
|
trailing_paren: "多余闭合括号:发现 {0} 个额外的 ')'",
|
|
192
190
|
empty_comment: '空注释行 —— 分号后没有内容',
|
package/dist/presets.js
CHANGED
|
@@ -27,7 +27,6 @@ exports.PRESETS = {
|
|
|
27
27
|
recursive_call: 'warn',
|
|
28
28
|
variable_shadow: 'warn',
|
|
29
29
|
unused_local_fun: 'warn',
|
|
30
|
-
multiple_setq: 'info',
|
|
31
30
|
redundant_quotes: 'info',
|
|
32
31
|
trailing_paren: 'warn',
|
|
33
32
|
empty_comment: 'info',
|
|
@@ -119,7 +118,6 @@ exports.PRESETS = {
|
|
|
119
118
|
recursive_call: 'error',
|
|
120
119
|
variable_shadow: 'error',
|
|
121
120
|
unused_local_fun: 'error',
|
|
122
|
-
multiple_setq: 'info',
|
|
123
121
|
redundant_quotes: 'error',
|
|
124
122
|
trailing_paren: 'error',
|
|
125
123
|
empty_comment: 'error',
|
package/dist/rules.js
CHANGED
|
@@ -122,7 +122,6 @@ exports.RULES = [
|
|
|
122
122
|
description: '检测模块文件是否注册到 *modules*',
|
|
123
123
|
category: '架构',
|
|
124
124
|
},
|
|
125
|
-
{ name: 'multiple_setq', defaultSeverity: 'info', description: '检测多个连续 setq 可以合并', category: '风格' },
|
|
126
125
|
{
|
|
127
126
|
name: 'namespace_header',
|
|
128
127
|
defaultSeverity: 'off',
|
package/dist/runner.js
CHANGED
package/dist/utils.d.ts
CHANGED
|
@@ -11,6 +11,29 @@ export declare function codeLines(content: string): Generator<{
|
|
|
11
11
|
export declare function findMatchingParen(s: string, start: number): number;
|
|
12
12
|
/** Extract the next s-expression form starting at position */
|
|
13
13
|
export declare function extractForm(s: string, start: number): [string, number];
|
|
14
|
+
/**
|
|
15
|
+
* Get ranges of block comments (;| ... |;) in content.
|
|
16
|
+
* Returns array of {startLine, endLine} (1-indexed).
|
|
17
|
+
* startLine is the line containing ;|, endLine contains |;
|
|
18
|
+
*/
|
|
19
|
+
export declare function getBlockCommentRanges(content: string): Array<{
|
|
20
|
+
startLine: number;
|
|
21
|
+
endLine: number;
|
|
22
|
+
}>;
|
|
23
|
+
/** Check if a line (1-indexed) is inside a block comment */
|
|
24
|
+
export declare function isInsideBlockComment(line: number, ranges: Array<{
|
|
25
|
+
startLine: number;
|
|
26
|
+
endLine: number;
|
|
27
|
+
}>): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Given content and block comment ranges, return content with block comments
|
|
30
|
+
* replaced by spaces (preserving line structure). Useful for checks that
|
|
31
|
+
* need to ignore block-commented code.
|
|
32
|
+
*/
|
|
33
|
+
export declare function skipBlockComments(content: string, ranges: Array<{
|
|
34
|
+
startLine: number;
|
|
35
|
+
endLine: number;
|
|
36
|
+
}>): string;
|
|
14
37
|
/** Extract string literals from a line */
|
|
15
38
|
export declare function extractStrings(line: string): string[];
|
|
16
39
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.js
CHANGED
|
@@ -5,6 +5,9 @@ exports.stripLineRaw = stripLineRaw;
|
|
|
5
5
|
exports.codeLines = codeLines;
|
|
6
6
|
exports.findMatchingParen = findMatchingParen;
|
|
7
7
|
exports.extractForm = extractForm;
|
|
8
|
+
exports.getBlockCommentRanges = getBlockCommentRanges;
|
|
9
|
+
exports.isInsideBlockComment = isInsideBlockComment;
|
|
10
|
+
exports.skipBlockComments = skipBlockComments;
|
|
8
11
|
exports.extractStrings = extractStrings;
|
|
9
12
|
function stripLineInternal(line, keepQuotes) {
|
|
10
13
|
const out = [];
|
|
@@ -101,6 +104,98 @@ function extractForm(s, start) {
|
|
|
101
104
|
const end = findMatchingParen(s, i);
|
|
102
105
|
return [s.slice(i, end + 1), end + 1];
|
|
103
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Get ranges of block comments (;| ... |;) in content.
|
|
109
|
+
* Returns array of {startLine, endLine} (1-indexed).
|
|
110
|
+
* startLine is the line containing ;|, endLine contains |;
|
|
111
|
+
*/
|
|
112
|
+
function getBlockCommentRanges(content) {
|
|
113
|
+
const ranges = [];
|
|
114
|
+
const lines = content.split('\n');
|
|
115
|
+
let inBlock = false;
|
|
116
|
+
let startLine = 0;
|
|
117
|
+
for (let i = 0; i < lines.length; i++) {
|
|
118
|
+
const line = lines[i];
|
|
119
|
+
if (!inBlock) {
|
|
120
|
+
// Look for ;| outside of strings and line comments
|
|
121
|
+
let inStr = false;
|
|
122
|
+
for (let j = 0; j < line.length; j++) {
|
|
123
|
+
const ch = line[j];
|
|
124
|
+
if (ch === ';' && !inStr) {
|
|
125
|
+
if (line[j + 1] === '|') {
|
|
126
|
+
inBlock = true;
|
|
127
|
+
startLine = i + 1; // 1-indexed
|
|
128
|
+
}
|
|
129
|
+
break; // Rest of line is comment or block comment start
|
|
130
|
+
}
|
|
131
|
+
if (ch === '"')
|
|
132
|
+
inStr = !inStr;
|
|
133
|
+
if (ch === '\\' && inStr)
|
|
134
|
+
j++; // skip escape
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Look for |; to end block comment
|
|
139
|
+
for (let j = 0; j < line.length; j++) {
|
|
140
|
+
if (line[j] === '|' && line[j + 1] === ';') {
|
|
141
|
+
ranges.push({ startLine, endLine: i + 1 }); // 1-indexed
|
|
142
|
+
inBlock = false;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Unterminated block comment
|
|
149
|
+
if (inBlock) {
|
|
150
|
+
ranges.push({ startLine, endLine: lines.length });
|
|
151
|
+
}
|
|
152
|
+
return ranges;
|
|
153
|
+
}
|
|
154
|
+
/** Check if a line (1-indexed) is inside a block comment */
|
|
155
|
+
function isInsideBlockComment(line, ranges) {
|
|
156
|
+
for (const r of ranges) {
|
|
157
|
+
if (line >= r.startLine && line <= r.endLine)
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Given content and block comment ranges, return content with block comments
|
|
164
|
+
* replaced by spaces (preserving line structure). Useful for checks that
|
|
165
|
+
* need to ignore block-commented code.
|
|
166
|
+
*/
|
|
167
|
+
function skipBlockComments(content, ranges) {
|
|
168
|
+
if (ranges.length === 0)
|
|
169
|
+
return content;
|
|
170
|
+
const lines = content.split('\n');
|
|
171
|
+
for (const r of ranges) {
|
|
172
|
+
for (let i = r.startLine - 1; i < Math.min(r.endLine, lines.length); i++) {
|
|
173
|
+
if (i === r.startLine - 1) {
|
|
174
|
+
// Keep content before ;|, replace rest with spaces
|
|
175
|
+
const idx = lines[i].indexOf(';|');
|
|
176
|
+
if (idx >= 0) {
|
|
177
|
+
lines[i] = lines[i].slice(0, idx) + ' '.repeat(lines[i].length - idx);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else if (i === r.endLine - 1 && r.endLine !== r.startLine) {
|
|
181
|
+
// Keep content after |;
|
|
182
|
+
const idx = lines[i].indexOf('|;');
|
|
183
|
+
if (idx >= 0) {
|
|
184
|
+
lines[i] = ' '.repeat(idx + 2) + lines[i].slice(idx + 2);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
// |; not found yet (unterminated), blank the whole line
|
|
188
|
+
lines[i] = ' '.repeat(lines[i].length);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
// Blank entire line
|
|
193
|
+
lines[i] = ' '.repeat(lines[i].length);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return lines.join('\n');
|
|
198
|
+
}
|
|
104
199
|
/** Extract string literals from a line */
|
|
105
200
|
function extractStrings(line) {
|
|
106
201
|
const strings = [];
|
package/dist/validate.js
CHANGED
|
@@ -285,31 +285,6 @@ function registerVisitorRules(visitor, state, checks, addIssue, config, file) {
|
|
|
285
285
|
});
|
|
286
286
|
}
|
|
287
287
|
}
|
|
288
|
-
if (checks['multiple_setq'] !== 'off') {
|
|
289
|
-
visitor.on('*', node => {
|
|
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;
|
|
295
|
-
let prevSetq = false;
|
|
296
|
-
for (const child of node.children) {
|
|
297
|
-
if ((0, parser_1.astIsList)(child, 'setq')) {
|
|
298
|
-
if (prevSetq) {
|
|
299
|
-
addIssue('multiple_setq', child.pos.line, (0, locale_1.t)('multiple_setq'));
|
|
300
|
-
prevSetq = false;
|
|
301
|
-
}
|
|
302
|
-
else {
|
|
303
|
-
prevSetq = true;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
else {
|
|
307
|
-
prevSetq = false;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
288
|
if (checks['recursive_call'] !== 'off') {
|
|
314
289
|
visitor.on('defun', node => {
|
|
315
290
|
if (!(0, parser_1.astIsList)(node))
|