Skip to content

Commit

Permalink
feat: morph/react make use of granular functions when morphing data b…
Browse files Browse the repository at this point in the history
…lock
  • Loading branch information
alex-vladut committed May 17, 2021
1 parent 6e184db commit 70bb638
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 77 deletions.
184 changes: 131 additions & 53 deletions morph/react/block-add-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,26 @@ export function enter(node, parent, state) {
if (data.isConstant) {
addConstantData(data, state)
} else {
addData(data, state)
addData(data, !!dataGroup.aggregate, state)
}
}

if (dataGroup.aggregate) {
dataGroup.name = getVariableName('aggregateData', state)
dataGroup.valueName = dataGroup.name
dataGroup.name = getDataVariableName(['aggregate'], state)
let importName = getAggregateImportName(dataGroup.aggregate.source, state)
state.variables.push(
`let ${dataGroup.name} = ${importName}.${
dataGroup.aggregate.value
}(${dataGroup.data.map((d) => d.name).join(', ')})`
}(${dataGroup.data
.map((d) => (d.isConstant ? d.name : d.variables.value))
.join(', ')})`
)
dataGroup.context = dataGroup.data[0].context
dataGroup.path = dataGroup.data[0].path
} else {
// no aggregate function so will use the data directly
dataGroup.name = dataGroup.data[0].name
dataGroup.valueName = dataGroup.data[0].valueName
dataGroup.variables = dataGroup.data[0].variables
// the list item data provider functionality makes use of the path
// so adding it to keep consistency with already generated data providers
dataGroup.context = dataGroup.data[0].context
Expand All @@ -37,8 +38,7 @@ export function enter(node, parent, state) {
}

function addConstantData(data, state) {
data.name = getVariableName('constantData', state)
data.valueName = data.name
data.name = getDataVariableName(['constant'], state)

if (data.format?.formatIn) {
let importName = getImportNameForSource(data.format.formatIn.source, state)
Expand All @@ -50,38 +50,103 @@ function addConstantData(data, state) {
}
}

function addData(data, state) {
data.name = getDataVariableName(data, state)
data.valueName = `${data.name}.value`
function addData(data, isAggregate, state) {
data.variables = {}

// at the moment it will create multiple instances for the same data key
// an optimization will be implemented to reuse a variable if possible
state.variables.push(`let ${data.name} = fromData.useData({ viewPath,`)
maybeDataContext(data, state)
maybeDataPath(data, state)
maybeDataFormat(data.format, state)
maybeDataValidate(data.validate, state)
state.variables.push('})')
if (data.uses.has('useDataValue') || isAggregate) {
let dataValueName = getDataVariableName(
[data.context, data.path, 'value'],
state
)
state.variables.push(
`let ${dataValueName} = fromData.${
data.format?.formatIn ? 'useDataFormat' : 'useDataValue'
}({ viewPath,`
)
maybeDataContext(data, state)
maybeDataPath(data, state)
maybeDataFormat(data, state)
state.variables.push('})')
data.variables.value = dataValueName
}

state.use('ViewsUseData')
}
if (data.validate && data.validate.type === 'js') {
if (data.uses.has('useDataIsValidInitial')) {
let dataIsValidInitialName = getDataVariableName(
[data.context, data.path, 'isValidInitial'],
state
)
state.variables.push(
`let ${dataIsValidInitialName} = fromData.useDataIsValidInitial({ viewPath,`
)
maybeDataContext(data, state)
maybeDataPath(data, state)
maybeDataValidate(data, state)
state.variables.push('})')
data.variables.isValidInitial = dataIsValidInitialName
}

function getDataVariableName(data, state) {
let name = `${toCamelCase(
[
data.context,
data.path ? data.path.replace(/\./g, '_') : null,
data.format?.formatIn?.value,
data.format?.formatOut?.value,
data.validate?.value,
data.validate?.required ? 'required' : null,
'data',
]
.filter(Boolean)
.map(toSnakeCase)
.join('_')
)}`
return getVariableName(name, state)
if (data.uses.has('useDataIsValid')) {
let dataIsValidName = getDataVariableName(
[data.context, data.path, 'isValid'],
state
)
state.variables.push(
`let ${dataIsValidName} = fromData.useDataIsValid({ viewPath,`
)
maybeDataContext(data, state)
maybeDataPath(data, state)
maybeDataValidate(data, state)
if (data.validate.required) {
state.variables.push('required: true,')
}
state.variables.push('})')
data.variables.isValid = dataIsValidName
}
}

if (data.uses.has('useDataChange')) {
let dataChangeName = getDataVariableName(
[data.context, data.path, 'change'],
state
)
state.variables.push(
`let ${dataChangeName} = fromData.useDataChange({ viewPath,`
)
maybeDataContext(data, state)
maybeDataPath(data, state)
maybeDataFormatOut(data, state)
state.variables.push('})')
data.variables.onChange = dataChangeName
}

if (data.uses.has('useDataSubmit')) {
let dataSubmitName = getDataVariableName(
[data.context, data.path, 'submit'],
state
)
state.variables.push(
`let ${dataSubmitName} = fromData.useDataSubmit({ viewPath,`
)
maybeDataContext(data, state)
state.variables.push('})')
data.variables.onSubmit = dataSubmitName
}

if (data.uses.has('useDataIsSubmitting')) {
let dataIsSubmittingName = getDataVariableName(
[data.context, data.path, 'isSubmitting'],
state
)
state.variables.push(
`let ${dataIsSubmittingName} = fromData.useDataIsSubmitting({ viewPath,`
)
maybeDataContext(data, state)
state.variables.push('})')
data.variables.isSubmitting = dataIsSubmittingName
}

state.use('ViewsUseData')
}

function maybeDataContext(dataDefinition, state) {
Expand All @@ -96,27 +161,26 @@ function maybeDataPath(dataDefinition, state) {
state.variables.push(`path: '${dataDefinition.path}',`)
}

function maybeDataFormat(format, state) {
if (!format) return
function maybeDataFormat(data, state) {
if (!data.format?.formatIn) return

if (format.formatIn) {
let importName = getFormatImportName(format.formatIn.source, state)
state.variables.push(`formatIn: ${importName}.${format.formatIn.value},`)
}
let importName = getFormatImportName(data.format.formatIn.source, state)
state.variables.push(`format: ${importName}.${data.format.formatIn.value},`)
}

if (format.formatOut) {
let importName = getFormatImportName(format.formatOut.source, state)
state.variables.push(`formatOut: ${importName}.${format.formatOut.value},`)
}
function maybeDataFormatOut(data, state) {
if (!data.format?.formatOut) return

let importName = getFormatImportName(data.format.formatOut.source, state)
state.variables.push(
`formatOut: ${importName}.${data.format.formatOut.value},`
)
}

function maybeDataValidate(validate, state) {
if (!validate || validate.type !== 'js') return
let importName = getValidateImportName(validate.source, state)
state.variables.push(`validate: ${importName}.${validate.value},`)
if (validate.required) {
state.variables.push('validateRequired: true,')
}
function maybeDataValidate(data, state) {
if (!data.validate || data.validate.type !== 'js') return
let importName = getValidateImportName(data.validate.source, state)
state.variables.push(`validate: ${importName}.${data.validate.value},`)
}

function getAggregateImportName(source, state) {
Expand Down Expand Up @@ -151,3 +215,17 @@ function getFormatImportName(source, state) {
}
return importName
}

function getDataVariableName(params, state) {
return getVariableName(transformToCamelCase([...params, 'data']), state)
}

function transformToCamelCase(args) {
return toCamelCase(
args
.filter(Boolean)
.map((arg) => arg.replace(/\./g, '_'))
.map(toSnakeCase)
.join('_')
)
}
49 changes: 43 additions & 6 deletions morph/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ function getScopedConditionPropValue(node, parent, state) {
}

let CHILD_VALUES = /!?props\.(isSelected|isHovered|isFocused|isSelectedHovered)/
let DATA_VALUES = /!?props\.(isInvalid|isInvalidInitial|isValid|isValidInitial|value|isSubmitting)$/
let DATA_VALUES = /!?props\.(isInvalid|isInvalidInitial|isValid|isValidInitial|isSubmitting|value|onSubmit|onChange)$/
let IS_HOVERED_OR_SELECTED_HOVER = /!?props\.(isHovered|isSelectedHovered)/
let IS_FLOW = /!?props\.(isFlow|flow)$/
export function isFlow(prop) {
Expand Down Expand Up @@ -620,11 +620,48 @@ export function getDataForLoc(blockNode, loc) {
}

export function replacePropWithDataValue(value, dataGroup) {
return value
.replace('props.value', dataGroup.valueName)
.replace('props.onChange', 'props.change')
.replace('props.onSubmit', 'props.submit')
.replace('props', dataGroup.name)
let propValue = value.replace('props.', '')
if (dataGroup.aggregate) {
if (propValue === 'value') {
return dataGroup.name
} else {
throw new Error(
`Property ${propValue} is not available on aggregate data, only "value" is a valid option`
)
}
} else if (dataGroup.data[0].isConstant) {
if (propValue === 'value') {
return dataGroup.data[0].name
} else {
throw new Error(
`Property ${propValue} is not available on constant data, only "value" is a valid option`
)
}
} else {
if (propValue === 'isInvalid') return `!${dataGroup.variables['isValid']}`
if (propValue === 'isInvalidInitial')
return `!${dataGroup.variables['isValidInitial']}`
return dataGroup.variables[propValue]
}
}

let PROP_TO_USE_DATA = {
isInvalid: 'useDataIsValid',
isInvalidInitial: 'useDataIsValidInitial',
isValid: 'useDataIsValid',
isValidInitial: 'useDataIsValidInitial',
isSubmitting: 'useDataIsSubmitting',
value: 'useDataValue',
onSubmit: 'useDataSubmit',
onChange: 'useDataChange',
}
export function maybeGetUseDataForValue(p) {
if (DATA_VALUES.test(p.value)) {
let [, value] = DATA_VALUES.exec(p.value)
return PROP_TO_USE_DATA[value]
} else {
return null
}
}

export function getImportNameForSource(source, state) {
Expand Down
5 changes: 3 additions & 2 deletions parse/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,15 +249,16 @@ export let getData = (maybeProp) => {
return {
value: fullPath,
isConstant: true,
uses: new Set(),
}
}
if (!isNaN(Number(fullPath))) {
return { value: Number(fullPath), isConstant: true }
return { value: Number(fullPath), isConstant: true, uses: new Set() }
}
let [context = null] = /\./.test(fullPath) ? fullPath.split('.') : [fullPath]
let path = context === fullPath ? null : fullPath.replace(`${context}.`, '')

return { path, context }
return { path, context, uses: new Set() }
}

function maybeSourceAndValue(input) {
Expand Down
47 changes: 31 additions & 16 deletions parse/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { isGoogleFont } from '../morph/fonts.js'
import getLoc from './get-loc.js'
import getTags from './get-tags.js'
import path from 'path'
import { maybeGetUseDataForValue } from '../morph/utils.js'

export default ({
convertSlotToProps = true,
Expand Down Expand Up @@ -399,6 +400,16 @@ export default ({
do {
index++
if (index === block.properties.length) {
if (
'validate' in currentData &&
!('type' in currentData.validate)
) {
// required keyword without validate
delete currentData.validate
}
currentData.loc.end = block.properties[index - 1].loc.end
currentDataGroup.loc.end = block.properties[index - 1].loc.end
currentDataGroup = null
break
}

Expand All @@ -423,34 +434,38 @@ export default ({
} else if (p.name === 'data') {
// data section finished - setting end line
currentData.loc.end = block.properties[index - 1].loc.end

if (
'validate' in currentData &&
!('type' in currentData.validate)
) {
// required keyword without validate
delete currentData.validate
}
currentData.loc.end = block.properties[index - 1].loc.end
if (currentData.uses.size) {
// if current data has an assignment then set currentDataGroup to null
// else leave it as probably is part of aggregate data group
currentDataGroup.loc.end = block.properties[index - 1].loc.end
currentDataGroup = null
}
} else {
if (
!currentDataGroup.aggregate &&
currentDataGroup.data.length > 1
) {
warnings.push({
type: `No aggregate function was provided, but ${currentDataGroup.data.length} data keys were found. Did you forget to specify an aggregate function?`,
line,
loc: block.loc,
})
}
if (
'validate' in currentData &&
!('type' in currentData.validate)
) {
// required keyword without validate
delete currentData.validate

let value = maybeGetUseDataForValue(p)
if (value) {
currentData.uses.add(value)
}
currentData.loc.end = block.properties[index - 1].loc.end
currentDataGroup.loc.end = block.properties[index - 1].loc.end
currentDataGroup = null
}
} while (
index < block.properties.length &&
['format', 'formatOut', 'validate', 'required', 'aggregate'].includes(
p.name
)
)
} while (index < block.properties.length && p.name !== 'data')
} else {
index++
}
Expand Down

0 comments on commit 70bb638

Please sign in to comment.