From 2e03f213204dd6c6f2c0e4e60318006b96970f08 Mon Sep 17 00:00:00 2001 From: wwsun Date: Fri, 26 Jul 2024 13:22:18 +0800 Subject: [PATCH] feat: check file errors & show errors overlay --- packages/core/src/helpers/ast/generate.ts | 15 ++--- packages/core/src/helpers/ast/parse.ts | 8 +-- packages/core/src/helpers/ast/traverse.ts | 4 +- packages/core/src/models/file.ts | 11 ++++ packages/core/src/models/interfaces.ts | 4 ++ packages/core/src/models/module.ts | 28 +++++--- packages/core/src/models/route-module.ts | 2 + packages/core/src/models/service-module.ts | 2 + packages/core/src/models/store-module.ts | 2 + packages/core/src/models/view-module.ts | 5 +- packages/core/src/models/workspace.ts | 17 +++++ packages/core/tests/helpers.test.ts | 2 +- .../src/components/files-error-overlay.tsx | 65 +++++++++++++++++++ packages/designer/src/components/index.ts | 1 + packages/designer/src/workspace-view.tsx | 3 +- 15 files changed, 138 insertions(+), 31 deletions(-) create mode 100644 packages/designer/src/components/files-error-overlay.tsx diff --git a/packages/core/src/helpers/ast/generate.ts b/packages/core/src/helpers/ast/generate.ts index 8749149..4efdfa4 100644 --- a/packages/core/src/helpers/ast/generate.ts +++ b/packages/core/src/helpers/ast/generate.ts @@ -3,7 +3,7 @@ */ import generator, { GeneratorOptions } from '@babel/generator'; import * as t from '@babel/types'; -import { logger, wrapCode } from '@music163/tango-helpers'; +import { Dict, logger, wrapCode } from '@music163/tango-helpers'; import { formatCode } from '../string'; const defaultGeneratorOptions: GeneratorOptions = { @@ -25,19 +25,12 @@ export function ast2code(ast: t.Node, options: GeneratorOptions = defaultGenerat return code; } -const bracketPattern = /^\(.+\)$/s; - /** * 是否被 () 包裹 - * * @example ({ foo: 'foo' }) -> true * @example { foo: 'foo' } -> false - * - * @param str 目标字符串 */ -function isWrappingWithBrackets(str: string) { - return bracketPattern.test(str); -} +const bracketPattern = /^\(.+\)$/; /** * 将表达式生成为块级代码 @@ -54,7 +47,7 @@ export function expression2code(node: t.Expression) { const isWrappingExpression = t.isObjectExpression(node) || t.isFunctionExpression(node); - if (isWrappingExpression && isWrappingWithBrackets(ret)) { + if (isWrappingExpression && bracketPattern.test(ret)) { // 如果是对象,输出包含 ({}),则去掉首尾的括号 ret = ret.slice(1, -1); } @@ -195,7 +188,7 @@ export function node2value(node: t.Node, isWrapCode = true): any { ); if (isSimpleObject) { // simple object: { key1, key2, key3 } - ret = node.properties.reduce((prev, propertyNode) => { + ret = node.properties.reduce((prev, propertyNode) => { if (propertyNode.type === 'ObjectProperty') { const key = keyNode2value(propertyNode.key); const value = node2value(propertyNode.value, isWrapCode); diff --git a/packages/core/src/helpers/ast/parse.ts b/packages/core/src/helpers/ast/parse.ts index 02ec0ad..e1a3436 100644 --- a/packages/core/src/helpers/ast/parse.ts +++ b/packages/core/src/helpers/ast/parse.ts @@ -68,11 +68,7 @@ export function isValidExpressionCode(code: string) { * @returns */ export function code2ast(code: string): t.File { - try { - return parse(code, babelParserConfig); - } catch (err) { - logger.error('[code2ast failed!]', err); - } + return parse(code, babelParserConfig); } /** @@ -175,7 +171,7 @@ export function value2node( * 将 js 普通对象解析为 t.Node */ export function object2node( - obj: object, + obj: Dict, getValueNode: (value: any, key?: string) => t.Expression = value2node, ) { if (!isPlainObject(obj)) { diff --git a/packages/core/src/helpers/ast/traverse.ts b/packages/core/src/helpers/ast/traverse.ts index 8eaadc3..07e31ae 100644 --- a/packages/core/src/helpers/ast/traverse.ts +++ b/packages/core/src/helpers/ast/traverse.ts @@ -1009,7 +1009,7 @@ export function serviceConfig2Node(payload: object) { }); } -export function updateServiceConfigToServiceFile(ast: t.File, config: Dict) { +export function updateServiceConfigToServiceFile(ast: t.File, config: Dict) { traverse(ast, { CallExpression(path) { const calleeName = keyNode2value(path.node.callee) as string; @@ -1017,7 +1017,7 @@ export function updateServiceConfigToServiceFile(ast: t.File, config: Dict { + const newPropertiesNodeMap: Dict = Object.keys(config).reduce((properties, key) => { const serviceConfig = config[key]; const property = t.objectProperty(t.identifier(key), serviceConfig2Node(serviceConfig)); properties[key] = property; diff --git a/packages/core/src/models/file.ts b/packages/core/src/models/file.ts index eadff56..fcf4c47 100644 --- a/packages/core/src/models/file.ts +++ b/packages/core/src/models/file.ts @@ -24,6 +24,16 @@ export class TangoFile { */ lastModified: number; + /** + * 文件是否存在错误 + */ + isError: boolean; + + /** + * 文件的错误消息 + */ + errorMessage: string; + _code: string; _cleanCode: string; @@ -40,6 +50,7 @@ export class TangoFile { this.filename = props.filename; this.type = props.type; this.lastModified = Date.now(); + this.isError = false; // 这里主要是为了解决 umi ts 编译错误的问题,@see https://github.com/umijs/umi/issues/7594 if (isSyncCode) { diff --git a/packages/core/src/models/interfaces.ts b/packages/core/src/models/interfaces.ts index d8a2cd7..c4141f3 100644 --- a/packages/core/src/models/interfaces.ts +++ b/packages/core/src/models/interfaces.ts @@ -319,6 +319,10 @@ export interface IWorkspace { get bizComps(): string[]; get baseComps(): string[]; get localComps(): string[]; + /** + * 文件错误列表 + */ + get fileErrors(): string[]; /** * 是否是合法的项目 */ diff --git a/packages/core/src/models/module.ts b/packages/core/src/models/module.ts index 468c133..53cda85 100644 --- a/packages/core/src/models/module.ts +++ b/packages/core/src/models/module.ts @@ -38,18 +38,26 @@ export class TangoModule extends TangoFile { */ update(code?: string, isFormatCode = true, refreshWorkspace = true) { this.lastModified = Date.now(); - if (isNil(code)) { - this._syncByAst(); - } else { - this._syncByCode(code, isFormatCode); - } - this._analysisAst(); + try { + if (isNil(code)) { + this._syncByAst(); + } else { + this._syncByCode(code, isFormatCode); + } + this._analysisAst(); - this.workspace.onFilesChange([this.filename]); + this.isError = false; + this.errorMessage = undefined; - if (refreshWorkspace) { - this.workspace.refresh([this.filename]); + this.workspace.onFilesChange([this.filename]); + + if (refreshWorkspace) { + this.workspace.refresh([this.filename]); + } + } catch (err: any) { + this.isError = true; + this.errorMessage = err.message; } } @@ -110,6 +118,8 @@ export class TangoJsModule extends TangoModule { makeObservable(this, { _code: observable, _cleanCode: observable, + isError: observable, + errorMessage: observable, code: computed, cleanCode: computed, update: action, diff --git a/packages/core/src/models/route-module.ts b/packages/core/src/models/route-module.ts index 2997289..6ef0130 100644 --- a/packages/core/src/models/route-module.ts +++ b/packages/core/src/models/route-module.ts @@ -28,6 +28,8 @@ export class TangoRouteModule extends TangoModule { _routes: observable, _code: observable, _cleanCode: observable, + isError: observable, + errorMessage: observable, routes: computed, code: computed, cleanCode: computed, diff --git a/packages/core/src/models/service-module.ts b/packages/core/src/models/service-module.ts index 5940462..8fa4f13 100644 --- a/packages/core/src/models/service-module.ts +++ b/packages/core/src/models/service-module.ts @@ -49,6 +49,8 @@ export class TangoServiceModule extends TangoModule { _baseConfig: observable, _code: observable, _cleanCode: observable, + isError: observable, + errorMessage: observable, serviceFunctions: computed, baseConfig: computed, cleanCode: computed, diff --git a/packages/core/src/models/store-module.ts b/packages/core/src/models/store-module.ts index ed208da..7875deb 100644 --- a/packages/core/src/models/store-module.ts +++ b/packages/core/src/models/store-module.ts @@ -31,6 +31,8 @@ export class TangoStoreEntryModule extends TangoModule { _stores: observable, _code: observable, _cleanCode: observable, + isError: observable, + errorMessage: observable, stores: computed, code: computed, cleanCode: computed, diff --git a/packages/core/src/models/view-module.ts b/packages/core/src/models/view-module.ts index 305d846..ff8fee7 100644 --- a/packages/core/src/models/view-module.ts +++ b/packages/core/src/models/view-module.ts @@ -138,6 +138,9 @@ export class TangoViewModule extends TangoModule implements IViewFile { _code: observable, _cleanCode: observable, + isError: observable, + errorMessage: observable, + code: computed, cleanCode: computed, @@ -320,7 +323,7 @@ export class TangoViewModule extends TangoModule implements IViewFile { updateNodeAttributes(nodeId: string, config: Record, relatedImports?: string[]) { if (relatedImports && relatedImports.length) { // 导入依赖的组件 - const newImportData = relatedImports.reduce((prev, name) => { + const newImportData = relatedImports.reduce>((prev, name) => { const proto = this.workspace.getPrototype(name); const { source, specifiers } = prototype2importDeclarationData(proto, this.filename); const existSpecifiers: IImportSpecifierData[] = prev[source]; diff --git a/packages/core/src/models/workspace.ts b/packages/core/src/models/workspace.ts index f596ef9..dfc360e 100644 --- a/packages/core/src/models/workspace.ts +++ b/packages/core/src/models/workspace.ts @@ -237,6 +237,16 @@ export class Workspace extends EventTarget implements IWorkspace { return Object.keys(this.componentsEntryModule?.exportList || {}); } + get fileErrors() { + const list: string[] = []; + this.files.forEach((file) => { + if (file.isError) { + list.push(file.errorMessage); + } + }); + return list; + } + constructor(options?: IWorkspaceOptions) { super(); this.history = new TangoHistory(this); @@ -274,6 +284,7 @@ export class Workspace extends EventTarget implements IWorkspace { activeViewFile: observable, pages: computed, bizComps: computed, + fileErrors: computed, setActiveRoute: action, addFile: action, removeFile: action, @@ -300,6 +311,12 @@ export class Workspace extends EventTarget implements IWorkspace { } } else { // 从设计切换到源码模式 + + if (this.fileErrors.length) { + // 工作区文件存在语法错误,不同步 + return; + } + this.editorState.clear(); this.editorState.addFiles(this.listFileData()); } diff --git a/packages/core/tests/helpers.test.ts b/packages/core/tests/helpers.test.ts index 2a1405e..39bee43 100644 --- a/packages/core/tests/helpers.test.ts +++ b/packages/core/tests/helpers.test.ts @@ -71,7 +71,7 @@ describe('helpers', () => { }); it('expression2code: arrowFunctionExpression', () => { - expect(expression2code(code2expression('() => {}'))).toEqual('() => {}'); + expect(expression2code(code2expression('() => {\n\n}\n'))).toEqual('() => {\n\n}'); }); it('expression2code: memberExpression', () => { diff --git a/packages/designer/src/components/files-error-overlay.tsx b/packages/designer/src/components/files-error-overlay.tsx new file mode 100644 index 0000000..5a2db6d --- /dev/null +++ b/packages/designer/src/components/files-error-overlay.tsx @@ -0,0 +1,65 @@ +import React, { useEffect, useState } from 'react'; +import { Box, css } from 'coral-system'; +import { observer, useWorkspace } from '@music163/tango-context'; +import { CloseOutlined } from '@ant-design/icons'; + +const errorMessageStyle = css` + padding: 12px; + white-space: pre-wrap; +`; + +/** + * 文件错误提示浮层 + */ +export const FilesErrorOverlay = observer(() => { + const [isVisible, setIsVisible] = useState(true); + const [errors, setErrors] = useState([]); + const workspace = useWorkspace(); + + useEffect(() => { + setErrors(workspace.fileErrors); + }, [workspace.fileErrors]); + + const handleClose = () => { + setIsVisible(false); + }; + + if (workspace.fileErrors.length === 0) { + return null; + } + + if (!isVisible) { + return null; + } + + return ( + + + + + {errors.map((error, index) => ( + {error} + ))} + + + + ); +}); diff --git a/packages/designer/src/components/index.ts b/packages/designer/src/components/index.ts index 824226b..9fbb628 100644 --- a/packages/designer/src/components/index.ts +++ b/packages/designer/src/components/index.ts @@ -4,3 +4,4 @@ export * from './input-kv'; export * from './variable-tree'; export * from './variable-tree-modal'; export * from './components-popover'; +export * from './files-error-overlay'; diff --git a/packages/designer/src/workspace-view.tsx b/packages/designer/src/workspace-view.tsx index efb13b6..8d92752 100644 --- a/packages/designer/src/workspace-view.tsx +++ b/packages/designer/src/workspace-view.tsx @@ -3,7 +3,7 @@ import cx from 'classnames'; import { Box, HTMLCoralProps } from 'coral-system'; import { observer, useDesigner } from '@music163/tango-context'; import { DesignerViewType } from '@music163/tango-core'; -import { ComponentsPopover } from './components'; +import { ComponentsPopover, FilesErrorOverlay } from './components'; export interface WorkspaceViewProps extends HTMLCoralProps<'div'> { /** @@ -35,6 +35,7 @@ export const WorkspaceView = observer((props: WorkspaceViewProps) => { {children} {/* 添加组件弹层 */} {display === 'block' && } + {mode === 'design' && } ); });