riot/compiler

View on GitHub
src/generators/template/bindings/each.js

Summary

Maintainability
A
1 hr
Test Coverage
import {
  BINDING_CONDITION_KEY,
  BINDING_EVALUATE_KEY,
  BINDING_GET_KEY_KEY,
  BINDING_INDEX_NAME_KEY,
  BINDING_ITEM_NAME_KEY,
  BINDING_TYPES,
  BINDING_TYPE_KEY,
  EACH_BINDING_TYPE,
} from '../constants.js'
import {
  createASTFromExpression,
  createSelectorProperties,
  createTemplateProperty,
  getAttributeExpression,
  getName,
  toScopedFunction,
} from '../utils.js'
import {
  findEachAttribute,
  findIfAttribute,
  findKeyAttribute,
} from '../find.js'
import {
  isExpressionStatement,
  isSequenceExpression,
} from '../../../utils/ast-nodes-checks.js'
import {
  nullNode,
  simplePropertyNode,
} from '../../../utils/custom-ast-nodes.js'
import { builders } from '../../../utils/build-types.js'
import compose from 'cumpa'
import { createNestedBindings } from '../builder.js'
import generateJavascript from '../../../utils/generate-javascript.js'
import { panic } from '@riotjs/util/misc.js'

const getEachItemName = (expression) =>
  isSequenceExpression(expression.left)
    ? expression.left.expressions[0]
    : expression.left
const getEachIndexName = (expression) =>
  isSequenceExpression(expression.left) ? expression.left.expressions[1] : null
const getEachValue = (expression) => expression.right
const nameToliteral = compose(builders.literal, getName)

const generateEachItemNameKey = (expression) =>
  simplePropertyNode(
    BINDING_ITEM_NAME_KEY,
    compose(nameToliteral, getEachItemName)(expression),
  )

const generateEachIndexNameKey = (expression) =>
  simplePropertyNode(
    BINDING_INDEX_NAME_KEY,
    compose(nameToliteral, getEachIndexName)(expression),
  )

const generateEachEvaluateKey = (
  expression,
  eachExpression,
  sourceFile,
  sourceCode,
) =>
  simplePropertyNode(
    BINDING_EVALUATE_KEY,
    compose(
      (e) => toScopedFunction(e, sourceFile, sourceCode),
      (e) => ({
        ...eachExpression,
        text: generateJavascript(e).code,
      }),
      getEachValue,
    )(expression),
  )

/**
 * Get the each expression properties to create properly the template binding
 * @param   { DomBinding.Expression } eachExpression - original each expression data
 * @param   { string } sourceFile - original tag file
 * @param   { string } sourceCode - original tag source code
 * @returns { Array } AST nodes that are needed to build an each binding
 */
export function generateEachExpressionProperties(
  eachExpression,
  sourceFile,
  sourceCode,
) {
  const ast = createASTFromExpression(eachExpression, sourceFile, sourceCode)
  const body = ast.program.body
  const firstNode = body[0]

  if (!isExpressionStatement(firstNode)) {
    panic(
      `The each directives supported should be of type "ExpressionStatement",you have provided a "${firstNode.type}"`,
    )
  }

  const { expression } = firstNode

  return [
    generateEachItemNameKey(expression),
    generateEachIndexNameKey(expression),
    generateEachEvaluateKey(expression, eachExpression, sourceFile, sourceCode),
  ]
}

/**
 * Transform a RiotParser.Node.Tag into an each binding
 * @param   { RiotParser.Node.Tag } sourceNode - tag containing the each attribute
 * @param   { string } selectorAttribute - attribute needed to select the target node
 * @param   { string } sourceFile - source file path
 * @param   { string } sourceCode - original source
 * @returns { AST.Node } an each binding node
 */
export default function createEachBinding(
  sourceNode,
  selectorAttribute,
  sourceFile,
  sourceCode,
) {
  const [ifAttribute, eachAttribute, keyAttribute] = [
    findIfAttribute,
    findEachAttribute,
    findKeyAttribute,
  ].map((f) => f(sourceNode))
  const attributeOrNull = (attribute) =>
    attribute
      ? toScopedFunction(
          getAttributeExpression(attribute),
          sourceFile,
          sourceCode,
        )
      : nullNode()

  return builders.objectExpression([
    simplePropertyNode(
      BINDING_TYPE_KEY,
      builders.memberExpression(
        builders.identifier(BINDING_TYPES),
        builders.identifier(EACH_BINDING_TYPE),
        false,
      ),
    ),
    simplePropertyNode(BINDING_GET_KEY_KEY, attributeOrNull(keyAttribute)),
    simplePropertyNode(BINDING_CONDITION_KEY, attributeOrNull(ifAttribute)),
    createTemplateProperty(
      createNestedBindings(
        sourceNode,
        sourceFile,
        sourceCode,
        selectorAttribute,
      ),
    ),
    ...createSelectorProperties(selectorAttribute),
    ...compose(
      generateEachExpressionProperties,
      getAttributeExpression,
    )(eachAttribute),
  ])
}