@sun-asterisk/sunlint
Version:
☀️ SunLint - Multi-language static analysis tool for code quality and security | Sun* Engineering Standards
158 lines (135 loc) • 4.92 kB
JavaScript
/**
* C023 Symbol-based Analyzer - Advanced Do not declare duplicate variable
* Purpose: Use AST + Symbol Resolution to analyze log content quality in catch blocks
*/
const { SyntaxKind } = require('ts-morph');
class C023SymbolBasedAnalyzer {
constructor(semanticEngine = null) {
this.ruleId = 'C023';
this.ruleName = 'Error declare duplicate variable names in the same scope. (Symbol-Based)';
this.semanticEngine = semanticEngine;
this.verbose = false;
}
async initialize(semanticEngine = null) {
if (semanticEngine) {
this.semanticEngine = semanticEngine;
}
this.verbose = semanticEngine?.verbose || false;
if (process.env.SUNLINT_DEBUG) {
console.log(`🔧 [C023 Symbol-Based] Analyzer initialized, verbose: ${this.verbose}`);
}
}
async analyzeFileBasic(filePath, options = {}) {
// This is the main entry point called by the hybrid analyzer
return await this.analyzeFileWithSymbols(filePath, options);
}
async analyzeFileWithSymbols(filePath, options = {}) {
const violations = [];
// Enable verbose mode if requested
const verbose = options.verbose || this.verbose;
if (!this.semanticEngine?.project) {
if (verbose) {
console.warn('[C023 Symbol-Based] No semantic engine available, skipping analysis');
}
return violations;
}
if (verbose) {
console.log(`🔍 [C023 Symbol-Based] Starting analysis for ${filePath}`);
}
try {
const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
if (!sourceFile) {
return violations;
}
this.checkScope(sourceFile, violations);
if (verbose) {
console.log(`🔍 [C023 Symbol-Based] Total violations found: ${violations.length}`);
}
return violations;
} catch (error) {
if (verbose) {
console.warn(`[C023 Symbol-Based] Analysis failed for ${filePath}:`, error.message);
}
return violations;
}
}
checkScope(node, violations, parentSeen = new Map()) {
const seen = new Map(parentSeen);
node.forEachChild((child) => {
switch (child.getKind()) {
case SyntaxKind.VariableStatement: {
for (const decl of child.getDeclarations()) {
this.checkDuplicate(decl, seen, violations);
}
break;
}
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.Constructor: { // ✅ also cover constructors
const funcSeen = new Map();
child.getParameters().forEach((p) =>
this.checkDuplicate(p, funcSeen, violations)
);
const body = child.getBody && child.getBody();
if (body) this.checkScope(body, violations, funcSeen);
break;
}
case SyntaxKind.Block: {
this.checkScope(child, violations, new Map(seen));
break;
}
case SyntaxKind.CatchClause: {
const catchSeen = new Map(seen);
const catchVar = child.getVariableDeclaration();
if (catchVar) {
this.checkDuplicate(catchVar, catchSeen, violations);
}
this.checkScope(child.getBlock(), violations, catchSeen);
break;
}
case SyntaxKind.ForStatement:
case SyntaxKind.ForOfStatement:
case SyntaxKind.ForInStatement: {
const loopSeen = new Map(seen);
const initializer = child.getInitializer && child.getInitializer();
if (initializer && initializer.getDeclarations) {
initializer.getDeclarations().forEach((decl) =>
this.checkDuplicate(decl, loopSeen, violations)
);
}
const statement = child.getStatement && child.getStatement();
if (statement) this.checkScope(statement, violations, loopSeen);
break;
}
default: {
this.checkScope(child, violations, seen);
}
}
});
}
checkDuplicate(node, seen, violations) {
const nameNode = node.getNameNode();
const name = nameNode.getText();
if (!name) return;
const filePath = node.getSourceFile().getFilePath();
if (seen.has(name)) {
violations.push({
ruleId: this.ruleId,
severity: 'warning',
message: `Duplicate variable name found: "${name}"`,
source: this.ruleId,
file: filePath,
line: node.getStartLineNumber(),
column: node.getStart() - node.getStartLinePos(),
description: `[SYMBOL-BASED] Duplicate variable names can lead to confusion and bugs. Use unique names.`,
suggestion: 'Rename variables to ensure uniqueness',
category: 'naming'
});
} else {
seen.set(name, node);
}
}
}
module.exports = C023SymbolBasedAnalyzer;