Skip to content

Commit

Permalink
fix: convert field name to valid variable name before defining it
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Nov 28, 2024
1 parent 5fcde09 commit 4965ded
Show file tree
Hide file tree
Showing 5 changed files with 692 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/compiler/fields/object_field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* file that was distributed with this source code.
*/

import { toVariableName } from '../../helpers.js'
import type { CompilerField, FieldNode, CompilerParent } from '../../types.js'

export function createObjectField(
Expand All @@ -23,7 +24,7 @@ export function createObjectField(
fieldNameExpression: `'${node.fieldName}'`,
fieldPathExpression: wildCardPath,
wildCardPath: wildCardPath,
variableName: `${node.propertyName}_${variablesCounter}`,
variableName: `${toVariableName(node.propertyName)}_${variablesCounter}`,
valueExpression: `${parent.variableName}.value['${node.fieldName}']`,
outputExpression: `${parent.variableName}_out['${node.propertyName}']`,
isArrayMember: false,
Expand Down
95 changes: 95 additions & 0 deletions src/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* @vinejs/compiler
*
* (c) VineJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

const NUMBER_CHAR_RE = /\d/
const VALID_CHARS = /[A-Za-z0-9]+/

/**
* Returns if the first charcater is uppercase or not
*/
function isUppercase(char = ''): boolean | undefined {
if (NUMBER_CHAR_RE.test(char)) {
return undefined
}
return char !== char.toLowerCase()
}

/**
* Converts the first character to uppercase
*/
function upperFirst(value: string): string {
return value ? value[0].toUpperCase() + value.slice(1) : ''
}

/**
* Converts the first character to lowercase
*/
function lowerFirst(value: string) {
return value ? value[0].toLowerCase() + value.slice(1) : ''
}

/**
* Splits the string value by special characters
*/
function splitByCase(value: string) {
const parts: string[] = []
if (!value || typeof value !== 'string') {
return parts
}

let buff = ''
let previousUpper: boolean | undefined
let previousSplitter: boolean | undefined

for (const char of value) {
const isSplitter = !VALID_CHARS.test(char)
if (isSplitter === true) {
parts.push(buff)
buff = ''
previousUpper = undefined
continue
}

const isUpper = isUppercase(char)
if (previousSplitter === false) {
if (previousUpper === false && isUpper === true) {
parts.push(buff)
buff = char
previousUpper = isUpper
continue
}

if (previousUpper === true && isUpper === false && buff.length > 1) {
const lastChar = buff.at(-1)
parts.push(buff.slice(0, Math.max(0, buff.length - 1)))
buff = lastChar + char
previousUpper = isUpper
continue
}
}

buff += char
previousUpper = isUpper
previousSplitter = isSplitter
}

parts.push(buff)
return parts
}

/**
* Converts the value to a valid JavaScript variable name
*/
export function toVariableName(value: string) {
const pascalCase = splitByCase(value)
.map((p) => upperFirst(p.toLowerCase()))
.join('')

return /^[0-9]+/.test(pascalCase) ? `var_${pascalCase}` : lowerFirst(pascalCase)
}
146 changes: 146 additions & 0 deletions tests/unit/nodes/array.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,4 +483,150 @@ test.group('Array node', () => {
...getClosingOutput(),
])
})

test('define array field name with special characters', async ({ assert }) => {
const compiler = new Compiler({
type: 'root',
schema: {
type: 'array',
allowNull: false,
isOptional: false,
bail: true,
fieldName: '*',
propertyName: '*',
validations: [
{
implicit: false,
isAsync: false,
ruleFnId: 'ref://2',
},
],
each: {
type: 'object',
fieldName: '*',
propertyName: '*',
allowNull: false,
isOptional: false,
bail: false,
validations: [],
groups: [],
allowUnknownProperties: false,
properties: [
{
type: 'array',
fieldName: 'primary.contacts',
propertyName: 'primary.contacts',
allowNull: false,
isOptional: false,
bail: false,
each: {
type: 'literal',
fieldName: '*',
propertyName: '*',
allowNull: false,
isOptional: false,
bail: false,
validations: [],
},
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);`,
`if (ensureIsArray(root_item)) {`,
`if (root_item.isValid) {`,
` refs['ref://2'].validator(root_item.value, refs['ref://2'].options, root_item);`,
`}`,
`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 (ensureIsObject(root_item_item)) {`,
`const root_item_item_out = {};`,
`root_item_out[root_item_i] = root_item_item_out;`,
`const primaryContacts_3 = defineValue(root_item_item.value['primary.contacts'], {`,
` data: root,`,
` meta: meta,`,
` name: 'primary.contacts',`,
` wildCardPath: '*.primary.contacts',`,
` getFieldPath() {`,
` return root_item_item.getFieldPath() + '.' + 'primary.contacts';`,
` },`,
` mutate: defineValue,`,
` report: report,`,
` isValid: true,`,
` parent: root_item_item.value,`,
` isArrayMember: false,`,
`});`,
`ensureExists(primaryContacts_3);`,
`if (ensureIsArray(primaryContacts_3)) {`,
`const primaryContacts_3_out = [];`,
`root_item_item_out['primary.contacts'] = primaryContacts_3_out;`,
`const primaryContacts_3_items_size = primaryContacts_3.value.length;`,
`for (let primaryContacts_3_i = 0; primaryContacts_3_i < primaryContacts_3_items_size; primaryContacts_3_i++) {`,
`const primaryContacts_3_item = defineValue(primaryContacts_3.value[primaryContacts_3_i], {`,
` data: root,`,
` meta: meta,`,
` name: primaryContacts_3_i,`,
` wildCardPath: '*.primary.contacts.*',`,
` getFieldPath() {`,
` return primaryContacts_3.getFieldPath() + '.' + primaryContacts_3_i;`,
` },`,
` mutate: defineValue,`,
` report: report,`,
` isValid: true,`,
` parent: primaryContacts_3.value,`,
` isArrayMember: true,`,
`});`,
`ensureExists(primaryContacts_3_item);`,
`if (primaryContacts_3_item.isDefined && primaryContacts_3_item.isValid) {`,
`primaryContacts_3_out[primaryContacts_3_i] = primaryContacts_3_item.value;`,
`}`,
`}`,
`}`,
`}`,
`}`,
`}`,
`}`,
...getClosingOutput(),
])
})
})
Loading

0 comments on commit 4965ded

Please sign in to comment.