From 23bc4e880478ebcd720fb38385c41fde4c5ddf0a Mon Sep 17 00:00:00 2001 From: catloversg <152669316+catloversg@users.noreply.github.com> Date: Sun, 16 Feb 2025 16:27:26 +0700 Subject: [PATCH] MISC: Show user-friendly error message when there is syntax error in scripts (#1963) --- src/Script/RamCalculations.ts | 4 +- src/ScriptEditor/ui/ScriptEditorRoot.tsx | 2 +- src/utils/ScriptTransformer.ts | 74 +++++++++++++++++------- 3 files changed, 56 insertions(+), 24 deletions(-) diff --git a/src/Script/RamCalculations.ts b/src/Script/RamCalculations.ts index 8dced94a9..443f2c0b1 100644 --- a/src/Script/RamCalculations.ts +++ b/src/Script/RamCalculations.ts @@ -146,7 +146,7 @@ function parseOnlyRamCalculate( const scriptFileType = getFileType(script.filename); let moduleAST; try { - moduleAST = parseAST(script.code, scriptFileType); + moduleAST = parseAST(script.filename, script.server, script.code, scriptFileType); } catch (error) { return { errorCode: RamCalculationErrorCode.ImportError, @@ -552,7 +552,7 @@ export function calculateRamUsage( ): RamCalculation { try { const fileType = getFileType(scriptName); - const ast = typeof input === "string" ? parseAST(input, fileType) : input; + const ast = typeof input === "string" ? parseAST(scriptName, server, input, fileType) : input; return parseOnlyRamCalculate(ast, scriptName, server, getFileTypeFeature(fileType), otherScripts); } catch (error) { return { diff --git a/src/ScriptEditor/ui/ScriptEditorRoot.tsx b/src/ScriptEditor/ui/ScriptEditorRoot.tsx index 763919bde..238b878e6 100644 --- a/src/ScriptEditor/ui/ScriptEditorRoot.tsx +++ b/src/ScriptEditor/ui/ScriptEditorRoot.tsx @@ -276,7 +276,7 @@ function Root(props: IProps): React.ReactElement { } let ast; try { - ast = parseAST(newCode, getFileType(currentScript.path)); + ast = parseAST(currentScript.path, currentScript.hostname, newCode, getFileType(currentScript.path)); makeModelsForImports(ast, server); } catch (error) { showRAMError({ diff --git a/src/utils/ScriptTransformer.ts b/src/utils/ScriptTransformer.ts index f7d8133c0..ced983da9 100644 --- a/src/utils/ScriptTransformer.ts +++ b/src/utils/ScriptTransformer.ts @@ -63,32 +63,64 @@ export function getFileTypeFeature(fileType: FileType): FileTypeFeature { return result; } -export function parseAST(code: string, fileType: FileType): AST { +export function parseAST(scriptName: string, hostname: string, code: string, fileType: FileType): AST { const fileTypeFeature = getFileTypeFeature(fileType); let ast: AST; - /** - * acorn is much faster than babel-parser, especially when parsing many big JS files, so we use it to parse the AST of - * JS code. babel-parser is only useful when we have to parse JSX and TypeScript. - */ - if (fileType === FileType.JS) { - ast = acorn.parse(code, { sourceType: "module", ecmaVersion: "latest" }); - } else { - const plugins = []; - if (fileTypeFeature.isReact) { - plugins.push("jsx"); + try { + /** + * acorn is much faster than babel-parser, especially when parsing many big JS files, so we use it to parse the AST of + * JS code. babel-parser is only useful when we have to parse JSX and TypeScript. + */ + if (fileType === FileType.JS) { + ast = acorn.parse(code, { sourceType: "module", ecmaVersion: "latest" }); + } else { + const plugins = []; + if (fileTypeFeature.isReact) { + plugins.push("jsx"); + } + if (fileTypeFeature.isTypeScript) { + plugins.push("typescript"); + } + ast = babel.packages.parser.parse(code, { + sourceType: "module", + ecmaVersion: "latest", + /** + * The usage of the "estree" plugin is mandatory. We use acorn-walk to walk the AST. acorn-walk only supports the + * ESTree AST format, but babel-parser uses the Babel AST format by default. + */ + plugins: [["estree", { classFeatures: true }], ...plugins], + }).program; } - if (fileTypeFeature.isTypeScript) { - plugins.push("typescript"); - } - ast = babel.packages.parser.parse(code, { - sourceType: "module", - ecmaVersion: "latest", + } catch (error) { + /** + * The message of syntax errors may be cryptic for players without programming experience. For example, some players + * asked us what "Unexpected token" means. Therefore, we will catch the error here and provide a user-friendly error + * message. + */ + if (error instanceof SyntaxError) { + let errorLocation = "unknown"; /** - * The usage of the "estree" plugin is mandatory. We use acorn-walk to walk the AST. acorn-walk only supports the - * ESTree AST format, but babel-parser uses the Babel AST format by default. + * Some browsers (e.g., Firefox, Chrome) add the "loc" property to the error object. This property provides the + * line and column numbers of the error. */ - plugins: [["estree", { classFeatures: true }], ...plugins], - }).program; + if ( + "loc" in error && + error.loc && + typeof error.loc === "object" && + "line" in error.loc && + "column" in error.loc + ) { + errorLocation = `Line ${error.loc.line}, Column: ${error.loc.column}`; + } + throw new Error( + `Syntax error in ${scriptName}, server: ${hostname}. Error location: ${errorLocation}. Error message: ${error.message}.`, + { + cause: error, + }, + ); + } else { + throw error; + } } return ast; }