@atlisp/lint 0.1.5 → 0.1.6

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 (114) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +182 -58
  3. package/atlisp-lint.default.json +31 -1
  4. package/dist/atlisp-lint.default.json +90 -0
  5. package/dist/cache.d.ts +2 -2
  6. package/dist/cache.js +6 -6
  7. package/dist/checks/append-single.d.ts +3 -0
  8. package/dist/checks/append-single.js +17 -0
  9. package/dist/checks/arg-count.d.ts +3 -0
  10. package/dist/checks/arg-count.js +123 -0
  11. package/dist/checks/assoc-without-cdr.d.ts +5 -0
  12. package/dist/checks/assoc-without-cdr.js +32 -0
  13. package/dist/checks/comment-style.d.ts +3 -0
  14. package/dist/checks/comment-style.js +24 -0
  15. package/dist/checks/cond-duplicate.d.ts +5 -0
  16. package/dist/checks/cond-duplicate.js +52 -0
  17. package/dist/checks/cond-simplify.d.ts +3 -0
  18. package/dist/checks/cond-simplify.js +45 -0
  19. package/dist/checks/constant-condition.js +4 -4
  20. package/dist/checks/dangerous-calls.js +2 -2
  21. package/dist/checks/dangling-defun.d.ts +3 -0
  22. package/dist/checks/dangling-defun.js +10 -28
  23. package/dist/checks/double-not.js +1 -1
  24. package/dist/checks/duplicate-defun.d.ts +6 -0
  25. package/dist/checks/duplicate-defun.js +50 -0
  26. package/dist/checks/dynamic-doc.d.ts +3 -0
  27. package/dist/checks/dynamic-doc.js +21 -0
  28. package/dist/checks/empty-branch.js +1 -1
  29. package/dist/checks/empty-catch.d.ts +3 -0
  30. package/dist/checks/empty-catch.js +34 -0
  31. package/dist/checks/eq-usage.d.ts +3 -0
  32. package/dist/checks/eq-usage.js +25 -0
  33. package/dist/checks/error-handling.d.ts +3 -0
  34. package/dist/checks/error-handling.js +56 -0
  35. package/dist/checks/extra-parens.d.ts +3 -0
  36. package/dist/checks/extra-parens.js +45 -0
  37. package/dist/checks/function-complexity.js +1 -1
  38. package/dist/checks/function-order.d.ts +3 -0
  39. package/dist/checks/function-order.js +33 -0
  40. package/dist/checks/global-naming.d.ts +3 -0
  41. package/dist/checks/global-naming.js +62 -0
  42. package/dist/checks/identical-branches.d.ts +5 -0
  43. package/dist/checks/identical-branches.js +54 -0
  44. package/dist/checks/index.d.ts +3 -0
  45. package/dist/checks/index.js +117 -0
  46. package/dist/checks/lambda-syntax.d.ts +3 -0
  47. package/dist/checks/lambda-syntax.js +25 -0
  48. package/dist/checks/long-function-call.d.ts +3 -0
  49. package/dist/checks/long-function-call.js +54 -0
  50. package/dist/checks/loop-optimization.d.ts +3 -0
  51. package/dist/checks/loop-optimization.js +17 -0
  52. package/dist/checks/magic-number.d.ts +3 -0
  53. package/dist/checks/magic-number.js +21 -0
  54. package/dist/checks/misplaced-else.js +1 -1
  55. package/dist/checks/missing-export.js +2 -2
  56. package/dist/checks/mixed-indent.d.ts +3 -0
  57. package/dist/checks/mixed-indent.js +19 -0
  58. package/dist/checks/module-reg.js +1 -1
  59. package/dist/checks/multiple-setq.js +1 -1
  60. package/dist/checks/no-return.d.ts +3 -0
  61. package/dist/checks/no-return.js +51 -0
  62. package/dist/checks/nth-usage.d.ts +3 -0
  63. package/dist/checks/nth-usage.js +17 -0
  64. package/dist/checks/quote-style.d.ts +3 -0
  65. package/dist/checks/quote-style.js +25 -0
  66. package/dist/checks/quote-vs-function.js +1 -1
  67. package/dist/checks/recursive-call.js +1 -1
  68. package/dist/checks/redundant-if.d.ts +3 -0
  69. package/dist/checks/redundant-if.js +17 -0
  70. package/dist/checks/redundant-let.js +1 -1
  71. package/dist/checks/redundant-nil-else.js +1 -1
  72. package/dist/checks/redundant-progn.js +1 -1
  73. package/dist/checks/redundant-quotes.js +1 -1
  74. package/dist/checks/redundant-setq.js +1 -1
  75. package/dist/checks/self-compare.js +1 -1
  76. package/dist/checks/setq-multiple.d.ts +3 -0
  77. package/dist/checks/setq-multiple.js +17 -0
  78. package/dist/checks/setq-single-arg.d.ts +5 -0
  79. package/dist/checks/setq-single-arg.js +30 -0
  80. package/dist/checks/shadow-builtin.d.ts +3 -0
  81. package/dist/checks/shadow-builtin.js +77 -0
  82. package/dist/checks/single-arg-and-or.js +1 -1
  83. package/dist/checks/strcat-usage.d.ts +3 -0
  84. package/dist/checks/strcat-usage.js +25 -0
  85. package/dist/checks/type-check.d.ts +3 -0
  86. package/dist/checks/type-check.js +26 -0
  87. package/dist/checks/unused-let.js +1 -1
  88. package/dist/checks/unused-package-dep.js +3 -3
  89. package/dist/checks/variable-shadow.js +1 -1
  90. package/dist/checks/while-constant.d.ts +5 -0
  91. package/dist/checks/while-constant.js +40 -0
  92. package/dist/config.d.ts +1 -0
  93. package/dist/config.js +70 -2
  94. package/dist/disable.js +1 -1
  95. package/dist/formatters.d.ts +1 -0
  96. package/dist/formatters.js +18 -2
  97. package/dist/index.js +53 -13
  98. package/dist/lib/lint-sbcl.lisp +161 -0
  99. package/dist/locale.js +24 -0
  100. package/dist/presets.d.ts +4 -0
  101. package/dist/presets.js +158 -0
  102. package/dist/project.js +37 -6
  103. package/dist/rules.d.ts +9 -0
  104. package/dist/rules.js +238 -0
  105. package/dist/runner.d.ts +2 -0
  106. package/dist/runner.js +198 -11
  107. package/dist/sbcl.js +1 -1
  108. package/dist/stub-packages.json +41 -0
  109. package/dist/types.d.ts +6 -0
  110. package/dist/validate.d.ts +8 -0
  111. package/dist/validate.js +125 -0
  112. package/dist/watch.d.ts +9 -0
  113. package/dist/watch.js +113 -0
  114. package/package.json +1 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 VitalGG
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @atlisp/lint
2
2
 
