autosnippet 3.0.7 → 3.0.8
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/lib/core/AstAnalyzer.js +5 -14
- package/lib/core/ast/ensure-grammars.js +47 -152
- package/lib/core/ast/index.js +66 -73
- package/lib/core/ast/lang-dart.js +4 -8
- package/lib/core/ast/lang-go.js +3 -7
- package/lib/core/ast/lang-java.js +3 -7
- package/lib/core/ast/lang-javascript.js +4 -8
- package/lib/core/ast/lang-kotlin.js +3 -7
- package/lib/core/ast/lang-objc.js +3 -7
- package/lib/core/ast/lang-python.js +3 -7
- package/lib/core/ast/lang-rust.js +3 -7
- package/lib/core/ast/lang-swift.js +3 -7
- package/lib/core/ast/lang-typescript.js +6 -12
- package/lib/core/ast/parser-init.js +82 -0
- package/lib/service/guard/GuardCheckEngine.js +172 -8
- package/package.json +4 -41
- package/resources/grammars/tree-sitter-dart.wasm +0 -0
- package/resources/grammars/tree-sitter-go.wasm +0 -0
- package/resources/grammars/tree-sitter-java.wasm +0 -0
- package/resources/grammars/tree-sitter-javascript.wasm +0 -0
- package/resources/grammars/tree-sitter-kotlin.wasm +0 -0
- package/resources/grammars/tree-sitter-objc.wasm +0 -0
- package/resources/grammars/tree-sitter-python.wasm +0 -0
- package/resources/grammars/tree-sitter-rust.wasm +0 -0
- package/resources/grammars/tree-sitter-swift.wasm +0 -0
- package/resources/grammars/tree-sitter-tsx.wasm +0 -0
- package/resources/grammars/tree-sitter-typescript.wasm +0 -0
package/lib/core/AstAnalyzer.js
CHANGED
|
@@ -13,17 +13,7 @@
|
|
|
13
13
|
* 插件注册入口: lib/core/ast/index.js
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
const require = createRequire(import.meta.url);
|
|
19
|
-
|
|
20
|
-
let Parser;
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
Parser = require('tree-sitter');
|
|
24
|
-
} catch {
|
|
25
|
-
// 在没有 tree-sitter 的环境中优雅降级
|
|
26
|
-
}
|
|
16
|
+
import { getParserClass, isParserReady } from './ast/parser-init.js';
|
|
27
17
|
|
|
28
18
|
// ──────────────────────────────────────────────────────────────────
|
|
29
19
|
// 插件注册表
|
|
@@ -290,7 +280,7 @@ function generateContextForAgent(projectSummary) {
|
|
|
290
280
|
* 检查 Tree-sitter 是否可用(至少有一个语言插件注册)
|
|
291
281
|
*/
|
|
292
282
|
function isAvailable() {
|
|
293
|
-
return
|
|
283
|
+
return isParserReady() && _langPlugins.size > 0;
|
|
294
284
|
}
|
|
295
285
|
|
|
296
286
|
/**
|
|
@@ -307,7 +297,8 @@ function supportedLanguages() {
|
|
|
307
297
|
const _parserCache = new Map();
|
|
308
298
|
|
|
309
299
|
function _getParser(lang) {
|
|
310
|
-
|
|
300
|
+
const ParserClass = getParserClass();
|
|
301
|
+
if (!ParserClass) {
|
|
311
302
|
return null;
|
|
312
303
|
}
|
|
313
304
|
if (_parserCache.has(lang)) {
|
|
@@ -324,7 +315,7 @@ function _getParser(lang) {
|
|
|
324
315
|
if (!grammar) {
|
|
325
316
|
return null;
|
|
326
317
|
}
|
|
327
|
-
const parser = new
|
|
318
|
+
const parser = new ParserClass();
|
|
328
319
|
parser.setLanguage(grammar);
|
|
329
320
|
_parserCache.set(lang, parser);
|
|
330
321
|
return parser;
|
|
@@ -1,200 +1,97 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module ast/ensure-grammars
|
|
3
|
-
* @description
|
|
3
|
+
* @description 检查 .wasm 语法文件可用性
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* 迁移至 web-tree-sitter (WASM) 后,不再需要运行时 npm install。
|
|
6
|
+
* 所有 .wasm 文件随包一起发布在 resources/grammars/。
|
|
7
|
+
* 此模块保留旧接口以兼容调用方,但内部逻辑改为检查 .wasm 文件。
|
|
7
8
|
*
|
|
8
9
|
* 使用方式:
|
|
9
10
|
* import { ensureGrammars } from '../core/ast/ensure-grammars.js';
|
|
10
11
|
* const result = await ensureGrammars(['typescript', 'javascript'], { logger });
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
|
-
import
|
|
14
|
-
import { createRequire } from 'node:module';
|
|
14
|
+
import fs from 'node:fs';
|
|
15
15
|
import path from 'node:path';
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
17
|
-
import { promisify } from 'node:util';
|
|
18
17
|
import { LanguageService } from '../../shared/LanguageService.js';
|
|
19
18
|
|
|
20
|
-
const
|
|
21
|
-
const require = createRequire(import.meta.url);
|
|
19
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
20
|
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
*/
|
|
26
|
-
const LANG_TO_PACKAGE = {
|
|
27
|
-
objectivec: 'tree-sitter-objc',
|
|
28
|
-
swift: 'tree-sitter-swift',
|
|
29
|
-
typescript: 'tree-sitter-typescript',
|
|
30
|
-
tsx: 'tree-sitter-typescript', // tsx 与 typescript 共用同一个包
|
|
31
|
-
javascript: 'tree-sitter-javascript',
|
|
32
|
-
python: 'tree-sitter-python',
|
|
33
|
-
java: 'tree-sitter-java',
|
|
34
|
-
kotlin: 'tree-sitter-kotlin',
|
|
35
|
-
go: 'tree-sitter-go',
|
|
36
|
-
dart: 'tree-sitter-dart',
|
|
37
|
-
rust: 'tree-sitter-rust',
|
|
38
|
-
};
|
|
21
|
+
/** .wasm 文件存放目录 */
|
|
22
|
+
const GRAMMARS_DIR = path.resolve(__dirname, '..', '..', '..', 'resources', 'grammars');
|
|
39
23
|
|
|
40
24
|
/**
|
|
41
|
-
*
|
|
25
|
+
* 语言 ID → .wasm 文件名映射
|
|
42
26
|
*/
|
|
43
|
-
const
|
|
44
|
-
'tree-sitter-objc
|
|
45
|
-
'tree-sitter-swift
|
|
46
|
-
'tree-sitter-typescript
|
|
47
|
-
'tree-sitter-
|
|
48
|
-
'tree-sitter-
|
|
49
|
-
'tree-sitter-
|
|
50
|
-
'tree-sitter-
|
|
51
|
-
'tree-sitter-
|
|
52
|
-
'tree-sitter-
|
|
53
|
-
'tree-sitter-
|
|
27
|
+
const LANG_TO_WASM = {
|
|
28
|
+
objectivec: 'tree-sitter-objc.wasm',
|
|
29
|
+
swift: 'tree-sitter-swift.wasm',
|
|
30
|
+
typescript: 'tree-sitter-typescript.wasm',
|
|
31
|
+
tsx: 'tree-sitter-tsx.wasm',
|
|
32
|
+
javascript: 'tree-sitter-javascript.wasm',
|
|
33
|
+
python: 'tree-sitter-python.wasm',
|
|
34
|
+
java: 'tree-sitter-java.wasm',
|
|
35
|
+
kotlin: 'tree-sitter-kotlin.wasm',
|
|
36
|
+
go: 'tree-sitter-go.wasm',
|
|
37
|
+
dart: 'tree-sitter-dart.wasm',
|
|
38
|
+
rust: 'tree-sitter-rust.wasm',
|
|
54
39
|
};
|
|
55
40
|
|
|
56
41
|
/**
|
|
57
|
-
*
|
|
42
|
+
* 检查 .wasm 文件是否存在
|
|
58
43
|
*/
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
require.resolve(pkgName);
|
|
62
|
-
return true;
|
|
63
|
-
} catch {
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
44
|
+
function isWasmAvailable(wasmFileName) {
|
|
45
|
+
return fs.existsSync(path.join(GRAMMARS_DIR, wasmFileName));
|
|
66
46
|
}
|
|
67
47
|
|
|
68
48
|
/**
|
|
69
|
-
*
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// lib/core/ast/ensure-grammars.js → 向上 3 级到包根
|
|
74
|
-
return path.resolve(path.dirname(thisFile), '..', '..', '..');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* 按需安装缺失的 tree-sitter 语法包
|
|
49
|
+
* 检查所需语言的 .wasm 文件是否就绪
|
|
50
|
+
*
|
|
51
|
+
* 保持旧接口签名以兼容 bootstrap 等调用方。
|
|
52
|
+
* WASM 模式下不会执行 npm install —— 文件随包分发。
|
|
79
53
|
*
|
|
80
|
-
* @param {string[]} detectedLanguages - 检测到的语言列表
|
|
54
|
+
* @param {string[]} detectedLanguages - 检测到的语言列表
|
|
81
55
|
* @param {object} [options]
|
|
82
56
|
* @param {object} [options.logger] - Logger 实例(可选)
|
|
83
|
-
* @param {number} [options.timeout=60000] - npm install 超时 (ms)
|
|
84
57
|
* @returns {Promise<{installed: string[], skipped: string[], failed: string[], alreadyAvailable: string[]}>}
|
|
85
58
|
*/
|
|
86
59
|
export async function ensureGrammars(detectedLanguages, options = {}) {
|
|
87
|
-
const { logger
|
|
60
|
+
const { logger } = options;
|
|
88
61
|
|
|
89
62
|
const result = {
|
|
90
|
-
installed: [], //
|
|
91
|
-
skipped: [],
|
|
92
|
-
failed: [],
|
|
93
|
-
alreadyAvailable: [],
|
|
63
|
+
installed: [], // WASM 模式下始终为空(不再运行时安装)
|
|
64
|
+
skipped: [],
|
|
65
|
+
failed: [],
|
|
66
|
+
alreadyAvailable: [],
|
|
94
67
|
};
|
|
95
68
|
|
|
96
69
|
if (!detectedLanguages || detectedLanguages.length === 0) {
|
|
97
70
|
return result;
|
|
98
71
|
}
|
|
99
72
|
|
|
100
|
-
// 1) 去重: 多个语言可能映射到同一个包 (如 typescript + tsx → tree-sitter-typescript)
|
|
101
|
-
const neededPackages = new Map(); // pkgName → [langIds]
|
|
102
73
|
for (const lang of detectedLanguages) {
|
|
103
|
-
const
|
|
104
|
-
if (!
|
|
74
|
+
const wasmFile = LANG_TO_WASM[lang];
|
|
75
|
+
if (!wasmFile) {
|
|
76
|
+
result.skipped.push(lang);
|
|
105
77
|
continue;
|
|
106
78
|
}
|
|
107
|
-
if (!neededPackages.has(pkg)) {
|
|
108
|
-
neededPackages.set(pkg, []);
|
|
109
|
-
}
|
|
110
|
-
neededPackages.get(pkg).push(lang);
|
|
111
|
-
}
|
|
112
79
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
for (const [pkg, langs] of neededPackages) {
|
|
116
|
-
if (isPackageInstalled(pkg)) {
|
|
117
|
-
result.skipped.push(pkg);
|
|
118
|
-
result.alreadyAvailable.push(...langs);
|
|
80
|
+
if (isWasmAvailable(wasmFile)) {
|
|
81
|
+
result.alreadyAvailable.push(lang);
|
|
119
82
|
} else {
|
|
120
|
-
|
|
83
|
+
result.failed.push(lang);
|
|
84
|
+
logger?.warn?.(`[ensure-grammars] Missing .wasm file: ${wasmFile} for language "${lang}"`);
|
|
121
85
|
}
|
|
122
86
|
}
|
|
123
87
|
|
|
124
|
-
if (
|
|
125
|
-
logger?.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// 3) 批量安装缺失的包
|
|
130
|
-
const pkgRoot = getPackageRoot();
|
|
131
|
-
const installArgs = toInstall.map((pkg) => {
|
|
132
|
-
const ver = PACKAGE_VERSIONS[pkg];
|
|
133
|
-
return ver ? `${pkg}@${ver}` : pkg;
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
logger?.info?.(`[ensure-grammars] Installing missing grammars: ${toInstall.join(', ')}`);
|
|
137
|
-
|
|
138
|
-
try {
|
|
139
|
-
const { stderr } = await execFileAsync(
|
|
140
|
-
'npm',
|
|
141
|
-
['install', '--no-save', '--no-audit', '--no-fund', ...installArgs],
|
|
142
|
-
{
|
|
143
|
-
cwd: pkgRoot,
|
|
144
|
-
timeout,
|
|
145
|
-
env: { ...process.env, NODE_ENV: '' }, // 避免 NODE_ENV=production 跳过 optional
|
|
146
|
-
}
|
|
88
|
+
if (result.failed.length > 0) {
|
|
89
|
+
logger?.warn?.(
|
|
90
|
+
`[ensure-grammars] ${result.failed.length} grammar(s) missing. ` +
|
|
91
|
+
`Expected in: ${GRAMMARS_DIR}`
|
|
147
92
|
);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
logger?.warn?.(`[ensure-grammars] npm stderr: ${stderr.slice(0, 200)}`);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// 4) 逐个验证安装结果
|
|
154
|
-
for (const pkg of toInstall) {
|
|
155
|
-
// 清除 require cache 以便重新检测
|
|
156
|
-
try {
|
|
157
|
-
delete require.cache[require.resolve(pkg)];
|
|
158
|
-
} catch {
|
|
159
|
-
/* not cached */
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (isPackageInstalled(pkg)) {
|
|
163
|
-
result.installed.push(pkg);
|
|
164
|
-
const langs = neededPackages.get(pkg) || [];
|
|
165
|
-
result.alreadyAvailable.push(...langs);
|
|
166
|
-
logger?.info?.(`[ensure-grammars] ✓ ${pkg} installed successfully`);
|
|
167
|
-
} else {
|
|
168
|
-
result.failed.push(pkg);
|
|
169
|
-
logger?.warn?.(
|
|
170
|
-
`[ensure-grammars] ✗ ${pkg} install reported success but package not resolvable`
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
} catch (err) {
|
|
175
|
-
logger?.warn?.(`[ensure-grammars] npm install failed: ${err.message}`);
|
|
176
|
-
// 批量失败 → 逐个重试
|
|
177
|
-
for (const pkg of toInstall) {
|
|
178
|
-
try {
|
|
179
|
-
const ver = PACKAGE_VERSIONS[pkg];
|
|
180
|
-
const spec = ver ? `${pkg}@${ver}` : pkg;
|
|
181
|
-
await execFileAsync('npm', ['install', '--no-save', '--no-audit', '--no-fund', spec], {
|
|
182
|
-
cwd: pkgRoot,
|
|
183
|
-
timeout: timeout / 2,
|
|
184
|
-
});
|
|
185
|
-
if (isPackageInstalled(pkg)) {
|
|
186
|
-
result.installed.push(pkg);
|
|
187
|
-
const langs = neededPackages.get(pkg) || [];
|
|
188
|
-
result.alreadyAvailable.push(...langs);
|
|
189
|
-
logger?.info?.(`[ensure-grammars] ✓ ${pkg} installed (retry)`);
|
|
190
|
-
} else {
|
|
191
|
-
result.failed.push(pkg);
|
|
192
|
-
}
|
|
193
|
-
} catch {
|
|
194
|
-
result.failed.push(pkg);
|
|
195
|
-
logger?.warn?.(`[ensure-grammars] ✗ ${pkg} install failed permanently`);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
93
|
+
} else {
|
|
94
|
+
logger?.info?.('[ensure-grammars] All required grammar .wasm files available');
|
|
198
95
|
}
|
|
199
96
|
|
|
200
97
|
return result;
|
|
@@ -205,7 +102,6 @@ export async function ensureGrammars(detectedLanguages, options = {}) {
|
|
|
205
102
|
* 由于 loadPlugins() 是幂等的(_loaded 标志),需要重置标志后重新加载
|
|
206
103
|
*/
|
|
207
104
|
export async function reloadPlugins() {
|
|
208
|
-
// 动态 import 获取模块并重置 _loaded 状态
|
|
209
105
|
const astIndex = await import('./index.js');
|
|
210
106
|
if (typeof astIndex._resetForReload === 'function') {
|
|
211
107
|
astIndex._resetForReload();
|
|
@@ -220,7 +116,6 @@ export async function reloadPlugins() {
|
|
|
220
116
|
* @returns {string[]} 需要的语言 ID 列表
|
|
221
117
|
*/
|
|
222
118
|
export function inferLanguagesFromStats(langStats) {
|
|
223
|
-
// 从 LanguageService 派生,仅覆盖 tsx(tree-sitter 需要独立解析器)
|
|
224
119
|
const bareMap = LanguageService.bareExtToLangMap;
|
|
225
120
|
|
|
226
121
|
const langs = new Set();
|
package/lib/core/ast/index.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module ast/index
|
|
3
|
-
* @description 语言 AST
|
|
3
|
+
* @description 语言 AST 插件自动加载器(web-tree-sitter WASM 版)
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* 初始化流程:
|
|
6
|
+
* 1. 调用 initParser() — 初始化 web-tree-sitter WASM 运行时
|
|
7
|
+
* 2. 并行加载所有 .wasm 语法文件
|
|
8
|
+
* 3. 将 Language 对象注入每个 lang-*.js 插件
|
|
9
|
+
* 4. 注册到 AstAnalyzer
|
|
10
|
+
*
|
|
11
|
+
* .wasm 文件位于 resources/grammars/,随 npm 包一起发布。
|
|
12
|
+
* 不再依赖原生 tree-sitter 编译,任何平台即装即用。
|
|
7
13
|
*
|
|
8
14
|
* 使用方式:
|
|
9
15
|
* import '../core/ast/index.js'; // 副作用: 注册所有可用语言插件
|
|
@@ -14,6 +20,7 @@
|
|
|
14
20
|
*/
|
|
15
21
|
|
|
16
22
|
import { registerLanguage } from '../AstAnalyzer.js';
|
|
23
|
+
import { initParser, loadLanguageWasm, isParserReady } from './parser-init.js';
|
|
17
24
|
|
|
18
25
|
let _loaded = false;
|
|
19
26
|
|
|
@@ -25,6 +32,23 @@ export function _resetForReload() {
|
|
|
25
32
|
_loaded = false;
|
|
26
33
|
}
|
|
27
34
|
|
|
35
|
+
/**
|
|
36
|
+
* 语言注册表 — langId → { wasmFile, module, setGrammarFn, langId, tsxWasmFile?, setTsxGrammarFn? }
|
|
37
|
+
*/
|
|
38
|
+
const LANG_REGISTRY = [
|
|
39
|
+
{ langId: 'objectivec', wasmFile: 'tree-sitter-objc.wasm', module: './lang-objc.js', setFn: 'setGrammar' },
|
|
40
|
+
{ langId: 'swift', wasmFile: 'tree-sitter-swift.wasm', module: './lang-swift.js', setFn: 'setGrammar' },
|
|
41
|
+
{ langId: 'typescript', wasmFile: 'tree-sitter-typescript.wasm', module: './lang-typescript.js', setFn: 'setGrammar' },
|
|
42
|
+
{ langId: 'tsx', wasmFile: 'tree-sitter-tsx.wasm', module: './lang-typescript.js', setFn: 'setTsxGrammar', pluginKey: 'tsxPlugin' },
|
|
43
|
+
{ langId: 'javascript', wasmFile: 'tree-sitter-javascript.wasm', module: './lang-javascript.js', setFn: 'setGrammar' },
|
|
44
|
+
{ langId: 'python', wasmFile: 'tree-sitter-python.wasm', module: './lang-python.js', setFn: 'setGrammar' },
|
|
45
|
+
{ langId: 'java', wasmFile: 'tree-sitter-java.wasm', module: './lang-java.js', setFn: 'setGrammar' },
|
|
46
|
+
{ langId: 'kotlin', wasmFile: 'tree-sitter-kotlin.wasm', module: './lang-kotlin.js', setFn: 'setGrammar' },
|
|
47
|
+
{ langId: 'go', wasmFile: 'tree-sitter-go.wasm', module: './lang-go.js', setFn: 'setGrammar' },
|
|
48
|
+
{ langId: 'dart', wasmFile: 'tree-sitter-dart.wasm', module: './lang-dart.js', setFn: 'setGrammar' },
|
|
49
|
+
{ langId: 'rust', wasmFile: 'tree-sitter-rust.wasm', module: './lang-rust.js', setFn: 'setGrammar' },
|
|
50
|
+
];
|
|
51
|
+
|
|
28
52
|
/**
|
|
29
53
|
* 加载并注册所有可用的语言 AST 插件
|
|
30
54
|
* 幂等 — 多次调用只执行一次
|
|
@@ -35,87 +59,56 @@ export async function loadPlugins() {
|
|
|
35
59
|
}
|
|
36
60
|
_loaded = true;
|
|
37
61
|
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
} catch {
|
|
43
|
-
/* tree-sitter-objc not installed */
|
|
62
|
+
// 1. 初始化 web-tree-sitter WASM 运行时
|
|
63
|
+
await initParser();
|
|
64
|
+
if (!isParserReady()) {
|
|
65
|
+
return; // web-tree-sitter 不可用,优雅降级(和以前缺少 tree-sitter 一样)
|
|
44
66
|
}
|
|
45
67
|
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
// TypeScript
|
|
55
|
-
try {
|
|
56
|
-
const { plugin: tsPlugin, tsxPlugin } = await import('./lang-typescript.js');
|
|
57
|
-
registerLanguage('typescript', tsPlugin);
|
|
58
|
-
if (tsxPlugin) {
|
|
59
|
-
registerLanguage('tsx', tsxPlugin);
|
|
68
|
+
// 2. 按顺序加载所有 .wasm 语法文件(并行加载偶发竞态导致失败)
|
|
69
|
+
const wasmResults = [];
|
|
70
|
+
for (const entry of LANG_REGISTRY) {
|
|
71
|
+
try {
|
|
72
|
+
const lang = await loadLanguageWasm(entry.wasmFile);
|
|
73
|
+
wasmResults.push({ status: 'fulfilled', value: lang });
|
|
74
|
+
} catch (err) {
|
|
75
|
+
wasmResults.push({ status: 'rejected', reason: err });
|
|
60
76
|
}
|
|
61
|
-
} catch {
|
|
62
|
-
/* tree-sitter-typescript not installed */
|
|
63
77
|
}
|
|
64
78
|
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
const { plugin } = await import('./lang-javascript.js');
|
|
68
|
-
registerLanguage('javascript', plugin);
|
|
69
|
-
} catch {
|
|
70
|
-
/* tree-sitter-javascript not installed */
|
|
71
|
-
}
|
|
79
|
+
// 3. 逐个加载插件模块并注入 Grammar
|
|
80
|
+
const moduleCache = new Map();
|
|
72
81
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
registerLanguage('python', plugin);
|
|
77
|
-
} catch {
|
|
78
|
-
/* tree-sitter-python not installed */
|
|
79
|
-
}
|
|
82
|
+
for (let i = 0; i < LANG_REGISTRY.length; i++) {
|
|
83
|
+
const entry = LANG_REGISTRY[i];
|
|
84
|
+
const wasmResult = wasmResults[i];
|
|
80
85
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
registerLanguage('java', plugin);
|
|
85
|
-
} catch {
|
|
86
|
-
/* tree-sitter-java not installed */
|
|
87
|
-
}
|
|
86
|
+
if (wasmResult.status !== 'fulfilled' || !wasmResult.value) {
|
|
87
|
+
continue; // wasm 加载失败,跳过此语言
|
|
88
|
+
}
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
const { plugin } = await import('./lang-kotlin.js');
|
|
92
|
-
registerLanguage('kotlin', plugin);
|
|
93
|
-
} catch {
|
|
94
|
-
/* tree-sitter-kotlin not installed */
|
|
95
|
-
}
|
|
90
|
+
const language = wasmResult.value;
|
|
96
91
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
92
|
+
try {
|
|
93
|
+
// 模块缓存(TypeScript 模块被 typescript + tsx 共用)
|
|
94
|
+
let mod = moduleCache.get(entry.module);
|
|
95
|
+
if (!mod) {
|
|
96
|
+
mod = await import(entry.module);
|
|
97
|
+
moduleCache.set(entry.module, mod);
|
|
98
|
+
}
|
|
104
99
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const { plugin } = await import('./lang-dart.js');
|
|
108
|
-
registerLanguage('dart', plugin);
|
|
109
|
-
} catch {
|
|
110
|
-
/* tree-sitter-dart not installed */
|
|
111
|
-
}
|
|
100
|
+
// 注入 Grammar
|
|
101
|
+
mod[entry.setFn](language);
|
|
112
102
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
103
|
+
// 注册到 AstAnalyzer
|
|
104
|
+
const pluginKey = entry.pluginKey || 'plugin';
|
|
105
|
+
const plugin = mod[pluginKey];
|
|
106
|
+
if (plugin) {
|
|
107
|
+
registerLanguage(entry.langId, plugin);
|
|
108
|
+
}
|
|
109
|
+
} catch {
|
|
110
|
+
/* 插件加载失败,静默跳过 */
|
|
111
|
+
}
|
|
119
112
|
}
|
|
120
113
|
}
|
|
121
114
|
|
|
@@ -7,13 +7,9 @@
|
|
|
7
7
|
* Builder, BLoC/Cubit, Provider/Riverpod, Freezed
|
|
8
8
|
*
|
|
9
9
|
* 注意: tree-sitter-dart 目前尚无兼容 tree-sitter ≥0.25 的稳定版。
|
|
10
|
-
*
|
|
10
|
+
* 已迁移至 web-tree-sitter (WASM),无原生编译依赖。
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { createRequire } from 'node:module';
|
|
14
|
-
|
|
15
|
-
const require = createRequire(import.meta.url);
|
|
16
|
-
|
|
17
13
|
function walkDart(root, ctx) {
|
|
18
14
|
_walkNode(root, ctx, null);
|
|
19
15
|
}
|
|
@@ -647,11 +643,11 @@ function _maxNesting(node, depth) {
|
|
|
647
643
|
|
|
648
644
|
let _grammar = null;
|
|
649
645
|
function getGrammar() {
|
|
650
|
-
if (!_grammar) {
|
|
651
|
-
_grammar = require('tree-sitter-dart');
|
|
652
|
-
}
|
|
653
646
|
return _grammar;
|
|
654
647
|
}
|
|
648
|
+
export function setGrammar(grammar) {
|
|
649
|
+
_grammar = grammar;
|
|
650
|
+
}
|
|
655
651
|
|
|
656
652
|
export const plugin = {
|
|
657
653
|
getGrammar,
|
package/lib/core/ast/lang-go.js
CHANGED
|
@@ -7,10 +7,6 @@
|
|
|
7
7
|
* Goroutine, Channel, Middleware (http.Handler chain)
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { createRequire } from 'node:module';
|
|
11
|
-
|
|
12
|
-
const require = createRequire(import.meta.url);
|
|
13
|
-
|
|
14
10
|
function walkGo(root, ctx) {
|
|
15
11
|
for (let i = 0; i < root.namedChildCount; i++) {
|
|
16
12
|
const child = root.namedChild(i);
|
|
@@ -516,11 +512,11 @@ function _maxNesting(node, depth) {
|
|
|
516
512
|
|
|
517
513
|
let _grammar = null;
|
|
518
514
|
function getGrammar() {
|
|
519
|
-
if (!_grammar) {
|
|
520
|
-
_grammar = require('tree-sitter-go');
|
|
521
|
-
}
|
|
522
515
|
return _grammar;
|
|
523
516
|
}
|
|
517
|
+
export function setGrammar(grammar) {
|
|
518
|
+
_grammar = grammar;
|
|
519
|
+
}
|
|
524
520
|
|
|
525
521
|
export const plugin = {
|
|
526
522
|
getGrammar,
|
|
@@ -6,10 +6,6 @@
|
|
|
6
6
|
* 模式: Singleton, Builder, Factory, DI, Stream Pipeline
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { createRequire } from 'node:module';
|
|
10
|
-
|
|
11
|
-
const require = createRequire(import.meta.url);
|
|
12
|
-
|
|
13
9
|
function walkJava(root, ctx) {
|
|
14
10
|
_walkJavaNode(root, ctx, null);
|
|
15
11
|
}
|
|
@@ -421,11 +417,11 @@ function _maxNesting(node, depth) {
|
|
|
421
417
|
|
|
422
418
|
let _grammar = null;
|
|
423
419
|
function getGrammar() {
|
|
424
|
-
if (!_grammar) {
|
|
425
|
-
_grammar = require('tree-sitter-java');
|
|
426
|
-
}
|
|
427
420
|
return _grammar;
|
|
428
421
|
}
|
|
422
|
+
export function setGrammar(grammar) {
|
|
423
|
+
_grammar = grammar;
|
|
424
|
+
}
|
|
429
425
|
|
|
430
426
|
export const plugin = {
|
|
431
427
|
getGrammar,
|
|
@@ -2,13 +2,9 @@
|
|
|
2
2
|
* @module lang-javascript
|
|
3
3
|
* @description JavaScript AST Walker 插件
|
|
4
4
|
*
|
|
5
|
-
* 与 TypeScript walker 共享大部分逻辑,grammar 使用 tree-sitter
|
|
5
|
+
* 与 TypeScript walker 共享大部分逻辑,grammar 使用 web-tree-sitter (WASM)
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { createRequire } from 'node:module';
|
|
9
|
-
|
|
10
|
-
const require = createRequire(import.meta.url);
|
|
11
|
-
|
|
12
8
|
// JavaScript walker 与 TypeScript walker 结构相同
|
|
13
9
|
// 复用 lang-typescript 的 walker 逻辑
|
|
14
10
|
|
|
@@ -258,11 +254,11 @@ function _maxNesting(node, depth) {
|
|
|
258
254
|
|
|
259
255
|
let _grammar = null;
|
|
260
256
|
function getGrammar() {
|
|
261
|
-
if (!_grammar) {
|
|
262
|
-
_grammar = require('tree-sitter-javascript');
|
|
263
|
-
}
|
|
264
257
|
return _grammar;
|
|
265
258
|
}
|
|
259
|
+
export function setGrammar(grammar) {
|
|
260
|
+
_grammar = grammar;
|
|
261
|
+
}
|
|
266
262
|
|
|
267
263
|
export const plugin = {
|
|
268
264
|
getGrammar,
|
|
@@ -6,10 +6,6 @@
|
|
|
6
6
|
* 模式: Singleton (object), Factory (companion), DSL, Flow, Sealed
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { createRequire } from 'node:module';
|
|
10
|
-
|
|
11
|
-
const require = createRequire(import.meta.url);
|
|
12
|
-
|
|
13
9
|
function walkKotlin(root, ctx) {
|
|
14
10
|
_walkKtNode(root, ctx, null);
|
|
15
11
|
}
|
|
@@ -409,11 +405,11 @@ function _maxNesting(node, depth) {
|
|
|
409
405
|
|
|
410
406
|
let _grammar = null;
|
|
411
407
|
function getGrammar() {
|
|
412
|
-
if (!_grammar) {
|
|
413
|
-
_grammar = require('tree-sitter-kotlin');
|
|
414
|
-
}
|
|
415
408
|
return _grammar;
|
|
416
409
|
}
|
|
410
|
+
export function setGrammar(grammar) {
|
|
411
|
+
_grammar = grammar;
|
|
412
|
+
}
|
|
417
413
|
|
|
418
414
|
export const plugin = {
|
|
419
415
|
getGrammar,
|
|
@@ -3,10 +3,6 @@
|
|
|
3
3
|
* @description ObjC AST Walker 插件 — 从 AstAnalyzer.js 迁移
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { createRequire } from 'node:module';
|
|
7
|
-
|
|
8
|
-
const require = createRequire(import.meta.url);
|
|
9
|
-
|
|
10
6
|
// ── ObjC AST 遍历 ──
|
|
11
7
|
|
|
12
8
|
function walkObjC(root, ctx) {
|
|
@@ -374,11 +370,11 @@ function _maxNesting(node, depth) {
|
|
|
374
370
|
|
|
375
371
|
let _grammar = null;
|
|
376
372
|
function getGrammar() {
|
|
377
|
-
if (!_grammar) {
|
|
378
|
-
_grammar = require('tree-sitter-objc');
|
|
379
|
-
}
|
|
380
373
|
return _grammar;
|
|
381
374
|
}
|
|
375
|
+
export function setGrammar(grammar) {
|
|
376
|
+
_grammar = grammar;
|
|
377
|
+
}
|
|
382
378
|
|
|
383
379
|
export const plugin = {
|
|
384
380
|
getGrammar,
|
|
@@ -6,10 +6,6 @@
|
|
|
6
6
|
* 模式: Singleton, Factory, Context Manager, Decorator pattern, Data Class
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { createRequire } from 'node:module';
|
|
10
|
-
|
|
11
|
-
const require = createRequire(import.meta.url);
|
|
12
|
-
|
|
13
9
|
function walkPython(root, ctx) {
|
|
14
10
|
_walkPyNode(root, ctx, null);
|
|
15
11
|
}
|
|
@@ -357,11 +353,11 @@ function _maxNesting(node, depth) {
|
|
|
357
353
|
|
|
358
354
|
let _grammar = null;
|
|
359
355
|
function getGrammar() {
|
|
360
|
-
if (!_grammar) {
|
|
361
|
-
_grammar = require('tree-sitter-python');
|
|
362
|
-
}
|
|
363
356
|
return _grammar;
|
|
364
357
|
}
|
|
358
|
+
export function setGrammar(grammar) {
|
|
359
|
+
_grammar = grammar;
|
|
360
|
+
}
|
|
365
361
|
|
|
366
362
|
export const plugin = {
|
|
367
363
|
getGrammar,
|
|
@@ -7,10 +7,6 @@
|
|
|
7
7
|
* Async (tokio/async-std), Unsafe block, Derive macro
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { createRequire } from 'node:module';
|
|
11
|
-
|
|
12
|
-
const require = createRequire(import.meta.url);
|
|
13
|
-
|
|
14
10
|
function walkRust(root, ctx) {
|
|
15
11
|
for (let i = 0; i < root.namedChildCount; i++) {
|
|
16
12
|
const child = root.namedChild(i);
|
|
@@ -681,11 +677,11 @@ function _maxNesting(node, depth) {
|
|
|
681
677
|
|
|
682
678
|
let _grammar = null;
|
|
683
679
|
function getGrammar() {
|
|
684
|
-
if (!_grammar) {
|
|
685
|
-
_grammar = require('tree-sitter-rust');
|
|
686
|
-
}
|
|
687
680
|
return _grammar;
|
|
688
681
|
}
|
|
682
|
+
export function setGrammar(grammar) {
|
|
683
|
+
_grammar = grammar;
|
|
684
|
+
}
|
|
689
685
|
|
|
690
686
|
export const plugin = {
|
|
691
687
|
getGrammar,
|
|
@@ -3,10 +3,6 @@
|
|
|
3
3
|
* @description Swift AST Walker 插件 — 从 AstAnalyzer.js 迁移
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { createRequire } from 'node:module';
|
|
7
|
-
|
|
8
|
-
const require = createRequire(import.meta.url);
|
|
9
|
-
|
|
10
6
|
// ── Swift AST 遍历 ──
|
|
11
7
|
|
|
12
8
|
function walkSwift(root, ctx) {
|
|
@@ -323,11 +319,11 @@ function _maxNesting(node, depth) {
|
|
|
323
319
|
|
|
324
320
|
let _grammar = null;
|
|
325
321
|
function getGrammar() {
|
|
326
|
-
if (!_grammar) {
|
|
327
|
-
_grammar = require('tree-sitter-swift');
|
|
328
|
-
}
|
|
329
322
|
return _grammar;
|
|
330
323
|
}
|
|
324
|
+
export function setGrammar(grammar) {
|
|
325
|
+
_grammar = grammar;
|
|
326
|
+
}
|
|
331
327
|
|
|
332
328
|
export const plugin = {
|
|
333
329
|
getGrammar,
|
|
@@ -6,10 +6,6 @@
|
|
|
6
6
|
* 模式检测: Singleton, Factory, Observer, React Hook/Component, Middleware, Decorator
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { createRequire } from 'node:module';
|
|
10
|
-
|
|
11
|
-
const require = createRequire(import.meta.url);
|
|
12
|
-
|
|
13
9
|
function walkTypeScript(root, ctx) {
|
|
14
10
|
_walkTSNode(root, ctx, null);
|
|
15
11
|
}
|
|
@@ -471,12 +467,11 @@ function _maxNesting(node, depth) {
|
|
|
471
467
|
|
|
472
468
|
let _tsGrammar = null;
|
|
473
469
|
function getGrammar() {
|
|
474
|
-
if (!_tsGrammar) {
|
|
475
|
-
const ts = require('tree-sitter-typescript');
|
|
476
|
-
_tsGrammar = ts.typescript;
|
|
477
|
-
}
|
|
478
470
|
return _tsGrammar;
|
|
479
471
|
}
|
|
472
|
+
export function setGrammar(grammar) {
|
|
473
|
+
_tsGrammar = grammar;
|
|
474
|
+
}
|
|
480
475
|
|
|
481
476
|
export const plugin = {
|
|
482
477
|
getGrammar,
|
|
@@ -488,12 +483,11 @@ export const plugin = {
|
|
|
488
483
|
// TSX 插件 — 共享 walker,不同 grammar
|
|
489
484
|
let _tsxGrammar = null;
|
|
490
485
|
function getTsxGrammar() {
|
|
491
|
-
if (!_tsxGrammar) {
|
|
492
|
-
const ts = require('tree-sitter-typescript');
|
|
493
|
-
_tsxGrammar = ts.tsx;
|
|
494
|
-
}
|
|
495
486
|
return _tsxGrammar;
|
|
496
487
|
}
|
|
488
|
+
export function setTsxGrammar(grammar) {
|
|
489
|
+
_tsxGrammar = grammar;
|
|
490
|
+
}
|
|
497
491
|
|
|
498
492
|
export const tsxPlugin = {
|
|
499
493
|
getGrammar: getTsxGrammar,
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module ast/parser-init
|
|
3
|
+
* @description web-tree-sitter 初始化器
|
|
4
|
+
*
|
|
5
|
+
* 统一管理 WASM 版 Parser 的生命周期:
|
|
6
|
+
* 1. 调用 Parser.init() 初始化 WASM 运行时(仅一次)
|
|
7
|
+
* 2. 加载 .wasm 语法文件为 Language 对象
|
|
8
|
+
* 3. 提供同步的 Parser 构造与语言设置 API
|
|
9
|
+
*
|
|
10
|
+
* 所有 async 操作(init + wasm 加载)集中在 loadPlugins() 阶段完成,
|
|
11
|
+
* 下游 analyzeFile / findCallExpressions 等保持同步调用。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import { readFile } from 'node:fs/promises';
|
|
17
|
+
|
|
18
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
|
|
20
|
+
/** 预编译 .wasm 文件存放目录 */
|
|
21
|
+
const GRAMMARS_DIR = path.resolve(__dirname, '..', '..', '..', 'resources', 'grammars');
|
|
22
|
+
|
|
23
|
+
let Parser = null;
|
|
24
|
+
/** web-tree-sitter 模块命名空间 — Language.load 在这里 */
|
|
25
|
+
let _namespace = null;
|
|
26
|
+
let _initialized = false;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 初始化 web-tree-sitter WASM 运行时
|
|
30
|
+
* 幂等 — 多次调用只执行一次
|
|
31
|
+
*/
|
|
32
|
+
export async function initParser() {
|
|
33
|
+
if (_initialized) return;
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// web-tree-sitter ESM: 导出 { Parser, Language, ... } 命名空间
|
|
37
|
+
const mod = await import('web-tree-sitter');
|
|
38
|
+
_namespace = mod.default || mod;
|
|
39
|
+
// v0.25 导出 { Parser, Language, ... },需要提取 Parser 类
|
|
40
|
+
Parser = typeof _namespace === 'function' ? _namespace : _namespace.Parser;
|
|
41
|
+
await Parser.init();
|
|
42
|
+
_initialized = true;
|
|
43
|
+
} catch (err) {
|
|
44
|
+
// web-tree-sitter 不可用时优雅降级
|
|
45
|
+
Parser = null;
|
|
46
|
+
_initialized = false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 获取 Parser 构造函数
|
|
52
|
+
* @returns {typeof import('web-tree-sitter') | null}
|
|
53
|
+
*/
|
|
54
|
+
export function getParserClass() {
|
|
55
|
+
return Parser;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 检查 parser 是否已初始化
|
|
60
|
+
*/
|
|
61
|
+
export function isParserReady() {
|
|
62
|
+
return _initialized && Parser !== null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 从 resources/grammars/ 加载指定语言的 .wasm 文件
|
|
67
|
+
* @param {string} wasmFileName — 如 'tree-sitter-javascript.wasm'
|
|
68
|
+
* @returns {Promise<object|null>} Language 对象,失败返回 null
|
|
69
|
+
*/
|
|
70
|
+
export async function loadLanguageWasm(wasmFileName) {
|
|
71
|
+
if (!_initialized || !_namespace) return null;
|
|
72
|
+
|
|
73
|
+
const wasmPath = path.join(GRAMMARS_DIR, wasmFileName);
|
|
74
|
+
try {
|
|
75
|
+
// 自行读取 wasm 文件为 Uint8Array,绕过 ESM 下 __require("fs/promises") 的兼容问题
|
|
76
|
+
const buffer = await readFile(wasmPath);
|
|
77
|
+
const Language = _namespace.Language || Parser.Language;
|
|
78
|
+
return await Language.load(new Uint8Array(buffer));
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -122,6 +122,7 @@ const BUILT_IN_RULES = {
|
|
|
122
122
|
languages: ['javascript', 'typescript'],
|
|
123
123
|
dimension: 'file',
|
|
124
124
|
category: 'style',
|
|
125
|
+
excludePaths: /(?:^|[\/\\])(?:test|tests|__tests__|spec|__mocks__|mock|mocks|fixtures?)[\/\\]|[\/\\](?:test_|spec_)[^\/\\]*\.(?:js|ts)$|\.(?:test|spec)\.(?:js|ts)$/,
|
|
125
126
|
},
|
|
126
127
|
'js-no-console-log': {
|
|
127
128
|
message: '生产代码应移除 console.log,使用专用日志库',
|
|
@@ -130,6 +131,7 @@ const BUILT_IN_RULES = {
|
|
|
130
131
|
languages: ['javascript', 'typescript'],
|
|
131
132
|
dimension: 'file',
|
|
132
133
|
category: 'style',
|
|
134
|
+
excludePaths: /(?:^|[\/\\])(?:test|tests|__tests__|spec|mock|mocks|__mocks__|scripts|tools|debug)[\/\\]|[\/\\](?:test_|spec_|mock)[^\/\\]*\.(?:js|ts)$|\.(?:test|spec)\.(?:js|ts)$/,
|
|
133
135
|
},
|
|
134
136
|
'js-no-debugger': {
|
|
135
137
|
message: '生产代码中不应包含 debugger 语句',
|
|
@@ -267,6 +269,7 @@ const BUILT_IN_RULES = {
|
|
|
267
269
|
languages: ['go'],
|
|
268
270
|
dimension: 'file',
|
|
269
271
|
category: 'correctness',
|
|
272
|
+
excludePaths: /(?:^|[\/\\])(?:tests?|testdata|_test)[\/\\]|_test\.go$/,
|
|
270
273
|
},
|
|
271
274
|
'go-no-init-abuse': {
|
|
272
275
|
message: 'init() 函数副作用难以追踪,避免在 init 中执行复杂逻辑',
|
|
@@ -279,10 +282,11 @@ const BUILT_IN_RULES = {
|
|
|
279
282
|
'go-no-global-var': {
|
|
280
283
|
message: '全局可变变量导致并发安全问题,考虑使用依赖注入',
|
|
281
284
|
severity: 'info',
|
|
282
|
-
pattern: '^var\\s
|
|
285
|
+
pattern: '^var\\s+(?!_\\s)[a-zA-Z]\\w*\\s+(?!=[^=])',
|
|
283
286
|
languages: ['go'],
|
|
284
287
|
dimension: 'file',
|
|
285
288
|
category: 'style',
|
|
289
|
+
excludePaths: /(?:^|[\/\\])(?:tests?|testdata)[\/\\]|_test\.go$/,
|
|
286
290
|
},
|
|
287
291
|
|
|
288
292
|
// ══════════════════════════════════════════════════════════
|
|
@@ -298,17 +302,18 @@ const BUILT_IN_RULES = {
|
|
|
298
302
|
category: 'style',
|
|
299
303
|
},
|
|
300
304
|
'dart-avoid-dynamic': {
|
|
301
|
-
message: '
|
|
305
|
+
message: '避免直接使用 dynamic 作为变量/参数类型,使用具体类型或泛型提升类型安全',
|
|
302
306
|
severity: 'warning',
|
|
303
|
-
pattern: '\\bdynamic\\b',
|
|
307
|
+
pattern: '(?<!<\\w*,\\s*)(?<!<)\\bdynamic\\b(?!\\s*>)',
|
|
304
308
|
languages: ['dart'],
|
|
305
309
|
dimension: 'file',
|
|
306
310
|
category: 'style',
|
|
311
|
+
fixSuggestion: '使用 Object? 或具体类型替代 dynamic;Map<String, dynamic> 用于 JSON 序列化时可保留',
|
|
307
312
|
},
|
|
308
313
|
'dart-no-set-state-after-dispose': {
|
|
309
314
|
message: 'setState 调用前应检查 mounted 状态,避免 disposed 后调用',
|
|
310
|
-
severity: '
|
|
311
|
-
pattern: 'setState\\s*\\(',
|
|
315
|
+
severity: 'info',
|
|
316
|
+
pattern: '(?<!mounted\\)\\s*)setState\\s*\\(',
|
|
312
317
|
languages: ['dart'],
|
|
313
318
|
dimension: 'file',
|
|
314
319
|
category: 'correctness',
|
|
@@ -372,7 +377,9 @@ const BUILT_IN_RULES = {
|
|
|
372
377
|
dimension: 'file',
|
|
373
378
|
category: 'correctness',
|
|
374
379
|
fixSuggestion: '使用 ? 操作符传播错误,或 .unwrap_or_default() / .expect("原因")',
|
|
375
|
-
excludePaths: /(?:^|[\/\\])tests
|
|
380
|
+
excludePaths: /(?:^|[\/\\])(?:tests?|test_helpers|benches|examples)[\/\\]|[\/\\]test_[^\/\\]*\.rs$|_test\.rs$/,
|
|
381
|
+
skipComments: true,
|
|
382
|
+
skipTestBlocks: true,
|
|
376
383
|
},
|
|
377
384
|
'rust-no-expect-without-msg': {
|
|
378
385
|
message: 'expect() 应提供有意义的错误消息,帮助定位 panic 原因',
|
|
@@ -399,7 +406,9 @@ const BUILT_IN_RULES = {
|
|
|
399
406
|
languages: ['rust'],
|
|
400
407
|
dimension: 'file',
|
|
401
408
|
category: 'correctness',
|
|
402
|
-
excludePaths: /(?:^|[\/\\])tests
|
|
409
|
+
excludePaths: /(?:^|[\/\\])(?:tests?|test_helpers|benches|examples)[\/\\]|_test\.rs$/,
|
|
410
|
+
skipComments: true,
|
|
411
|
+
skipTestBlocks: true,
|
|
403
412
|
},
|
|
404
413
|
'rust-clone-overuse': {
|
|
405
414
|
message: '频繁 .clone() 可能暗示所有权设计问题,考虑使用借用或 Cow',
|
|
@@ -409,6 +418,9 @@ const BUILT_IN_RULES = {
|
|
|
409
418
|
dimension: 'file',
|
|
410
419
|
category: 'performance',
|
|
411
420
|
fixSuggestion: '分析是否可用 &T 借用替代,或使用 Cow<T> 延迟克隆',
|
|
421
|
+
excludePaths: /(?:^|[\/\\])(?:tests?|test_helpers|benches|examples)[\/\\]|_test\.rs$/,
|
|
422
|
+
skipComments: true,
|
|
423
|
+
skipTestBlocks: true,
|
|
412
424
|
},
|
|
413
425
|
'rust-no-panic-in-lib': {
|
|
414
426
|
message: 'panic!() 在库代码中应避免使用,返回 Result 让调用方决定如何处理',
|
|
@@ -417,7 +429,9 @@ const BUILT_IN_RULES = {
|
|
|
417
429
|
languages: ['rust'],
|
|
418
430
|
dimension: 'file',
|
|
419
431
|
category: 'correctness',
|
|
420
|
-
excludePaths: /(?:^|[\/\\])tests
|
|
432
|
+
excludePaths: /(?:^|[\/\\])(?:tests?|test_helpers|benches|examples)[\/\\]|main\.rs$/,
|
|
433
|
+
skipComments: true,
|
|
434
|
+
skipTestBlocks: true,
|
|
421
435
|
},
|
|
422
436
|
'rust-std-mutex-in-async': {
|
|
423
437
|
message: 'async 代码中不应使用 std::sync::Mutex,MutexGuard 不是 Send',
|
|
@@ -589,6 +603,8 @@ export class GuardCheckEngine {
|
|
|
589
603
|
type: 'regex',
|
|
590
604
|
fixSuggestion: rule.fixSuggestion || null,
|
|
591
605
|
...(rule.excludePaths ? { excludePaths: rule.excludePaths } : {}),
|
|
606
|
+
...(rule.skipComments ? { skipComments: true } : {}),
|
|
607
|
+
...(rule.skipTestBlocks ? { skipTestBlocks: true } : {}),
|
|
592
608
|
});
|
|
593
609
|
}
|
|
594
610
|
}
|
|
@@ -655,6 +671,14 @@ export class GuardCheckEngine {
|
|
|
655
671
|
|
|
656
672
|
const lines = (code || '').split(/\r?\n/);
|
|
657
673
|
|
|
674
|
+
// 预计算注释行掩码 — 供 skipComments 规则使用
|
|
675
|
+
// 识别: // 行注释, /// doc, //! inner doc, /* block */, # Python/Shell 行注释
|
|
676
|
+
const commentLines = this._buildCommentMask(lines, language);
|
|
677
|
+
|
|
678
|
+
// 预计算测试块掩码 — 供 skipTestBlocks 规则使用
|
|
679
|
+
// Rust: #[cfg(test)] mod tests { ... } 内联测试模块
|
|
680
|
+
const testBlockLines = this._buildTestBlockMask(lines, language);
|
|
681
|
+
|
|
658
682
|
for (const rule of rules) {
|
|
659
683
|
// 跳过空模式或特殊标记 (?!) — 由 code-level 检查接管
|
|
660
684
|
if (!rule.pattern || rule.pattern === '(?!)') {
|
|
@@ -669,7 +693,19 @@ export class GuardCheckEngine {
|
|
|
669
693
|
continue;
|
|
670
694
|
}
|
|
671
695
|
|
|
696
|
+
const shouldSkipComments = !!rule.skipComments;
|
|
697
|
+
const shouldSkipTestBlocks = !!rule.skipTestBlocks;
|
|
698
|
+
|
|
672
699
|
for (let i = 0; i < lines.length; i++) {
|
|
700
|
+
// skipComments: 跳过注释行(doc comments / 行注释 / 块注释内)
|
|
701
|
+
if (shouldSkipComments && commentLines[i]) {
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
// skipTestBlocks: 跳过内联测试模块(Rust #[cfg(test)] 块等)
|
|
705
|
+
if (shouldSkipTestBlocks && testBlockLines[i]) {
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
|
|
673
709
|
if (re.test(lines[i])) {
|
|
674
710
|
violations.push({
|
|
675
711
|
ruleId: rule.id || rule.name,
|
|
@@ -1179,6 +1215,134 @@ export class GuardCheckEngine {
|
|
|
1179
1215
|
return cached;
|
|
1180
1216
|
}
|
|
1181
1217
|
|
|
1218
|
+
/**
|
|
1219
|
+
* 构建内联测试块掩码
|
|
1220
|
+
* 目前支持 Rust #[cfg(test)] mod xxx { ... } 块
|
|
1221
|
+
* @param {string[]} lines
|
|
1222
|
+
* @param {string} language
|
|
1223
|
+
* @returns {boolean[]} 每行是否在测试块内
|
|
1224
|
+
*/
|
|
1225
|
+
_buildTestBlockMask(lines, language) {
|
|
1226
|
+
const mask = new Array(lines.length).fill(false);
|
|
1227
|
+
|
|
1228
|
+
// 目前仅 Rust 需要 — #[cfg(test)] 内联测试模块
|
|
1229
|
+
if (language !== 'rust') return mask;
|
|
1230
|
+
|
|
1231
|
+
let inTestBlock = false;
|
|
1232
|
+
let braceDepth = 0;
|
|
1233
|
+
|
|
1234
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1235
|
+
const trimmed = lines[i].trimStart();
|
|
1236
|
+
|
|
1237
|
+
if (!inTestBlock) {
|
|
1238
|
+
// 检测 #[cfg(test)] 属性行
|
|
1239
|
+
if (/^#\[cfg\(test\)\]/.test(trimmed)) {
|
|
1240
|
+
// 向后找 mod xxx { — 标记为测试块起始
|
|
1241
|
+
// 可能在同一行: #[cfg(test)] mod tests {
|
|
1242
|
+
// 也可能在下一行: mod tests {
|
|
1243
|
+
const restOfLine = trimmed.slice('#[cfg(test)]'.length).trim();
|
|
1244
|
+
if (/^mod\s+\w+/.test(restOfLine)) {
|
|
1245
|
+
// 同一行有 mod 声明
|
|
1246
|
+
inTestBlock = true;
|
|
1247
|
+
braceDepth = 0;
|
|
1248
|
+
// 计算本行的花括号
|
|
1249
|
+
for (const ch of lines[i]) {
|
|
1250
|
+
if (ch === '{') braceDepth++;
|
|
1251
|
+
else if (ch === '}') braceDepth--;
|
|
1252
|
+
}
|
|
1253
|
+
mask[i] = true;
|
|
1254
|
+
if (braceDepth <= 0) inTestBlock = false; // 单行 mod 声明 (mod tests;)
|
|
1255
|
+
continue;
|
|
1256
|
+
}
|
|
1257
|
+
// 检查下一行是否是 mod xxx {
|
|
1258
|
+
if (i + 1 < lines.length && /^\s*mod\s+\w+/.test(lines[i + 1])) {
|
|
1259
|
+
mask[i] = true; // #[cfg(test)] 行本身也标记
|
|
1260
|
+
inTestBlock = true;
|
|
1261
|
+
braceDepth = 0;
|
|
1262
|
+
// 下一行会在循环中处理
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
// 单行 #[cfg(test)] 但后面不是 mod — 不处理
|
|
1266
|
+
}
|
|
1267
|
+
} else {
|
|
1268
|
+
// 正在测试块内 — 追踪花括号深度
|
|
1269
|
+
mask[i] = true;
|
|
1270
|
+
for (const ch of lines[i]) {
|
|
1271
|
+
if (ch === '{') braceDepth++;
|
|
1272
|
+
else if (ch === '}') braceDepth--;
|
|
1273
|
+
}
|
|
1274
|
+
if (braceDepth <= 0) {
|
|
1275
|
+
inTestBlock = false; // 测试块结束
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
return mask;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/**
|
|
1284
|
+
* 构建注释行掩码 — 识别行注释和块注释内部行
|
|
1285
|
+
*
|
|
1286
|
+
* 支持的注释形式:
|
|
1287
|
+
* // 行注释, /// 文档注释, //! 内部文档注释 (C/Java/JS/TS/Go/Rust/Swift/Kotlin/Dart)
|
|
1288
|
+
* # 行注释 (Python)
|
|
1289
|
+
* /* ... * / 块注释 (C/Java/JS/TS/Go/Rust/Swift/Kotlin)
|
|
1290
|
+
* \"\"\" ... \"\"\" (Python doc-string — 简化: 整行以 \"\"\" 开头的行)
|
|
1291
|
+
*
|
|
1292
|
+
* @param {string[]} lines
|
|
1293
|
+
* @param {string} language
|
|
1294
|
+
* @returns {boolean[]} 每行是否为注释行
|
|
1295
|
+
*/
|
|
1296
|
+
_buildCommentMask(lines, language) {
|
|
1297
|
+
const mask = new Array(lines.length).fill(false);
|
|
1298
|
+
let inBlock = false; // 是否在 /* ... */ 块内
|
|
1299
|
+
|
|
1300
|
+
const usesHash = language === 'python'; // Python 用 # 注释
|
|
1301
|
+
const usesSlash = !usesHash; // 其他语言用 //
|
|
1302
|
+
|
|
1303
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1304
|
+
const trimmed = lines[i].trimStart();
|
|
1305
|
+
|
|
1306
|
+
// 块注释延续
|
|
1307
|
+
if (inBlock) {
|
|
1308
|
+
mask[i] = true;
|
|
1309
|
+
if (trimmed.includes('*/')) {
|
|
1310
|
+
inBlock = false;
|
|
1311
|
+
}
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// 块注释开始(同行不闭合)
|
|
1316
|
+
if (usesSlash && /^\s*\/\*/.test(lines[i])) {
|
|
1317
|
+
mask[i] = true;
|
|
1318
|
+
if (!trimmed.includes('*/')) {
|
|
1319
|
+
inBlock = true;
|
|
1320
|
+
}
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// 行注释: // 或 /// 或 //!
|
|
1325
|
+
if (usesSlash && /^\s*\/\//.test(lines[i])) {
|
|
1326
|
+
mask[i] = true;
|
|
1327
|
+
continue;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// Python 行注释: #
|
|
1331
|
+
if (usesHash && /^\s*#/.test(lines[i])) {
|
|
1332
|
+
mask[i] = true;
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// Python docstring 行 (简化: 整行以 """ 或 ''' 开头)
|
|
1337
|
+
if (usesHash && /^\s*("""|''')/.test(lines[i])) {
|
|
1338
|
+
mask[i] = true;
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
return mask;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1182
1346
|
/**
|
|
1183
1347
|
* 获取内置规则列表
|
|
1184
1348
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autosnippet",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.8",
|
|
4
4
|
"description": "Extract code patterns into a knowledge base for AI coding assistants",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/bootstrap.js",
|
|
@@ -88,9 +88,9 @@
|
|
|
88
88
|
"open": "^8.0.4",
|
|
89
89
|
"ora": "^8.0.1",
|
|
90
90
|
"socket.io": "^4.8.1",
|
|
91
|
-
"tree-sitter": "^0.25.0",
|
|
92
91
|
"undici": "^6.23.0",
|
|
93
92
|
"uuid": "^9.0.1",
|
|
93
|
+
"web-tree-sitter": "^0.25.0",
|
|
94
94
|
"winston": "^3.11.0",
|
|
95
95
|
"zod": "^4.3.6"
|
|
96
96
|
},
|
|
@@ -110,7 +110,8 @@
|
|
|
110
110
|
"resources/native-ui/combined-window.swift",
|
|
111
111
|
"resources/native-ui/native-ui",
|
|
112
112
|
"resources/native-ui/README.md",
|
|
113
|
-
"resources/openChrome.applescript"
|
|
113
|
+
"resources/openChrome.applescript",
|
|
114
|
+
"resources/grammars"
|
|
114
115
|
],
|
|
115
116
|
"directories": {
|
|
116
117
|
"doc": "docs",
|
|
@@ -126,45 +127,7 @@
|
|
|
126
127
|
"prettier": "^3.1.1",
|
|
127
128
|
"supertest": "^6.3.3"
|
|
128
129
|
},
|
|
129
|
-
"optionalDependencies": {
|
|
130
|
-
"tree-sitter-go": "^0.25.0",
|
|
131
|
-
"tree-sitter-java": "^0.23.5",
|
|
132
|
-
"tree-sitter-javascript": "^0.25.0",
|
|
133
|
-
"tree-sitter-kotlin": "^0.3.8",
|
|
134
|
-
"tree-sitter-objc": "^3.0.2",
|
|
135
|
-
"tree-sitter-python": "^0.25.0",
|
|
136
|
-
"tree-sitter-swift": "^0.7.1",
|
|
137
|
-
"tree-sitter-typescript": "^0.23.2",
|
|
138
|
-
"tree-sitter-rust": "^0.23.2"
|
|
139
|
-
},
|
|
140
130
|
"overrides": {
|
|
141
|
-
"tree-sitter-kotlin": {
|
|
142
|
-
"tree-sitter": "$tree-sitter"
|
|
143
|
-
},
|
|
144
|
-
"tree-sitter-java": {
|
|
145
|
-
"tree-sitter": "$tree-sitter"
|
|
146
|
-
},
|
|
147
|
-
"tree-sitter-python": {
|
|
148
|
-
"tree-sitter": "$tree-sitter"
|
|
149
|
-
},
|
|
150
|
-
"tree-sitter-javascript": {
|
|
151
|
-
"tree-sitter": "$tree-sitter"
|
|
152
|
-
},
|
|
153
|
-
"tree-sitter-typescript": {
|
|
154
|
-
"tree-sitter": "$tree-sitter"
|
|
155
|
-
},
|
|
156
|
-
"tree-sitter-swift": {
|
|
157
|
-
"tree-sitter": "$tree-sitter"
|
|
158
|
-
},
|
|
159
|
-
"tree-sitter-objc": {
|
|
160
|
-
"tree-sitter": "$tree-sitter"
|
|
161
|
-
},
|
|
162
|
-
"tree-sitter-rust": {
|
|
163
|
-
"tree-sitter": "$tree-sitter"
|
|
164
|
-
},
|
|
165
|
-
"tree-sitter-c": {
|
|
166
|
-
"tree-sitter": "$tree-sitter"
|
|
167
|
-
},
|
|
168
131
|
"minimatch": "^10.2.2",
|
|
169
132
|
"diff": "^8.0.3",
|
|
170
133
|
"glob": "^11.0.0"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|