mirror of
https://gitee.com/ByteDance/flowgram.ai.git
synced 2025-07-07 17:43:29 +08:00
feat: add ASTMatch API in variable-core (#127)
This commit is contained in:
parent
52bbf2794d
commit
20b1dc1ae9
@ -1,15 +1,12 @@
|
|||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ArrayType,
|
|
||||||
ASTFactory,
|
ASTFactory,
|
||||||
ASTKind,
|
ASTKind,
|
||||||
type BaseType,
|
type BaseType,
|
||||||
CustomType,
|
|
||||||
isMatchAST,
|
|
||||||
ObjectType,
|
|
||||||
type UnionJSON,
|
type UnionJSON,
|
||||||
useScopeAvailable,
|
useScopeAvailable,
|
||||||
|
ASTMatch,
|
||||||
} from '@flowgram.ai/fixed-layout-editor';
|
} from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
|
||||||
import { createASTFromJSONSchema } from '../utils';
|
import { createASTFromJSONSchema } from '../utils';
|
||||||
@ -47,14 +44,14 @@ export function useVariableTree<TreeData>({
|
|||||||
const getVariableTypeIcon = useCallback((variable: VariableField) => {
|
const getVariableTypeIcon = useCallback((variable: VariableField) => {
|
||||||
const _type = variable.type;
|
const _type = variable.type;
|
||||||
|
|
||||||
if (isMatchAST(_type, ArrayType)) {
|
if (ASTMatch.isArray(_type)) {
|
||||||
return (
|
return (
|
||||||
(ArrayIcons as any)[_type.items?.kind.toLowerCase()] ||
|
(ArrayIcons as any)[_type.items?.kind.toLowerCase()] ||
|
||||||
VariableTypeIcons[ASTKind.Array.toLowerCase()]
|
VariableTypeIcons[ASTKind.Array.toLowerCase()]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMatchAST(_type, CustomType)) {
|
if (ASTMatch.isCustomType(_type)) {
|
||||||
return VariableTypeIcons[_type.typeName.toLowerCase()];
|
return VariableTypeIcons[_type.typeName.toLowerCase()];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +93,7 @@ export function useVariableTree<TreeData>({
|
|||||||
const isTypeFiltered = checkTypeFiltered(type);
|
const isTypeFiltered = checkTypeFiltered(type);
|
||||||
|
|
||||||
let children: TreeData[] | undefined;
|
let children: TreeData[] | undefined;
|
||||||
if (isMatchAST(type, ObjectType)) {
|
if (ASTMatch.isObject(type)) {
|
||||||
children = (type.properties || [])
|
children = (type.properties || [])
|
||||||
.map((_property) => renderVariable(_property as VariableField, [...parentFields, variable]))
|
.map((_property) => renderVariable(_property as VariableField, [...parentFields, variable]))
|
||||||
.filter(Boolean) as TreeData[];
|
.filter(Boolean) as TreeData[];
|
||||||
|
|||||||
@ -1,15 +1,12 @@
|
|||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ArrayType,
|
|
||||||
ASTFactory,
|
ASTFactory,
|
||||||
ASTKind,
|
ASTKind,
|
||||||
type BaseType,
|
type BaseType,
|
||||||
CustomType,
|
|
||||||
isMatchAST,
|
|
||||||
ObjectType,
|
|
||||||
type UnionJSON,
|
type UnionJSON,
|
||||||
useScopeAvailable,
|
useScopeAvailable,
|
||||||
|
ASTMatch,
|
||||||
} from '@flowgram.ai/free-layout-editor';
|
} from '@flowgram.ai/free-layout-editor';
|
||||||
|
|
||||||
import { createASTFromJSONSchema } from '../utils';
|
import { createASTFromJSONSchema } from '../utils';
|
||||||
@ -47,14 +44,14 @@ export function useVariableTree<TreeData>({
|
|||||||
const getVariableTypeIcon = useCallback((variable: VariableField) => {
|
const getVariableTypeIcon = useCallback((variable: VariableField) => {
|
||||||
const _type = variable.type;
|
const _type = variable.type;
|
||||||
|
|
||||||
if (isMatchAST(_type, ArrayType)) {
|
if (ASTMatch.isArray(_type)) {
|
||||||
return (
|
return (
|
||||||
(ArrayIcons as any)[_type.items?.kind.toLowerCase()] ||
|
(ArrayIcons as any)[_type.items?.kind.toLowerCase()] ||
|
||||||
VariableTypeIcons[ASTKind.Array.toLowerCase()]
|
VariableTypeIcons[ASTKind.Array.toLowerCase()]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMatchAST(_type, CustomType)) {
|
if (ASTMatch.isCustomType(_type)) {
|
||||||
return VariableTypeIcons[_type.typeName.toLowerCase()];
|
return VariableTypeIcons[_type.typeName.toLowerCase()];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +93,7 @@ export function useVariableTree<TreeData>({
|
|||||||
const isTypeFiltered = checkTypeFiltered(type);
|
const isTypeFiltered = checkTypeFiltered(type);
|
||||||
|
|
||||||
let children: TreeData[] | undefined;
|
let children: TreeData[] | undefined;
|
||||||
if (isMatchAST(type, ObjectType)) {
|
if (ASTMatch.isObject(type)) {
|
||||||
children = (type.properties || [])
|
children = (type.properties || [])
|
||||||
.map((_property) => renderVariable(_property as VariableField, [...parentFields, variable]))
|
.map((_property) => renderVariable(_property as VariableField, [...parentFields, variable]))
|
||||||
.filter(Boolean) as TreeData[];
|
.filter(Boolean) as TreeData[];
|
||||||
|
|||||||
@ -233,8 +233,7 @@ return available.variables.map(renderVariable)
|
|||||||
```tsx pure title="use-variable-tree.tsx"
|
```tsx pure title="use-variable-tree.tsx"
|
||||||
import {
|
import {
|
||||||
type BaseVariableField,
|
type BaseVariableField,
|
||||||
isMatchAST,
|
ASTMatch,
|
||||||
ObjectType,
|
|
||||||
} from '@flowgram.ai/fixed-layout-editor';
|
} from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
|
||||||
// ....
|
// ....
|
||||||
@ -243,7 +242,7 @@ const renderVariable = (variable: BaseVariableField) => ({
|
|||||||
title: variable.meta?.title,
|
title: variable.meta?.title,
|
||||||
key: variable.key,
|
key: variable.key,
|
||||||
// Only Object Type can drilldown
|
// 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 {
|
import {
|
||||||
type BaseVariableField,
|
type BaseVariableField,
|
||||||
type BaseType,
|
type BaseType,
|
||||||
isMatchAST,
|
ASTMatch,
|
||||||
ObjectType,
|
|
||||||
ArrayType,
|
|
||||||
} from '@flowgram.ai/fixed-layout-editor';
|
} from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
|
||||||
// ....
|
// ....
|
||||||
@ -267,10 +264,10 @@ const getTypeChildren = (type?: BaseType): BaseVariableField[] => {
|
|||||||
if (!type) return [];
|
if (!type) return [];
|
||||||
|
|
||||||
// get properties of Object
|
// get properties of Object
|
||||||
if (isMatchAST(type, ObjectType)) return type.properties;
|
if (ASTMatch.isObject(type)) return type.properties;
|
||||||
|
|
||||||
// get items type of Array
|
// 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) => ({
|
const renderVariable = (variable: BaseVariableField) => ({
|
||||||
|
|||||||
@ -235,8 +235,7 @@ return available.variables.map(renderVariable)
|
|||||||
```tsx pure title="use-variable-tree.tsx"
|
```tsx pure title="use-variable-tree.tsx"
|
||||||
import {
|
import {
|
||||||
type BaseVariableField,
|
type BaseVariableField,
|
||||||
isMatchAST,
|
ASTMatch,
|
||||||
ObjectType,
|
|
||||||
} from '@flowgram.ai/fixed-layout-editor';
|
} from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
|
||||||
// ....
|
// ....
|
||||||
@ -245,7 +244,7 @@ const renderVariable = (variable: BaseVariableField) => ({
|
|||||||
title: variable.meta?.title,
|
title: variable.meta?.title,
|
||||||
key: variable.key,
|
key: variable.key,
|
||||||
// Only Object Type can drilldown
|
// 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 {
|
import {
|
||||||
type BaseVariableField,
|
type BaseVariableField,
|
||||||
type BaseType,
|
type BaseType,
|
||||||
isMatchAST,
|
ASTMatch,
|
||||||
ObjectType,
|
|
||||||
ArrayType,
|
|
||||||
} from '@flowgram.ai/fixed-layout-editor';
|
} from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
|
||||||
// ....
|
// ....
|
||||||
@ -269,10 +266,10 @@ const getTypeChildren = (type?: BaseType): BaseVariableField[] => {
|
|||||||
if (!type) return [];
|
if (!type) return [];
|
||||||
|
|
||||||
// get properties of Object
|
// get properties of Object
|
||||||
if (isMatchAST(type, ObjectType)) return type.properties;
|
if (ASTMatch.isObject(type)) return type.properties;
|
||||||
|
|
||||||
// get items type of Array
|
// 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) => ({
|
const renderVariable = (variable: BaseVariableField) => ({
|
||||||
|
|||||||
@ -2,11 +2,11 @@ import { vi, describe, test, expect } from 'vitest';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
ASTKind,
|
ASTKind,
|
||||||
|
ASTMatch,
|
||||||
ObjectType,
|
ObjectType,
|
||||||
NumberType,
|
NumberType,
|
||||||
VariableEngine,
|
VariableEngine,
|
||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
isMatchAST,
|
|
||||||
} from '../../src';
|
} from '../../src';
|
||||||
import { simpleVariableList } from '../../__mocks__/variables';
|
import { simpleVariableList } from '../../__mocks__/variables';
|
||||||
import { getContainer } from '../../__mocks__/container';
|
import { getContainer } from '../../__mocks__/container';
|
||||||
@ -167,7 +167,7 @@ describe('test Basic Variable Declaration', () => {
|
|||||||
type: ASTKind.Number,
|
type: ASTKind.Number,
|
||||||
meta: { label: 'test Label' },
|
meta: { label: 'test Label' },
|
||||||
});
|
});
|
||||||
expect(isMatchAST(declaration.type, NumberType)).toBeTruthy();
|
expect(ASTMatch.is(declaration.type, NumberType)).toBeTruthy();
|
||||||
expect(declarationChangeTimes).toBe(2);
|
expect(declarationChangeTimes).toBe(2);
|
||||||
expect(anyVariableChangeTimes).toBe(2);
|
expect(anyVariableChangeTimes).toBe(2);
|
||||||
expect(typeChangeTimes).toBe(1);
|
expect(typeChangeTimes).toBe(1);
|
||||||
@ -186,7 +186,7 @@ describe('test Basic Variable Declaration', () => {
|
|||||||
},
|
},
|
||||||
meta: { label: 'test Label' },
|
meta: { label: 'test Label' },
|
||||||
});
|
});
|
||||||
expect(isMatchAST(declaration.type, ObjectType)).toBeTruthy();
|
expect(ASTMatch.is(declaration.type, ObjectType)).toBeTruthy();
|
||||||
expect(declarationChangeTimes).toBe(3);
|
expect(declarationChangeTimes).toBe(3);
|
||||||
expect(anyVariableChangeTimes).toBe(3);
|
expect(anyVariableChangeTimes).toBe(3);
|
||||||
expect(typeChangeTimes).toBe(2);
|
expect(typeChangeTimes).toBe(2);
|
||||||
|
|||||||
@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -16,5 +16,6 @@ export * from './type';
|
|||||||
export * from './expression';
|
export * from './expression';
|
||||||
|
|
||||||
export { ASTFactory } from './factory';
|
export { ASTFactory } from './factory';
|
||||||
|
export { ASTMatch } from './match';
|
||||||
export { injectToAST, postConstructAST } from './utils/inversify';
|
export { injectToAST, postConstructAST } from './utils/inversify';
|
||||||
export { isMatchAST } from './utils/helpers';
|
export { isMatchAST } from './utils/helpers';
|
||||||
|
|||||||
73
packages/variable-engine/variable-core/src/ast/match.ts
Normal file
73
packages/variable-engine/variable-core/src/ast/match.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { ASTNodeJSON, ASTNodeJSONOrKind } from '../types';
|
import { ASTNodeJSON, ASTNodeJSONOrKind } from '../types';
|
||||||
|
import { ASTMatch } from '../match';
|
||||||
import { ASTNode } from '../ast-node';
|
import { ASTNode } from '../ast-node';
|
||||||
|
|
||||||
export function updateChildNodeHelper(
|
export function updateChildNodeHelper(
|
||||||
@ -53,9 +54,15 @@ export function getAllChildren(ast: ASTNode): ASTNode[] {
|
|||||||
return [...ast.children, ...ast.children.map((_child) => getAllChildren(_child)).flat()];
|
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>(
|
export function isMatchAST<TargetASTNode extends ASTNode>(
|
||||||
node?: ASTNode,
|
node?: ASTNode,
|
||||||
targetType?: { kind: string; new (...args: any[]): TargetASTNode }
|
targetType?: { kind: string; new (...args: any[]): TargetASTNode }
|
||||||
): node is TargetASTNode {
|
): node is TargetASTNode {
|
||||||
return node?.kind === targetType?.kind;
|
return ASTMatch.is(node, targetType);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user