3
- AutoLISP 静态分析工具 — 括号匹配、安全检查、编码检测、命名规范、函数复杂度、SBCL 语法校验。
3
+ AutoLISP 静态分析工具 — 括号匹配、安全检查、编码检测、命名规范、函数复杂度、跨文件分析、SBCL 语法校验。
4
4
 
5
5
  ## 安装
6
6
 
@@ -22,62 +22,153 @@ npx @atlisp/lint
22
22
  # 输出 JSON 格式
23
23
  npx @atlisp/lint --format json
24
24
 
25
- # 自动修复可修复问题(尾部空格、BOM)
25
+ # 自动修复可修复问题
26
26
  npx @atlisp/lint --fix
27
+
28
+ # 监听文件变化自动重检
29
+ npx @atlisp/lint --watch
27
30
  ```
28
31
 
29
32
  ## CLI 选项
30
33
 
31
34
  ```
32
- --src <dir> 指定源码目录(可多次使用)
33
- --test <dir> 指定测试目录(可多次使用)
34
- --config <path> 指定配置文件路径
35
- --staged 仅检查 git 暂存区中的 .lsp 文件
36
- --format <format> 输出格式:default(默认)| json
37
- --init 在当前目录生成 atlisp-lint.json
38
- --install-hook 安装 git pre-commit hook
39
- --fix 自动修复尾部空格、UTF-8 BOM 等问题
40
- --cache 启用文件哈希缓存,跳过未变更的文件
41
- --clear-cache 清除缓存目录
42
- --parallel 开启多线程并行检查(使用 Worker Threads)
43
- --hook-args <args> 自定义 pre-commit hook 参数(配合 --install-hook
35
+ --src <dir> 指定源码目录(可多次使用)
36
+ --test <dir> 指定测试目录(可多次使用)
37
+ --config <path> 指定配置文件路径
38
+ --staged 仅检查 git 暂存区中的 .lsp 文件
39
+ --format <format> 输出格式:default(默认)| json
40
+ --init 在当前目录生成 atlisp-lint.json
41
+ --install-hook 安装 git pre-commit hook
42
+ --fix 自动修复尾部空格、UTF-8 BOM、多余括号等问题
43
+ --cache 启用文件哈希缓存,跳过未变更的文件
44
+ --clear-cache 清除缓存目录
45
+ --parallel 开启多线程并行检查(使用 Worker Threads)
46
+ --project 启用跨文件分析(Phase 1b
47
+ --watch 监听模式,文件变动自动重新检查
48
+ --hook-args <args> 自定义 pre-commit hook 参数(配合 --install-hook)
44
49
  ```
45
50
 
46
- ## 检查规则
47
-
48
- ### 阶段 1:静态检查
51
+ ## 架构:三相检测
52
+
53
+ ### Phase 1a — 单文件静态检查(74 条规则)
54
+
55
+ 所有 74 条规则在单文件范围内运行,支持 `--parallel` 多线程并行。
56
+
57
+ | 规则 | 默认 | 类别 | 说明 |
58
+ |------|------|------|------|
59
+ | `parens` | error | 语法 | 括号不匹配 |
60
+ | `trailing_paren` | warn | 语法 | 多余闭合括号 |
61
+ | `extra_parens` | warn | 语法 | 可疑的连续右括号 |
62
+ | `encoding` | error | 编码 | UTF-8 BOM 检查 / 非 UTF-8 编码 |
63
+ | `quit_exit` | error | 安全 | quit/exit 调用(会终止 CAD 进程) |
64
+ | `command_shell` | error | 安全 | command shell(命令注入风险) |
65
+ | `startapp` | warn | 安全 | startapp(启动外部程序) |
66
+ | `vl_registry_write` | warn | 安全 | vl-registry-write(修改注册表) |
67
+ | `token_in_url` | warn | 安全 | URL 中含 token=(凭据泄露) |
68
+ | `vlax_without_loading` | warn | 安全 | vlax-* 未调用 vl-load-com |
69
+ | `cl_syntax` | warn | 语法 | Common Lisp 特有语法(如 &key) |
70
+ | `line_length` | warn | 风格 | 行长度超过限制 |
71
+ | `function_complexity` | warn | 复杂度 | 函数行数或嵌套深度超限 |
72
+ | `trailing_whitespace` | warn | 风格 | 行尾多余空格 |
73
+ | `empty_comment` | warn | 风格 | 空注释行(只有 ; 没有内容) |
74
+ | `parameter_naming` | warn | 最佳实践 | 参数首字母大写 |
75
+ | `unused_variable` | warn | 最佳实践 | 变量赋值但未使用 |
76
+ | `unused_parameter` | warn | 最佳实践 | 参数从未使用 |
77
+ | `unused_let_binding` | warn | 最佳实践 | let 绑定从未使用 |
78
+ | `unused_local_fun` | warn | 最佳实践 | defun / 局部函数未使用 |
79
+ | `missing_doc` | warn | 风格 | 函数缺少注释说明 |
80
+ | `error_handling` | warn | 最佳实践 | 使用 command 但缺少 \*error\* 处理 |
81
+ | `global_naming` | warn | 风格 | 全局变量未用 \*...\* 命名 |
82
+ | `constant_condition` | warn | 风格 | if/while/cond 中常量条件 |
83
+ | `redundant_progn` | warn | 风格 | 分支中多余 progn |
84
+ | `empty_branch` | warn | 风格 | if/cond 中空分支 |
85
+ | `redundant_cond` | warn | 风格 | 单子句 cond 或末尾 T 子句 |
86
+ | `redundant_if` | warn | 风格 | if/when 中冗余 progn |
87
+ | `redundant_let` | warn | 风格 | 无绑定 let 建议改用 progn |
88
+ | `redundant_quotes` | warn | 风格 | 冗余双引号 ''x |
89
+ | `redundant_setq` | warn | 风格 | setq 赋值自身 |
90
+ | `redundant_nil_else` | warn | 风格 | if 中冗余 nil 分支 |
91
+ | `single_arg_and_or` | warn | 风格 | 单参数 and/or 可简化 |
92
+ | `double_not` | warn | 风格 | 双重 (not (not ...)) |
93
+ | `multiple_setq` | warn | 风格 | 连续 setq 可合并 |
94
+ | `setq_multiple` | warn | 风格 | 连续 setq 可合并 |
95
+ | `setq_single_arg` | warn | 正确性 | setq 缺少值 |
96
+ | `assoc_without_cdr` | warn | 正确性 | assoc 结果未用 cdr 提取 |
97
+ | `identical_branches` | warn | 正确性 | if 的 then/else 分支完全相同 |
98
+ | `while_constant` | warn | 正确性 | while 使用常量条件 |
99
+ | `cond_duplicate` | warn | 正确性 | cond 中存在重复条件 |
100
+ | `recursive_call` | warn | 正确性 | 函数自调用(可能无限递归) |
101
+ | `self_compare` | warn | 正确性 | 自比较表达式 |
102
+ | `variable_shadow` | warn | 最佳实践 | setq 覆盖 let 绑定的同名变量 |
103
+ | `misplaced_else` | warn | 风格 | (if (not ...) ...) 交换分支 |
104
+ | `quote_vs_function` | warn | 风格 | mapcar/apply 中用引用 lambda |
105
+ | `commented_code` | warn | 风格 | 被注释掉的代码 |
106
+ | `quote_style` | warn | 风格 | (quote ...) 建议缩写 |
107
+ | `eq_usage` | warn | 最佳实践 | eq 与数字比较 |
108
+ | `lambda_syntax` | warn | 风格 | (function (lambda ...)) 建议缩写 |
109
+ | `comment_style` | warn | 风格 | ; 后缺少空格 |
110
+ | `empty_catch` | warn | 最佳实践 | vl-catch-all-apply 未检查结果 |
111
+ | `nth_usage` | warn | 风格 | (nth 0 ...) 建议 car |
112
+ | `append_single` | warn | 风格 | (append (list ...)) 建议 cons |
113
+ | `mixed_indent` | warn | 风格 | 缩进混用 tab 和空格 |
114
+ | `long_function_call` | warn | 风格 | 单行参数过多 |
115
+ | `no_return` | warn | 最佳实践 | 函数无返回值 |
116
+ | `shadow_builtin` | warn | 最佳实践 | defun 覆盖内置函数 |
117
+ | `dynamic_doc` | warn | 最佳实践 | C: 命令缺少 (princ) |
118
+ | `open_without_close` | warn | 最佳实践 | open/close 数量不匹配 |
119
+ | `strcat_usage` | warn | 最佳实践 | + 拼接而非 strcat |
120
+ | `cond_simplify` | warn | 风格 | 单分支 cond 可简化为 if |
121
+ | `arg_count` | warn | 最佳实践 | 参数数量不匹配 |
122
+ | `bare_function_names` | off | 风格 | defun 缺少命名空间前缀 |
123
+ | `function_order` | off | 最佳实践 | 定义在使用之后 |
124
+ | `magic_number` | off | 风格 | 硬编码魔法数字 |
125
+ | `module_registration` | off | 架构 | 模块文件未注册到 \*modules\* |
126
+ | `namespace_header` | off | 架构 | 缺少 (in-package ...) 头 |
127
+ | `loop_optimization` | off | 最佳实践 | 优化 while/assoc 循环 |
128
+ | `type_check` | off | 最佳实践 | getvar 结果未类型检查 |
129
+
130
+ ### Phase 1b — 跨文件分析(--project)
131
+
132
+ 通过 `--project` 启用,构建项目符号表后进行以下检查:
49
133
 
50
134
  | 规则 | 默认 | 说明 |
51
135
  |------|------|------|
52
- | `parens` | error | 括号不匹配 |
53
- | `encoding` | error | UTF-8 编码 |
54
- | `cl_syntax` | warn | Common Lisp 特有语法(如 `&key`) |
55
- | `quit_exit` | error | 调用 `quit` / `exit`(会终止 CAD) |
56
- | `command_shell` | error | 调用 `command` 执行 shell |
57
- | `startapp` | warn | 调用 `startapp` 启动外部程序 |
58
- | `vl_registry_write` | warn | 写注册表 |
59
- | `vlax_without_loading` | warn | 使用 `vlax-*` 前未调用 `vl-load-com` |
60
- | `token_in_url` | warn | URL 中包含 `token=`(凭据泄露) |
61
- | `open_without_close` | warn | open/close 数量不匹配(资源泄露) |
62
- | `bare_function_names` | warn | defun 缺少命名空间前缀 |
63
- | `line_length` | warn | 行长度超过限制 |
64
- | `function_complexity` | warn | 函数行数或嵌套深度超限 |
65
- | `parameter_naming` | warn | 参数首字母大写 |
66
- | `unused_variable` | warn | 变量被赋值但未使用 |
67
- | `missing_doc` | warn | 函数缺少注释说明 |
68
- | `trailing_whitespace` | warn | 行尾多余空格 |
69
- | `module_registration` | off | 模块文件未注册到 `*modules*` |
70
- | `namespace_header` | off | 缺少 `(in-package ...)` 头 |
71
-
72
- ### 阶段 2:SBCL 语法校验
73
-
74
- 调用 SBCL 对每个 `.lsp` 文件做 Common Lisp reader 级别的深度解析:
136
+ | `dangling_defun` | warn | 函数定义但从未被任何文件调用 |
137
+ | `missing_export` | warn | 函数未注册到 @::\*modules\* |
138
+ | `unused_package_dep` | warn | 导入的包从未被引用 |
139
+
140
+ 同时检测 `duplicate_defun`(函数名在多个文件中重复定义)。
141
+
142
+ ### Phase 2 SBCL 语法校验(可选)
143
+
144
+ 调用 SBCL 对每个 `.lsp` 文件做 Common Lisp reader 级别深度解析:
75
145
 
76
146
  - 语法错误 — reader 无法解析的内容
77
147
  - 符号未找到 — 未注册到 stub packages 中的符号
78
148
  - CL-ism — `#(`、`#\`、`#.` 等 AutoLISP 无效语法
79
149
  - defmacro — 默认不允许,除非文件在允许列表中
80
150
 
151
+ ## --fix 自动修复
152
+
153
+ ### 预扫描修复(fixFile)
154
+
155
+ | 规则 | 修复方式 |
156
+ |------|----------|
157
+ | `trailing_whitespace` | 删除行尾空格 |
158
+ | `encoding (BOM)` | 删除 UTF-8 BOM |
159
+ | `trailing_paren` | 删除多余闭合括号 |
160
+ | `empty_comment` | 删除空注释行 |
161
+
162
+ ### 规则级修复(applyFixes)
163
+
164
+ | 规则 | 修复方式 |
165
+ |------|----------|
166
+ | `redundant_quotes` | ''x → 'x |
167
+ | `double_not` | (not (not ...)) → (not ...) |
168
+ | `redundant_nil_else` | (if cond x nil) → (if cond x) |
169
+ | `single_arg_and_or` | (and x) → x |
170
+ | `redundant_setq` | 删除 (setq x x) 行 |
171
+
81
172
  ## 行内忽略
82
173
 
83
174
  在代码行尾添加注释可以忽略特定规则的检查:
@@ -100,24 +191,9 @@ npx @atlisp/lint --fix
100
191
  "checks": {
101
192
  "parens": "error",
102
193
  "encoding": "error",
103
- "cl_syntax": "warn",
104
- "quit_exit": "error",
105
- "command_shell": "error",
106
- "startapp": "warn",
107
- "vl_registry_write": "warn",
108
- "vlax_without_loading": "warn",
109
- "token_in_url": "warn",
110
- "open_without_close": "warn",
111
- "bare_function_names": "warn",
112
- "line_length": "warn",
113
- "function_complexity": "warn",
114
- "parameter_naming": "warn",
115
- "unused_variable": "warn",
116
- "missing_doc": "warn",
117
- "trailing_whitespace": "warn",
118
- "module_registration": "off",
119
- "namespace_header": "off"
194
+ "trailing_whitespace": "warn"
120
195
  },
196
+ "preset": "recommended",
121
197
  "line_length": {
122
198
  "max": 100,
123
199
  "tab_width": 2
@@ -147,9 +223,33 @@ npx @atlisp/lint --fix
147
223
  }
148
224
  ```
149
225
 
226
+ ### 配置预设
227
+
228
+ 通过 `preset` 字段选择预设,预设应用后再叠加用户配置(用户设置优先级最高):
229
+
230
+ | 预设 | 说明 |
231
+ |------|------|
232
+ | `recommended` | 推荐(默认值) |
233
+ | `strict` | 严格模式(大部分规则升为 error) |
234
+ | `relaxed` | 宽松模式(仅保留 4 条 error 规则) |
235
+
236
+ 预设会覆盖 `checks` 中对应规则的默认值,用户可以在同一个文件中继续覆盖预设值。
237
+
238
+ ### 目录级覆盖
239
+
240
+ 在某个目录下放置 `.atlisp-lint.json`,该目录及其子目录中的文件会自动合并该覆盖配置。
241
+
242
+ ```json
243
+ {
244
+ "checks": {
245
+ "line_length": "off"
246
+ }
247
+ }
248
+ ```
249
+
150
250
  ## 多语言
151
251
 
152
- 支持中文和英文,通过配置文件的 `locale` 字段切换:
252
+ 支持中文和英文,通过配置文件 `locale` 字段切换:
153
253
 
154
254
  ```json
155
255
  { "locale": "zh" } // 中文(默认)
@@ -169,6 +269,27 @@ npx @atlisp/lint --install-hook --hook-args "--config ci-config.json"
169
269
  npx @atlisp/lint --staged
170
270
  ```
171
271
 
272
+ ## 缓存
273
+
274
+ ```bash
275
+ # 启用缓存
276
+ npx @atlisp/lint --cache
277
+
278
+ # 清除缓存
279
+ npx @atlisp/lint --clear-cache
280
+ ```
281
+
282
+ 基于文件内容 MD5 哈希缓存,跳过内容未变更的文件。
283
+
284
+ ## 忽略文件
285
+
286
+ 在项目根目录创建 `.atlisp-lint-ignore`,使用 gitignore 风格模式排除文件:
287
+
288
+ ```
289
+ **/test/**
290
+ **/vendor/**
291
+ ```
292
+
172
293
  ## 开发
173
294
 
174
295
  ```bash
@@ -180,6 +301,9 @@ npm run build
180
301
 
181
302
  # 测试
182
303
  npm test
304
+
305
+ # 运行所有检查
306
+ npm run format:check && npm run lint && npm run typecheck && npm test
183
307
  ```
184
308
 
185
309
  ## 许可
@@ -46,9 +46,39 @@
46
46
  "quote_vs_function": "warn",
47
47
  "commented_code": "warn",
48
48
  "double_not": "warn",
49
+ "setq_single_arg": "warn",
50
+ "assoc_without_cdr": "warn",
51
+ "identical_branches": "warn",
52
+ "while_constant": "warn",
53
+ "cond_duplicate": "warn",
54
+ "duplicate_defun": "warn",
49
55
  "dangling_defun": "warn",
50
56
  "missing_export": "warn",
51
- "unused_package_dep": "warn"
57
+ "unused_package_dep": "warn",
58
+ "error_handling": "warn",
59
+ "global_naming": "warn",
60
+ "extra_parens": "warn",
61
+ "arg_count": "warn",
62
+ "strcat_usage": "warn",
63
+ "cond_simplify": "warn",
64
+ "quote_style": "warn",
65
+ "eq_usage": "warn",
66
+ "lambda_syntax": "warn",
67
+ "comment_style": "warn",
68
+ "empty_catch": "warn",
69
+ "nth_usage": "warn",
70
+ "append_single": "warn",
71
+ "setq_multiple": "warn",
72
+ "function_order": "off",
73
+ "magic_number": "off",
74
+ "mixed_indent": "warn",
75
+ "long_function_call": "warn",
76
+ "no_return": "warn",
77
+ "shadow_builtin": "warn",
78
+ "dynamic_doc": "warn",
79
+ "loop_optimization": "off",
80
+ "type_check": "off",
81
+ "redundant_if": "warn"
52
82
  },
53
83
  "line_length": {
54
84
  "max": 100,
@@ -0,0 +1,90 @@
1
+ {
2
+ "locale": "zh",
3
+ "source": {
4
+ "globs": ["**/*.lsp"],
5
+ "exclude": ["**/node_modules/**", "**/vendor/**", "**/.git/**"]
6
+ },
7
+ "checks": {
8
+ "parens": "error",
9
+ "encoding": "warn",
10
+ "cl_syntax": "warn",
11
+ "quit_exit": "error",
12
+ "command_shell": "error",
13
+ "startapp": "warn",
14
+ "vl_registry_write": "warn",
15
+ "vlax_without_loading": "off",
16
+ "token_in_url": "warn",
17
+ "open_without_close": "warn",
18
+ "bare_function_names": "off",
19
+ "line_length": "warn",
20
+ "function_complexity": "warn",
21
+ "parameter_naming": "warn",
22
+ "unused_variable": "warn",
23
+ "missing_doc": "warn",
24
+ "error_handling": "warn",
25
+ "global_naming": "warn",
26
+ "extra_parens": "warn",
27
+ "arg_count": "warn",
28
+ "strcat_usage": "warn",
29
+ "cond_simplify": "warn",
30
+ "redundant_progn": "warn",
31
+ "quote_style": "warn",
32
+ "eq_usage": "warn",
33
+ "lambda_syntax": "warn",
34
+ "comment_style": "warn",
35
+ "empty_catch": "warn",
36
+ "nth_usage": "warn",
37
+ "append_single": "warn",
38
+ "setq_multiple": "warn",
39
+ "function_order": "off",
40
+ "magic_number": "off",
41
+ "mixed_indent": "warn",
42
+ "long_function_call": "warn",
43
+ "no_return": "warn",
44
+ "shadow_builtin": "warn",
45
+ "dynamic_doc": "warn",
46
+ "loop_optimization": "off",
47
+ "type_check": "off",
48
+ "redundant_if": "warn",
49
+ "trailing_whitespace": "warn",
50
+ "module_registration": "off",
51
+ "namespace_header": "off"
52
+ },
53
+ "line_length": {
54
+ "max": 100,
55
+ "tab_width": 2
56
+ },
57
+ "function_complexity": {
58
+ "max_lines": 60,
59
+ "max_nesting": 6
60
+ },
61
+ "cl_syntax": {
62
+ "keywords": ["&key"]
63
+ },
64
+ "dangerous_calls": {
65
+ "quit": "error",
66
+ "exit": "error",
67
+ "command_shell": "error",
68
+ "startapp": "warn",
69
+ "vl_registry_write": "warn"
70
+ },
71
+ "module_registration": {
72
+ "severity": "off",
73
+ "patterns": [],
74
+ "dirs": ["modules"]
75
+ },
76
+ "namespace_header": {
77
+ "severity": "off",
78
+ "package": "",
79
+ "deps": [],
80
+ "search_lines": 15
81
+ },
82
+ "bare_function_names": {
83
+ "allowlist": ["T", "nil"],
84
+ "namespace_pattern": "^[a-z]+:"
85
+ },
86
+ "sbcl": {
87
+ "walk_exclude": [".vscode", "vendor", ".git"],
88
+ "defmacro_allow_files": ["compat-cl"]
89
+ }
90
+ }
package/dist/cache.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare function isCached(filepath: string, rootDir: string): boolean;
2
- export declare function markCached(filepath: string, rootDir: string): void;
1
+ export declare function isCached(filepath: string, rootDir: string, content?: string): boolean;
2
+ export declare function markCached(filepath: string, rootDir: string, content?: string): void;
3
3
  export declare function clearCache(rootDir: string): void;
