Просмотр исходного кода

feat: add ASTMatch API in variable-core (#127)

Yiwei Mao 9 месяцев назад
Родитель
Сommit
20b1dc1ae9

+ 4 - 7
apps/demo-fixed-layout/src/plugins/sync-variable-plugin/variable-selector/use-variable-tree.ts

@@ -1,15 +1,12 @@
 import { useCallback, useMemo } from 'react';
 
 import {
-  ArrayType,
   ASTFactory,
   ASTKind,
   type BaseType,
-  CustomType,
-  isMatchAST,
-  ObjectType,
   type UnionJSON,
   useScopeAvailable,
+  ASTMatch,
 } from '@flowgram.ai/fixed-layout-editor';
 
 import { createASTFromJSONSchema } from '../utils';
@@ -47,14 +44,14 @@ export function useVariableTree<TreeData>({
   const getVariableTypeIcon = useCallback((variable: VariableField) => {
     const _type = variable.type;
 
-    if (isMatchAST(_type, ArrayType)) {
+    if (ASTMatch.isArray(_type)) {
       return (
         (ArrayIcons as any)[_type.items?.kind.toLowerCase()] ||
         VariableTypeIcons[ASTKind.Array.toLowerCase()]
       );
     }
 
-    if (isMatchAST(_type, CustomType)) {
+    if (ASTMatch.isCustomType(_type)) {
       return VariableTypeIcons[_type.typeName.toLowerCase()];
     }
 
@@ -96,7 +93,7 @@ export function useVariableTree<TreeData>({
     const isTypeFiltered = checkTypeFiltered(type);
 
     let children: TreeData[] | undefined;
-    if (isMatchAST(type, ObjectType)) {
+    if (ASTMatch.isObject(type)) {
       children = (type.properties || [])
         .map((_property) => renderVariable(_property as VariableField, [...parentFields, variable]))
         .filter(Boolean) as TreeData[];

+ 4 - 7
apps/demo-free-layout/src/plugins/sync-variable-plugin/variable-selector/use-variable-tree.ts

@@ -1,15 +1,12 @@
 import { useCallback, useMemo } from 'react';
 
 import {
-  ArrayType,
   ASTFactory,
   ASTKind,
   type BaseType,
-  CustomType,
-  isMatchAST,
-  ObjectType,
   type UnionJSON,
   useScopeAvailable,
+  ASTMatch,
 } from '@flowgram.ai/free-layout-editor';
 
 import { createASTFromJSONSchema } from '../utils';
@@ -47,14 +44,14 @@ export function useVariableTree<TreeData>({
   const getVariableTypeIcon = useCallback((variable: VariableField) => {
     const _type = variable.type;
 
-    if (isMatchAST(_type, ArrayType)) {
+    if (ASTMatch.isArray(_type)) {
       return (
         (ArrayIcons as any)[_type.items?.kind.toLowerCase()] ||
         VariableTypeIcons[ASTKind.Array.toLowerCase()]
       );
     }
 
-    if (isMatchAST(_type, CustomType)) {
+    if (ASTMatch.isCustomType(_type)) {
       return VariableTypeIcons[_type.typeName.toLowerCase()];
     }
 
@@ -96,7 +93,7 @@ export function useVariableTree<TreeData>({
     const isTypeFiltered = checkTypeFiltered(type);
 
     let children: TreeData[] | undefined;
-    if (isMatchAST(type, ObjectType)) {
+    if (ASTMatch.isObject(type)) {
       children = (type.properties || [])
         .map((_property) => renderVariable(_property as VariableField, [...parentFields, variable]))
         .filter(Boolean) as TreeData[];

+ 5 - 8
apps/docs/src/en/guide/advanced/variable.mdx

@@ -233,8 +233,7 @@ return available.variables.map(renderVariable)
 ```tsx pure title="use-variable-tree.tsx"
 import {
   type BaseVariableField,
-  isMatchAST,
-  ObjectType,
+  ASTMatch,
 } from '@flowgram.ai/fixed-layout-editor';
 
 // ....
@@ -243,7 +242,7 @@ const renderVariable = (variable: BaseVariableField) => ({
   title: variable.meta?.title,
   key: variable.key,
   // Only Object Type can drilldown
-  children: isMatchAST(type, ObjectType) ? type.properties.map(renderVariable) : [],
+  children: ASTMatch.isObject(type) ? type.properties.map(renderVariable) : [],
 });
 
 // ....
@@ -256,9 +255,7 @@ const renderVariable = (variable: BaseVariableField) => ({
 import {
   type BaseVariableField,
   type BaseType,
-  isMatchAST,
-  ObjectType,
-  ArrayType,
+  ASTMatch,
 } from '@flowgram.ai/fixed-layout-editor';
 
 // ....
@@ -267,10 +264,10 @@ const getTypeChildren = (type?: BaseType): BaseVariableField[] => {
   if (!type) return [];
 
   // get properties of Object
-  if (isMatchAST(type, ObjectType)) return type.properties;
+  if (ASTMatch.isObject(type)) return type.properties;
 
   // get items type of Array
-  if (isMatchAST(type, ArrayType)) return getTypeChildren(type.items);
+  if (ASTMatch.isArray(type)) return getTypeChildren(type.items);
 };
 
 const renderVariable = (variable: BaseVariableField) => ({

+ 5 - 8
apps/docs/src/zh/guide/advanced/variable.mdx

@@ -235,8 +235,7 @@ return available.variables.map(renderVariable)
 ```tsx pure title="use-variable-tree.tsx"
 import {
   type BaseVariableField,
-  isMatchAST,
-  ObjectType,
+  ASTMatch,
 } from '@flowgram.ai/fixed-layout-editor';
 
 // ....
@@ -245,7 +244,7 @@ const renderVariable = (variable: BaseVariableField) => ({
   title: variable.meta?.title,
   key: variable.key,
   // Only Object Type can drilldown
-  children: isMatchAST(type, ObjectType) ? type.properties.map(renderVariable) : [],
+  children: ASTMatch.isObject(type) ? type.properties.map(renderVariable) : [],
 });
 
 // ....
@@ -258,9 +257,7 @@ const renderVariable = (variable: BaseVariableField) => ({
 import {
   type BaseVariableField,
   type BaseType,
-  isMatchAST,
-  ObjectType,
-  ArrayType,
+  ASTMatch,
 } from '@flowgram.ai/fixed-layout-editor';
 
 // ....
@@ -269,10 +266,10 @@ const getTypeChildren = (type?: BaseType): BaseVariableField[] => {
   if (!type) return [];
 
   // get properties of Object
-  if (isMatchAST(type, ObjectType)) return type.properties;
+  if (ASTMatch.isObject(type)) return type.properties;
 
   // get items type of Array
-  if (isMatchAST(type, ArrayType)) return getTypeChildren(type.items);
+  if (ASTMatch.isArray(type)) return getTypeChildren(type.items);
 };
 
 const renderVariable = (variable: BaseVariableField) => ({

+ 3 - 3
packages/variable-engine/variable-core/__tests__/ast/variable-declaration.test.ts

@@ -2,11 +2,11 @@ import { vi, describe, test, expect } from 'vitest';
 
 import {
   ASTKind,
+  ASTMatch,
   ObjectType,
   NumberType,
   VariableEngine,
   VariableDeclaration,
-  isMatchAST,
 } from '../../src';
 import { simpleVariableList } from '../../__mocks__/variables';
 import { getContainer } from '../../__mocks__/container';
@@ -167,7 +167,7 @@ describe('test Basic Variable Declaration', () => {
       type: ASTKind.Number,
       meta: { label: 'test Label' },
     });
-    expect(isMatchAST(declaration.type, NumberType)).toBeTruthy();
+    expect(ASTMatch.is(declaration.type, NumberType)).toBeTruthy();
     expect(declarationChangeTimes).toBe(2);
     expect(anyVariableChangeTimes).toBe(2);
     expect(typeChangeTimes).toBe(1);
@@ -186,7 +186,7 @@ describe('test Basic Variable Declaration', () => {
       },
       meta: { label: 'test Label' },
     });
-    expect(isMatchAST(declaration.type, ObjectType)).toBeTruthy();
+    expect(ASTMatch.is(declaration.type, ObjectType)).toBeTruthy();
     expect(declarationChangeTimes).toBe(3);
     expect(anyVariableChangeTimes).toBe(3);
     expect(typeChangeTimes).toBe(2);

+ 74 - 0
packages/variable-engine/variable-core/__tests__/ast/variable-match.test.ts

@@ -0,0 +1,74 @@
+import { vi, describe, test, expect } from 'vitest';
+
+import {
+  ASTMatch,
+  ObjectType,
+  NumberType,
+  VariableEngine,
+  StringType,
+  VariableDeclarationList,
+  BooleanType,
+  IntegerType,
+  MapType,
+  ArrayType,
+} from '../../src';
+import { simpleVariableList } from '../../__mocks__/variables';
+import { getContainer } from '../../__mocks__/container';
+
+vi.mock('nanoid', () => {
+  let mockId = 0;
+  return {
+    nanoid: () => 'mocked-id-' + mockId++,
+  };
+});
+
+/**
+ * 测试基本的变量声明场景
+ */
+describe('test Basic Variable Declaration', () => {
+  const container = getContainer();
+  const variableEngine = container.get(VariableEngine);
+  const testScope = variableEngine.createScope('test');
+
+  test('test simple variable match', () => {
+    const simpleCase = testScope.ast.set('simple case', simpleVariableList);
+
+    if (!ASTMatch.isVariableDeclarationList(simpleCase)) {
+      throw new Error('simpleCase is not a VariableDeclarationList');
+    }
+    expect(ASTMatch.isVariableDeclarationList(simpleCase)).toBeTruthy();
+    expect(ASTMatch.is(simpleCase, VariableDeclarationList)).toBeTruthy();
+
+    const stringDeclaration = simpleCase.declarations[0];
+    const booleanDeclaration = simpleCase.declarations[1];
+    const numberDeclaration = simpleCase.declarations[2];
+    const integerDeclaration = simpleCase.declarations[3];
+    const objectDeclaration = simpleCase.declarations[4];
+    const mapDeclaration = simpleCase.declarations[5];
+    const arrayProperty = testScope.output.globalVariableTable.getByKeyPath(['object', 'key4']);
+
+    expect(ASTMatch.isString(stringDeclaration.type)).toBeTruthy();
+    expect(ASTMatch.is(stringDeclaration.type, StringType)).toBeTruthy();
+
+    expect(ASTMatch.isBoolean(booleanDeclaration.type)).toBeTruthy();
+    expect(ASTMatch.is(booleanDeclaration.type, BooleanType)).toBeTruthy();
+
+    expect(ASTMatch.isNumber(numberDeclaration.type)).toBeTruthy();
+    expect(ASTMatch.is(numberDeclaration.type, NumberType)).toBeTruthy();
+
+    expect(ASTMatch.isInteger(integerDeclaration.type)).toBeTruthy();
+    expect(ASTMatch.is(integerDeclaration.type, IntegerType)).toBeTruthy();
+
+    expect(ASTMatch.isObject(objectDeclaration.type)).toBeTruthy();
+    expect(ASTMatch.is(objectDeclaration.type, ObjectType)).toBeTruthy();
+
+    expect(ASTMatch.isMap(mapDeclaration.type)).toBeTruthy();
+    expect(ASTMatch.is(mapDeclaration.type, MapType)).toBeTruthy();
+
+    if (!ASTMatch.isProperty(arrayProperty)) {
+      throw new Error('arrayProperty is not a Property');
+    }
+    expect(ASTMatch.isArray(arrayProperty.type)).toBeTruthy();
+    expect(ASTMatch.is(arrayProperty.type, ArrayType)).toBeTruthy();
+  });
+});

+ 1 - 0
packages/variable-engine/variable-core/src/ast/index.ts

@@ -16,5 +16,6 @@ export * from './type';
 export * from './expression';
 
 export { ASTFactory } from './factory';
+export { ASTMatch } from './match';
 export { injectToAST, postConstructAST } from './utils/inversify';
 export { isMatchAST } from './utils/helpers';

+ 73 - 0
packages/variable-engine/variable-core/src/ast/match.ts

@@ -0,0 +1,73 @@
+import { ASTKind } from './types';
+import {
+  type StringType,
+  type NumberType,
+  type BooleanType,
+  type IntegerType,
+  type ObjectType,
+  type ArrayType,
+  type MapType,
+  type CustomType,
+} from './type';
+import { type EnumerateExpression, type KeyPathExpression } from './expression';
+import {
+  type Property,
+  type VariableDeclaration,
+  type VariableDeclarationList,
+} from './declaration';
+import { type ASTNode } from './ast-node';
+
+export namespace ASTMatch {
+  /**
+   * 类型相关
+   * @returns
+   */
+  export const isString = (node?: ASTNode): node is StringType => node?.kind === ASTKind.String;
+
+  export const isNumber = (node?: ASTNode): node is NumberType => node?.kind === ASTKind.Number;
+
+  export const isBoolean = (node?: ASTNode): node is BooleanType => node?.kind === ASTKind.Boolean;
+
+  export const isInteger = (node?: ASTNode): node is IntegerType => node?.kind === ASTKind.Integer;
+
+  export const isObject = (node?: ASTNode): node is ObjectType => node?.kind === ASTKind.Object;
+
+  export const isArray = (node?: ASTNode): node is ArrayType => node?.kind === ASTKind.Array;
+
+  export const isMap = (node?: ASTNode): node is MapType => node?.kind === ASTKind.Map;
+
+  export const isCustomType = (node?: ASTNode): node is CustomType =>
+    node?.kind === ASTKind.CustomType;
+
+  /**
+   * 声明相关
+   */
+  export const isVariableDeclaration = <VariableMeta = any>(
+    node?: ASTNode
+  ): node is VariableDeclaration<VariableMeta> => node?.kind === ASTKind.VariableDeclaration;
+
+  export const isProperty = <VariableMeta = any>(node?: ASTNode): node is Property<VariableMeta> =>
+    node?.kind === ASTKind.Property;
+
+  export const isVariableDeclarationList = (node?: ASTNode): node is VariableDeclarationList =>
+    node?.kind === ASTKind.VariableDeclarationList;
+
+  /**
+   * 表达式相关
+   */
+  export const isEnumerateExpression = (node?: ASTNode): node is EnumerateExpression =>
+    node?.kind === ASTKind.EnumerateExpression;
+
+  export const isKeyPathExpression = (node?: ASTNode): node is KeyPathExpression =>
+    node?.kind === ASTKind.KeyPathExpression;
+
+  /**
+   * Check AST Match by ASTClass
+   */
+  export function is<TargetASTNode extends ASTNode>(
+    node?: ASTNode,
+    targetType?: { kind: string; new (...args: any[]): TargetASTNode }
+  ): node is TargetASTNode {
+    return node?.kind === targetType?.kind;
+  }
+}

+ 8 - 1
packages/variable-engine/variable-core/src/ast/utils/helpers.ts

@@ -1,4 +1,5 @@
 import { ASTNodeJSON, ASTNodeJSONOrKind } from '../types';
+import { ASTMatch } from '../match';
 import { ASTNode } from '../ast-node';
 
 export function updateChildNodeHelper(
@@ -53,9 +54,15 @@ export function getAllChildren(ast: ASTNode): ASTNode[] {
   return [...ast.children, ...ast.children.map((_child) => getAllChildren(_child)).flat()];
 }
 
+/**
+ * isMatchAST is same as ASTMatch.is
+ * @param node
+ * @param targetType
+ * @returns
+ */
 export function isMatchAST<TargetASTNode extends ASTNode>(
   node?: ASTNode,
   targetType?: { kind: string; new (...args: any[]): TargetASTNode }
 ): node is TargetASTNode {
-  return node?.kind === targetType?.kind;
+  return ASTMatch.is(node, targetType);
 }