Browse Source

fix: add formula validation - WIP

pull/7268/head
Pranav C 9 months 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()) {
let type: formulaTypes;
let returnType: formulaTypes
if (parsedTree.type === JSEPNode.CALL_EXP) {
const calleeName = parsedTree.callee.name.toUpperCase()
// 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) {
errors.add(t('msg.formula.requiredArgumentsFormula', { requiredArguments: validation.args.rqd, calleeName }))
} 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) {
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))
// 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
if (parsedTree.callee.type === JSEPNode.IDENTIFIER) {
const expectedType = formulas[calleeName.toUpperCase()].type
if (expectedType === formulaTypes.NUMERIC) {
if (calleeName === 'WEEKDAY') {
// parsedTree.arguments[0] = date
@ -242,7 +266,7 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
typeErrors,
)
} 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) {
if (calleeName === 'DATEADD') {
@ -438,7 +462,21 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
}
validateAgainstMeta(parsedTree.left, 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) {
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
} else if (parsedTree.type === JSEPNode.COMPOUND) {
if (parsedTree.body.length) {
@ -447,10 +485,11 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
} else {
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') {
return typeErrors
}
@ -460,10 +499,14 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
} else if (expectedType === formulaTypes.NUMERIC) {
if (typeof parsedTree.value !== 'number') {
typeErrors.add(t('msg.formula.numericTypeIsExpected'))
} else {
type = formulaTypes.NUMERIC
}
} else if (expectedType === formulaTypes.STRING) {
if (typeof parsedTree.value !== 'string') {
typeErrors.add(t('msg.formula.stringTypeIsExpected'))
} else {
type = formulaTypes.STRING
}
}
} else if (parsedTree.type === JSEPNode.IDENTIFIER) {
@ -475,6 +518,7 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
if (col.uidt === UITypes.Formula) {
const foundType = getRootDataType(jsep(col.colOptions?.formula_raw))
type = foundType
if (foundType === 'N/A') {
typeErrors.add(t('msg.formula.notSupportedToReferenceColumn', { columnName: col.title }))
} else if (expectedType !== foundType) {
@ -504,6 +548,7 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
}),
)
}
type = formulaTypes.STRING
break
// numeric
@ -523,6 +568,7 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
}),
)
}
type = formulaTypes.NUMERIC
break
// date
@ -539,6 +585,7 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
}),
)
}
type = formulaTypes.DATE
break
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) {
const calleeName = parsedTree.callee.name.toUpperCase()
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 {

Loading…
Cancel
Save