4
4
  //# sourceMappingURL=cache.d.ts.map
package/dist/cache.js CHANGED
@@ -46,22 +46,22 @@ function getCacheDir(rootDir) {
46
46
  function fileHash(content) {
47
47
  return crypto.createHash('md5').update(content).digest('hex');
48
48
  }
49
- function isCached(filepath, rootDir) {
49
+ function isCached(filepath, rootDir, content) {
50
50
  const cacheDir = getCacheDir(rootDir);
51
51
  if (!fs.existsSync(cacheDir))
52
52
  return false;
53
- const content = fs.readFileSync(filepath, 'utf-8');
54
- const hash = fileHash(content);
53
+ const src = content !== undefined ? content : fs.readFileSync(filepath, 'utf-8');
54
+ const hash = fileHash(src);
55
55
  const cacheFile = path.join(cacheDir, hash);
56
56
  return fs.existsSync(cacheFile);
57
57
  }
58
- function markCached(filepath, rootDir) {
58
+ function markCached(filepath, rootDir, content) {
59
59
  const cacheDir = getCacheDir(rootDir);
60
60
  if (!fs.existsSync(cacheDir)) {
61
61
  fs.mkdirSync(cacheDir, { recursive: true });
62
62
  }
63
- const content = fs.readFileSync(filepath, 'utf-8');
64
- const hash = fileHash(content);
63
+ const src = content !== undefined ? content : fs.readFileSync(filepath, 'utf-8');
64
+ const hash = fileHash(src);
65
65
  const cacheFile = path.join(cacheDir, hash);
66
66
  fs.writeFileSync(cacheFile, '');
67
67
  }
@@ -0,0 +1,3 @@
1
+ import { Issue } from '../types';
2
+ export declare function checkAppendSingle(content: string, file: string): Issue[];
3
+ //# sourceMappingURL=append-single.d.ts.map
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkAppendSingle = checkAppendSingle;
4
+ const locale_1 = require("../locale");
5
+ const utils_1 = require("../utils");
6
+ function checkAppendSingle(content, file) {
7
+ 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 (/\(append\s+\(list\b/.test(stripped)) {
12
+ issues.push({ file, line: i + 1, severity: 'warn', rule: 'append_single', message: (0, locale_1.t)('append_single') });
13
+ }
14
+ }
15
+ return issues;
16
+ }
17
+ //# sourceMappingURL=append-single.js.map
@@ -0,0 +1,3 @@
1
+ import { Issue } from '../types';
2
+ export declare function checkArgCount(content: string, file: string): Issue[];
3
+ //# sourceMappingURL=arg-count.d.ts.map
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkArgCount = checkArgCount;
4
+ const locale_1 = require("../locale");
5
+ const utils_1 = require("../utils");
6
+ function checkArgCount(content, file) {
7
+ const issues = [];
8
+ const lines = content.split('\n');
9
+ const defuns = [];
10
+ // Collect all defun definitions
11
+ for (let i = 0; i < lines.length; i++) {
12
+ const stripped = (0, utils_1.stripLine)(lines[i]);
13
+ const m = stripped.match(/\(defun\s+(\S+)\s+\(([^)]*)\)/);
14
+ if (m) {
15
+ const all = m[2];
16
+ const slashIdx = all.indexOf('/');
17
+ const paramStr = slashIdx >= 0 ? all.slice(0, slashIdx).trim() : all;
18
+ const params = paramStr.split(/\s+/).filter(Boolean);
19
+ defuns.push({ name: m[1], line: i + 1, paramCount: params.length });
20
+ }
21
+ }
22
+ // Check calls against definitions
23
+ for (const fn of defuns) {
24
+ if (fn.name.startsWith('c:') || fn.name.includes('/'))
25
+ continue;
26
+ if (fn.paramCount === 0)
27
+ continue;
28
+ for (let i = 0; i < lines.length; i++) {
29
+ const stripped = (0, utils_1.stripLine)(lines[i]);
30
+ const re = new RegExp('\\(\\s*' + fn.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\s+');
31
+ // Don't match the definition line
32
+ if (stripped.includes('defun') && stripped.includes(fn.name))
33
+ continue;
34
+ const callMatch = re.exec(stripped);
35
+ if (callMatch) {
36
+ // Collect all text from the call across lines
37
+ let callText = stripped.slice(callMatch.index);
38
+ let parenDepth = 0;
39
+ for (const ch of callText) {
40
+ if (ch === '(')
41
+ parenDepth++;
42
+ else if (ch === ')')
43
+ parenDepth--;
44
+ }
45
+ // If not closed on this line, read next lines
46
+ let lineIdx = i + 1;
47
+ while (parenDepth > 0 && lineIdx < lines.length) {
48
+ const nextStripped = (0, utils_1.stripLine)(lines[lineIdx]);
49
+ callText += ' ' + nextStripped;
50
+ for (const ch of nextStripped) {
51
+ if (ch === '(')
52
+ parenDepth++;
53
+ else if (ch === ')')
54
+ parenDepth--;
55
+ }
56
+ lineIdx++;
57
+ }
58
+ // Extract args: remove the function call prefix
59
+ const afterName = callText.slice(callMatch[0].length - 1); // keep the leading (
60
+ // Find matching closing paren
61
+ let closeIdx = -1;
62
+ let pd = 1;
63
+ for (let j = 0; j < afterName.length; j++) {
64
+ if (afterName[j] === '(')
65
+ pd++;
66
+ else if (afterName[j] === ')') {
67
+ pd--;
68
+ if (pd === 0) {
69
+ closeIdx = j;
70
+ break;
71
+ }
72
+ }
73
+ }
74
+ const argsStr = closeIdx >= 0 ? afterName.slice(0, closeIdx).trim() : afterName.trim();
75
+ if (!argsStr)
76
+ continue;
77
+ // Count space-delimited args at depth 0
78
+ let argCount = 0;
79
+ let depth = 0;
80
+ let inArg = false;
81
+ for (const ch of argsStr) {
82
+ if (ch === '(') {
83
+ depth++;
84
+ if (!inArg) {
85
+ inArg = true;
86
+ }
87
+ }
88
+ else if (ch === ')') {
89
+ depth--;
90
+ if (depth === 0) {
91
+ if (inArg) {
92
+ argCount++;
93
+ inArg = false;
94
+ }
95
+ }
96
+ }
97
+ else if (depth === 0 && (ch === ' ' || ch === '\t')) {
98
+ if (inArg) {
99
+ argCount++;
100
+ inArg = false;
101
+ }
102
+ }
103
+ else if (!inArg) {
104
+ inArg = true;
105
+ }
106
+ }
107
+ if (inArg)
108
+ argCount++;
109
+ if (argCount !== fn.paramCount) {
110
+ issues.push({
111
+ file,
112
+ line: i + 1,
113
+ severity: 'warn',
114
+ rule: 'arg_count',
115
+ message: (0, locale_1.t)('arg_count', fn.name, argCount, fn.paramCount),
116
+ });
117
+ }
118
+ }
119
+ }
120
+ }
121
+ return issues;
122
+ }
123
+ //# sourceMappingURL=arg-count.js.map
@@ -0,0 +1,5 @@
1
+ import { Issue } from '../types';
2
+ import { AstNode } from '@atlisp/parser';
3
+ export declare function checkAssocWithoutCdr(content: string, file: string): Issue[];
4
+ export declare function checkAssocWithoutCdrAst(ast: AstNode, file: string): Issue[];
5
+ //# sourceMappingURL=assoc-without-cdr.d.ts.map