mr-sliy 1.0.0

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 (50) hide show
  1. package/.env.example +145 -0
  2. package/database/schema.sql +187 -0
  3. package/package.json +74 -0
  4. package/scripts/download-tree-sitter.js +171 -0
  5. package/scripts/postinstall.js +134 -0
  6. package/src/agent/agent.js +563 -0
  7. package/src/agent.js +87 -0
  8. package/src/cli/index.js +1643 -0
  9. package/src/config/index.js +232 -0
  10. package/src/engine/dualModeEngine.js +486 -0
  11. package/src/index.js +165 -0
  12. package/src/middlewares/errorHandler.js +166 -0
  13. package/src/middlewares/index.js +23 -0
  14. package/src/routes/aiRoutes.js +117 -0
  15. package/src/routes/configRoutes.js +31 -0
  16. package/src/routes/index.js +75 -0
  17. package/src/routes/issueRoutes.js +195 -0
  18. package/src/routes/projectRoutes.js +46 -0
  19. package/src/routes/reportRoutes.js +40 -0
  20. package/src/routes/scanRoutes.js +245 -0
  21. package/src/routes/userRoutes.js +47 -0
  22. package/src/services/ast/parser.js +503 -0
  23. package/src/services/detection/detector.js +934 -0
  24. package/src/services/llm/providers.js +1107 -0
  25. package/src/services/rag/agent.js +375 -0
  26. package/src/services/vector/knowledgeBase.js +863 -0
  27. package/src/skills/Skill.js +38 -0
  28. package/src/skills/code-analysis/index.js +272 -0
  29. package/src/skills/code-detection/index.js +166 -0
  30. package/src/skills/code-detection/rules/console-log.js +45 -0
  31. package/src/skills/code-detection/rules/deep-nesting.js +76 -0
  32. package/src/skills/code-detection/rules/duplicate-code.js +57 -0
  33. package/src/skills/code-detection/rules/high-complexity.js +109 -0
  34. package/src/skills/code-detection/rules/index.js +59 -0
  35. package/src/skills/code-detection/rules/long-functions.js +54 -0
  36. package/src/skills/code-detection/rules/magic-numbers.js +48 -0
  37. package/src/skills/code-detection/rules/missing-comment.js +64 -0
  38. package/src/skills/code-detection/rules/null-check.js +71 -0
  39. package/src/skills/code-detection/rules/unnecessary-else.js +46 -0
  40. package/src/skills/code-detection/rules/unused-functions.js +57 -0
  41. package/src/skills/code-detection/rules/unused-imports.js +57 -0
  42. package/src/skills/code-detection/rules/unused-variables.js +54 -0
  43. package/src/skills/code-optimization/index.js +319 -0
  44. package/src/skills/index.js +152 -0
  45. package/src/utils/crypto.js +212 -0
  46. package/src/utils/database.js +125 -0
  47. package/src/utils/helpers.js +226 -0
  48. package/src/utils/logger.js +202 -0
  49. package/src/utils/mysql.js +198 -0
  50. package/src/utils/response.js +124 -0
