Skip to content

Commit f2c9c34

Browse files
Copilotqaiu
andcommitted
Add TypeScript compiler integration - core implementation
Co-authored-by: qaiu <29825328+qaiu@users.noreply.github.com>
1 parent a97268c commit f2c9c34

File tree

8 files changed

+653
-2
lines changed

8 files changed

+653
-2
lines changed

web-front/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"monaco-editor": "^0.45.0",
2020
"qrcode": "^1.5.4",
2121
"splitpanes": "^4.0.4",
22+
"typescript": "^5.9.3",
2223
"vue": "^3.5.12",
2324
"vue-clipboard3": "^2.0.0",
2425
"vue-router": "^4.5.1",

web-front/src/utils/playgroundApi.js

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,56 @@ export const playgroundApi = {
141141
} catch (error) {
142142
throw new Error(error.response?.data?.error || error.response?.data?.msg || error.message || '获取解析器失败');
143143
}
144+
},
145+
146+
/**
147+
* 保存TypeScript代码及其编译结果
148+
*/
149+
async saveTypeScriptCode(parserId, tsCode, es5Code, compileErrors, compilerVersion, compileOptions, isValid) {
150+
try {
151+
const response = await axios.post('/v2/playground/typescript', {
152+
parserId,
153+
tsCode,
154+
es5Code,
155+
compileErrors,
156+
compilerVersion,
157+
compileOptions,
158+
isValid
159+
});
160+
return response.data;
161+
} catch (error) {
162+
throw new Error(error.response?.data?.error || error.response?.data?.msg || error.message || '保存TypeScript代码失败');
163+
}
164+
},
165+
166+
/**
167+
* 根据parserId获取TypeScript代码
168+
*/
169+
async getTypeScriptCode(parserId) {
170+
try {
171+
const response = await axios.get(`/v2/playground/typescript/${parserId}`);
172+
return response.data;
173+
} catch (error) {
174+
throw new Error(error.response?.data?.error || error.response?.data?.msg || error.message || '获取TypeScript代码失败');
175+
}
176+
},
177+
178+
/**
179+
* 更新TypeScript代码
180+
*/
181+
async updateTypeScriptCode(parserId, tsCode, es5Code, compileErrors, compilerVersion, compileOptions, isValid) {
182+
try {
183+
const response = await axios.put(`/v2/playground/typescript/${parserId}`, {
184+
tsCode,
185+
es5Code,
186+
compileErrors,
187+
compilerVersion,
188+
compileOptions,
189+
isValid
190+
});
191+
return response.data;
192+
} catch (error) {
193+
throw new Error(error.response?.data?.error || error.response?.data?.msg || error.message || '更新TypeScript代码失败');
194+
}
144195
}
145196
};
146-

web-front/src/utils/tsCompiler.js

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
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

Comments
 (0)