Merge pull request #546 from smartxworks/fix/validators-bugs-2

Fix some validator bugs
This commit is contained in:
yz-yu 2022-07-27 18:11:19 +08:00 committed by GitHub
commit 95da710e0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 138 additions and 24 deletions

View File

@ -20,12 +20,21 @@ describe('Field test', () => {
'value',
]);
expect(field.refComponentInfos['list' as ComponentId].refProperties).toEqual([
'[0]',
'[0].text',
'0',
'0.text',
]);
expect(field.rawValue).toEqual('{{input.value}} + {{list[0].text}}');
});
it('allow using question mask in expression', () => {
const field = new FieldModel('{{api.fetch?.data }}');
expect(field.isDynamic).toEqual(true);
expect(field.refComponentInfos['api' as ComponentId].refProperties).toEqual([
'fetch',
'fetch.data',
]);
});
it('parse inline variable in expression', () => {
const field = new FieldModel('{{ [].length }}');
expect(field.isDynamic).toEqual(true);
@ -71,6 +80,12 @@ describe('Field test', () => {
expect(field.refComponentInfos).toEqual({});
});
it('should not count variables declared in iife in refs', () => {
const field = new FieldModel('{{(function() {const foo = "bar"})() }}');
expect(field.isDynamic).toEqual(true);
expect(field.refComponentInfos).toEqual({});
});
it('get value by path', () => {
const field = new FieldModel({ foo: [{}, { bar: { baz: 'Hello, world!' } }] });
expect(field.getPropertyByPath('foo.1.bar.baz')?.getValue()).toEqual('Hello, world!');

View File

@ -5,6 +5,7 @@ import {
UseDependencyInExpressionSchema,
LocalVariableInIIFEExpressionSchema,
DynamicStateTraitAnyTypeSchema,
NestedObjectExpressionSchema,
} from './mock';
import { SchemaValidator } from '../../src/validator';
import { registry } from '../services';
@ -68,5 +69,11 @@ describe('Validate component', () => {
);
expect(_result.length).toBe(0);
});
it('allow use nested object value in expression', () => {
const _result = schemaValidator.validate(
new AppModel(NestedObjectExpressionSchema, registry)
);
expect(_result.length).toBe(0);
});
});
});

View File

@ -414,3 +414,44 @@ export const DynamicStateTraitSchema: ComponentSchema[] = [
traits: [],
},
];
export const NestedObjectExpressionSchema: ComponentSchema[] = [
{
id: 'api0',
type: 'core/v1/dummy',
properties: {},
traits: [
{
type: 'core/v1/fetch',
properties: {
url: '',
method: 'get',
lazy: false,
disabled: false,
headers: {},
body: {},
bodyType: 'json',
onComplete: [
{
componentId: '',
method: {
name: '',
},
},
],
onError: [
{
componentId: '',
method: {
name: '',
parameters: {},
},
// should not warn api0.fetch.code
disabled: '{{ api0.fetch.code !== 401 }}',
},
],
},
},
],
},
];

View File

