import { CodeParser, ParsedFile, ImportInfo, ApiCallInfo } from '../parsers/index.js';
import { ModernizationRules, ModernizationSuggestion, PerformanceImpact } from '../data/modernization-rules.js';
// Re-export types for external use
export { ModernizationSuggestion, PerformanceImpact } from '../data/modernization-rules.js';
/**
* 現代化分析結果
*/
export interface ModernizationAnalysis {
summary: {
totalFiles: number;
totalSuggestions: number;
potentialPerformanceGain: number;
bundleSizeReduction: number;
};
suggestions: ModernizationSuggestion[];
fileAnalysis: FileModernizationResult[];
riskAssessment: RiskAssessment;
}
/**
* 單一檔案的現代化結果
*/
export interface FileModernizationResult {
filePath: string;
suggestions: ModernizationSuggestion[];
legacyPatterns: LegacyPattern[];
modernizableApis: ModernizableApi[];
}
/**
* 過時的程式碼模式
*/
export interface LegacyPattern {
type: 'deprecated-api' | 'outdated-library' | 'inefficient-pattern' | 'compatibility-hack';
pattern: string;
description: string;
location: { line: number; column: number };
severity: 'low' | 'medium' | 'high';
}
/**
* 可現代化的 API
*/
export interface ModernizableApi {
currentApi: string;
modernApi: string;
reason: string;
compatibility: string;
migrationComplexity: 'trivial' | 'simple' | 'moderate' | 'complex';
location: { line: number; column: number };
}
/**
* 風險評估
*/
export interface RiskAssessment {
overallRisk: 'low' | 'medium' | 'high';
breakingChanges: BreakingChange[];
compatibilityIssues: CompatibilityIssue[];
migrationEffort: {
estimatedHours: number;
complexity: 'low' | 'medium' | 'high';
priority: 'low' | 'medium' | 'high';
};
}
export interface BreakingChange {
type: string;
description: string;
impact: 'minor' | 'major' | 'critical';
mitigation: string;
}
export interface CompatibilityIssue {
api: string;
browsers: string[];
workaround?: string;
}
/**
* 程式碼現代化分析器
* 掃描程式碼找出可現代化的部分,提供升級建議
*/
export class ModernizationAnalyzer {
private parser: CodeParser;
private rules: ModernizationRules;
constructor(parser: CodeParser) {
this.parser = parser;
this.rules = new ModernizationRules();
}
/**
* 分析專案的現代化機會
*/
async analyze(
projectPath: string,
includePatterns: string[],
excludePatterns: string[]
): Promise<ModernizationAnalysis> {
// 解析專案檔案
const parsedFiles = await this.parser.parseProject(projectPath, includePatterns, excludePatterns);
// 分析每個檔案
const fileAnalysis: FileModernizationResult[] = [];
const allSuggestions: ModernizationSuggestion[] = [];
for (const file of parsedFiles) {
const result = this.analyzeFile(file);
fileAnalysis.push(result);
allSuggestions.push(...result.suggestions);
}
// 彙總分析結果
const summary = this.generateSummary(fileAnalysis, allSuggestions);
const riskAssessment = this.assessRisk(allSuggestions);
return {
summary,
suggestions: allSuggestions,
fileAnalysis,
riskAssessment
};
}
/**
* 分析單一檔案
*/
private analyzeFile(file: ParsedFile): FileModernizationResult {
const suggestions: ModernizationSuggestion[] = [];
const legacyPatterns: LegacyPattern[] = [];
const modernizableApis: ModernizableApi[] = [];
// 分析 imports - 檢查過時的函式庫
this.analyzeImports(file.imports, suggestions, legacyPatterns);
// 分析 API 呼叫 - 檢查可現代化的 API
this.analyzeApiCalls(file.apiCalls, suggestions, modernizableApis);
// 分析 AST 模式 - 檢查程式碼模式
this.analyzeCodePatterns(file.ast, suggestions, legacyPatterns);
return {
filePath: file.filePath,
suggestions,
legacyPatterns,
modernizableApis
};
}
/**
* 分析 import 語句
*/
private analyzeImports(
imports: ImportInfo[],
suggestions: ModernizationSuggestion[],
legacyPatterns: LegacyPattern[]
) {
for (const imp of imports) {
const rule = this.rules.getLibraryRule(imp.source);
if (rule) {
suggestions.push({
id: `modernize-${imp.source}`,
type: 'library-replacement',
title: `升級 ${imp.source} 到 ${rule.modernAlternative}`,
description: rule.reason,
currentCode: `import ... from '${imp.source}';`,
modernCode: rule.migrationExample,
location: imp.loc,
impact: rule.impact,
difficulty: rule.difficulty,
breaking: rule.breaking
});
if (rule.deprecated) {
legacyPatterns.push({
type: 'outdated-library',
pattern: imp.source,
description: `${imp.source} 已過時,建議使用 ${rule.modernAlternative}`,
location: imp.loc || { line: 0, column: 0 },
severity: rule.severity || 'medium'
});
}
}
}
}
/**
* 分析 API 呼叫
*/
private analyzeApiCalls(
apiCalls: ApiCallInfo[],
suggestions: ModernizationSuggestion[],
modernizableApis: ModernizableApi[]
) {
for (const call of apiCalls) {
const apiName = call.method ? `${call.api}.${call.method}` : call.api;
const rule = this.rules.getApiRule(apiName);
if (rule) {
suggestions.push({
id: `api-${apiName}`,
type: 'api-modernization',
title: `使用現代 ${rule.modernAlternative} 替代 ${apiName}`,
description: rule.reason,
currentCode: this.generateApiCallCode(call),
modernCode: rule.migrationExample,
location: call.loc,
impact: rule.impact,
difficulty: rule.difficulty,
breaking: rule.breaking
});
modernizableApis.push({
currentApi: apiName,
modernApi: rule.modernAlternative,
reason: rule.reason,
compatibility: rule.compatibility || '現代瀏覽器',
migrationComplexity: rule.difficulty,
location: call.loc || { line: 0, column: 0 }
});
}
}
}
/**
* 分析程式碼模式
*/
private analyzeCodePatterns(
ast: any,
suggestions: ModernizationSuggestion[],
legacyPatterns: LegacyPattern[]
) {
// 檢查常見的過時模式
this.findCallbackPatterns(ast, suggestions);
this.findVarDeclarations(ast, suggestions, legacyPatterns);
this.findIIFEPatterns(ast, suggestions);
this.findForLoopPatterns(ast, suggestions);
}
/**
* 檢查回調函式模式,建議使用 Promise/async-await
*/
private findCallbackPatterns(ast: any, suggestions: ModernizationSuggestion[]) {
this.traverseAST(ast, (node) => {
if (node.type === 'CallExpression' && node.arguments?.length > 0) {
const lastArg = node.arguments[node.arguments.length - 1];
// 檢測函式作為最後一個參數的回調模式
if (lastArg?.type === 'FunctionExpression' && !lastArg.async) {
const params = lastArg.params || [];
// 檢查是否是 error-first callback 模式 (err, result)
const isErrorFirstCallback = params.length >= 2 &&
params[0]?.name?.toLowerCase().match(/^(err|error|e)$/);
// 檢查是否是常見的異步 API 回調
const calleeName = this.getCalleeName(node.callee);
const asyncPatterns = [
'readFile', 'writeFile', 'readdir', 'stat', 'mkdir', 'unlink',
'request', 'get', 'post', 'put', 'delete',
'query', 'execute', 'find', 'save',
'setTimeout', 'setInterval'
];
const isAsyncPattern = asyncPatterns.some(p =>
calleeName.toLowerCase().includes(p.toLowerCase())
);
if (isErrorFirstCallback || isAsyncPattern) {
const funcName = this.getCalleeName(node.callee);
suggestions.push({
id: `callback-to-promise-${node.loc?.start?.line || 0}`,
type: 'pattern-modernization',
title: '將回調函式轉換為 Promise/async-await',
description: isErrorFirstCallback
? '偵測到 error-first callback 模式,建議使用 Promise 包裝或使用原生 Promise API'
: `偵測到 ${funcName} 使用回調模式,建議使用 Promise 版本的 API`,
currentCode: `${funcName}(..., function(${params.map((p: any) => p.name || 'arg').join(', ')}) {\n // callback logic\n});`,
modernCode: isErrorFirstCallback
? `// 使用 Promise 包裝\nconst result = await new Promise((resolve, reject) => {\n ${funcName}(..., (err, result) => {\n if (err) reject(err);\n else resolve(result);\n });\n});\n\n// 或使用 util.promisify (Node.js)\nimport { promisify } from 'util';\nconst ${funcName}Async = promisify(${funcName});\nconst result = await ${funcName}Async(...);`
: `// 使用 async/await\nconst result = await ${funcName}Async(...);\n\n// 或使用 Promise\n${funcName}(...).then(result => {\n // handle result\n}).catch(err => {\n // handle error\n});`,
location: node.loc ? { line: node.loc.start.line, column: node.loc.start.column } : undefined,
impact: { performance: 0, bundle: 0, maintainability: 4 },
difficulty: 'moderate',
breaking: true
});
}
}
}
});
}
/**
* 取得呼叫者名稱
*/
private getCalleeName(callee: any): string {
if (!callee) return 'unknown';
if (callee.type === 'Identifier') {
return callee.name || 'unknown';
}
if (callee.type === 'MemberExpression') {
const obj = this.getCalleeName(callee.object);
const prop = callee.property?.name || callee.property?.value || 'unknown';
return `${obj}.${prop}`;
}
return 'unknown';
}
/**
* 檢查 var 宣告,建議使用 let/const
*/
private findVarDeclarations(
ast: any,
suggestions: ModernizationSuggestion[],
legacyPatterns: LegacyPattern[]
) {
this.traverseAST(ast, (node) => {
if (node.type === 'VariableDeclaration' && node.kind === 'var') {
const suggestion = node.declarations?.some((d: any) =>
d.init && (d.init.type === 'FunctionExpression' || d.init.type === 'ArrowFunctionExpression')
) ? 'const' : 'let';
suggestions.push({
id: `var-to-${suggestion}`,
type: 'syntax-modernization',
title: `將 var 改為 ${suggestion}`,
description: `使用 ${suggestion} 可提供更好的作用域控制和意圖表達`,
currentCode: `var ${node.declarations?.[0]?.id?.name || 'variable'} = ...`,
modernCode: `${suggestion} ${node.declarations?.[0]?.id?.name || 'variable'} = ...`,
location: node.loc ? { line: node.loc.start.line, column: node.loc.start.column } : undefined,
impact: { performance: 0, bundle: 0, maintainability: 2 },
difficulty: 'trivial',
breaking: false
});
legacyPatterns.push({
type: 'inefficient-pattern',
pattern: 'var declaration',
description: '使用 var 宣告變數,建議改用 let 或 const',
location: node.loc ? { line: node.loc.start.line, column: node.loc.start.column } : { line: 0, column: 0 },
severity: 'low'
});
}
});
}
/**
* 檢查 IIFE 模式,建議使用模組
*/
private findIIFEPatterns(ast: any, suggestions: ModernizationSuggestion[]) {
this.traverseAST(ast, (node) => {
// 檢測 IIFE: (function() { ... })() 或 (() => { ... })()
if (node.type === 'CallExpression') {
const callee = node.callee;
// 檢查是否是 IIFE 模式
const isIIFE = (
// (function() {})()
(callee.type === 'FunctionExpression') ||
// (() => {})()
(callee.type === 'ArrowFunctionExpression') ||
// (function() {})() 被括號包裹
(callee.type === 'SequenceExpression' &&
callee.expressions?.some((e: any) =>
e.type === 'FunctionExpression' || e.type === 'ArrowFunctionExpression'
))
);
if (isIIFE) {
// 檢查 IIFE 內部是否有 window.xxx = 或 global.xxx = 的賦值(模組模式)
const hasModulePattern = this.checkForModulePattern(callee.body);
// 檢查是否只是用於隔離作用域
const hasPrivateVars = this.checkForPrivateVariables(callee.body);
suggestions.push({
id: `iife-to-module-${node.loc?.start?.line || 0}`,
type: 'pattern-modernization',
title: '將 IIFE 轉換為 ES6 模組',
description: hasModulePattern
? '偵測到 IIFE 模組模式 (將方法掛載到 window/global),建議轉換為 ES6 模組的 export/import'
: hasPrivateVars
? '偵測到 IIFE 用於隔離私有變數,在 ES6 模組中每個檔案已有獨立作用域,可直接使用模組'
: '偵測到 IIFE,如果用於隔離作用域,可考慮使用區塊作用域 ({ let/const }) 或模組',
currentCode: `(function() {
var privateVar = 'secret';
${hasModulePattern ? "window.MyModule = { ... };" : "// private logic"}
})();`,
modernCode: hasModulePattern
? `// myModule.js
const privateVar = 'secret';
export function publicMethod() {
return privateVar;
}
// main.js
import { publicMethod } from './myModule.js';`
: `// 使用區塊作用域
{
const privateVar = 'secret';
// logic here
}
// 或使用 ES6 模組 (推薦)
// module.js
const privateVar = 'secret';
export const result = doSomething(privateVar);`,
location: node.loc ? { line: node.loc.start.line, column: node.loc.start.column } : undefined,
impact: { performance: 0, bundle: 0, maintainability: 4 },
difficulty: hasModulePattern ? 'moderate' : 'simple',
breaking: hasModulePattern
});
}
}
});
}
/**
* 檢查是否有模組模式 (window.xxx = 或 global.xxx =)
*/
private checkForModulePattern(body: any): boolean {
if (!body) return false;
let hasModulePattern = false;
this.traverseAST(body, (node) => {
if (node.type === 'AssignmentExpression' &&
node.left?.type === 'MemberExpression') {
const obj = node.left.object;
if (obj?.type === 'Identifier' &&
(obj.name === 'window' || obj.name === 'global' || obj.name === 'self')) {
hasModulePattern = true;
}
}
});
return hasModulePattern;
}
/**
* 檢查是否有私有變數宣告
*/
private checkForPrivateVariables(body: any): boolean {
if (!body) return false;
let hasPrivateVars = false;
this.traverseAST(body, (node) => {
if (node.type === 'VariableDeclaration') {
hasPrivateVars = true;
}
});
return hasPrivateVars;
}
/**
* 檢查 for 迴圈,建議使用現代迭代方法
*/
private findForLoopPatterns(ast: any, suggestions: ModernizationSuggestion[]) {
this.traverseAST(ast, (node) => {
// 檢測傳統 for 迴圈: for (let i = 0; i < arr.length; i++)
if (node.type === 'ForStatement') {
const analysis = this.analyzeForLoop(node);
if (analysis.isConvertible) {
suggestions.push({
id: `for-to-${analysis.suggestedMethod}-${node.loc?.start?.line || 0}`,
type: 'pattern-modernization',
title: `將 for 迴圈轉換為 ${analysis.suggestedMethod}()`,
description: analysis.reason || `傳統 for 迴圈可使用陣列方法 ${analysis.suggestedMethod}() 取代,程式碼更簡潔、可讀性更高`,
currentCode: `for (let i = 0; i < ${analysis.arrayName || 'array'}.length; i++) {
${analysis.loopAction || '// loop body'}
}`,
modernCode: this.generateModernLoopCode(analysis),
location: node.loc ? { line: node.loc.start.line, column: node.loc.start.column } : undefined,
impact: { performance: 0, bundle: 0, maintainability: 4 },
difficulty: 'simple',
breaking: false
});
}
}
// 檢測 for...in 迴圈用於陣列 (反模式)
if (node.type === 'ForInStatement') {
suggestions.push({
id: `for-in-to-for-of-${node.loc?.start?.line || 0}`,
type: 'pattern-modernization',
title: '考慮將 for...in 改為 for...of 或陣列方法',
description: 'for...in 會遍歷所有可枚舉屬性(包括原型鏈),對於陣列應使用 for...of 或陣列方法',
currentCode: `for (const key in object) {
// iterating
}`,
modernCode: `// 對於陣列,使用 for...of
for (const item of array) {
// process item
}
// 對於物件,使用 Object.entries/keys/values
for (const [key, value] of Object.entries(object)) {
// process key and value
}
// 或使用 forEach
Object.entries(object).forEach(([key, value]) => {
// process key and value
});`,
location: node.loc ? { line: node.loc.start.line, column: node.loc.start.column } : undefined,
impact: { performance: 5, bundle: 0, maintainability: 3 },
difficulty: 'simple',
breaking: false
});
}
});
}
/**
* 分析 for 迴圈是否可轉換
*/
private analyzeForLoop(node: any): {
isConvertible: boolean;
suggestedMethod: 'map' | 'filter' | 'forEach' | 'reduce' | 'find' | 'some' | 'every';
reason?: string;
arrayName?: string;
loopAction?: string;
} {
const { init, test, update, body } = node;
// 檢查基本結構
if (!init || !test || !update || !body) {
return { isConvertible: false, suggestedMethod: 'forEach' };
}
// 檢查是否是標準的 for (let i = 0; i < arr.length; i++) 模式
// 初始化: let/var i = 0
if (init.type !== 'VariableDeclaration' ||
!init.declarations?.[0]?.init ||
init.declarations[0].init.value !== 0) {
return { isConvertible: false, suggestedMethod: 'forEach' };
}
const iteratorName = init.declarations[0].id?.name;
// 測試條件: i < arr.length
let arrayName = '';
if (test.type === 'BinaryExpression' && (test.operator === '<' || test.operator === '<=')) {
if (test.right?.type === 'MemberExpression' &&
test.right.property?.name === 'length') {
arrayName = this.getCalleeName(test.right.object);
}
}
if (!arrayName) {
return { isConvertible: false, suggestedMethod: 'forEach' };
}
// 分析迴圈體
const bodyStatements = body.type === 'BlockStatement' ? body.body : [body];
let hasPush = false;
let hasConditional = false;
let hasReturn = false;
let hasBreak = false;
let pushTarget = '';
let loopAction = '';
this.traverseAST(body, (childNode) => {
// 檢查 push 操作
if (childNode.type === 'CallExpression' &&
childNode.callee?.type === 'MemberExpression' &&
childNode.callee.property?.name === 'push') {
hasPush = true;
pushTarget = this.getCalleeName(childNode.callee.object);
}
// 檢查條件判斷
if (childNode.type === 'IfStatement') {
hasConditional = true;
}
// 檢查提前返回
if (childNode.type === 'ReturnStatement') {
hasReturn = true;
}
// 檢查 break
if (childNode.type === 'BreakStatement') {
hasBreak = true;
}
});
// 根據模式決定建議的方法
if (hasReturn || hasBreak) {
return {
isConvertible: true,
suggestedMethod: 'find',
reason: '迴圈包含提前退出,可能適合使用 find()、some() 或 every()',
arrayName,
loopAction: 'if (condition) return item;'
};
}
if (hasPush && hasConditional) {
return {
isConvertible: true,
suggestedMethod: 'filter',
reason: '迴圈包含條件判斷後 push 元素,適合使用 filter()',
arrayName,
loopAction: `if (condition) ${pushTarget}.push(${arrayName}[i]);`
};
}
if (hasPush) {
return {
isConvertible: true,
suggestedMethod: 'map',
reason: '迴圈將處理結果 push 到新陣列,適合使用 map()',
arrayName,
loopAction: `${pushTarget}.push(transform(${arrayName}[i]));`
};
}
return {
isConvertible: true,
suggestedMethod: 'forEach',
reason: '標準迭代迴圈,可使用 forEach() 或 for...of',
arrayName,
loopAction: `// process ${arrayName}[i]`
};
}
/**
* 生成現代迴圈程式碼
*/
private generateModernLoopCode(analysis: {
suggestedMethod: string;
arrayName?: string;
}): string {
const arr = analysis.arrayName || 'array';
switch (analysis.suggestedMethod) {
case 'map':
return `const result = ${arr}.map(item => {
// transform item
return transformedItem;
});`;
case 'filter':
return `const result = ${arr}.filter(item => {
// return true to keep item
return condition;
});`;
case 'find':
return `const found = ${arr}.find(item => {
// return true when item matches
return condition;
});
// 或使用 some() 檢查是否存在
const exists = ${arr}.some(item => condition);`;
case 'reduce':
return `const result = ${arr}.reduce((acc, item) => {
// accumulate result
return acc + item;
}, initialValue);`;
case 'forEach':
default:
return `${arr}.forEach(item => {
// process item
});
// 或使用 for...of (支援 break/continue)
for (const item of ${arr}) {
// process item
}`;
}
}
/**
* 生成摘要資訊
*/
private generateSummary(
fileAnalysis: FileModernizationResult[],
suggestions: ModernizationSuggestion[]
) {
const totalFiles = fileAnalysis.length;
const totalSuggestions = suggestions.length;
const potentialPerformanceGain = suggestions.reduce(
(total, s) => total + (s.impact?.performance || 0), 0
);
const bundleSizeReduction = suggestions.reduce(
(total, s) => total + (s.impact?.bundle || 0), 0
);
return {
totalFiles,
totalSuggestions,
potentialPerformanceGain,
bundleSizeReduction
};
}
/**
* 評估現代化風險
*/
private assessRisk(suggestions: ModernizationSuggestion[]): RiskAssessment {
const breakingChanges = suggestions
.filter(s => s.breaking)
.map(s => ({
type: s.type,
description: s.description,
impact: s.difficulty === 'complex' ? 'critical' as const : 'major' as const,
mitigation: `請詳細測試 ${s.title} 的變更`
}));
const compatibilityIssues: CompatibilityIssue[] = []; // TODO: 實作相容性檢查
const complexSuggestions = suggestions.filter(s => s.difficulty === 'complex').length;
const moderateSuggestions = suggestions.filter(s => s.difficulty === 'moderate').length;
const estimatedHours =
complexSuggestions * 8 +
moderateSuggestions * 2 +
(suggestions.length - complexSuggestions - moderateSuggestions) * 0.5;
const overallRisk =
breakingChanges.length > 5 ? 'high' :
breakingChanges.length > 2 ? 'medium' : 'low';
return {
overallRisk: overallRisk as any,
breakingChanges,
compatibilityIssues,
migrationEffort: {
estimatedHours,
complexity: estimatedHours > 40 ? 'high' : estimatedHours > 16 ? 'medium' : 'low',
priority: overallRisk === 'low' && estimatedHours < 16 ? 'high' : 'medium'
}
};
}
/**
* 生成 API 呼叫程式碼
*/
private generateApiCallCode(call: ApiCallInfo): string {
const args = call.arguments?.map(arg =>
typeof arg === 'string' ? `'${arg}'` : JSON.stringify(arg)
).join(', ') || '';
if (call.method) {
return `${call.api}.${call.method}(${args})`;
} else {
return `${call.api}(${args})`;
}
}
/**
* 遍歷 AST
*/
private traverseAST(node: any, visitor: (node: any) => void) {
if (!node || typeof node !== 'object') return;
visitor(node);
for (const key in node) {
const child = node[key];
if (child && typeof child === 'object') {
if (Array.isArray(child)) {
for (const item of child) {
if (item && typeof item === 'object' && item.type) {
this.traverseAST(item, visitor);
}
}
} else if (child.type) {
this.traverseAST(child, visitor);
}
}
}
}
}