mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-04-18 22:00:22 +08:00
Merge pull request #210 from webzard-io/feat/validate-exp
validate expression
This commit is contained in:
commit
77eaba8fa4
@ -5,7 +5,9 @@ import {
|
||||
SlotName,
|
||||
TraitType,
|
||||
} from '../../src/AppModel/IAppModel';
|
||||
import { AppSchema } from './mock';
|
||||
import { AppSchema, EventHanlderMockSchema } from './mock';
|
||||
import { produce } from 'immer';
|
||||
import { get } from 'lodash-es';
|
||||
|
||||
describe('ComponentModel test', () => {
|
||||
it('compute component property', () => {
|
||||
@ -21,9 +23,9 @@ describe('ComponentModel test', () => {
|
||||
expect(button1.parentSlot).toEqual('content');
|
||||
expect(button1.prevSilbling).toBe(appModel.getComponentById('text2' as ComponentId));
|
||||
expect(button1.nextSilbing).toBe(null);
|
||||
expect(button1.properties['text'].value).toEqual({ raw: 'text', format: 'plain' });
|
||||
expect(button1.rawProperties.text).toEqual({ raw: 'text', format: 'plain' });
|
||||
const apiFetch = appModel.getComponentById('apiFetch' as ComponentId)!;
|
||||
expect(apiFetch.stateKeys).toEqual(['fetch']);
|
||||
expect([...Object.keys(apiFetch.stateExample)]).toEqual(['fetch']);
|
||||
expect(apiFetch.methods[0].name).toEqual('triggerFetch');
|
||||
});
|
||||
});
|
||||
@ -40,9 +42,11 @@ describe('update component property', () => {
|
||||
expect(newSchema[5].properties.value).toEqual({ raw: 'hello', format: 'md' });
|
||||
});
|
||||
|
||||
it("update a new property that component don't have",()=>{
|
||||
expect(newSchema[5].properties.newProperty).toEqual("a property that didn't exist before");
|
||||
})
|
||||
it("update a new property that component don't have", () => {
|
||||
expect(newSchema[5].properties.newProperty).toEqual(
|
||||
"a property that didn't exist before"
|
||||
);
|
||||
});
|
||||
|
||||
it('keep immutable after updating component properties', () => {
|
||||
expect(origin).not.toBe(newSchema);
|
||||
@ -52,6 +56,23 @@ describe('update component property', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('update event trait handlers(array) property', () => {
|
||||
const appModel = new AppModel(EventHanlderMockSchema);
|
||||
const button1 = appModel.getComponentById('button1' as any)!;
|
||||
const oldHandlers = button1.traits[0].rawProperties.handlers;
|
||||
const newHandlers = produce(oldHandlers, (draft: any) => {
|
||||
draft[1].method.parameters.value = 'hello';
|
||||
});
|
||||
button1.updateTraitProperties(button1.traits[0].id, { handlers: newHandlers });
|
||||
const newSchema = appModel.toSchema();
|
||||
|
||||
it('update trait array properties', () => {
|
||||
expect(
|
||||
get(newSchema[0].traits[0].properties, 'handlers[1].method.parameters.value')
|
||||
).toEqual('hello');
|
||||
});
|
||||
});
|
||||
|
||||
describe('append to another component', () => {
|
||||
const appModel = new AppModel(AppSchema.spec.components);
|
||||
const origin = appModel.toSchema();
|
||||
|
45
packages/editor/__tests__/model/fieldModel.spec.ts
Normal file
45
packages/editor/__tests__/model/fieldModel.spec.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { FieldModel } from '../../src/AppModel/FieldModel';
|
||||
|
||||
describe('Field test', () => {
|
||||
it('parse static property', () => {
|
||||
const field = new FieldModel('Hello, world!');
|
||||
expect(field.isDynamic).toEqual(false);
|
||||
expect(field.refs).toEqual({});
|
||||
expect(field.rawValue).toEqual('Hello, world!');
|
||||
});
|
||||
|
||||
it('parse expression', () => {
|
||||
const field = new FieldModel('{{input.value}} + {{list[0].text}}');
|
||||
expect(field.isDynamic).toEqual(true);
|
||||
expect(field.refs).toEqual({ input: ['value'], list: ['[0]', '[0].text'] });
|
||||
expect(field.rawValue).toEqual('{{input.value}} + {{list[0].text}}');
|
||||
});
|
||||
|
||||
it('parse object property', () => {
|
||||
const field = new FieldModel({ raw: '{{input.value}}', format: 'md' });
|
||||
expect(field.isDynamic).toEqual(false);
|
||||
expect(field.refs).toEqual({});
|
||||
expect(field.rawValue).toEqual({ raw: '{{input.value}}', format: 'md' });
|
||||
expect(field.getProperty('raw')!.rawValue).toEqual('{{input.value}}');
|
||||
expect(field.getProperty('raw')!.isDynamic).toEqual(true);
|
||||
expect(field.getProperty('raw')!.refs).toEqual({ input: ['value'] });
|
||||
expect(field.getProperty('format')!.rawValue).toEqual('md');
|
||||
expect(field.getProperty('format')!.isDynamic).toEqual(false);
|
||||
expect(field.getProperty('format')!.refs).toEqual({});
|
||||
});
|
||||
|
||||
it('parse array property', () => {
|
||||
const field = new FieldModel({ data: [1, '{{fetch.data}}'] });
|
||||
expect(field.isDynamic).toEqual(false);
|
||||
expect(field.refs).toEqual({});
|
||||
expect(field.rawValue).toEqual({ data: [1, '{{fetch.data}}'] });
|
||||
expect(field.getProperty('data')!.rawValue).toEqual([1, '{{fetch.data}}']);
|
||||
expect(field.getProperty('data')!.isDynamic).toEqual(false);
|
||||
expect(field.getProperty('data')!.refs).toEqual({});
|
||||
expect(field.getProperty('data')!.getProperty(0)!.rawValue).toEqual(1);
|
||||
expect(field.getProperty('data')!.getProperty(0)!.isDynamic).toEqual(false);
|
||||
expect(field.getProperty('data')!.getProperty(1)!.rawValue).toEqual('{{fetch.data}}');
|
||||
expect(field.getProperty('data')!.getProperty(1)!.isDynamic).toEqual(true);
|
||||
expect(field.getProperty('data')!.getProperty(1)!.refs).toEqual({ fetch: ['data'] });
|
||||
});
|
||||
});
|
@ -148,3 +148,40 @@ export const DuplicatedIdSchema: ComponentSchema[] = [
|
||||
traits: [],
|
||||
},
|
||||
];
|
||||
|
||||
export const EventHanlderMockSchema: ComponentSchema[] = [
|
||||
{
|
||||
id: 'button1',
|
||||
type: 'chakra_ui/v1/button',
|
||||
properties: {},
|
||||
traits: [
|
||||
{
|
||||
type: 'core/v1/event',
|
||||
properties: {
|
||||
handlers: [
|
||||
{
|
||||
type: 'onClick',
|
||||
componentId: 'input1',
|
||||
method: {
|
||||
name: 'setInputValue',
|
||||
parameters: {
|
||||
value: '666',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'onClick',
|
||||
componentId: 'input2',
|
||||
method: {
|
||||
name: 'setInputValue',
|
||||
parameters: {
|
||||
value: '666',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
@ -1,5 +1,9 @@
|
||||
import { registry } from '../../src/setup';
|
||||
import { ComponentInvalidSchema,ComponentPropertyExpressionSchema } from './mock';
|
||||
import {
|
||||
ComponentInvalidSchema,
|
||||
ComponentPropertyExpressionSchema,
|
||||
ComponentWrongPropertyExpressionSchema,
|
||||
} from './mock';
|
||||
import { SchemaValidator } from '../../src/validator';
|
||||
|
||||
const schemaValidator = new SchemaValidator(registry);
|
||||
@ -13,9 +17,21 @@ describe('Validate component', () => {
|
||||
it('detect wrong type', () => {
|
||||
expect(result[1].message).toBe(`must be string`);
|
||||
});
|
||||
it('ignore expreesion', () => {
|
||||
it('ignore expression', () => {
|
||||
const result = schemaValidator.validate(ComponentPropertyExpressionSchema);
|
||||
expect(result.length).toBe(0);
|
||||
})
|
||||
});
|
||||
})
|
||||
describe('validate expression', () => {
|
||||
const result = schemaValidator.validate(ComponentWrongPropertyExpressionSchema);
|
||||
it('detect using non-exist variables in expression', () => {
|
||||
expect(result[0].message).toBe(`Cannot find 'data' in store.`);
|
||||
});
|
||||
it('detect using non-exist variables in expression of array property', () => {
|
||||
expect(result[1].message).toBe(`Cannot find 'fetch' in store.`);
|
||||
});
|
||||
it('detect using property which does not exist in component state spec', () => {
|
||||
expect(result[2].message).toBe(`Component 'input1' does not have property 'noValue'.`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -57,16 +57,61 @@ export const ComponentInvalidSchema: ComponentSchema[] = [
|
||||
|
||||
export const ComponentPropertyExpressionSchema: ComponentSchema[] = [
|
||||
{
|
||||
id: 'text1',
|
||||
id: 'list',
|
||||
type: 'chakra_ui/v1/list',
|
||||
properties: {
|
||||
listData: '{{data}}',
|
||||
template: '{{template}}',
|
||||
listData: '{{ [] }}',
|
||||
template: '{{ {} }}',
|
||||
},
|
||||
traits: [],
|
||||
},
|
||||
];
|
||||
|
||||
export const ComponentWrongPropertyExpressionSchema: ComponentSchema[] = [
|
||||
{
|
||||
id: 'input1',
|
||||
type: 'chakra_ui/v1/input',
|
||||
properties: {
|
||||
variant: 'outline',
|
||||
placeholder: '{{data.value}}',
|
||||
size: 'md',
|
||||
isDisabled: false,
|
||||
isRequired: false,
|
||||
defaultValue: '',
|
||||
},
|
||||
traits: [],
|
||||
},
|
||||
{
|
||||
id: 'button1',
|
||||
type: 'chakra_ui/v1/button',
|
||||
properties: {
|
||||
text: {
|
||||
raw: '{{fetch.data.value}}',
|
||||
format: 'md',
|
||||
},
|
||||
},
|
||||
traits: [
|
||||
{
|
||||
type: 'core/v1/event',
|
||||
properties: {
|
||||
handlers: [
|
||||
{
|
||||
type: 'onClick',
|
||||
componentId: 'input1',
|
||||
method: {
|
||||
name: 'setInputValue',
|
||||
parameters: {
|
||||
value: '{{input1.noValue}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const TraitInvalidSchema: ComponentSchema[] = [
|
||||
{
|
||||
id: 'text1',
|
||||
|
@ -38,6 +38,9 @@
|
||||
"@sunmao-ui/chakra-ui-lib": "^0.1.3",
|
||||
"@sunmao-ui/core": "^0.3.5",
|
||||
"@sunmao-ui/runtime": "^0.3.9",
|
||||
"acorn": "^8.7.0",
|
||||
"acorn-loose": "^8.3.0",
|
||||
"acorn-walk": "^8.2.0",
|
||||
"ajv": "^8.8.2",
|
||||
"codemirror": "^5.63.3",
|
||||
"formik": "^2.2.9",
|
||||
|
@ -1,8 +1,4 @@
|
||||
import {
|
||||
ComponentSchema,
|
||||
MethodSchema,
|
||||
RuntimeComponent,
|
||||
} from '@sunmao-ui/core';
|
||||
import { ComponentSchema, MethodSchema, RuntimeComponent } from '@sunmao-ui/core';
|
||||
import { registry } from '../setup';
|
||||
import { genComponent, genTrait } from './utils';
|
||||
import {
|
||||
@ -12,7 +8,6 @@ import {
|
||||
IComponentModel,
|
||||
SlotName,
|
||||
StyleSlotName,
|
||||
StateKey,
|
||||
ITraitModel,
|
||||
IFieldModel,
|
||||
EventName,
|
||||
@ -22,19 +17,28 @@ import {
|
||||
} from './IAppModel';
|
||||
import { TraitModel } from './TraitModel';
|
||||
import { FieldModel } from './FieldModel';
|
||||
type ComponentSpecModel = RuntimeComponent<MethodName, StyleSlotName, SlotName, EventName>
|
||||
const SlotTraitType: TraitType = 'core/v1/slot' as TraitType;
|
||||
export class ComponentModel implements IComponentModel {
|
||||
private spec: ComponentSpecModel;
|
||||
import { merge } from 'lodash-es';
|
||||
import { parseTypeBox } from '@sunmao-ui/runtime';
|
||||
|
||||
const SlotTraitType: TraitType = 'core/v1/slot' as TraitType;
|
||||
|
||||
type ComponentSpecModel = RuntimeComponent<
|
||||
MethodName,
|
||||
StyleSlotName,
|
||||
SlotName,
|
||||
EventName
|
||||
>;
|
||||
export class ComponentModel implements IComponentModel {
|
||||
spec: ComponentSpecModel;
|
||||
id: ComponentId;
|
||||
type: ComponentType;
|
||||
properties: Record<string, IFieldModel> = {};
|
||||
properties: IFieldModel;
|
||||
children: Record<SlotName, IComponentModel[]> = {};
|
||||
parent: IComponentModel | null = null;
|
||||
parentId: ComponentId | null = null;
|
||||
parentSlot: SlotName | null = null;
|
||||
traits: ITraitModel[] = [];
|
||||
stateExample: Record<string, any> = {};
|
||||
_isDirty = false;
|
||||
|
||||
constructor(public appModel: IAppModel, private schema: ComponentSchema) {
|
||||
@ -45,35 +49,16 @@ export class ComponentModel implements IComponentModel {
|
||||
this.spec = registry.getComponentByType(this.type) as any;
|
||||
|
||||
this.traits = schema.traits.map(t => new TraitModel(t, this));
|
||||
// find slot trait
|
||||
this.traits.forEach(t => {
|
||||
if (t.type === 'core/v1/slot') {
|
||||
this.parentId = t.rawProperties.container.id;
|
||||
this.parentSlot = t.rawProperties.container.slot;
|
||||
}
|
||||
});
|
||||
|
||||
for (const key in schema.properties) {
|
||||
this.properties[key] = new FieldModel(schema.properties[key]);
|
||||
}
|
||||
this.genStateExample()
|
||||
this.parentId = this._slotTrait?.rawProperties.container.id;
|
||||
this.parentSlot = this._slotTrait?.rawProperties.container.slot;
|
||||
this.properties = new FieldModel(schema.properties);
|
||||
}
|
||||
|
||||
get slots() {
|
||||
return (this.spec ? this.spec.spec.slots : []) as SlotName[];
|
||||
}
|
||||
|
||||
get stateKeys() {
|
||||
if (!this.spec) return [];
|
||||
const componentStateKeys = Object.keys(
|
||||
this.spec.spec.state.properties || {}
|
||||
) as StateKey[];
|
||||
const traitStateKeys: StateKey[] = this.traits.reduce(
|
||||
(acc, t) => acc.concat(t.stateKeys),
|
||||
[] as StateKey[]
|
||||
);
|
||||
return [...componentStateKeys, ...traitStateKeys];
|
||||
}
|
||||
|
||||
get events() {
|
||||
return (this.spec ? this.spec.spec.events : []) as EventName[];
|
||||
}
|
||||
@ -85,7 +70,7 @@ export class ComponentModel implements IComponentModel {
|
||||
componentMethods.push({
|
||||
name: methodName,
|
||||
parameters: this.spec.spec.methods[methodName as MethodName]!,
|
||||
})
|
||||
});
|
||||
}
|
||||
const traitMethods: MethodSchema[] = this.traits.reduce(
|
||||
(acc, t) => acc.concat(t.methods),
|
||||
@ -99,11 +84,7 @@ export class ComponentModel implements IComponentModel {
|
||||
}
|
||||
|
||||
get rawProperties() {
|
||||
const obj: Record<string, any> = {};
|
||||
for (const key in this.properties) {
|
||||
obj[key] = this.properties[key].value;
|
||||
}
|
||||
return obj;
|
||||
return this.properties.rawValue;
|
||||
}
|
||||
|
||||
get prevSilbling() {
|
||||
@ -146,11 +127,7 @@ export class ComponentModel implements IComponentModel {
|
||||
}
|
||||
|
||||
updateComponentProperty(propertyName: string, value: any) {
|
||||
if (!Reflect.has(this.properties, propertyName)) {
|
||||
this.properties[propertyName] = new FieldModel(value)
|
||||
} else {
|
||||
this.properties[propertyName].update(value);
|
||||
}
|
||||
this.properties.update({ [propertyName]: value });
|
||||
this._isDirty = true;
|
||||
}
|
||||
|
||||
@ -159,6 +136,7 @@ export class ComponentModel implements IComponentModel {
|
||||
const trait = new TraitModel(traitSchema, this);
|
||||
this.traits.push(trait);
|
||||
this._isDirty = true;
|
||||
this.genStateExample()
|
||||
return trait;
|
||||
}
|
||||
|
||||
@ -200,6 +178,7 @@ export class ComponentModel implements IComponentModel {
|
||||
if (traitIndex === -1) return;
|
||||
this.traits.splice(traitIndex, 1);
|
||||
this._isDirty = true;
|
||||
this.genStateExample()
|
||||
}
|
||||
|
||||
changeId(newId: ComponentId) {
|
||||
@ -215,7 +194,7 @@ export class ComponentModel implements IComponentModel {
|
||||
child.parentId = newId;
|
||||
const slotTrait = child.traits.find(t => t.type === SlotTraitType);
|
||||
if (slotTrait) {
|
||||
slotTrait.properties.container.update({ id: newId, slot });
|
||||
slotTrait.properties.update({ container: { id: newId, slot } });
|
||||
slotTrait._isDirty = true;
|
||||
}
|
||||
child._isDirty = true;
|
||||
@ -263,15 +242,15 @@ export class ComponentModel implements IComponentModel {
|
||||
const trait = this.traits.find(t => t.id === traitId);
|
||||
if (!trait) return;
|
||||
for (const property in properties) {
|
||||
trait.properties[property].update(properties[property]);
|
||||
trait._isDirty = true;
|
||||
trait.updateProperty(property, properties[property]);
|
||||
}
|
||||
trait._isDirty = true;
|
||||
this._isDirty = true;
|
||||
}
|
||||
|
||||
updateSlotTrait(parent: ComponentId, slot: SlotName) {
|
||||
if (this._slotTrait) {
|
||||
this._slotTrait.properties.container.update({ id: parent, slot });
|
||||
this._slotTrait.properties.update({ container: { id: parent, slot } });
|
||||
this._slotTrait._isDirty = true;
|
||||
} else {
|
||||
this.addTrait(SlotTraitType, { container: { id: parent, slot } });
|
||||
@ -290,4 +269,15 @@ export class ComponentModel implements IComponentModel {
|
||||
}
|
||||
traverse(this);
|
||||
}
|
||||
|
||||
// should be called after changing traits length
|
||||
private genStateExample() {
|
||||
if (!this.spec) return [];
|
||||
const componentStateSpec = this.spec.spec.state;
|
||||
const traitsStateSpec = this.traits.map(t => t.spec.spec.state);
|
||||
const stateSpecs = [componentStateSpec, ...traitsStateSpec];
|
||||
this.stateExample = stateSpecs.reduce((res, jsonSchema) => {
|
||||
return merge(res, parseTypeBox(jsonSchema as any, true));
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,115 @@
|
||||
import { IFieldModel } from './IAppModel';
|
||||
import { parseExpression } from '@sunmao-ui/runtime';
|
||||
import * as acorn from 'acorn';
|
||||
import * as acornLoose from 'acorn-loose';
|
||||
import { simple as simpleWalk } from 'acorn-walk';
|
||||
import { flattenDeep, isArray, isObject } from 'lodash-es';
|
||||
import { ComponentId, IFieldModel, ModuleId } from './IAppModel';
|
||||
|
||||
const regExp = new RegExp('.*{{.*}}.*');
|
||||
|
||||
export class FieldModel implements IFieldModel {
|
||||
value: any;
|
||||
isDynamic = false;
|
||||
// refs: Array<ComponentId | ModuleId> = []
|
||||
refs: Record<ComponentId | ModuleId, string[]> = {};
|
||||
private value: unknown | Array<IFieldModel> | Record<string, IFieldModel>;
|
||||
|
||||
constructor(value: unknown) {
|
||||
this.update(value);
|
||||
}
|
||||
|
||||
get rawValue() {
|
||||
if (isObject(this.value)) {
|
||||
if (isArray(this.value)) {
|
||||
return this.value.map(field => field.rawValue);
|
||||
} else {
|
||||
const _thisValue = this.value as Record<string, IFieldModel>;
|
||||
const res: Record<string, any> = {};
|
||||
for (const key in _thisValue) {
|
||||
res[key] = _thisValue[key].rawValue;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
return this.value;
|
||||
}
|
||||
|
||||
update(value: unknown) {
|
||||
this.value = value;
|
||||
if (isObject(value)) {
|
||||
if (!isObject(this.value)) {
|
||||
this.value = isArray(value) ? [] : {};
|
||||
}
|
||||
|
||||
for (const key in value) {
|
||||
const val = (value as Record<string, unknown>)[key];
|
||||
const _thisValue = this.value as Record<string, IFieldModel>;
|
||||
if (!_thisValue[key]) {
|
||||
_thisValue[key] = new FieldModel(val);
|
||||
} else {
|
||||
_thisValue[key].update(val);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.value = value;
|
||||
}
|
||||
this.isDynamic = typeof value === 'string' && regExp.test(value);
|
||||
this.parseReferences();
|
||||
}
|
||||
|
||||
getProperty(key: string | number): FieldModel | undefined {
|
||||
if (typeof this.value === 'object') {
|
||||
return (this.value as any)[key];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.value
|
||||
}
|
||||
|
||||
traverse(cb: (f: IFieldModel, key: string) => void) {
|
||||
function _traverse(field: FieldModel, key: string) {
|
||||
if (isObject(field.value)) {
|
||||
for (const _key in field.value) {
|
||||
const val = field.getProperty(_key);
|
||||
if (val instanceof FieldModel) {
|
||||
_traverse(val, `${key}.${_key}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cb(field, key);
|
||||
}
|
||||
}
|
||||
_traverse(this, '');
|
||||
}
|
||||
|
||||
private parseReferences() {
|
||||
if (!this.isDynamic || typeof this.value !== 'string') return;
|
||||
|
||||
const exps = flattenDeep(
|
||||
parseExpression(this.value as string).filter(exp => typeof exp !== 'string')
|
||||
);
|
||||
|
||||
exps.forEach(exp => {
|
||||
let lastIdentifier: ComponentId = '' as ComponentId;
|
||||
simpleWalk((acornLoose as typeof acorn).parse(exp, { ecmaVersion: 2020 }), {
|
||||
Expression: node => {
|
||||
switch (node.type) {
|
||||
case 'Identifier':
|
||||
const key = exp.slice(node.start, node.end) as ComponentId;
|
||||
this.refs[key] = [];
|
||||
lastIdentifier = key;
|
||||
break;
|
||||
case 'MemberExpression':
|
||||
const str = exp.slice(node.start, node.end);
|
||||
let path = str.replace(lastIdentifier, '');
|
||||
if (path.startsWith('.')) {
|
||||
path = path.slice(1, path.length)
|
||||
}
|
||||
this.refs[lastIdentifier].push(path);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,9 @@
|
||||
import { ComponentSchema, TraitSchema, MethodSchema } from '@sunmao-ui/core';
|
||||
import {
|
||||
ComponentSchema,
|
||||
TraitSchema,
|
||||
MethodSchema,
|
||||
RuntimeTrait,
|
||||
} from '@sunmao-ui/core';
|
||||
|
||||
export type ComponentId = string & {
|
||||
kind: 'componentId';
|
||||
@ -27,9 +32,6 @@ export type MethodName = string & {
|
||||
export type StyleSlotName = string & {
|
||||
kind: 'styleSlotName';
|
||||
};
|
||||
export type StateKey = string & {
|
||||
kind: 'stateKey';
|
||||
};
|
||||
export type EventName = string & {
|
||||
kind: 'eventName';
|
||||
};
|
||||
@ -59,7 +61,7 @@ export interface IComponentModel {
|
||||
appModel: IAppModel;
|
||||
id: ComponentId;
|
||||
type: ComponentType;
|
||||
properties: Record<string, IFieldModel>;
|
||||
properties: IFieldModel;
|
||||
// just like properties in schema
|
||||
rawProperties: Record<string, any>;
|
||||
children: Record<SlotName, IComponentModel[]>;
|
||||
@ -69,8 +71,9 @@ export interface IComponentModel {
|
||||
traits: ITraitModel[];
|
||||
slots: SlotName[];
|
||||
styleSlots: StyleSlotName[];
|
||||
// both component's stateKeys and traits's stateKeys
|
||||
stateKeys: StateKey[];
|
||||
// fake data generated by the stateSpecs of component and its traits.
|
||||
// for validator to validate expression
|
||||
stateExample: Record<string, any>;
|
||||
// both component's methods and traits's methods
|
||||
methods: MethodSchema[];
|
||||
events: EventName[];
|
||||
@ -98,22 +101,26 @@ export interface IComponentModel {
|
||||
|
||||
export interface ITraitModel {
|
||||
// trait id only exists in model, doesnt exist in schema
|
||||
spec: RuntimeTrait;
|
||||
id: TraitId;
|
||||
parent: IComponentModel;
|
||||
type: TraitType;
|
||||
rawProperties: Record<string, any>;
|
||||
properties: Record<string, IFieldModel>;
|
||||
properties: IFieldModel;
|
||||
methods: MethodSchema[];
|
||||
stateKeys: StateKey[];
|
||||
_isDirty: boolean;
|
||||
toSchema(): TraitSchema;
|
||||
updateProperty: (key: string, value: any) => void;
|
||||
}
|
||||
|
||||
export interface IFieldModel {
|
||||
value: any;
|
||||
// value: any;
|
||||
isDynamic: boolean;
|
||||
update: (value: any) => void;
|
||||
update: (value: unknown) => void;
|
||||
getProperty: (key: string) => IFieldModel | void;
|
||||
getValue: () => unknown | void | IFieldModel;
|
||||
traverse: (cb: (f: IFieldModel, key: string) => void) => void;
|
||||
rawValue: any;
|
||||
// ids of used components in the expression
|
||||
// dependencies: Array<ComponentId | ModuleId>;
|
||||
refs: Record<ComponentId | ModuleId, string[]>;
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import {
|
||||
TraitType,
|
||||
ITraitModel,
|
||||
IFieldModel,
|
||||
StateKey,
|
||||
TraitId,
|
||||
} from './IAppModel';
|
||||
import { FieldModel } from './FieldModel';
|
||||
@ -15,10 +14,10 @@ let traitIdCount = 0;
|
||||
|
||||
export class TraitModel implements ITraitModel {
|
||||
private schema: TraitSchema;
|
||||
private spec: RuntimeTrait;
|
||||
spec: RuntimeTrait;
|
||||
id: TraitId;
|
||||
type: TraitType;
|
||||
properties: Record<string, IFieldModel> = {};
|
||||
properties: IFieldModel;
|
||||
_isDirty = false;
|
||||
|
||||
constructor(trait: TraitSchema, public parent: IComponentModel) {
|
||||
@ -28,28 +27,17 @@ export class TraitModel implements ITraitModel {
|
||||
this.id = `${this.parent.id}_trait${traitIdCount++}` as TraitId;
|
||||
this.spec = registry.getTraitByType(this.type);
|
||||
|
||||
for (const key in trait.properties) {
|
||||
this.properties[key] = new FieldModel(trait.properties[key]);
|
||||
}
|
||||
this.properties;
|
||||
this.properties = new FieldModel(trait.properties);
|
||||
}
|
||||
|
||||
get rawProperties() {
|
||||
const obj: Record<string, any> = {};
|
||||
for (const key in this.properties) {
|
||||
obj[key] = this.properties[key].value;
|
||||
}
|
||||
return obj;
|
||||
return this.properties.rawValue
|
||||
}
|
||||
|
||||
get methods() {
|
||||
return this.spec ? this.spec.spec.methods : []
|
||||
}
|
||||
|
||||
get stateKeys() {
|
||||
return (this.spec ? Object.keys(this.spec.spec.state.properties || {}) : []) as StateKey[];
|
||||
}
|
||||
|
||||
toSchema(): TraitSchema {
|
||||
if (this._isDirty) {
|
||||
this.schema = genTrait(this.type, this.rawProperties);
|
||||
@ -58,11 +46,7 @@ export class TraitModel implements ITraitModel {
|
||||
}
|
||||
|
||||
updateProperty(key: string, value: any) {
|
||||
if (this.properties[key]) {
|
||||
this.properties[key].update(value);
|
||||
} else {
|
||||
this.properties[key] = new FieldModel(value);
|
||||
}
|
||||
this.properties.update({[key]: value})
|
||||
this._isDirty = true;
|
||||
this.parent._isDirty = true;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ class EditorStore {
|
||||
// currentEditingComponents, it could be app's or module's components
|
||||
_selectedComponentId = '';
|
||||
_hoverComponentId = '';
|
||||
_dragOverComponentId: string = '';
|
||||
_dragOverComponentId = '';
|
||||
// current editor editing target(app or module)
|
||||
currentEditingTarget: EditingTarget = {
|
||||
kind: 'app',
|
||||
|
1
packages/editor/src/global.d.ts
vendored
Normal file
1
packages/editor/src/global.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
declare module 'acorn-loose' {}
|
@ -15,7 +15,7 @@ export class ModifyComponentPropertiesLeafOperation extends BaseLeafOperation<Mo
|
||||
const component = appModel.getComponentById(this.context.componentId as ComponentId);
|
||||
if (component) {
|
||||
for (const property in this.context.properties) {
|
||||
const oldValue = component.properties[property]?.value;
|
||||
const oldValue = component.rawProperties[property];
|
||||
// assign previous data
|
||||
this.previousState[property] = oldValue;
|
||||
let newValue = this.context.properties[property];
|
||||
|
@ -21,7 +21,7 @@ export class ModifyTraitPropertiesLeafOperation extends BaseLeafOperation<Modify
|
||||
}
|
||||
const trait = component.traits[this.context.traitIndex];
|
||||
for (const property in this.context.properties) {
|
||||
const oldValue = trait.properties[property]?.value;
|
||||
const oldValue = trait.rawProperties[property];
|
||||
this.previousState[property] = oldValue;
|
||||
let newValue = this.context.properties[property];
|
||||
if (_.isFunction(newValue)) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { ComponentSchema, RuntimeComponent } from '@sunmao-ui/core';
|
||||
import { Registry } from '@sunmao-ui/runtime';
|
||||
import Ajv from 'ajv';
|
||||
import { PropertiesValidatorRule } from '.';
|
||||
import { AppModel } from '../AppModel/AppModel';
|
||||
import {
|
||||
ISchemaValidator,
|
||||
@ -18,6 +19,7 @@ export class SchemaValidator implements ISchemaValidator {
|
||||
private traitRules: TraitValidatorRule[] = [];
|
||||
private componentRules: ComponentValidatorRule[] = [];
|
||||
private allComponentsRules: AllComponentsValidatorRule[] = [];
|
||||
private propertiesRules: PropertiesValidatorRule[] = [];
|
||||
private componentIdSpecMap: Record<
|
||||
string,
|
||||
RuntimeComponent<string, string, string, string>
|
||||
@ -42,6 +44,9 @@ export class SchemaValidator implements ISchemaValidator {
|
||||
case 'trait':
|
||||
this.traitRules.push(rule);
|
||||
break;
|
||||
case 'properties':
|
||||
this.propertiesRules.push(rule);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -74,6 +79,19 @@ export class SchemaValidator implements ISchemaValidator {
|
||||
this.result = this.result.concat(r);
|
||||
}
|
||||
});
|
||||
|
||||
// validate component properties
|
||||
this.propertiesRules.forEach(rule => {
|
||||
const r = rule.validate({
|
||||
properties: component.properties,
|
||||
component,
|
||||
...baseContext,
|
||||
});
|
||||
if (r.length > 0) {
|
||||
this.result = this.result.concat(r);
|
||||
}
|
||||
});
|
||||
|
||||
this.traitRules.forEach(rule => {
|
||||
component.traits.forEach(trait => {
|
||||
const r = rule.validate({
|
||||
@ -84,6 +102,18 @@ export class SchemaValidator implements ISchemaValidator {
|
||||
if (r.length > 0) {
|
||||
this.result = this.result.concat(r);
|
||||
}
|
||||
// validate trait properties
|
||||
this.propertiesRules.forEach(rule => {
|
||||
const r = rule.validate({
|
||||
properties: trait.properties,
|
||||
trait,
|
||||
component,
|
||||
...baseContext,
|
||||
});
|
||||
if (r.length > 0) {
|
||||
this.result = this.result.concat(r);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ComponentSchema } from '@sunmao-ui/core';
|
||||
import { ComponentSchema, RuntimeComponent } from '@sunmao-ui/core';
|
||||
import { Registry } from '@sunmao-ui/runtime';
|
||||
import Ajv, { ValidateFunction } from 'ajv';
|
||||
import { IAppModel, IComponentModel, ITraitModel } from '../AppModel/IAppModel';
|
||||
import { IAppModel, IComponentModel, IFieldModel, ITraitModel } from '../AppModel/IAppModel';
|
||||
|
||||
export interface ValidatorMap {
|
||||
components: Record<string, ValidateFunction>;
|
||||
@ -9,10 +9,15 @@ export interface ValidatorMap {
|
||||
}
|
||||
|
||||
interface BaseValidateContext {
|
||||
components: ComponentSchema[];
|
||||
validators: ValidatorMap;
|
||||
registry: Registry;
|
||||
appModel: IAppModel;
|
||||
ajv: Ajv;
|
||||
componentIdSpecMap: Record<
|
||||
string,
|
||||
RuntimeComponent<string, string, string, string>
|
||||
>;
|
||||
}
|
||||
|
||||
export interface ComponentValidateContext extends BaseValidateContext {
|
||||
@ -23,11 +28,19 @@ export interface TraitValidateContext extends BaseValidateContext {
|
||||
trait: ITraitModel;
|
||||
component: IComponentModel;
|
||||
}
|
||||
|
||||
export interface PropertiesValidateContext extends BaseValidateContext {
|
||||
properties: IFieldModel;
|
||||
trait?: ITraitModel;
|
||||
component: IComponentModel;
|
||||
}
|
||||
|
||||
export type AllComponentsValidateContext = BaseValidateContext;
|
||||
|
||||
export type ValidateContext =
|
||||
| ComponentValidateContext
|
||||
| TraitValidateContext
|
||||
| PropertiesValidateContext
|
||||
| AllComponentsValidateContext;
|
||||
|
||||
export interface ComponentValidatorRule {
|
||||
@ -36,6 +49,11 @@ export interface ComponentValidatorRule {
|
||||
fix?: (validateContext: ComponentValidateContext) => void;
|
||||
}
|
||||
|
||||
export interface PropertiesValidatorRule {
|
||||
kind: 'properties';
|
||||
validate: (validateContext: PropertiesValidateContext) => ValidateErrorResult[];
|
||||
fix?: (validateContext: PropertiesValidateContext) => void;
|
||||
}
|
||||
export interface AllComponentsValidatorRule {
|
||||
kind: 'allComponents';
|
||||
validate: (validateContext: AllComponentsValidateContext) => ValidateErrorResult[];
|
||||
@ -51,6 +69,7 @@ export interface TraitValidatorRule {
|
||||
export type ValidatorRule =
|
||||
| ComponentValidatorRule
|
||||
| AllComponentsValidatorRule
|
||||
| PropertiesValidatorRule
|
||||
| TraitValidatorRule;
|
||||
|
||||
export interface ISchemaValidator {
|
||||
|
@ -3,45 +3,6 @@ import {
|
||||
ComponentValidateContext,
|
||||
ValidateErrorResult,
|
||||
} from '../interfaces';
|
||||
import { isExpression } from '../utils';
|
||||
|
||||
class ComponentPropertyValidatorRule implements ComponentValidatorRule {
|
||||
kind: 'component' = 'component';
|
||||
|
||||
validate({ component, validators }: ComponentValidateContext): ValidateErrorResult[] {
|
||||
const results: ValidateErrorResult[] = [];
|
||||
const validate = validators.components[component.type];
|
||||
if (!validate) {
|
||||
results.push({
|
||||
message: `Component is not registered: ${component.type}.`,
|
||||
componentId: component.id,
|
||||
});
|
||||
return results;
|
||||
}
|
||||
const properties = component.rawProperties
|
||||
const valid = validate(properties);
|
||||
if (!valid) {
|
||||
validate.errors!.forEach(error => {
|
||||
if (error.keyword === 'type') {
|
||||
const { instancePath } = error;
|
||||
const path = instancePath.split('/')[1];
|
||||
const value = properties[path];
|
||||
// if value is an expression, skip it
|
||||
if (isExpression(value)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
results.push({
|
||||
message: error.message || '',
|
||||
componentId: component.id,
|
||||
property: error.instancePath,
|
||||
});
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleValidatorRule implements ComponentValidatorRule {
|
||||
kind: 'component' = 'component';
|
||||
@ -52,11 +13,11 @@ class ModuleValidatorRule implements ComponentValidatorRule {
|
||||
}
|
||||
|
||||
const results: ValidateErrorResult[] = [];
|
||||
let moduleSpec
|
||||
let moduleSpec;
|
||||
try {
|
||||
moduleSpec = registry.getModuleByType(component.rawProperties.type.value as string);
|
||||
} catch (err) {
|
||||
moduleSpec = undefined
|
||||
moduleSpec = undefined;
|
||||
}
|
||||
if (!moduleSpec) {
|
||||
results.push({
|
||||
@ -68,7 +29,4 @@ class ModuleValidatorRule implements ComponentValidatorRule {
|
||||
return results;
|
||||
}
|
||||
}
|
||||
export const ComponentRules = [
|
||||
new ComponentPropertyValidatorRule(),
|
||||
new ModuleValidatorRule(),
|
||||
];
|
||||
export const ComponentRules = [new ModuleValidatorRule()];
|
||||
|
98
packages/editor/src/validator/rules/PropertiesRules.ts
Normal file
98
packages/editor/src/validator/rules/PropertiesRules.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import { get, has } from 'lodash-es';
|
||||
import { ComponentId } from '../../AppModel/IAppModel';
|
||||
import {
|
||||
PropertiesValidatorRule,
|
||||
PropertiesValidateContext,
|
||||
ValidateErrorResult,
|
||||
} from '../interfaces';
|
||||
|
||||
class PropertySchemaValidatorRule implements PropertiesValidatorRule {
|
||||
kind: 'properties' = 'properties';
|
||||
|
||||
validate({
|
||||
properties,
|
||||
component,
|
||||
trait,
|
||||
validators,
|
||||
}: PropertiesValidateContext): ValidateErrorResult[] {
|
||||
const results: ValidateErrorResult[] = [];
|
||||
let validate
|
||||
|
||||
if (trait) {
|
||||
validate = validators.traits[trait.type]
|
||||
} else {
|
||||
validate = validators.components[component.type];
|
||||
}
|
||||
|
||||
if (!validate) return results;
|
||||
|
||||
const valid = validate(properties.rawValue);
|
||||
if (valid) return results;
|
||||
validate.errors!.forEach(error => {
|
||||
// todo: detect deep error
|
||||
const { instancePath, params } = error;
|
||||
let key = '';
|
||||
if (instancePath) {
|
||||
key = instancePath.split('/')[1];
|
||||
} else {
|
||||
key = params.missingProperty;
|
||||
}
|
||||
const fieldModel = properties.getProperty(key);
|
||||
// if field is expression, ignore type error
|
||||
// fieldModel could be undefiend. if is undefined, still throw error.
|
||||
if (get(fieldModel, 'isDynamic') !== true) {
|
||||
results.push({
|
||||
message: error.message || '',
|
||||
componentId: component.id,
|
||||
property: error.instancePath,
|
||||
traitType: trait?.type,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
class ExpressionValidatorRule implements PropertiesValidatorRule {
|
||||
kind: 'properties' = 'properties';
|
||||
|
||||
validate({
|
||||
properties,
|
||||
component,
|
||||
trait,
|
||||
appModel,
|
||||
}: PropertiesValidateContext): ValidateErrorResult[] {
|
||||
const results: ValidateErrorResult[] = [];
|
||||
|
||||
// validate expression
|
||||
properties.traverse((fieldModel, key) => {
|
||||
Object.keys(fieldModel.refs).forEach((id: string) => {
|
||||
const targetComponent = appModel.getComponentById(id as ComponentId);
|
||||
if (!targetComponent) {
|
||||
results.push({
|
||||
message: `Cannot find '${id}' in store.`,
|
||||
componentId: component.id,
|
||||
property: key,
|
||||
traitType: trait?.type,
|
||||
});
|
||||
} else {
|
||||
const paths = fieldModel.refs[id as ComponentId];
|
||||
paths.forEach(path => {
|
||||
if (!has(targetComponent.stateExample, path)) {
|
||||
results.push({
|
||||
message: `Component '${id}' does not have property '${path}'.`,
|
||||
componentId: component.id,
|
||||
property: key,
|
||||
traitType: trait?.type,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
export const PropertiesRules = [new PropertySchemaValidatorRule(), new ExpressionValidatorRule()];
|
@ -8,48 +8,6 @@ import { EventHandlerSchema } from '@sunmao-ui/runtime';
|
||||
import { isExpression } from '../utils';
|
||||
import { ComponentId, EventName } from '../../AppModel/IAppModel';
|
||||
|
||||
class TraitPropertyValidatorRule implements TraitValidatorRule {
|
||||
kind: 'trait' = 'trait';
|
||||
|
||||
validate({
|
||||
trait,
|
||||
component,
|
||||
validators,
|
||||
}: TraitValidateContext): ValidateErrorResult[] {
|
||||
const results: ValidateErrorResult[] = [];
|
||||
const validate = validators.traits[trait.type];
|
||||
if (!validate) {
|
||||
results.push({
|
||||
message: `Trait is not registered: ${trait.type}.`,
|
||||
componentId: component.id,
|
||||
});
|
||||
return results;
|
||||
}
|
||||
const valid = validate(trait.rawProperties);
|
||||
if (!valid) {
|
||||
validate.errors!.forEach(error => {
|
||||
if (error.keyword === 'type') {
|
||||
const { instancePath } = error;
|
||||
const path = instancePath.split('/')[1];
|
||||
const value = trait.rawProperties[path];
|
||||
|
||||
// if value is an expression, skip it
|
||||
if (isExpression(value)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
results.push({
|
||||
message: error.message || '',
|
||||
componentId: component.id,
|
||||
traitType: trait.type,
|
||||
property: error.instancePath,
|
||||
});
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
class EventHandlerValidatorRule implements TraitValidatorRule {
|
||||
kind: 'trait' = 'trait';
|
||||
traitMethods = ['setValue', 'resetValue', 'triggerFetch'];
|
||||
@ -112,7 +70,7 @@ class EventHandlerValidatorRule implements TraitValidatorRule {
|
||||
if (error.keyword === 'type') {
|
||||
const { instancePath } = error;
|
||||
const path = instancePath.split('/')[1];
|
||||
const value = trait.properties[path];
|
||||
const value = trait.rawProperties[path];
|
||||
|
||||
// if value is an expression, skip it
|
||||
if (isExpression(value)) {
|
||||
@ -132,7 +90,7 @@ class EventHandlerValidatorRule implements TraitValidatorRule {
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
export const TraitRules = [
|
||||
new TraitPropertyValidatorRule(),
|
||||
new EventHandlerValidatorRule(),
|
||||
];
|
||||
|
@ -2,5 +2,6 @@ import { ValidatorRule } from '..';
|
||||
import { AllComponentsRules } from './AllComponentsRules';
|
||||
import { ComponentRules } from './ComponentRules';
|
||||
import { TraitRules } from './TraitRules';
|
||||
import { PropertiesRules } from './PropertiesRules';
|
||||
|
||||
export const rules: ValidatorRule[] = [...AllComponentsRules, ...ComponentRules, ...TraitRules];
|
||||
export const rules: ValidatorRule[] = [...AllComponentsRules, ...ComponentRules, ...TraitRules, ...PropertiesRules];
|
||||
|
@ -29,6 +29,7 @@ export * from './types';
|
||||
export * from './types/TraitPropertiesSchema';
|
||||
export * from './constants';
|
||||
export * from './services/registry';
|
||||
export * from './services/stateStore';
|
||||
export { ModuleRenderer } from './components/_internal/ModuleRenderer';
|
||||
export { default as Text, TextPropertySchema } from './components/_internal/Text';
|
||||
|
||||
|
@ -11,8 +11,8 @@ import {
|
||||
UnionKind,
|
||||
} from '@sinclair/typebox';
|
||||
|
||||
export function parseTypeBox(tSchema: TSchema): Static<typeof tSchema> {
|
||||
if (tSchema.modifier === OptionalModifier) {
|
||||
export function parseTypeBox(tSchema: TSchema, noOptional = false): Static<typeof tSchema> {
|
||||
if (tSchema.modifier === OptionalModifier && !noOptional) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -31,14 +31,14 @@ export function parseTypeBox(tSchema: TSchema): Static<typeof tSchema> {
|
||||
case tSchema.kind === ObjectKind: {
|
||||
const obj: Static<typeof tSchema> = {};
|
||||
for (const key in tSchema.properties) {
|
||||
obj[key] = parseTypeBox(tSchema.properties[key]);
|
||||
obj[key] = parseTypeBox(tSchema.properties[key], noOptional);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
case tSchema.kind === UnionKind && 'anyOf' in tSchema && tSchema.anyOf.length > 0:
|
||||
case tSchema.kind === UnionKind && 'oneOf' in tSchema && tSchema.oneOf.length > 0: {
|
||||
const subSchema = (tSchema.anyOf || tSchema.oneOf)[0];
|
||||
return parseTypeBox(subSchema);
|
||||
return parseTypeBox(subSchema, noOptional);
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
|
17
yarn.lock
17
yarn.lock
@ -3416,11 +3416,23 @@ acorn-jsx@^5.3.1:
|
||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
||||
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
||||
|
||||
acorn-loose@^8.3.0:
|
||||
version "8.3.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn-loose/-/acorn-loose-8.3.0.tgz#0cd62461d21dce4f069785f8d3de136d5525029a"
|
||||
integrity sha512-75lAs9H19ldmW+fAbyqHdjgdCrz0pWGXKmnqFoh8PyVd1L2RIb4RzYrSjmopeqv3E1G3/Pimu6GgLlrGbrkF7w==
|
||||
dependencies:
|
||||
acorn "^8.5.0"
|
||||
|
||||
acorn-walk@^7.1.1:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
|
||||
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
|
||||
|
||||
acorn-walk@^8.2.0:
|
||||
version "8.2.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
|
||||
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
|
||||
|
||||
acorn@^7.1.1, acorn@^7.4.0:
|
||||
version "7.4.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
|
||||
@ -3431,6 +3443,11 @@ acorn@^8.2.4:
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2"
|
||||
integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==
|
||||
|
||||
acorn@^8.5.0, acorn@^8.7.0:
|
||||
version "8.7.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf"
|
||||
integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==
|
||||
|
||||
add-stream@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa"
|
||||
|
Loading…
x
Reference in New Issue
Block a user