@ -20,6 +20,12 @@ import escodegen from 'escodegen';
import { JSONSchema7 } from 'json-schema';
export type FunctionNode = ASTNode & { params: ASTNode[] };
export type DeclaratorNode = ASTNode & { id: ASTNode };
export type LiteralNode = ASTNode & { raw: string };
export type ExpressionNode = ASTNode & {
object: ExpressionNode;
property: ExpressionNode | LiteralNode;
};
export class FieldModel implements IFieldModel {
isDynamic = false;
refComponentInfos: Record<ComponentId | ModuleId, RefInfo> = {};
@ -161,7 +167,7 @@ export class FieldModel implements IFieldModel {
this.astNodes[exp] = node as ASTNode;
// These are varirables of iife, they should be count in refs.
// These are variables of iife, they should be count in refs.
let localVariables: ASTNode[] = [];
simpleWalk(node, {
@ -190,18 +196,17 @@ export class FieldModel implements IFieldModel {
break;
case 'MemberExpression':
const str = exp.slice(expressionNode.start, expressionNode.end);
let path = str.replace(lastIdentifier, '');
if (path.startsWith('.')) {
path = path.slice(1, path.length);
}
this.refComponentInfos[lastIdentifier]?.refProperties.push(path);
this.refComponentInfos[lastIdentifier]?.refProperties.push(
this.genPathFromMemberExpressionNode(expressionNode as ExpressionNode)
);
break;
default:
}
},
VariableDeclarator: declarator => {
localVariables.push((declarator as DeclaratorNode).id);
},
});
// remove localVariables from refs
for (const key in this.refComponentInfos) {
if (localVariables.some(({ name }) => key === name)) {
@ -211,6 +216,21 @@ export class FieldModel implements IFieldModel {
});
}
private genPathFromMemberExpressionNode(expNode: ExpressionNode) {
const path: string[] = [];
function travel(node: ExpressionNode) {
path.unshift(
node.property?.name || (node.property as LiteralNode)?.raw || node.name
);
if (node.object) {
travel(node.object);
}
}
travel(expNode);
return path.slice(1).join('.');
}
private onReferenceIdChange({ oldId, newId }: AppModelEventType['idChange']) {
if (!this.componentModel) {
return;

View File

@ -77,7 +77,7 @@ class ExpressionValidatorRule implements PropertiesValidatorRule {
private checkObjHasPath(obj: Record<string, any>, path: string) {
const arr = path.split('.');
const curr = obj;
let curr = obj;
for (const key of arr) {
const value = curr[key];
if (value === undefined) {
@ -86,6 +86,7 @@ class ExpressionValidatorRule implements PropertiesValidatorRule {
// if meet AnyTypePlaceholder, return true and skip
return true;
}
curr = value;
}
return true;
}

View File

@ -94,6 +94,7 @@ class EventHandlerValidatorRule implements TraitValidatorRule {
traitType: trait?.type,
traitIndex,
});
return results;
});
}
} else {

View File

@ -93,5 +93,13 @@ describe('generateDefaultValueFromSpec function', () => {
it('can parse any type', () => {
expect(generateDefaultValueFromSpec({})).toEqual('');
expect(generateDefaultValueFromSpec({}, true)).toEqual(AnyTypePlaceholder);
expect(
(
generateDefaultValueFromSpec(
Type.Object({ foo: Type.Object({ bar: Type.Any() }) }),
true
) as any
).foo.bar
).toEqual(AnyTypePlaceholder);
});
});

View File

@ -23,20 +23,40 @@ export function StringUnion<T extends string[]>(values: [...T], options?: any) {
);
}
function getArray(items: JSONSchema7Definition[]): JSONSchema7Type[] {
function getArray(
items: JSONSchema7Definition[],
returnPlaceholderForAny = false
): JSONSchema7Type[] {
return items.map(item =>
isJSONSchema(item) ? generateDefaultValueFromSpec(item) : null
isJSONSchema(item)
? generateDefaultValueFromSpec(item, returnPlaceholderForAny)
: null
);
}
function getObject(spec: JSONSchema7): JSONSchema7Object {
function getObject(
spec: JSONSchema7,
returnPlaceholderForAny = false
): JSONSchema7Object | string {
const obj: JSONSchema7Object = {};
if (spec.allOf && spec.allOf.length > 0) {
return (getArray(spec.allOf) as JSONSchema7Object[]).reduce((prev, cur) => {
prev = Object.assign(prev, cur);
return prev;
}, obj);
return (getArray(spec.allOf, returnPlaceholderForAny) as JSONSchema7Object[]).reduce(
(prev, cur) => {
prev = Object.assign(prev, cur);
return prev;
},
obj
);
}
// if not specific property, treat it as any type
if (!spec.properties) {
if (returnPlaceholderForAny) {
return AnyTypePlaceholder;
}
return {};
}
for (const key in spec.properties) {
@ -44,7 +64,7 @@ function getObject(spec: JSONSchema7): JSONSchema7Object {
if (typeof subSpec === 'boolean') {
obj[key] = null;
} else if (subSpec) {
obj[key] = generateDefaultValueFromSpec(subSpec);
obj[key] = generateDefaultValueFromSpec(subSpec, returnPlaceholderForAny);
}
}
return obj;
@ -54,11 +74,12 @@ export function generateDefaultValueFromSpec(
spec: JSONSchema7,
returnPlaceholderForAny = false
): JSONSchema7Type {
console.log(spec);
if (!spec.type) {
if ((spec.anyOf && spec.anyOf!.length > 0) || (spec.oneOf && spec.oneOf.length > 0)) {
const subSpec = (spec.anyOf! || spec.oneOf)[0];
if (typeof subSpec === 'boolean') return null;
return generateDefaultValueFromSpec(subSpec);
return generateDefaultValueFromSpec(subSpec, returnPlaceholderForAny);
}
// It is any type
@ -77,7 +98,7 @@ export function generateDefaultValueFromSpec(
const subSpec = {
type: spec.type[0],
} as JSONSchema7;
return generateDefaultValueFromSpec(subSpec);
return generateDefaultValueFromSpec(subSpec, returnPlaceholderForAny);
}
case spec.type === 'string':
if (spec.enum && spec.enum.length > 0) {
@ -90,16 +111,16 @@ export function generateDefaultValueFromSpec(
case spec.type === 'array':
return spec.items
? Array.isArray(spec.items)
? getArray(spec.items)
? getArray(spec.items, returnPlaceholderForAny)
: isJSONSchema(spec.items)
? [generateDefaultValueFromSpec(spec.items)]
? [generateDefaultValueFromSpec(spec.items, returnPlaceholderForAny)]
: null
: [];
case spec.type === 'number':
case spec.type === 'integer':
return 0;
case spec.type === 'object':
return getObject(spec);
return getObject(spec, returnPlaceholderForAny);
case spec.type === 'null':
return null;
default: