Browse Source

fix: add formula validation - WIP

pull/7268/head
Pranav C 1 year ago
parent
commit
8cbe49cd4d
  1. 65
      packages/nc-gui/components/smartsheet/column/FormulaOptions.vue

65
packages/nc-gui/components/smartsheet/column/FormulaOptions.vue

@ -190,7 +190,7 @@ function parseAndValidateFormula(formula: string) {
} }
function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = new Set()) { function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = new Set()) {
let type: formulaTypes; let returnType: formulaTypes
if (parsedTree.type === JSEPNode.CALL_EXP) { if (parsedTree.type === JSEPNode.CALL_EXP) {
const calleeName = parsedTree.callee.name.toUpperCase() const calleeName = parsedTree.callee.name.toUpperCase()
// validate function name // validate function name
@ -203,17 +203,41 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
if (validation.args.rqd !== undefined && validation.args.rqd !== parsedTree.arguments.length) { if (validation.args.rqd !== undefined && validation.args.rqd !== parsedTree.arguments.length) {
errors.add(t('msg.formula.requiredArgumentsFormula', { requiredArguments: validation.args.rqd, calleeName })) errors.add(t('msg.formula.requiredArgumentsFormula', { requiredArguments: validation.args.rqd, calleeName }))
} else if (validation.args.min !== undefined && validation.args.min > parsedTree.arguments.length) { } else if (validation.args.min !== undefined && validation.args.min > parsedTree.arguments.length) {
errors.add(t('msg.formula.minRequiredArgumentsFormula', { minRequiredArguments: validation.args.min, calleeName })) errors.add(
t('msg.formula.minRequiredArgumentsFormula', {
minRequiredArguments: validation.args.min,
calleeName,
}),
)
} else if (validation.args.max !== undefined && validation.args.max < parsedTree.arguments.length) { } else if (validation.args.max !== undefined && validation.args.max < parsedTree.arguments.length) {
errors.add(t('msg.formula.maxRequiredArgumentsFormula', { maxRequiredArguments: validation.args.max, calleeName })) errors.add(
t('msg.formula.maxRequiredArgumentsFormula', {
maxRequiredArguments: validation.args.max,
calleeName,
}),
)
} }
} }
parsedTree.arguments.map((arg: Record<string, any>) => validateAgainstMeta(arg, errors)) parsedTree.arguments.map((arg: Record<string, any>) => validateAgainstMeta(arg, errors))
// get args type and validate
const validateResult = parsedTree.arguments.map((arg) => {
return validateAgainstMeta(arg, errors, typeErrors)
})
const argsTypes = validateResult.map((v: any) => v.returnType);
if (typeof validateResult[0].returnType === 'function') {
returnType = formulas[calleeName].returnType(argsTypes)
} else if (validateResult[0]) {
returnType = formulas[calleeName].returnType
}
// validate data type // validate data type
if (parsedTree.callee.type === JSEPNode.IDENTIFIER) { if (parsedTree.callee.type === JSEPNode.IDENTIFIER) {
const expectedType = formulas[calleeName.toUpperCase()].type const expectedType = formulas[calleeName.toUpperCase()].type
if (expectedType === formulaTypes.NUMERIC) { if (expectedType === formulaTypes.NUMERIC) {
if (calleeName === 'WEEKDAY') { if (calleeName === 'WEEKDAY') {
// parsedTree.arguments[0] = date // parsedTree.arguments[0] = date
@ -242,7 +266,7 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
typeErrors, typeErrors,
) )
} else { } else {
parsedTree.arguments.map((arg: Record<string, any>) => validateAgainstType(arg, expectedType, null, typeErrors)) parsedTree.arguments.map((arg: Record<string, any>) => validateAgainstType(arg, expectedType, null, typeErrors, argsTypes))
} }
} else if (expectedType === formulaTypes.DATE) { } else if (expectedType === formulaTypes.DATE) {
if (calleeName === 'DATEADD') { if (calleeName === 'DATEADD') {
@ -438,7 +462,21 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
} }
validateAgainstMeta(parsedTree.left, errors) validateAgainstMeta(parsedTree.left, errors)
validateAgainstMeta(parsedTree.right, errors) validateAgainstMeta(parsedTree.right, errors)
// todo: type extraction for binary exps
returnType = formulaTypes.NUMERIC
} else if (parsedTree.type === JSEPNode.LITERAL || parsedTree.type === JSEPNode.UNARY_EXP) { } else if (parsedTree.type === JSEPNode.LITERAL || parsedTree.type === JSEPNode.UNARY_EXP) {
if (parsedTree.type === JSEPNode.LITERAL) {
if (typeof parsedTree.value === 'number') {
returnType = formulaTypes.NUMERIC
} else if (typeof parsedTree.value === 'string') {
returnType = formulaTypes.STRING
} else if (typeof parsedTree.value === 'boolean') {
returnType = formulaTypes.BOOLEAN
} else {
returnType = formulaTypes.STRING
}
}
// do nothing // do nothing
} else if (parsedTree.type === JSEPNode.COMPOUND) { } else if (parsedTree.type === JSEPNode.COMPOUND) {
if (parsedTree.body.length) { if (parsedTree.body.length) {
@ -447,10 +485,11 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
} else { } else {
errors.add(t('msg.formula.cantSaveFieldFormulaInvalid')) errors.add(t('msg.formula.cantSaveFieldFormulaInvalid'))
} }
return {errors, type} return { errors, returnType }
} }
function validateAgainstType(parsedTree: any, expectedType: string, func: any, typeErrors = new Set()) { function validateAgainstType(parsedTree: any, expectedType: string, func: any, typeErrors = new Set(), argTypes: formulaTypes = []) {
let type
if (parsedTree === false || typeof parsedTree === 'undefined') { if (parsedTree === false || typeof parsedTree === 'undefined') {
return typeErrors return typeErrors
} }
@ -460,10 +499,14 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
} else if (expectedType === formulaTypes.NUMERIC) { } else if (expectedType === formulaTypes.NUMERIC) {
if (typeof parsedTree.value !== 'number') { if (typeof parsedTree.value !== 'number') {
typeErrors.add(t('msg.formula.numericTypeIsExpected')) typeErrors.add(t('msg.formula.numericTypeIsExpected'))
} else {
type = formulaTypes.NUMERIC
} }
} else if (expectedType === formulaTypes.STRING) { } else if (expectedType === formulaTypes.STRING) {
if (typeof parsedTree.value !== 'string') { if (typeof parsedTree.value !== 'string') {
typeErrors.add(t('msg.formula.stringTypeIsExpected')) typeErrors.add(t('msg.formula.stringTypeIsExpected'))
} else {
type = formulaTypes.STRING
} }
} }
} else if (parsedTree.type === JSEPNode.IDENTIFIER) { } else if (parsedTree.type === JSEPNode.IDENTIFIER) {
@ -475,6 +518,7 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
if (col.uidt === UITypes.Formula) { if (col.uidt === UITypes.Formula) {
const foundType = getRootDataType(jsep(col.colOptions?.formula_raw)) const foundType = getRootDataType(jsep(col.colOptions?.formula_raw))
type = foundType
if (foundType === 'N/A') { if (foundType === 'N/A') {
typeErrors.add(t('msg.formula.notSupportedToReferenceColumn', { columnName: col.title })) typeErrors.add(t('msg.formula.notSupportedToReferenceColumn', { columnName: col.title }))
} else if (expectedType !== foundType) { } else if (expectedType !== foundType) {
@ -504,6 +548,7 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
}), }),
) )
} }
type = formulaTypes.STRING
break break
// numeric // numeric
@ -523,6 +568,7 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
}), }),
) )
} }
type = formulaTypes.NUMERIC
break break
// date // date
@ -539,6 +585,7 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
}), }),
) )
} }
type = formulaTypes.DATE
break break
case UITypes.Rollup: { case UITypes.Rollup: {
@ -616,6 +663,8 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
}), }),
) )
} }
type = formulaTypes.NUMERIC
} else if (parsedTree.type === JSEPNode.CALL_EXP) { } else if (parsedTree.type === JSEPNode.CALL_EXP) {
const calleeName = parsedTree.callee.name.toUpperCase() const calleeName = parsedTree.callee.name.toUpperCase()
if (formulas[calleeName]?.type && expectedType !== formulas[calleeName].type) { if (formulas[calleeName]?.type && expectedType !== formulas[calleeName].type) {
@ -626,8 +675,10 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
}), }),
) )
} }
// todo: derive type from returnType
type = formulas[calleeName]?.type
} }
return typeErrors return { type, typeErrors }
} }
function getRootDataType(parsedTree: any): any { function getRootDataType(parsedTree: any): any {

Loading…
Cancel
Save