|
| 1 | +import * as ts from 'typescript'; |
| 2 | + |
| 3 | +/** |
| 4 | + * TypeScript编译器工具类 |
| 5 | + * 用于在浏览器中将TypeScript代码编译为ES5 JavaScript |
| 6 | + */ |
| 7 | + |
| 8 | +/** |
| 9 | + * 编译TypeScript代码为ES5 JavaScript |
| 10 | + * @param {string} sourceCode - TypeScript源代码 |
| 11 | + * @param {string} fileName - 文件名(默认为script.ts) |
| 12 | + * @returns {Object} 编译结果 { success: boolean, code: string, errors: Array } |
| 13 | + */ |
| 14 | +export function compileToES5(sourceCode, fileName = 'script.ts') { |
| 15 | + try { |
| 16 | + // 编译选项 |
| 17 | + const compilerOptions = { |
| 18 | + target: ts.ScriptTarget.ES5, // 目标版本:ES5 |
| 19 | + module: ts.ModuleKind.None, // 不使用模块系统 |
| 20 | + lib: ['lib.es5.d.ts', 'lib.dom.d.ts'], // 包含ES5和DOM类型定义 |
| 21 | + removeComments: false, // 保留注释 |
| 22 | + noEmitOnError: false, // 即使有错误也生成代码 |
| 23 | + noImplicitAny: false, // 允许隐式any类型 |
| 24 | + strictNullChecks: false, // 不进行严格的null检查 |
| 25 | + suppressImplicitAnyIndexErrors: true, // 抑制隐式any索引错误 |
| 26 | + downlevelIteration: true, // 支持ES5迭代器降级 |
| 27 | + esModuleInterop: true, // 启用ES模块互操作性 |
| 28 | + allowJs: true, // 允许编译JavaScript文件 |
| 29 | + checkJs: false // 不检查JavaScript文件 |
| 30 | + }; |
| 31 | + |
| 32 | + // 执行编译 |
| 33 | + const result = ts.transpileModule(sourceCode, { |
| 34 | + compilerOptions, |
| 35 | + fileName, |
| 36 | + reportDiagnostics: true |
| 37 | + }); |
| 38 | + |
| 39 | + // 检查是否有诊断信息(错误/警告) |
| 40 | + const diagnostics = result.diagnostics || []; |
| 41 | + const errors = diagnostics.map(diagnostic => { |
| 42 | + const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); |
| 43 | + let location = ''; |
| 44 | + if (diagnostic.file && diagnostic.start !== undefined) { |
| 45 | + const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); |
| 46 | + location = `(${line + 1},${character + 1})`; |
| 47 | + } |
| 48 | + return { |
| 49 | + message, |
| 50 | + location, |
| 51 | + category: ts.DiagnosticCategory[diagnostic.category], |
| 52 | + code: diagnostic.code |
| 53 | + }; |
| 54 | + }); |
| 55 | + |
| 56 | + // 过滤出真正的错误(不包括警告) |
| 57 | + const realErrors = errors.filter(e => e.category === 'Error'); |
| 58 | + |
| 59 | + return { |
| 60 | + success: realErrors.length === 0, |
| 61 | + code: result.outputText || '', |
| 62 | + errors: errors, |
| 63 | + hasWarnings: errors.some(e => e.category === 'Warning'), |
| 64 | + sourceMap: result.sourceMapText |
| 65 | + }; |
| 66 | + } catch (error) { |
| 67 | + return { |
| 68 | + success: false, |
| 69 | + code: '', |
| 70 | + errors: [{ |
| 71 | + message: error.message || '编译失败', |
| 72 | + location: '', |
| 73 | + category: 'Error', |
| 74 | + code: 0 |
| 75 | + }] |
| 76 | + }; |
| 77 | + } |
| 78 | +} |
| 79 | + |
| 80 | +/** |
| 81 | + * 检查代码是否为TypeScript代码 |
| 82 | + * 简单的启发式检查,看是否包含TypeScript特有的语法 |
| 83 | + * @param {string} code - 代码字符串 |
| 84 | + * @returns {boolean} 是否为TypeScript代码 |
| 85 | + */ |
| 86 | +export function isTypeScriptCode(code) { |
| 87 | + if (!code || typeof code !== 'string') { |
| 88 | + return false; |
| 89 | + } |
| 90 | + |
| 91 | + // TypeScript特有的语法模式 |
| 92 | + const tsPatterns = [ |
| 93 | + /:\s*(string|number|boolean|any|void|never|unknown|object)\b/, // 类型注解 |
| 94 | + /interface\s+\w+/, // interface声明 |
| 95 | + /type\s+\w+\s*=/, // type别名 |
| 96 | + /enum\s+\w+/, // enum声明 |
| 97 | + /<\w+>/, // 泛型 |
| 98 | + /implements\s+\w+/, // implements关键字 |
| 99 | + /as\s+(string|number|boolean|any|const)/, // as类型断言 |
| 100 | + /public|private|protected|readonly/, // 访问修饰符 |
| 101 | + /:\s*\w+\[\]/, // 数组类型注解 |
| 102 | + /\?\s*:/ // 可选属性 |
| 103 | + ]; |
| 104 | + |
| 105 | + // 如果匹配任何TypeScript特有模式,则认为是TypeScript代码 |
| 106 | + return tsPatterns.some(pattern => pattern.test(code)); |
| 107 | +} |
| 108 | + |
| 109 | +/** |
| 110 | + * 格式化编译错误信息 |
| 111 | + * @param {Array} errors - 错误数组 |
| 112 | + * @returns {string} 格式化后的错误信息 |
| 113 | + */ |
| 114 | +export function formatCompileErrors(errors) { |
| 115 | + if (!errors || errors.length === 0) { |
| 116 | + return ''; |
| 117 | + } |
| 118 | + |
| 119 | + return errors.map((error, index) => { |
| 120 | + const prefix = `[${error.category}]`; |
| 121 | + const location = error.location ? ` ${error.location}` : ''; |
| 122 | + const code = error.code ? ` (TS${error.code})` : ''; |
| 123 | + return `${index + 1}. ${prefix}${location}${code}: ${error.message}`; |
| 124 | + }).join('\n'); |
| 125 | +} |
| 126 | + |
| 127 | +/** |
| 128 | + * 验证编译后的代码是否为有效的ES5 |
| 129 | + * @param {string} code - 编译后的代码 |
| 130 | + * @returns {Object} { valid: boolean, error: string } |
| 131 | + */ |
| 132 | +export function validateES5Code(code) { |
| 133 | + try { |
| 134 | + // 尝试使用Function构造函数验证语法 |
| 135 | + // eslint-disable-next-line no-new-func |
| 136 | + new Function(code); |
| 137 | + return { valid: true, error: null }; |
| 138 | + } catch (error) { |
| 139 | + return { valid: false, error: error.message }; |
| 140 | + } |
| 141 | +} |
| 142 | + |
| 143 | +/** |
| 144 | + * 提取代码中的元数据注释 |
| 145 | + * @param {string} code - 代码字符串 |
| 146 | + * @returns {Object} 元数据对象 |
| 147 | + */ |
| 148 | +export function extractMetadata(code) { |
| 149 | + const metadata = {}; |
| 150 | + const metaRegex = /\/\/\s*@(\w+)\s+(.+)/g; |
| 151 | + let match; |
| 152 | + |
| 153 | + while ((match = metaRegex.exec(code)) !== null) { |
| 154 | + const [, key, value] = match; |
| 155 | + metadata[key] = value.trim(); |
| 156 | + } |
| 157 | + |
| 158 | + return metadata; |
| 159 | +} |
| 160 | + |
| 161 | +export default { |
| 162 | + compileToES5, |
| 163 | + isTypeScriptCode, |
| 164 | + formatCompileErrors, |
| 165 | + validateES5Code, |
| 166 | + extractMetadata |
| 167 | +}; |
0 commit comments