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

This commit is contained in:
Yiwei Mao 2025-04-07 15:30:22 +08:00 committed by GitHub
parent 52bbf2794d
commit 20b1dc1ae9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 177 additions and 34 deletions

View File

@ -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[];

View File

@ -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[];

View File

@ -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) => ({

View File

@ -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) => ({

View File

@ -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);

View File

@ -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();
});
});

View File

@ -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';

View 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;
}
}

View File

@ -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);
}