@@ -0,0 +1,109 @@
1
+ const { generateUUID, getFileLanguage } = require('../../../utils/helpers');
2
+
3
+ const meta = {
4
+ id: 'high_complexity',
5
+ name: '圈复杂度检测',
6
+ description: '检测圈复杂度过高的函数',
7
+ category: 'code-smell',
8
+ defaultSeverity: 'medium'
9
+ };
10
+
11
+ const DEFAULT_THRESHOLD = 10;
12
+
13
+ function detect(tree, sourceCode, filePath, ruleConfig) {
14
+ const issues = [];
15
+ const threshold = ruleConfig.threshold || DEFAULT_THRESHOLD;
16
+
17
+ try {
18
+ const lines = sourceCode.split('\n');
19
+ const funcDeclarations = [];
20
+
21
+ for (let i = 0; i < lines.length; i++) {
22
+ const match = lines[i].trim().match(/function\s+(\w+)\s*\(/);
23
+ if (match) {
24
+ let braceCount = 0;
25
+ const hasOpenBrace = lines[i].includes('{');
26
+ if (hasOpenBrace) braceCount++;
27
+
28
+ let endLine = i;
29
+ for (let j = i + 1; j < lines.length; j++) {
30
+ const lineBraces = (lines[j].match(/\{/g) || []).length;
31
+ const closeBraces = (lines[j].match(/\}/g) || []).length;
32
+ braceCount += lineBraces - closeBraces;
33
+
34
+ if (braceCount <= 0) {
35
+ endLine = j;
36
+ break;
37
+ }
38
+ }
39
+
40
+ funcDeclarations.push({
41
+ name: match[1],
42
+ startLine: i,
43
+ endLine: endLine,
44
+ text: lines[i].trim()
45
+ });
46
+ }
47
+ }
48
+
49
+ funcDeclarations.forEach(funcDecl => {
50
+ let complexity = 1;
51
+
52
+ for (let i = funcDecl.startLine; i <= funcDecl.endLine; i++) {
53
+ const line = lines[i];
54
+ if (!line) continue;
55
+
56
+ const trimmed = line.trim();
57
+
58
+ if (trimmed.startsWith('if (') || trimmed.startsWith('if(')) {
59
+ complexity++;
60
+ }
61
+ if (trimmed.startsWith('for (')) {
62
+ complexity++;
63
+ }
64
+ if (trimmed.startsWith('while (')) {
65
+ complexity++;
66
+ }
67
+ if (trimmed.startsWith('case ')) {
68
+ complexity++;
69
+ }
70
+ if (trimmed.startsWith('catch (')) {
71
+ complexity++;
72
+ }
73
+
74
+ const andCount = (trimmed.match(/&&/g) || []).length;
75
+ const orCount = (trimmed.match(/\|\|/g) || []).length;
76
+ complexity += andCount + orCount;
77
+ }
78
+
79
+ if (complexity > threshold) {
80
+ issues.push({
81
+ id: generateUUID(),
82
+ filePath,
83
+ fileName: filePath.split(/[/\\]/).pop(),
84
+ language: getFileLanguage(filePath),
85
+ issueType: 'high_complexity',
86
+ severity: ruleConfig.severity || meta.defaultSeverity,
87
+ message: `函数"${funcDecl.name}"圈复杂度过高(${complexity})`,
88
+ suggestion: '简化逻辑结构,减少嵌套层级',
89
+ lineStart: funcDecl.startLine + 1,
90
+ lineEnd: funcDecl.endLine + 1,
91
+ columnStart: 0,
92
+ columnEnd: funcDecl.text.length,
93
+ codeSnippet: funcDecl.text.substring(0, 100),
94
+ astNodeType: 'function_declaration',
95
+ metadata: {
96
+ complexity,
97
+ threshold
98
+ }
99
+ });
100
+ }
101
+ });
102
+ } catch (error) {
103
+ console.error('检测圈复杂度失败:', error);
104
+ }
105
+
106
+ return issues;
107
+ }
108
+
109
+ module.exports = { meta, detect };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * 检测规则索引
3
+ * 所有检测规则的统一入口
4
+ */
5
+
6
+ const { config } = require('../../../config');
7
+
8
+ const rules = {
9
+ unused_variables: require('./unused-variables'),
10
+ unused_imports: require('./unused-imports'),
11
+ unused_functions: require('./unused-functions'),
12
+ magic_numbers: require('./magic-numbers'),
13
+ long_functions: require('./long-functions'),
14
+ high_complexity: require('./high-complexity'),
15
+ deep_nesting: require('./deep-nesting'),
16
+ null_check: require('./null-check'),
17
+ unnecessary_else: require('./unnecessary-else'),
18
+ console_log: require('./console-log'),
19
+ duplicate_code: require('./duplicate-code'),
20
+ missing_comment: require('./missing-comment')
21
+ };
22
+
23
+ function getRuleConfig(ruleId) {
24
+ const ruleConfigMap = {
25
+ unused_variables: { enabled: config.detection.unusedVariables, severity: 'medium' },
26
+ unused_imports: { enabled: config.detection.unusedImports, severity: 'low' },
27
+ unused_functions: { enabled: config.detection.unusedFunctions, severity: 'high' },
28
+ magic_numbers: { enabled: config.detection.magicNumbers, severity: 'low' },
29
+ long_functions: { enabled: true, severity: 'medium', threshold: config.detection.maxFunctionLines },
30
+ high_complexity: { enabled: true, severity: 'medium', threshold: config.detection.maxCyclomaticComplexity },
31
+ deep_nesting: { enabled: config.detection.enableDeepNestingCheck, severity: 'medium', threshold: config.detection.maxNestingDepth },
32
+ null_check: { enabled: config.detection.enableNullCheck, severity: 'high' },
33
+ unnecessary_else: { enabled: true, severity: 'low' },
34
+ console_log: { enabled: config.detection.enableConsoleLogCheck, severity: 'low' },
35
+ duplicate_code: { enabled: config.detection.enableDuplicateCodeCheck, severity: 'medium' },
36
+ missing_comment: { enabled: config.detection.enableCommentCheck, severity: 'low' }
37
+ };
38
+
39
+ return ruleConfigMap[ruleId] || { enabled: false, severity: 'low' };
40
+ }
41
+
42
+ function getAllRules() {
43
+ return Object.entries(rules).map(([id, rule]) => ({
44
+ id,
45
+ ...rule.meta,
46
+ config: getRuleConfig(id)
47
+ }));
48
+ }
49
+
50
+ function getEnabledRules() {
51
+ return getAllRules().filter(rule => rule.config.enabled);
52
+ }
53
+
54
+ module.exports = {
55
+ rules,
56
+ getRuleConfig,
57
+ getAllRules,
58
+ getEnabledRules
59
+ };
@@ -0,0 +1,54 @@
1
+ const { getNodesByType } = require('../../../services/ast/parser');
2
+ const { generateUUID, getFileLanguage } = require('../../../utils/helpers');
3
+
4
+ const meta = {
5
+ id: 'long_functions',
6
+ name: '过长函数检测',
7
+ description: '检测行数超过阈值的函数',
8
+ category: 'code-smell',
9
+ defaultSeverity: 'medium'
10
+ };
11
+
12
+ const DEFAULT_THRESHOLD = 50;
13
+
14
+ function detect(tree, sourceCode, filePath, ruleConfig) {
15
+ const issues = [];
16
+ const threshold = ruleConfig.threshold || DEFAULT_THRESHOLD;
17
+
18
+ try {
19
+ const functionDeclarations = getNodesByType(tree.rootNode, 'function_declaration');
20
+
21
+ functionDeclarations.forEach(funcDecl => {
22
+ const funcLines = funcDecl.endPosition.row - funcDecl.startPosition.row + 1;
23
+
24
+ if (funcLines > threshold) {
25
+ issues.push({
26
+ id: generateUUID(),
27
+ filePath,
28
+ fileName: filePath.split(/[/\\]/).pop(),
29
+ language: getFileLanguage(filePath),
30
+ issueType: 'long_function',
31
+ severity: ruleConfig.severity || meta.defaultSeverity,
32
+ message: `函数过长(${funcLines}行),建议拆分`,
33
+ suggestion: '将长函数拆分为多个小函数,每个函数负责单一职责',
34
+ lineStart: funcDecl.startPosition.row + 1,
35
+ lineEnd: funcDecl.endPosition.row + 1,
36
+ columnStart: funcDecl.startPosition.column,
37
+ columnEnd: funcDecl.endPosition.column,
38
+ codeSnippet: funcDecl.text.substring(0, 100),
39
+ astNodeType: 'function_declaration',
40
+ metadata: {
41
+ lineCount: funcLines,
42
+ threshold
43
+ }
44
+ });
45
+ }
46
+ });
47
+ } catch (error) {
48
+ console.error('检测过长函数失败:', error);
49
+ }
50
+
51
+ return issues;
52
+ }
53
+
54
+ module.exports = { meta, detect };
@@ -0,0 +1,48 @@
1
+ const { getNodesByType } = require('../../../services/ast/parser');
2
+ const { generateUUID, getFileLanguage } = require('../../../utils/helpers');
3
+
4
+ const meta = {
5
+ id: 'magic_numbers',
6
+ name: '魔法数字检测',
7
+ description: '检测代码中出现的魔法数字',
8
+ category: 'code-smell',
9
+ defaultSeverity: 'low'
10
+ };
11
+
12
+ function detect(tree, sourceCode, filePath, ruleConfig) {
13
+ const issues = [];
14
+
15
+ try {
16
+ const numberNodes = getNodesByType(tree.rootNode, 'number');
17
+ const magicThresholds = [100, 1000, 60, 24, 7, 30, 365, 86400];
18
+
19
+ numberNodes.forEach(numNode => {
20
+ const numValue = parseFloat(numNode.text);
21
+
22
+ if (magicThresholds.includes(numValue) || numValue > 1000) {
23
+ issues.push({
24
+ id: generateUUID(),
25
+ filePath,
26
+ fileName: filePath.split(/[/\\]/).pop(),
27
+ language: getFileLanguage(filePath),
28
+ issueType: 'magic_number',
29
+ severity: ruleConfig.severity || meta.defaultSeverity,
30
+ message: `发现魔法数字: ${numValue}`,
31
+ suggestion: '将魔法数字提取为常量并添加说明性名称',
32
+ lineStart: numNode.startPosition.row + 1,
33
+ lineEnd: numNode.endPosition.row + 1,
34
+ columnStart: numNode.startPosition.column,
35
+ columnEnd: numNode.endPosition.column,
36
+ codeSnippet: numNode.text,
37
+ astNodeType: 'number'
38
+ });
39
+ }
40
+ });
41
+ } catch (error) {
42
+ console.error('检测魔法数字失败:', error);
43
+ }
44
+
45
+ return issues;
46
+ }
47
+
48
+ module.exports = { meta, detect };
@@ -0,0 +1,64 @@
1
+ const { getNodesByType } = require('../../../services/ast/parser');
2
+ const { generateUUID, getFileLanguage } = require('../../../utils/helpers');
3
+
4
+ const meta = {
5
+ id: 'missing_comment',
6
+ name: '缺少注释检测',
7
+ description: '检测较长函数是否缺少注释说明',
8
+ category: 'code-smell',
9
+ defaultSeverity: 'low'
10
+ };
11
+
12
+ const DEFAULT_MIN_LINES = 10;
13
+
14
+ function detect(tree, sourceCode, filePath, ruleConfig) {
15
+ const issues = [];
16
+ const minLines = ruleConfig.threshold || DEFAULT_MIN_LINES;
17
+
18
+ try {
19
+ const functionDeclarations = getNodesByType(tree.rootNode, 'function_declaration');
20
+
21
+ functionDeclarations.forEach(funcDecl => {
22
+ const funcLines = funcDecl.endPosition.row - funcDecl.startPosition.row + 1;
23
+ if (funcLines > minLines) {
24
+ const lineNum = funcDecl.startPosition.row;
25
+ let hasComment = false;
26
+
27
+ if (lineNum > 0) {
28
+ const previousLine = sourceCode.split('\n')[lineNum - 1];
29
+ if (previousLine.trim().startsWith('//') || previousLine.trim().startsWith('/**') || previousLine.trim().startsWith('/*')) {
30
+ hasComment = true;
31
+ }
32
+ }
33
+
34
+ if (!hasComment) {
35
+ const funcNameMatch = funcDecl.text.match(/function\s+(\w+)/);
36
+ const funcName = funcNameMatch ? funcNameMatch[1] : 'anonymous';
37
+
38
+ issues.push({
39
+ id: generateUUID(),
40
+ filePath,
41
+ fileName: filePath.split(/[/\\]/).pop(),
42
+ language: getFileLanguage(filePath),
43
+ issueType: 'missing_comment',
44
+ severity: ruleConfig.severity || meta.defaultSeverity,
45
+ message: `函数"${funcName}"缺少注释说明`,
46
+ suggestion: '添加函数说明注释,提高代码可读性',
47
+ lineStart: funcDecl.startPosition.row + 1,
48
+ lineEnd: funcDecl.endPosition.row + 1,
49
+ columnStart: funcDecl.startPosition.column,
50
+ columnEnd: funcDecl.endPosition.column,
51
+ codeSnippet: funcDecl.text.substring(0, 50),
52
+ astNodeType: 'function_declaration'
53
+ });
54
+ }
55
+ }
56
+ });
57
+ } catch (error) {
58
+ console.error('检测缺少注释失败:', error);
59
+ }
60
+
61
+ return issues;
62
+ }
63
+
64
+ module.exports = { meta, detect };
@@ -0,0 +1,71 @@
1
+ const { getNodesByType, traverseAST } = require('../../../services/ast/parser');
2
+ const { generateUUID, getFileLanguage } = require('../../../utils/helpers');
3
+
4
+ const meta = {
5
+ id: 'null_check',
6
+ name: '空值检查检测',
7
+ description: '检测函数参数缺少空值检查的情况',
8
+ category: 'bug-risk',
9
+ defaultSeverity: 'high'
10
+ };
11
+
12
+ function detect(tree, sourceCode, filePath, ruleConfig) {
13
+ const issues = [];
14
+
15
+ try {
16
+ const functionDeclarations = getNodesByType(tree.rootNode, 'function_declaration');
17
+
18
+ functionDeclarations.forEach(funcDecl => {
19
+ const identifiers = getNodesByType(funcDecl, 'identifier');
20
+ const params = funcDecl.text.match(/function\s+\w+\s*\(([^)]*)\)/);
21
+
22
+ if (params) {
23
+ const paramNames = params[1].split(',').map(p => p.trim()).filter(p => p.length > 0);
24
+
25
+ paramNames.forEach(param => {
26
+ const paramUsages = identifiers.filter(id => id.text === param);
27
+ if (paramUsages.length > 1) {
28
+ let hasNullCheck = false;
29
+
30
+ traverseAST(funcDecl, (node) => {
31
+ if (node.type === 'if_statement') {
32
+ if (node.text.includes(`!${param}`) || node.text.includes(`${param} !== null`) ||
33
+ node.text.includes(`${param} !== undefined`) || node.text.includes(`${param} != null`)) {
34
+ hasNullCheck = true;
35
+ }
36
+ }
37
+ });
38
+
39
+ if (!hasNullCheck && paramUsages.length > 2) {
40
+ issues.push({
41
+ id: generateUUID(),
42
+ filePath,
43
+ fileName: filePath.split(/[/\\]/).pop(),
44
+ language: getFileLanguage(filePath),
45
+ issueType: 'null_check',
46
+ severity: ruleConfig.severity || meta.defaultSeverity,
47
+ message: `参数"${param}"缺少空值检查`,
48
+ suggestion: '添加空值检查以防止运行时错误',
49
+ lineStart: funcDecl.startPosition.row + 1,
50
+ lineEnd: funcDecl.endPosition.row + 1,
51
+ columnStart: funcDecl.startPosition.column,
52
+ columnEnd: funcDecl.endPosition.column,
53
+ codeSnippet: funcDecl.text.substring(0, 100),
54
+ astNodeType: 'function_declaration',
55
+ metadata: {
56
+ paramName: param
57
+ }
58
+ });
59
+ }
60
+ }
61
+ });
62
+ }
63
+ });
64
+ } catch (error) {
65
+ console.error('检测空值检查失败:', error);
66
+ }
67
+
68
+ return issues;
69
+ }
70
+
71
+ module.exports = { meta, detect };
@@ -0,0 +1,46 @@
1
+ const { traverseAST } = require('../../../services/ast/parser');
2
+ const { generateUUID, getFileLanguage } = require('../../../utils/helpers');
3
+
4
+ const meta = {
5
+ id: 'unnecessary_else',
6
+ name: '不必要else检测',
7
+ description: '检测return语句后不必要的else语句',
8
+ category: 'code-smell',
9
+ defaultSeverity: 'low'
10
+ };
11
+
12
+ function detect(tree, sourceCode, filePath, ruleConfig) {
13
+ const issues = [];
14
+
15
+ try {
16
+ traverseAST(tree.rootNode, (node) => {
17
+ if (node.type === 'if_statement' && node.text.includes('else')) {
18
+ const ifBody = node.text.split('else')[0];
19
+ if (ifBody.includes('return ') || ifBody.includes('throw ')) {
20
+ issues.push({
21
+ id: generateUUID(),
22
+ filePath,
23
+ fileName: filePath.split(/[/\\]/).pop(),
24
+ language: getFileLanguage(filePath),
25
+ issueType: 'unnecessary_else',
26
+ severity: ruleConfig.severity || meta.defaultSeverity,
27
+ message: 'return后使用不必要的else',
28
+ suggestion: '删除return后的else语句以简化代码',
29
+ lineStart: node.startPosition.row + 1,
30
+ lineEnd: node.endPosition.row + 1,
31
+ columnStart: node.startPosition.column,
32
+ columnEnd: node.endPosition.column,
33
+ codeSnippet: node.text.substring(0, 80),
34
+ astNodeType: 'if_statement'
35
+ });
36
+ }
37
+ }
38
+ });
39
+ } catch (error) {
40
+ console.error('检测不必要else失败:', error);
41
+ }
42
+
43
+ return issues;
44
+ }
45
+
46
+ module.exports = { meta, detect };
@@ -0,0 +1,57 @@
1
+ const { getNodesByType } = require('../../../services/ast/parser');
2
+ const { generateUUID, getFileLanguage } = require('../../../utils/helpers');
3
+
4
+ const meta = {
5
+ id: 'unused_functions',
6
+ name: '未使用函数检测',
7
+ description: '检测已定义但从未被调用的函数',
8
+ category: 'code-smell',
9
+ defaultSeverity: 'high'
10
+ };
11
+
12
+ function detect(tree, sourceCode, filePath, ruleConfig) {
13
+ const issues = [];
14
+
15
+ try {
16
+ const functionDeclarations = getNodesByType(tree.rootNode, 'function_declaration');
17
+ const identifiers = getNodesByType(tree.rootNode, 'identifier');
18
+
19
+ functionDeclarations.forEach(funcDecl => {
20
+ const funcNameMatch = funcDecl.text.match(/function\s+(\w+)\s*\(/);
21
+
22
+ if (funcNameMatch) {
23
+ const funcName = funcNameMatch[1];
24
+
25
+ const isCalled = identifiers.some(id =>
26
+ id.text === funcName &&
27
+ id.startPosition.row !== funcDecl.startPosition.row
28
+ );
29
+
30
+ if (!isCalled) {
31
+ issues.push({
32
+ id: generateUUID(),
33
+ filePath,
34
+ fileName: filePath.split(/[/\\]/).pop(),
35
+ language: getFileLanguage(filePath),
36
+ issueType: 'unused_function',
37
+ severity: ruleConfig.severity || meta.defaultSeverity,
38
+ message: `函数"${funcName}"已定义但从未被调用`,
39
+ suggestion: '删除未使用的函数或确认是否应该在代码中使用它',
40
+ lineStart: funcDecl.startPosition.row + 1,
41
+ lineEnd: funcDecl.endPosition.row + 1,
42
+ columnStart: funcDecl.startPosition.column,
43
+ columnEnd: funcDecl.endPosition.column,
44
+ codeSnippet: funcDecl.text.substring(0, 100),
45
+ astNodeType: 'function_declaration'
46
+ });
47
+ }
48
+ }
49
+ });
50
+ } catch (error) {
51
+ console.error('检测未使用函数失败:', error);
52
+ }
53
+
54
+ return issues;
55
+ }
56
+
57
+ module.exports = { meta, detect };
@@ -0,0 +1,57 @@
1
+ const { getNodesByType } = require('../../../services/ast/parser');
2
+ const { generateUUID, getFileLanguage } = require('../../../utils/helpers');
3
+
4
+ const meta = {
5
+ id: 'unused_imports',
6
+ name: '未使用导入检测',
7
+ description: '检测已声明但从未使用的导入',
8
+ category: 'code-smell',
9
+ defaultSeverity: 'low'
10
+ };
11
+
12
+ function detect(tree, sourceCode, filePath, ruleConfig) {
13
+ const issues = [];
14
+
15
+ try {
16
+ const importStatements = getNodesByType(tree.rootNode, 'import_statement');
17
+ const identifiers = getNodesByType(tree.rootNode, 'identifier');
18
+
19
+ importStatements.forEach(importStmt => {
20
+ const importMatch = importStmt.text.match(/import\s+(\w+)\s+from/);
21
+
22
+ if (importMatch) {
23
+ const importedName = importMatch[1];
24
+
25
+ const isUsed = identifiers.some(id =>
26
+ id.text === importedName &&
27
+ id.startPosition.row !== importStmt.startPosition.row
28
+ );
29
+
30
+ if (!isUsed) {
31
+ issues.push({
32
+ id: generateUUID(),
33
+ filePath,
34
+ fileName: filePath.split(/[/\\]/).pop(),
35
+ language: getFileLanguage(filePath),
36
+ issueType: 'unused_import',
37
+ severity: ruleConfig.severity || meta.defaultSeverity,
38
+ message: `导入"${importedName}"已声明但从未使用`,
39
+ suggestion: '删除未使用的导入以减少打包体积',
40
+ lineStart: importStmt.startPosition.row + 1,
41
+ lineEnd: importStmt.endPosition.row + 1,
42
+ columnStart: importStmt.startPosition.column,
43
+ columnEnd: importStmt.endPosition.column,
44
+ codeSnippet: importStmt.text,
45
+ astNodeType: 'import_statement'
46
+ });
47
+ }
48
+ }
49
+ });
50
+ } catch (error) {
51
+ console.error('检测未使用导入失败:', error);
52
+ }
53
+
54
+ return issues;
55
+ }
56
+
57
+ module.exports = { meta, detect };
@@ -0,0 +1,54 @@
1
+ const { getNodesByType } = require('../../../services/ast/parser');
2
+ const { generateUUID, getFileLanguage } = require('../../../utils/helpers');
3
+
4
+ const meta = {
5
+ id: 'unused_variables',
6
+ name: '未使用变量检测',
7
+ description: '检测已声明但从未使用的变量',
8
+ category: 'code-smell',
9
+ defaultSeverity: 'medium'
10
+ };
11
+
12
+ function detect(tree, sourceCode, filePath, ruleConfig) {
13
+ const issues = [];
14
+
15
+ try {
16
+ const variableDeclarations = getNodesByType(tree.rootNode, 'variable_declarator');
17
+ const identifiers = getNodesByType(tree.rootNode, 'identifier');
18
+
19
+ variableDeclarations.forEach(varDecl => {
20
+ const varName = varDecl.text.split('=')[0].trim();
21
+
22
+ const isUsed = identifiers.some(id => {
23
+ return id.text === varName &&
24
+ (id.startPosition.row !== varDecl.startPosition.row ||
25
+ id.startPosition.column !== varDecl.startPosition.column);
26
+ });
27
+
28
+ if (!isUsed) {
29
+ issues.push({
30
+ id: generateUUID(),
31
+ filePath,
32
+ fileName: filePath.split(/[/\\]/).pop(),
33
+ language: getFileLanguage(filePath),
34
+ issueType: 'unused_variable',
35
+ severity: ruleConfig.severity || meta.defaultSeverity,
36
+ message: `变量"${varName}"已声明但从未使用`,
37
+ suggestion: '删除未使用的变量或根据需要使用它',
38
+ lineStart: varDecl.startPosition.row + 1,
39
+ lineEnd: varDecl.endPosition.row + 1,
40
+ columnStart: varDecl.startPosition.column,
41
+ columnEnd: varDecl.endPosition.column,
42
+ codeSnippet: varDecl.text,
43
+ astNodeType: 'variable_declarator'
44
+ });
45
+ }
46
+ });
47
+ } catch (error) {
48
+ console.error('检测未使用变量失败:', error);
49
+ }
50
+
51
+ return issues;
52
+ }
53
+
54
+ module.exports = { meta, detect };