diff --git a/src/compiler/nodes/array.ts b/src/compiler/nodes/array.ts index bc013bc..b9b2194 100644 --- a/src/compiler/nodes/array.ts +++ b/src/compiler/nodes/array.ts @@ -18,6 +18,7 @@ import { defineFieldValidations } from '../../scripts/field/validations.js' import type { CompilerField, CompilerParent, ArrayNode } from '../../types.js' import { defineArrayInitialOutput } from '../../scripts/array/initial_output.js' import { defineFieldExistenceValidations } from '../../scripts/field/existence_validations.js' +import { defineArrayVariables } from '../../scripts/array/variables.js' /** * Compiles an array schema node to JS string output. @@ -84,43 +85,50 @@ export class ArrayNodeCompiler extends BaseNode { ) /** - * Wrapping initialization of output + array elements - * validation inside `if array field is valid` block. - * - * Pre step: 3 + * Step 3: Define the code to validate the field is an array */ - const isArrayValidBlock = defineIsValidGuard({ - variableName: this.field.variableName, - bail: this.#node.bail, - guardedCodeSnippet: `${defineArrayInitialOutput({ + this.#buffer.writeStatement( + defineArrayVariables({ variableName: this.field.variableName, - outputExpression: this.field.outputExpression, - outputValueExpression: `[]`, - })}${this.#buffer.newLine}${this.#compileArrayElements()}`, - }) + }) + ) /** - * Wrapping field validations + "isArrayValidBlock" inside - * `if value is array` check. - * - * Pre step: 3 + * Step 4: Execute array validations */ - const isValueAnArrayBlock = defineArrayGuard({ - variableName: this.field.variableName, - guardedCodeSnippet: `${defineFieldValidations({ + this.#buffer.writeStatement( + defineFieldValidations({ variableName: this.field.variableName, validations: this.#node.validations, bail: this.#node.bail, - dropMissingCheck: true, - })}${this.#buffer.newLine}${isArrayValidBlock}`, + dropMissingCheck: false, + existenceCheckExpression: `${this.field.variableName}_is_array`, + }) + ) + + /** + * Step 5: If value is an array and array is valid, then + * we must validate the children and write the output + */ + const isArrayValidBlock = defineArrayGuard({ + variableName: this.field.variableName, + guardedCodeSnippet: `${this.#buffer.newLine}${defineIsValidGuard({ + variableName: this.field.variableName, + bail: this.#node.bail, + guardedCodeSnippet: `${defineArrayInitialOutput({ + variableName: this.field.variableName, + outputExpression: this.field.outputExpression, + outputValueExpression: `[]`, + })}${this.#buffer.newLine}${this.#compileArrayElements()}`, + })}`, }) /** - * Step 3: Define `if value is an array` block and `else if value is null` - * block. + * Step 6: Define `if value is an array + valid` block + * `else if value is null` block. */ this.#buffer.writeStatement( - `${isValueAnArrayBlock}${this.#buffer.newLine}${defineFieldNullOutput({ + `${isArrayValidBlock}${this.#buffer.newLine}${defineFieldNullOutput({ allowNull: this.#node.allowNull, outputExpression: this.field.outputExpression, variableName: this.field.variableName, diff --git a/src/compiler/nodes/object.ts b/src/compiler/nodes/object.ts index 4e4d795..c92fc44 100644 --- a/src/compiler/nodes/object.ts +++ b/src/compiler/nodes/object.ts @@ -20,6 +20,7 @@ import { defineObjectInitialOutput } from '../../scripts/object/initial_output.j import { defineMoveProperties } from '../../scripts/object/move_unknown_properties.js' import { defineFieldExistenceValidations } from '../../scripts/field/existence_validations.js' import type { CompilerField, CompilerParent, ObjectNode, ObjectGroupNode } from '../../types.js' +import { defineObjectVariables } from '../../scripts/object/variables.js' /** * Compiles an object schema node to JS string output. @@ -148,11 +149,33 @@ export class ObjectNodeCompiler extends BaseNode { }) ) + /** + * Step 3: Define the code to validate the field is an object + */ + this.#buffer.writeStatement( + defineObjectVariables({ + variableName: this.field.variableName, + }) + ) + + /** + * Step 4: Execute object validations + */ + this.#buffer.writeStatement( + defineFieldValidations({ + variableName: this.field.variableName, + validations: this.#node.validations, + bail: this.#node.bail, + dropMissingCheck: false, + existenceCheckExpression: `${this.field.variableName}_is_object`, + }) + ) + /** * Wrapping initialization of output + object children validations * validation inside `if object field is valid` block. * - * Pre step: 3 + * Pre step: 5 */ const isObjectValidBlock = defineIsValidGuard({ variableName: this.field.variableName, @@ -174,20 +197,15 @@ export class ObjectNodeCompiler extends BaseNode { * Wrapping field validations + "isObjectValidBlock" inside * `if value is object` check. * - * Pre step: 3 + * Pre step: 5 */ const isValueAnObject = defineObjectGuard({ variableName: this.field.variableName, - guardedCodeSnippet: `${defineFieldValidations({ - variableName: this.field.variableName, - validations: this.#node.validations, - bail: this.#node.bail, - dropMissingCheck: true, - })}${isObjectValidBlock}`, + guardedCodeSnippet: `${isObjectValidBlock}`, }) /** - * Step 3: Define `if value is an object` block and `else if value is null` + * Step 5: Define `if value is an object` block and `else if value is null` * block. */ this.#buffer.writeStatement( diff --git a/src/compiler/nodes/record.ts b/src/compiler/nodes/record.ts index 8ab3937..9766d20 100644 --- a/src/compiler/nodes/record.ts +++ b/src/compiler/nodes/record.ts @@ -18,6 +18,7 @@ import { defineFieldValidations } from '../../scripts/field/validations.js' import type { CompilerField, CompilerParent, RecordNode } from '../../types.js' import { defineObjectInitialOutput } from '../../scripts/object/initial_output.js' import { defineFieldExistenceValidations } from '../../scripts/field/existence_validations.js' +import { defineObjectVariables } from '../../scripts/object/variables.js' /** * Compiles a record schema node to JS string output. @@ -83,6 +84,28 @@ export class RecordNodeCompiler extends BaseNode { }) ) + /** + * Step 3: Define the code to validate the field is an object + */ + this.#buffer.writeStatement( + defineObjectVariables({ + variableName: this.field.variableName, + }) + ) + + /** + * Step 4: Execute object validations + */ + this.#buffer.writeStatement( + defineFieldValidations({ + variableName: this.field.variableName, + validations: this.#node.validations, + bail: this.#node.bail, + dropMissingCheck: false, + existenceCheckExpression: `${this.field.variableName}_is_object`, + }) + ) + /** * Wrapping initialization of output + tuple validation + array elements * validation inside `if array field is valid` block. @@ -107,12 +130,7 @@ export class RecordNodeCompiler extends BaseNode { */ const isValueAnObjectBlock = defineObjectGuard({ variableName: this.field.variableName, - guardedCodeSnippet: `${defineFieldValidations({ - variableName: this.field.variableName, - validations: this.#node.validations, - bail: this.#node.bail, - dropMissingCheck: true, - })}${this.#buffer.newLine}${isObjectValidBlock}`, + guardedCodeSnippet: `${this.#buffer.newLine}${isObjectValidBlock}`, }) /** diff --git a/src/compiler/nodes/tuple.ts b/src/compiler/nodes/tuple.ts index ea319fe..39347be 100644 --- a/src/compiler/nodes/tuple.ts +++ b/src/compiler/nodes/tuple.ts @@ -17,6 +17,7 @@ import { defineFieldValidations } from '../../scripts/field/validations.js' import type { CompilerField, CompilerParent, TupleNode } from '../../types.js' import { defineArrayInitialOutput } from '../../scripts/array/initial_output.js' import { defineFieldExistenceValidations } from '../../scripts/field/existence_validations.js' +import { defineArrayVariables } from '../../scripts/array/variables.js' /** * Compiles a tuple schema node to JS string output. @@ -77,45 +78,52 @@ export class TupleNodeCompiler extends BaseNode { ) /** - * Wrapping initialization of output + tuple validation - * validation inside `if array field is valid` block. - * - * Pre step: 3 + * Step 3: Define the code to validate the field is an array */ - const isArrayValidBlock = defineIsValidGuard({ - variableName: this.field.variableName, - bail: this.#node.bail, - guardedCodeSnippet: `${defineArrayInitialOutput({ + this.#buffer.writeStatement( + defineArrayVariables({ variableName: this.field.variableName, - outputExpression: this.field.outputExpression, - outputValueExpression: this.#node.allowUnknownProperties - ? `copyProperties(${this.field.variableName}.value)` - : `[]`, - })}${this.#compileTupleChildren()}`, - }) + }) + ) /** - * Wrapping field validations + "isArrayValidBlock" inside - * `if value is array` check. - * - * Pre step: 3 + * Step 4: Execute tuple (aka array) validations */ - const isValueAnArrayBlock = defineArrayGuard({ - variableName: this.field.variableName, - guardedCodeSnippet: `${defineFieldValidations({ + this.#buffer.writeStatement( + defineFieldValidations({ variableName: this.field.variableName, validations: this.#node.validations, bail: this.#node.bail, - dropMissingCheck: true, - })}${this.#buffer.newLine}${isArrayValidBlock}`, + dropMissingCheck: false, + existenceCheckExpression: `${this.field.variableName}_is_array`, + }) + ) + + /** + * Step 5: If value is an array and array is valid, then + * we must validate the children and write the output + */ + const isArrayValidBlock = defineArrayGuard({ + variableName: this.field.variableName, + guardedCodeSnippet: `${this.#buffer.newLine}${defineIsValidGuard({ + variableName: this.field.variableName, + bail: this.#node.bail, + guardedCodeSnippet: `${defineArrayInitialOutput({ + variableName: this.field.variableName, + outputExpression: this.field.outputExpression, + outputValueExpression: this.#node.allowUnknownProperties + ? `copyProperties(${this.field.variableName}.value)` + : `[]`, + })}${this.#buffer.newLine}${this.#compileTupleChildren()}`, + })}`, }) /** - * Step 3: Define `if value is an array` block and `else if value is null` - * block. + * Step 6: Define `if value is an array + valid` block + * `else if value is null` block. */ this.#buffer.writeStatement( - `${isValueAnArrayBlock}${this.#buffer.newLine}${defineFieldNullOutput({ + `${isArrayValidBlock}${this.#buffer.newLine}${defineFieldNullOutput({ allowNull: this.#node.allowNull, outputExpression: this.field.outputExpression, variableName: this.field.variableName, diff --git a/src/scripts/array/guard.ts b/src/scripts/array/guard.ts index 16928cc..2fef952 100644 --- a/src/scripts/array/guard.ts +++ b/src/scripts/array/guard.ts @@ -16,7 +16,7 @@ type ArrayGuardOptions = { * Returns JS fragment to wrap code inside an array conditional */ export function defineArrayGuard({ variableName, guardedCodeSnippet }: ArrayGuardOptions) { - return `if (ensureIsArray(${variableName})) { + return `if (${variableName}_is_array) { ${guardedCodeSnippet} }` } diff --git a/src/scripts/array/variables.ts b/src/scripts/array/variables.ts new file mode 100644 index 0000000..82b2e0d --- /dev/null +++ b/src/scripts/array/variables.ts @@ -0,0 +1,19 @@ +/* + * @vinejs/compiler + * + * (c) VineJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +type FieldOptions = { + variableName: string +} + +/** + * Returns JS fragment for defining the array variables + */ +export function defineArrayVariables({ variableName }: FieldOptions) { + return `const ${variableName}_is_array = ensureIsArray(${variableName});` +} diff --git a/src/scripts/field/validations.ts b/src/scripts/field/validations.ts index 0dbf46d..0437442 100644 --- a/src/scripts/field/validations.ts +++ b/src/scripts/field/validations.ts @@ -22,6 +22,12 @@ type ValidationOptions = { * rule is implicit or not */ dropMissingCheck: boolean + + /** + * The expression to use for performing the existence check. + * Defaults to "item.isDefined" + */ + existenceCheckExpression?: string } /** @@ -57,10 +63,12 @@ function emitValidationSnippet( { isAsync, implicit, ruleFnId }: ValidationNode, variableName: string, bail: boolean, - dropMissingCheck: boolean + dropMissingCheck: boolean, + existenceCheckExpression?: string ) { const rule = `refs['${ruleFnId}']` const callable = `${rule}.validator(${variableName}.value, ${rule}.options, ${variableName});` + existenceCheckExpression = existenceCheckExpression || `${variableName}.isDefined` /** * Add "isValid" condition when the bail flag is turned on. @@ -70,7 +78,7 @@ function emitValidationSnippet( /** * Add the "!is_[variableName]_missing" conditional when the rule is not implicit. */ - const implicitCondition = implicit || dropMissingCheck ? '' : `${variableName}.isDefined` + const implicitCondition = implicit || dropMissingCheck ? '' : existenceCheckExpression /** * Wrapping the validation invocation inside conditionals based upon @@ -90,8 +98,11 @@ export function defineFieldValidations({ validations, variableName, dropMissingCheck, + existenceCheckExpression, }: ValidationOptions) { return `${validations - .map((one) => emitValidationSnippet(one, variableName, bail, dropMissingCheck)) + .map((one) => + emitValidationSnippet(one, variableName, bail, dropMissingCheck, existenceCheckExpression) + ) .join('\n')}` } diff --git a/src/scripts/object/guard.ts b/src/scripts/object/guard.ts index 9862730..59ce71e 100644 --- a/src/scripts/object/guard.ts +++ b/src/scripts/object/guard.ts @@ -16,7 +16,7 @@ type ObjectGuardOptions = { * Returns JS fragment to wrap code inside an object conditional */ export function defineObjectGuard({ variableName, guardedCodeSnippet }: ObjectGuardOptions) { - return `if (ensureIsObject(${variableName})) { + return `if (${variableName}_is_object) { ${guardedCodeSnippet} }` } diff --git a/src/scripts/object/variables.ts b/src/scripts/object/variables.ts new file mode 100644 index 0000000..0c841b3 --- /dev/null +++ b/src/scripts/object/variables.ts @@ -0,0 +1,19 @@ +/* + * @vinejs/compiler + * + * (c) VineJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +type FieldOptions = { + variableName: string +} + +/** + * Returns JS fragment for defining the object variables + */ +export function defineObjectVariables({ variableName }: FieldOptions) { + return `const ${variableName}_is_object = ensureIsObject(${variableName});` +} diff --git a/tests/unit/nodes/array.spec.ts b/tests/unit/nodes/array.spec.ts index 4f39046..4a855be 100644 --- a/tests/unit/nodes/array.spec.ts +++ b/tests/unit/nodes/array.spec.ts @@ -62,10 +62,11 @@ test.group('Array node', () => { ` isArrayMember: false,`, '});', `ensureExists(root_item);`, - `if (ensureIsArray(root_item)) {`, - `if (root_item.isValid) {`, + `const root_item_is_array = ensureIsArray(root_item);`, + `if (root_item.isValid && root_item_is_array) {`, ` refs['ref://2'].validator(root_item.value, refs['ref://2'].options, root_item);`, `}`, + `if (root_item_is_array) {`, `if (root_item.isValid) {`, `const root_item_out = [];`, `out = root_item_out;`, @@ -145,10 +146,11 @@ test.group('Array node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsArray(root_item)) {`, - `if (root_item.isValid) {`, + `const root_item_is_array = ensureIsArray(root_item);`, + `if (root_item.isValid && root_item_is_array) {`, ` refs['ref://2'].validator(root_item.value, refs['ref://2'].options, root_item);`, `}`, + `if (root_item_is_array) {`, `if (root_item.isValid) {`, `const root_item_out = [];`, `out = root_item_out;`, @@ -224,7 +226,8 @@ test.group('Array node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsArray(root_item)) {`, + `const root_item_is_array = ensureIsArray(root_item);`, + `if (root_item_is_array) {`, `if (root_item.isValid) {`, `const root_item_out = [];`, `out = root_item_out;`, @@ -306,8 +309,11 @@ test.group('Array node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsArray(root_item)) {`, + `const root_item_is_array = ensureIsArray(root_item);`, + `if (root_item_is_array) {`, ` refs['ref://2'].validator(root_item.value, refs['ref://2'].options, root_item);`, + `}`, + `if (root_item_is_array) {`, `const root_item_out = [];`, `out = root_item_out;`, `const root_item_items_size = root_item.value.length;`, @@ -409,10 +415,11 @@ test.group('Array node', () => { ` isArrayMember: false,`, '});', `ensureExists(root_item);`, - `if (ensureIsArray(root_item)) {`, - `if (root_item.isValid) {`, + `const root_item_is_array = ensureIsArray(root_item);`, + `if (root_item.isValid && root_item_is_array) {`, ` refs['ref://2'].validator(root_item.value, refs['ref://2'].options, root_item);`, `}`, + `if (root_item_is_array) {`, `if (root_item.isValid) {`, `const root_item_out = [];`, `out = root_item_out;`, @@ -451,7 +458,8 @@ test.group('Array node', () => { ` isArrayMember: false,`, `});`, `ensureExists(contacts_3);`, - `if (ensureIsArray(contacts_3)) {`, + `const contacts_3_is_array = ensureIsArray(contacts_3);`, + `if (contacts_3_is_array) {`, `const contacts_3_out = [];`, `root_item_item_out['contacts'] = contacts_3_out;`, `const contacts_3_items_size = contacts_3.value.length;`, @@ -555,10 +563,11 @@ test.group('Array node', () => { ` isArrayMember: false,`, '});', `ensureExists(root_item);`, - `if (ensureIsArray(root_item)) {`, - `if (root_item.isValid) {`, + `const root_item_is_array = ensureIsArray(root_item);`, + `if (root_item.isValid && root_item_is_array) {`, ` refs['ref://2'].validator(root_item.value, refs['ref://2'].options, root_item);`, `}`, + `if (root_item_is_array) {`, `if (root_item.isValid) {`, `const root_item_out = [];`, `out = root_item_out;`, @@ -597,7 +606,8 @@ test.group('Array node', () => { ` isArrayMember: false,`, `});`, `ensureExists(primaryContacts_3);`, - `if (ensureIsArray(primaryContacts_3)) {`, + `const primaryContacts_3_is_array = ensureIsArray(primaryContacts_3);`, + `if (primaryContacts_3_is_array) {`, `const primaryContacts_3_out = [];`, `root_item_item_out['primary.contacts'] = primaryContacts_3_out;`, `const primaryContacts_3_items_size = primaryContacts_3.value.length;`, @@ -629,4 +639,104 @@ test.group('Array node', () => { ...getClosingOutput(), ]) }) + + test('create JS output with implicit array validations', async ({ assert }) => { + const compiler = new Compiler({ + type: 'root', + schema: { + type: 'array', + allowNull: false, + isOptional: false, + bail: true, + fieldName: '*', + propertyName: '*', + validations: [ + { + implicit: true, + isAsync: false, + ruleFnId: 'ref://2', + }, + { + implicit: false, + isAsync: false, + ruleFnId: 'ref://3', + }, + { + implicit: true, + isAsync: false, + ruleFnId: 'ref://4', + }, + ], + each: { + type: 'literal', + fieldName: '*', + propertyName: '*', + allowNull: false, + isOptional: false, + bail: false, + validations: [], + }, + }, + }) + + const compiledOutput = compiler.compile().toString() + validateCode(compiledOutput) + + assert.assertFormatted(compiledOutput, [ + ...getInitialOutput(), + `const root_item = defineValue(root, {`, + ` data: root,`, + ` meta: meta,`, + ` name: '',`, + ` wildCardPath: '',`, + ` getFieldPath() {`, + ` return '';`, + ` },`, + ` mutate: defineValue,`, + ` report: report,`, + ` isValid: true,`, + ` parent: root,`, + ` isArrayMember: false,`, + '});', + `ensureExists(root_item);`, + `const root_item_is_array = ensureIsArray(root_item);`, + `if (root_item.isValid) {`, + ` refs['ref://2'].validator(root_item.value, refs['ref://2'].options, root_item);`, + `}`, + `if (root_item.isValid && root_item_is_array) {`, + ` refs['ref://3'].validator(root_item.value, refs['ref://3'].options, root_item);`, + `}`, + `if (root_item.isValid) {`, + ` refs['ref://4'].validator(root_item.value, refs['ref://4'].options, root_item);`, + `}`, + `if (root_item_is_array) {`, + `if (root_item.isValid) {`, + `const root_item_out = [];`, + `out = root_item_out;`, + `const root_item_items_size = root_item.value.length;`, + `for (let root_item_i = 0; root_item_i < root_item_items_size; root_item_i++) {`, + `const root_item_item = defineValue(root_item.value[root_item_i], {`, + ` data: root,`, + ` meta: meta,`, + ` name: root_item_i,`, + ` wildCardPath: '*',`, + ` getFieldPath() {`, + ` return root_item_i;`, + ` },`, + ` mutate: defineValue,`, + ` report: report,`, + ` isValid: true,`, + ` parent: root_item.value,`, + ` isArrayMember: true,`, + '});', + `ensureExists(root_item_item);`, + `if (root_item_item.isDefined && root_item_item.isValid) {`, + ` root_item_out[root_item_i] = root_item_item.value;`, + `}`, + `}`, + `}`, + `}`, + ...getClosingOutput(), + ]) + }) }) diff --git a/tests/unit/nodes/object.spec.ts b/tests/unit/nodes/object.spec.ts index 8bf79d5..1033a34 100644 --- a/tests/unit/nodes/object.spec.ts +++ b/tests/unit/nodes/object.spec.ts @@ -56,10 +56,11 @@ test.group('Object node', () => { ` isArrayMember: false,`, '});', `ensureExists(root_item);`, - `if (ensureIsObject(root_item)) {`, - `if (root_item.isValid) {`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item.isValid && root_item_is_object) {`, ` refs['ref://2'].validator(root_item.value, refs['ref://2'].options, root_item);`, `}`, + `if (root_item_is_object) {`, `if (root_item.isValid) {`, `const root_item_out = {};`, `out = root_item_out;`, @@ -112,10 +113,11 @@ test.group('Object node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsObject(root_item)) {`, - `if (root_item.isValid) {`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item.isValid && root_item_is_object) {`, ` refs['ref://2'].validator(root_item.value, refs['ref://2'].options, root_item);`, `}`, + `if (root_item_is_object) {`, `if (root_item.isValid) {`, `const root_item_out = {};`, `out = root_item_out;`, @@ -165,7 +167,8 @@ test.group('Object node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsObject(root_item)) {`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item_is_object) {`, `if (root_item.isValid) {`, `const root_item_out = {};`, `out = root_item_out;`, @@ -231,7 +234,8 @@ test.group('Object node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsObject(root_item)) {`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item_is_object) {`, `if (root_item.isValid) {`, `const root_item_out = {};`, `out = root_item_out;`, @@ -308,8 +312,11 @@ test.group('Object node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsObject(root_item)) {`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item_is_object) {`, `refs['ref://2'].validator(root_item.value, refs['ref://2'].options, root_item);`, + `}`, + `if (root_item_is_object) {`, `const root_item_out = {};`, `out = root_item_out;`, `}`, @@ -363,10 +370,11 @@ test.group('Object node', () => { ` isArrayMember: false,`, '});', `ensureExists(root_item);`, - `if (ensureIsObject(root_item)) {`, - `if (root_item.isValid) {`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item.isValid && root_item_is_object) {`, ` refs['ref://2'].validator(root_item.value, refs['ref://2'].options, root_item);`, `}`, + `if (root_item_is_object) {`, `if (root_item.isValid) {`, `const root_item_out = {};`, `out = root_item_out;`, @@ -466,7 +474,8 @@ test.group('Object node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsObject(root_item)) {`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item_is_object) {`, `if (root_item.isValid) {`, `const root_item_out = {};`, `out = root_item_out;`, @@ -667,7 +676,8 @@ test.group('Object node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsObject(root_item)) {`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item_is_object) {`, `if (root_item.isValid) {`, `const root_item_out = {};`, `out = root_item_out;`, @@ -899,7 +909,8 @@ test.group('Object node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsObject(root_item)) {`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item_is_object) {`, `if (root_item.isValid) {`, `const root_item_out = {};`, `out = root_item_out;`, @@ -992,7 +1003,7 @@ test.group('Object node', () => { ]) }) - test('define else block for conditions', async ({ assert }) => { + test('define else block conditions', async ({ assert }) => { const compiler = new Compiler({ type: 'root', schema: { @@ -1124,7 +1135,8 @@ test.group('Object node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsObject(root_item)) {`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item_is_object) {`, `if (root_item.isValid) {`, `const root_item_out = {};`, `out = root_item_out;`, @@ -1292,7 +1304,8 @@ test.group('Object node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsObject(root_item)) {`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item_is_object) {`, `if (root_item.isValid) {`, `const root_item_out = {};`, `out = root_item_out;`, @@ -1379,7 +1392,8 @@ test.group('Object node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsObject(root_item)) {`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item_is_object) {`, `if (root_item.isValid) {`, `const root_item_out = {};`, `out = root_item_out;`, @@ -1543,7 +1557,8 @@ test.group('Object node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsObject(root_item)) {`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item_is_object) {`, `if (root_item.isValid) {`, `const root_item_out = {};`, `out = root_item_out;`, @@ -1653,4 +1668,77 @@ test.group('Object node', () => { ...getClosingOutput(), ]) }) + + test('create JS output with implicit object validations', async ({ assert }) => { + const compiler = new Compiler({ + type: 'root', + schema: { + type: 'object', + groups: [], + allowNull: false, + isOptional: false, + bail: true, + fieldName: '*', + propertyName: '*', + validations: [ + { + implicit: true, + isAsync: false, + ruleFnId: 'ref://2', + }, + { + implicit: false, + isAsync: false, + ruleFnId: 'ref://3', + }, + { + implicit: true, + isAsync: false, + ruleFnId: 'ref://4', + }, + ], + properties: [], + allowUnknownProperties: false, + }, + }) + + const compiledOutput = compiler.compile().toString() + validateCode(compiledOutput) + + assert.assertFormatted(compiledOutput, [ + ...getInitialOutput(), + `const root_item = defineValue(root, {`, + ` data: root,`, + ` meta: meta,`, + ` name: '',`, + ` wildCardPath: '',`, + ` getFieldPath() {`, + ` return '';`, + ` },`, + ` mutate: defineValue,`, + ` report: report,`, + ` isValid: true,`, + ` parent: root,`, + ` isArrayMember: false,`, + '});', + `ensureExists(root_item);`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item.isValid) {`, + ` refs['ref://2'].validator(root_item.value, refs['ref://2'].options, root_item);`, + `}`, + `if (root_item.isValid && root_item_is_object) {`, + ` refs['ref://3'].validator(root_item.value, refs['ref://3'].options, root_item);`, + `}`, + `if (root_item.isValid) {`, + ` refs['ref://4'].validator(root_item.value, refs['ref://4'].options, root_item);`, + `}`, + `if (root_item_is_object) {`, + `if (root_item.isValid) {`, + `const root_item_out = {};`, + `out = root_item_out;`, + '}', + `}`, + ...getClosingOutput(), + ]) + }) }) diff --git a/tests/unit/nodes/record.spec.ts b/tests/unit/nodes/record.spec.ts index 027dda0..2b0fa9b 100644 --- a/tests/unit/nodes/record.spec.ts +++ b/tests/unit/nodes/record.spec.ts @@ -62,10 +62,11 @@ test.group('Record node', () => { ` isArrayMember: false,`, '});', `ensureExists(root_item);`, - `if (ensureIsObject(root_item)) {`, - `if (root_item.isValid) {`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item.isValid && root_item_is_object) {`, ` refs['ref://2'].validator(root_item.value, refs['ref://2'].options, root_item);`, `}`, + `if (root_item_is_object) {`, `if (root_item.isValid) {`, `const root_item_out = {};`, `out = root_item_out;`, @@ -147,10 +148,11 @@ test.group('Record node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsObject(root_item)) {`, - `if (root_item.isValid) {`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item.isValid && root_item_is_object) {`, ` refs['ref://2'].validator(root_item.value, refs['ref://2'].options, root_item);`, `}`, + `if (root_item_is_object) {`, `if (root_item.isValid) {`, `const root_item_out = {};`, `out = root_item_out;`, @@ -229,7 +231,8 @@ test.group('Record node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsObject(root_item)) {`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item_is_object) {`, `if (root_item.isValid) {`, `const root_item_out = {};`, `out = root_item_out;`, @@ -314,7 +317,8 @@ test.group('Record node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsObject(root_item)) {`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item_is_object) {`, `if (root_item.isValid) {`, `const root_item_out = {};`, `out = root_item_out;`, @@ -402,8 +406,11 @@ test.group('Record node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsObject(root_item)) {`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item_is_object) {`, `refs['ref://2'].validator(root_item.value, refs['ref://2'].options, root_item);`, + `}`, + `if (root_item_is_object) {`, `const root_item_out = {};`, `out = root_item_out;`, `const root_item_keys = Object.keys(root_item.value);`, @@ -436,4 +443,106 @@ test.group('Record node', () => { ...getClosingOutput(), ]) }) + + test('create JS output with implicit validation', async ({ assert }) => { + const compiler = new Compiler({ + type: 'root', + schema: { + type: 'record', + allowNull: false, + isOptional: false, + bail: true, + fieldName: '*', + propertyName: '*', + validations: [ + { + implicit: true, + isAsync: false, + ruleFnId: 'ref://2', + }, + { + implicit: false, + isAsync: false, + ruleFnId: 'ref://3', + }, + { + implicit: true, + isAsync: false, + ruleFnId: 'ref://4', + }, + ], + each: { + type: 'literal', + fieldName: '*', + propertyName: '*', + allowNull: false, + isOptional: false, + bail: false, + validations: [], + }, + }, + }) + + const compiledOutput = compiler.compile().toString() + validateCode(compiledOutput) + + assert.assertFormatted(compiledOutput, [ + ...getInitialOutput(), + `const root_item = defineValue(root, {`, + ` data: root,`, + ` meta: meta,`, + ` name: '',`, + ` wildCardPath: '',`, + ` getFieldPath() {`, + ` return '';`, + ` },`, + ` mutate: defineValue,`, + ` report: report,`, + ` isValid: true,`, + ` parent: root,`, + ` isArrayMember: false,`, + '});', + `ensureExists(root_item);`, + `const root_item_is_object = ensureIsObject(root_item);`, + `if (root_item.isValid) {`, + ` refs['ref://2'].validator(root_item.value, refs['ref://2'].options, root_item);`, + `}`, + `if (root_item.isValid && root_item_is_object) {`, + ` refs['ref://3'].validator(root_item.value, refs['ref://3'].options, root_item);`, + `}`, + `if (root_item.isValid) {`, + ` refs['ref://4'].validator(root_item.value, refs['ref://4'].options, root_item);`, + `}`, + `if (root_item_is_object) {`, + `if (root_item.isValid) {`, + `const root_item_out = {};`, + `out = root_item_out;`, + `const root_item_keys = Object.keys(root_item.value);`, + `const root_item_keys_size = root_item_keys.length;`, + `for (let root_item_key_i = 0; root_item_key_i < root_item_keys_size; root_item_key_i++) {`, + `const root_item_i = root_item_keys[root_item_key_i];`, + `const root_item_item = defineValue(root_item.value[root_item_i], {`, + ` data: root,`, + ` meta: meta,`, + ` name: root_item_i,`, + ` wildCardPath: '*',`, + ` getFieldPath() {`, + ` return root_item_i;`, + ` },`, + ` mutate: defineValue,`, + ` report: report,`, + ` isValid: true,`, + ` parent: root_item.value,`, + ` isArrayMember: false,`, + '});', + `ensureExists(root_item_item);`, + `if (root_item_item.isDefined && root_item_item.isValid) {`, + ` root_item_out[root_item_i] = root_item_item.value;`, + `}`, + `}`, + `}`, + `}`, + ...getClosingOutput(), + ]) + }) }) diff --git a/tests/unit/nodes/tuple.spec.ts b/tests/unit/nodes/tuple.spec.ts index 65dd7f3..97fcd5c 100644 --- a/tests/unit/nodes/tuple.spec.ts +++ b/tests/unit/nodes/tuple.spec.ts @@ -65,10 +65,11 @@ test.group('Tuple node', () => { ` isArrayMember: false,`, '});', `ensureExists(root_item);`, - `if (ensureIsArray(root_item)) {`, - `if (root_item.isValid) {`, + `const root_item_is_array = ensureIsArray(root_item);`, + `if (root_item.isValid && root_item_is_array) {`, `refs['ref://2'].validator(root_item.value, refs['ref://2'].options, root_item);`, `}`, + `if (root_item_is_array) {`, `if (root_item.isValid) {`, `const root_item_out = [];`, `out = root_item_out;`, @@ -142,7 +143,8 @@ test.group('Tuple node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsArray(root_item)) {`, + `const root_item_is_array = ensureIsArray(root_item);`, + `if (root_item_is_array) {`, `if (root_item.isValid) {`, `const root_item_out = [];`, `out = root_item_out;`, @@ -218,7 +220,8 @@ test.group('Tuple node', () => { ` isArrayMember: false,`, '});', `ensureIsDefined(root_item);`, - `if (ensureIsArray(root_item)) {`, + `const root_item_is_array = ensureIsArray(root_item);`, + `if (root_item_is_array) {`, `if (root_item.isValid) {`, `const root_item_out = copyProperties(root_item.value);`, `out = root_item_out;`, @@ -294,7 +297,8 @@ test.group('Tuple node', () => { ` isArrayMember: false,`, '});', `ensureExists(root_item);`, - `if (ensureIsArray(root_item)) {`, + `const root_item_is_array = ensureIsArray(root_item);`, + `if (root_item_is_array) {`, `if (root_item.isValid) {`, `const root_item_out = [];`, `out = root_item_out;`, @@ -374,8 +378,11 @@ test.group('Tuple node', () => { ` isArrayMember: false,`, '});', `ensureExists(root_item);`, - `if (ensureIsArray(root_item)) {`, + `const root_item_is_array = ensureIsArray(root_item);`, + `if (root_item_is_array) {`, `refs['ref://2'].validator(root_item.value, refs['ref://2'].options, root_item);`, + `}`, + `if (root_item_is_array) {`, `const root_item_out = [];`, `out = root_item_out;`, `const root_item_item_0 = defineValue(root_item.value[0], {`, @@ -400,4 +407,104 @@ test.group('Tuple node', () => { ...getClosingOutput(), ]) }) + + test('create JS output for tuple node with implicit tuple validations', async ({ assert }) => { + const compiler = new Compiler({ + type: 'root', + schema: { + type: 'tuple', + allowNull: false, + isOptional: false, + bail: true, + fieldName: '*', + propertyName: '*', + allowUnknownProperties: false, + validations: [ + { + implicit: true, + isAsync: false, + ruleFnId: 'ref://2', + }, + { + implicit: false, + isAsync: false, + ruleFnId: 'ref://3', + }, + { + implicit: true, + isAsync: false, + ruleFnId: 'ref://4', + }, + ], + properties: [ + { + type: 'literal', + fieldName: '0', + propertyName: '0', + allowNull: false, + isOptional: false, + bail: false, + validations: [], + }, + ], + }, + }) + + const compiledOutput = compiler.compile().toString() + + validateCode(compiledOutput) + assert.assertFormatted(compiledOutput, [ + ...getInitialOutput(), + `const root_item = defineValue(root, {`, + ` data: root,`, + ` meta: meta,`, + ` name: '',`, + ` wildCardPath: '',`, + ` getFieldPath() {`, + ` return '';`, + ` },`, + ` mutate: defineValue,`, + ` report: report,`, + ` isValid: true,`, + ` parent: root,`, + ` isArrayMember: false,`, + '});', + `ensureExists(root_item);`, + `const root_item_is_array = ensureIsArray(root_item);`, + `if (root_item.isValid) {`, + ` refs['ref://2'].validator(root_item.value, refs['ref://2'].options, root_item);`, + `}`, + `if (root_item.isValid && root_item_is_array) {`, + ` refs['ref://3'].validator(root_item.value, refs['ref://3'].options, root_item);`, + `}`, + `if (root_item.isValid) {`, + ` refs['ref://4'].validator(root_item.value, refs['ref://4'].options, root_item);`, + `}`, + `if (root_item_is_array) {`, + `if (root_item.isValid) {`, + `const root_item_out = [];`, + `out = root_item_out;`, + `const root_item_item_0 = defineValue(root_item.value[0], {`, + ` data: root,`, + ` meta: meta,`, + ` name: 0,`, + ` wildCardPath: '0',`, + ` getFieldPath() {`, + ` return 0;`, + ` },`, + ` mutate: defineValue,`, + ` report: report,`, + ` isValid: true,`, + ` parent: root_item.value,`, + ` isArrayMember: true,`, + '});', + `ensureExists(root_item_item_0);`, + `if (root_item_item_0.isDefined && root_item_item_0.isValid) {`, + `root_item_out[0] = root_item_item_0.value;`, + `}`, + `}`, + `}`, + ...getClosingOutput(), + ]) + }) }) diff --git a/tests/unit/scripts/array/guard.spec.ts b/tests/unit/scripts/array/guard.spec.ts index fc7c7bc..25cccd8 100644 --- a/tests/unit/scripts/array/guard.spec.ts +++ b/tests/unit/scripts/array/guard.spec.ts @@ -18,11 +18,7 @@ test.group('Scripts | define array guard', () => { guardedCodeSnippet: 'console.log(contacts)', }) - assert.doesNotThrows(() => validateCode(jsOutput)) - assert.assertFormatted(jsOutput, [ - `if (ensureIsArray(contacts)) {`, - `console.log(contacts)`, - `}`, - ]) + assert.doesNotThrow(() => validateCode(jsOutput)) + assert.assertFormatted(jsOutput, [`if (contacts_is_array) {`, `console.log(contacts)`, `}`]) }) })