rofrischmann/bredon

View on GitHub
packages/bredon-validate/src/propertyValidators.js

Summary

Maintainability
F
6 days
Test Coverage
import {
  isInteger,
  isStringLiteral,
  isURL,
  isIdentifier,
  isFloat,
  isDimension,
  isFunctionExpression,
} from 'bredon-types'

import {
  isAngle,
  isColor,
  isCubicBezier,
  isFrames,
  isGlobal,
  isImage,
  isLength,
  isLengthPercentage,
  isNumber,
  isOpacity,
  isPercentage,
  isPosition,
  isRepeat,
  isSize,
  isSteps,
  isSvgLength,
  isTime,
} from './types'

import matchesKeyword from './utils/matchesKeyword'
import matchesIdentifier from './utils/matchesIdentifier'
import validateNodeList from './utils/validateNodeList'
import validateUnorderedNodeList from './utils/validateUnorderedNodeList'
import normalizeNodeList from './utils/normalizeNodeList'
import arrayReduce from './utils/arrayReduce'

const validators = {
  MozBinding: isURL,
  MozBorderBottomColors: (node, isMultiValue) =>
    validateNodeList(isColor)(normalizeNodeList(node, isMultiValue)),
  MozBorderLeftColors: (node, isMultiValue) =>
    validateNodeList(isColor)(normalizeNodeList(node, isMultiValue)),
  MozBorderRadiusBottomleft: (node, isMultiValue) =>
    validateNodeList(isLengthPercentage, 2)(
      normalizeNodeList(node, isMultiValue)
    ),
  MozBorderRadiusBottomright: (node, isMultiValue) =>
    validateNodeList(isLengthPercentage, 2)(
      normalizeNodeList(node, isMultiValue)
    ),
  MozBorderRadiusTopleft: (node, isMultiValue) =>
    validateNodeList(isLengthPercentage, 2)(
      normalizeNodeList(node, isMultiValue)
    ),
  MozBorderRadiusTopright: (node, isMultiValue) =>
    validateNodeList(isLengthPercentage, 2)(
      normalizeNodeList(node, isMultiValue)
    ),
  MozBorderRightColors: (node, isMultiValue) =>
    validateNodeList(isColor)(normalizeNodeList(node, isMultiValue)),
  MozBorderTopColors: (node, isMultiValue) =>
    validateNodeList(isColor)(normalizeNodeList(node, isMultiValue)),
  MozForceBrokenImageIcon: isInteger,
  // TODO: validate rect
  MozImageRegion: node => isFunctionExpression(node) && node.callee === 'rect',
  // TODO: keyword split
  MozOutlineRadiust: (node, isMultiValue) =>
    validateNodeList(isLengthPercentage, 16)(
      normalizeNodeList(node, isMultiValue)
    ),
  MozOutlineRadiusBottomleft: (node, isMultiValue) =>
    validateNodeList(isLengthPercentage, 2)(
      normalizeNodeList(node, isMultiValue)
    ),
  MozOutlineRadiusBottomright: (node, isMultiValue) =>
    validateNodeList(isLengthPercentage, 2)(
      normalizeNodeList(node, isMultiValue)
    ),
  MozOutlineRadiusTopleft: (node, isMultiValue) =>
    validateNodeList(isLengthPercentage, 2)(
      normalizeNodeList(node, isMultiValue)
    ),
  MozOutlineRadiusTopright: (node, isMultiValue) =>
    validateNodeList(isLengthPercentage, 2)(
      normalizeNodeList(node, isMultiValue)
    ),
  // TODO: order multi value
  WebkitBorderBefore: (node, isMultiValue) =>
    validateUnorderedNodeList([
      validators.WebkitBorderBeforeColor,
      validators.WebkitBorderBeforeStyle,
      validators.WebkitBorderBeforeStyle,
    ])(normalizeNodeList(node, isMultiValue)),
  WebkitBorderBeforeColor: isColor,
  WebkitBorderBeforeStyle: (node, isMultiValue) =>
    isMultiValue &&
    node.length <= 4 &&
    validateNodeList(matchesKeyword('WebkitBorderBeforeStyle')),
  WebkitBorderBeforeWidth: (node, isMultiValue) => {
    if (isMultiValue) {
      return (
        node.length <= 4 &&
        validateNodeList(
          node =>
            isLength(node) || matchesKeyword('WebkitBorderBeforeWidth')(node)
        )
      )
    }

    return isLength(node)
  },
  WebkitLineCamp: node => isInteger(node) && !node.negative,
  WebkitMaskPositionX: isLengthPercentage,
  WebkitMaskPositionY: isLengthPercentage,
  WebkitTapHighlightColor: isColor,
  WebkitTextFillColor: isColor,
  WebkitTextStrokeColor: isColor,
  WebkitTextStrokeWidth: isLength,
  animation: (node, isMultiValue) =>
    validateUnorderedNodeList([
      matchesKeyword('animationFillMode'),
      matchesKeyword('animationDirection'),
      matchesKeyword('animationPlayState'),
      node =>
        matchesKeyword('animationIterationCount')(node) ||
        validators.animationIterationCount(node),
      node =>
        validators.animationTimingFunction(node) ||
        matchesKeyword('animationTimingFunction')(node),
      validators.animationDuration,
      validators.animationDelay,
      validators.animationName,
    ])(normalizeNodeList(node, isMultiValue)),
  animationDelay: isTime,
  animationDuration: isTime,
  animationIterationCount: isInteger,
  animationName: node => isIdentifier(node) || isStringLiteral(node),
  animationTimingFunction: node =>
    isCubicBezier(node) || isFrames(node) || isSteps(node),
  backgroundColor: isColor,
  backgroundPosition: isPosition,
  backgroundPositionX: (node, isMultiValue) => {
    if (isMultiValue) {
      return (
        node.length === 2 &&
        matchesIdentifier(['left', 'right', 'x-start', 'x-end'])(node[0]) &&
        isLengthPercentage(node[1])
      )
    }

    return (
      matchesIdentifier(['center', 'left', 'right', 'x-start', 'x-end'])(
        node
      ) || isLengthPercentage(node)
    )
  },
  backgroundPositionY: (node, isMultiValue) => {
    if (isMultiValue) {
      return (
        node.length === 2 &&
        matchesIdentifier(['top', 'bottom', 'y-start', 'y-end'])(node[0]) &&
        isLengthPercentage(node[1])
      )
    }

    return (
      matchesIdentifier(['center', 'top', 'bottom', 'y-start', 'y-end'])(
        node
      ) || isLengthPercentage(node)
    )
  },
  backgroundRepeat: (node, isMultiValue) =>
    validateNodeList(
      matchesIdentifier(['repeat', 'space', 'round', 'no-repeat']),
      2
    )(normalizeNodeList(node, isMultiValue)),
  backgroundSize: (node, isMultiValue) =>
    validateNodeList(
      node => isLengthPercentage(node) || matchesIdentifier(['auto'])(node),
      2
    )(normalizeNodeList(node, isMultiValue)),
  baselineShift: node => isNumber(node) || isLength(node),
  behavior: isURL,
  blockSize: isSize,
  border: (node, isMultiValue) =>
    validateUnorderedNodeList([
      validators.borderWidth,
      validators.borderStyle,
      validators.borderColor,
    ])(normalizeNodeList(node, isMultiValue)),
  borderBlockEnd: (node, isMultiValue) =>
    validateUnorderedNodeList([
      validators.borderBlockEndWidth,
      validators.borderBlockEndColor,
      validators.borderBlockEndStyle,
    ])(normalizeNodeList(node, isMultiValue)),
  borderBlockEndColor: isColor,
  borderBlockEndStyle: (node, isMultiValue) =>
    isMultiValue &&
    node.length <= 4 &&
    validateNodeList(matchesKeyword('borderBlockEndStyle')),
  borderBlockEndWidth: (node, isMultiValue) => {
    if (isMultiValue && node.length <= 4) {
      return validateNodeList(
        node => isLength(node) || matchesKeyword('borderBlockEndWidth')(node)
      )
    }

    return isLength(node)
  },
  borderBlockStart: (node, isMultiValue) =>
    validateUnorderedNodeList([
      validators.borderBlockStartWidth,
      validators.borderBlockStartColor,
      validators.borderBlockStartStyle,
    ])(normalizeNodeList(node, isMultiValue)),
  borderBlockStartColor: isColor,
  borderBlockStartStyle: (node, isMultiValue) =>
    isMultiValue &&
    node.length <= 4 &&
    validateNodeList(matchesKeyword('borderBlockStartStyle')),
  borderBlockStartWidth: (node, isMultiValue) => {
    if (isMultiValue && node.length <= 4) {
      return validateNodeList(
        node => isLength(node) || matchesKeyword('borderBlockStartWidth')(node)
      )
    }

    return isLength(node)
  },
  borderBottom: (node, isMultiValue) =>
    validateUnorderedNodeList([
      validators.borderBottomWidth,
      validators.borderBottomColor,
      matchesKeyword('borderBottomStyle'),
    ])(normalizeNodeList(node, isMultiValue)),
  borderBottomColor: isColor,
  borderBottomLeftRadius: (node, isMultiValue) =>
    validateNodeList(isLengthPercentage, 2)(
      normalizeNodeList(node, isMultiValue)
    ),
  borderBottomRightRadius: (node, isMultiValue) =>
    validateNodeList(isLengthPercentage, 2)(
      normalizeNodeList(node, isMultiValue)
    ),
  borderBottomWidth: isLength,
  borderColor: (node, isMultiValue) =>
    validateUnorderedNodeList([
      validators.borderTopColor,
      validators.borderLeftColor,
      validators.borderBottomColor,
      validators.borderRightColor,
    ])(normalizeNodeList(node, isMultiValue)),
  borderImageOutset: (node, isMultiValue) =>
    validateNodeList(node => isNumber(node) || isLength(number), 4)(
      normalizeNodeList(node, isMultiValue)
    ),
  borderImageRepeat: (node, isMultiValue) =>
    isMultiValue &&
    node.length === 2 &&
    validateNodeList(matchesKeyword('borderImageRepeat')),
  borderImageWidth: (node, isMultiValue) => {
    if (isMultiValue && node.length <= 4) {
      return validateNodeList(
        node =>
          isLength(node) ||
          isPercentage(node) ||
          isNumber(node) ||
          matchesKeyword('borderImageWidth')(node)
      )
    }

    return isLength(node) || isPercentage(node) || isNumber(node)
  },
  borderInlineEnd: (node, isMultiValue) =>
    validateUnorderedNodeList([
      validators.borderInlineEndWidth,
      validators.borderInlineEndColor,
      validators.borderInlineEndStyle,
    ])(normalizeNodeList(node, isMultiValue)),
  borderInlineEndColor: isColor,
  borderInlineEndStyle: (node, isMultiValue) =>
    isMultiValue &&
    node.length <= 4 &&
    validateNodeList(matchesKeyword('borderInlineEndStyle')),
  borderInlineEndWidth: (node, isMultiValue) => {
    if (isMultiValue && node.length <= 4) {
      return validateNodeList(
        node => isLength(node) || matchesKeyword('borderInlineEndWidth')(node)
      )
    }

    return isLength(node)
  },
  borderInlineStart: (node, isMultiValue) =>
    validateUnorderedNodeList([
      validators.borderInlineStartWidth,
      validators.borderInlineStartColor,
      validators.borderInlineStartStyle,
    ])(normalizeNodeList(node, isMultiValue)),
  borderInlineStartColor: isColor,
  borderInlineStartStyle: (node, isMultiValue) =>
    isMultiValue &&
    node.length <= 4 &&
    validateNodeList(matchesKeyword('borderInlineStartStyle')),
  borderInlineStartWidth: (node, isMultiValue) => {
    if (isMultiValue && node.length <= 4) {
      return validateNodeList(
        node => isLength(node) || matchesKeyword('borderInlineStartWidth')(node)
      )
    }

    return isLength(node)
  },
  borderLeft: (node, isMultiValue) =>
    validateUnorderedNodeList([
      validators.borderLeftWidth,
      validators.borderLeftColor,
      matchesKeyword('borderLeftStyle'),
    ])(normalizeNodeList(node, isMultiValue)),
  borderLeftColor: isColor,
  borderLeftWidth: isLength,
  // TODO: keyword split
  borderRadius: (node, isMultiValue) =>
    validateNodeList(isLengthPercentage, 8)(
      normalizeNodeList(node, isMultiValue)
    ),
  borderRight: (node, isMultiValue) =>
    validateUnorderedNodeList([
      validators.borderRightWidth,
      validators.borderRightColor,
      matchesKeyword('borderRightStyle'),
    ])(normalizeNodeList(node, isMultiValue)),
  borderRightColor: isColor,
  borderRightWidth: isLength,
  borderSpacing: (node, isMultiValue) =>
    validateNodeList(isLength, 2)(normalizeNodeList(node, isMultiValue)),
  borderStyle: (node, isMultiValue) =>
    validateUnorderedNodeList([
      matchesKeyword('borderLeftStyle'),
      matchesKeyword('borderTopStyle'),
      matchesKeyword('borderBottomStyle'),
      matchesKeyword('borderRightStyle'),
    ])(normalizeNodeList(node, isMultiValue)),
  borderTop: (node, isMultiValue) =>
    validateUnorderedNodeList([
      validators.borderTopWidth,
      validators.borderTopColor,
      matchesKeyword('borderTopStyle'),
    ])(normalizeNodeList(node, isMultiValue)),
  borderTopColor: isColor,
  borderTopWidth: isLength,
  borderTopLeftRadius: (node, isMultiValue) =>
    validateNodeList(isLengthPercentage, 2)(
      normalizeNodeList(node, isMultiValue)
    ),
  borderTopRightRadius: (node, isMultiValue) =>
    validateNodeList(isLengthPercentage, 2)(
      normalizeNodeList(node, isMultiValue)
    ),
  borderWidth: (node, isMultiValue) =>
    validateUnorderedNodeList([
      validators.borderTopWidth,
      validators.borderLeftWidth,
      validators.borderBottomWidth,
      validators.borderRightWidth,
    ])(normalizeNodeList(node, isMultiValue)),
  bottom: isLengthPercentage,
  boxFlex: isNumber,
  boxFlexGroup: isInteger,
  boxOrdinalGroup: isInteger,
  caretColor: isColor,
  // TODO: is valid rect
  clip: node => isFunctionExpression(node) && node.callee === 'rect',
  color: isColor,
  columns: (node, isMultiValue) =>
    validateUnorderedNodeList([validators.columnWidth, validators.columnCount])(
      normalizeNodeList(node, isMultiValue)
    ),
  columnCount: isInteger,
  columnGap: isLength,
  columnRule: (node, isMultiValue) =>
    validateUnorderedNodeList([
      validators.columnRuleWidth,
      matchesKeyword('columnRuleStyle'),
      validators.columnRuleColor,
    ])(normalizeNodeList(node, isMultiValue)),
  columnRuleColor: isColor,
  columnRuleWidth: isLength,
  columnWidth: isLength,
  contain: (node, isMultiValue) =>
    validateUnorderedNodeList([
      matchesIdentifier(['size']),
      matchesIdentifier(['layout']),
      matchesIdentifier(['style']),
      matchesIdentifier(['paint']),
    ])(normalizeNodeList(node, isMultiValue)),
  fillOpacity: isOpacity,
  flex: (node, isMultiValue) => {
    if (isMultiValue) {
      return (
        (node.length === 2 &&
          validateUnorderedNodeList([
            validators.flexGrow,
            validators.flexBasis,
          ])(node)) ||
        (node.length === 3 &&
          ((validators.flexGrow(node[0]) &&
            validators.flexShrink(node[1]) &&
            validators.flexBasis(node[2])) ||
            (validators.flexBasis(node[0]) &&
              validators.flexGrow(node[1]) &&
              validators.flexShrink(node[2]))))
      )
    }

    return validators.flexBasis(node) || validators.flexGrow(node)
  },
  flexBasis: isSize,
  flexFlow: (value, isMultiValue) =>
    validateUnorderedNodeList([
      matchesKeyword('flexDirection'),
      matchesKeyword('flexWrap'),
    ])(normalizeNodeList(node, isMultiValue)),
  flexGrow: isNumber,
  flexShrink: isNumber,
  fontFamily: node => isStringLiteral(node) || isIdentifier(node),
  fontLanguageOverwrite: isStringLiteral,
  fontSize: isLengthPercentage,
  fontSizeAdjust: isFloat,
  fontWeight: node =>
    isInteger(node) &&
    [100, 200, 300, 400, 500, 600, 700, 800, 900].indexOf(node.value) !== -1,
  glyphOrientationHorizontal: isAngle,
  glyphOrientationVertical: isAngle,
  gridAutoFlow: (node, isMultiValue) =>
    validateUnorderedNodeList([
      matchesIdentifier(['row', 'column']),
      matchesIdentifier(['dense']),
    ])(normalizeNodeList(node, isMultiValue)),
  gridColumnGap: isLengthPercentage,
  gridGap: (node, isMultiValue) =>
    validateNodeList(isLengthPercentage, 2)(
      normalizeNodeList(node, isMultiValue)
    ),
  gridRowGap: isLengthPercentage,
  gridTemplateAreas: (node, isMultiValue) =>
    validateNodeList(isStringLiteral)(normalizeNodeList(node, isMultiValue)),
  height: isSize,
  inlineSize: isSize,
  kerning: isSvgLength,
  left: isLengthPercentage,
  letterSpacing: isLengthPercentage,
  lineHeight: node => isLength(node) || isNumber(node),
  listStyleImage: isURL,
  margin: (node, isMultiValue) =>
    validateNodeList(
      // margin-left is equivalent to -top, -bottom, -right
      node => validators.marginLeft(node) || matchesKeyword('marginLeft')(node),
      4
    )(normalizeNodeList(node, isMultiValue)),
  marginBlockEnd: isLengthPercentage,
  marginBlockStart: isLengthPercentage,
  marginBottom: isLengthPercentage,
  marginInlineEnd: isLengthPercentage,
  marginInlineStart: isLengthPercentage,
  marginLeft: isLengthPercentage,
  marginRight: isLengthPercentage,
  marginTop: isLengthPercentage,
  marker: isURL,
  markerEnd: isURL,
  markerMid: isURL,
  markerOffset: isLength,
  markerStart: isURL,
  maskBorderOutset: (node, isMultiValue) =>
    validateNodeList(node => isLength(node) || isNumber(node), 4)(
      normalizeNodeList(node, isMultiValue)
    ),
  maskBorderOutset: (node, isMultiValue) =>
    isMultiValue &&
    node.length === 2 &&
    validateNodeList(matchesKeyword('maskBorderOutset'))(node),
  maskBorderSource: isImage,
  maskBorderWidth: (node, isMultiValue) =>
    validateNodeList(
      node =>
        isLengthPercentage(node) ||
        isNumber(node) ||
        matchesKeyword('maskBorderWidth')(node),
      4
    )(normalizeNodeList(node, isMultiValue)),
  maskPosition: isPosition,
  maxBlockSize: isLengthPercentage,
  maxHeight: isLengthPercentage,
  maxInlineSize: isLengthPercentage,
  maxWidth: isLengthPercentage,
  minBlockSize: isLengthPercentage,
  minHeight: isLengthPercentage,
  minInlineSize: isLengthPercentage,
  minWidth: isLengthPercentage,
  msFilter: isStringLiteral,
  msFlexNegative: isNumber,
  msFlexOrder: isInteger,
  msFlexPositive: isNumber,
  objectPosition: isPosition,
  offsetBlockEnd: isLength,
  offsetBlockStart: isLength,
  offsetDistance: isLengthPercentage,
  offsetInlineEnd: isLength,
  offsetInlineStart: isLength,
  offsetAnchor: isPosition,
  offsetPosition: isPosition,
  opacity: isOpacity,
  order: isInteger,
  orphans: isInteger,
  outlineColor: isColor,
  outlineOffset: isLength,
  outlineWidth: isLength,
  padding: (node, isMultiValue) =>
    validateNodeList(
      // padding-left is equivalent to -top, -bottom, -right
      node =>
        validators.paddingLeft(node) || matchesKeyword('paddingLeft')(node),
      4
    )(normalizeNodeList(node, isMultiValue)),
  paddingBlockEnd: isLengthPercentage,
  paddingBlockStart: isLengthPercentage,
  paddingBottom: isLengthPercentage,
  paddingInlineEnd: isLengthPercentage,
  paddingInlineStart: isLengthPercentage,
  paddingLeft: isLengthPercentage,
  paddingRight: isLengthPercentage,
  paddingTop: isLengthPercentage,
  pauseAfter: isTime,
  pauseBefore: isTime,
  perspective: isLength,
  perspectiveOrigin: isPosition,
  quotes: (node, isMultiValue) =>
    isMultiValue &&
    node.length % 2 === 0 &&
    validateNodeList(isStringLiteral)(node),
  rest: (node, isMultiValue) =>
    validateUnorderedNodeList([validators.restAfter, validators.restBefore])(
      normalizeNodeList(node, isMultiValue)
    ),
  restAfter: isTime,
  restBefore: isTime,
  right: isLengthPercentage,
  shapeImageThreshold: isNumber,
  scrollSnapCoordinate: (node, isMultiValue, isValueList) =>
    (!isValueList && matchesIdentifier(['none'])(node)) || isPosition(node),
  scrollSnapDestination: isPosition,
  scrollSnapPointsX: isRepeat,
  scrollSnapPointsY: isRepeat,
  shapeMargin: isLengthPercentage,
  strokeDasharray: (node, isMultiValue) =>
    validateNodeList(isSvgLength)(normalizeNodeList(node, isMultiValue)),
  strokeDashoffset: isSvgLength,
  strokeMiterlimit: node =>
    (isInteger(node) && node.value >= 1) ||
    (isFloat(node) && node.integer >= 1),
  strokeOpacity: isOpacity,
  strokeWidth: isSvgLength,
  tabSize: isInteger,
  textDecoration: (node, isMultiValue) =>
    validateUnorderedNodeList([
      matchesKeyword('textDecorationStyle'),
      validators.textDecorationColor,
      node =>
        validators.textDecorationLine(node) ||
        matchesKeyword('textDecorationLine')(node),
    ])(normalizeNodeList(node, isMultiValue)),
  textDecorationColor: isColor,
  textDecorationLine: (node, isMultiValue) =>
    validateUnorderedNodeList([
      matchesIdentifier(['underline']),
      matchesIdentifier(['overline']),
      matchesIdentifier(['line-through']),
      matchesIdentifier(['blink']),
    ])(normalizeNodeList(node, isMultiValue)),
  textDecorationSkip: (node, isMultiValue) =>
    validateUnorderedNodeList([
      matchesIdentifier(['objects']),
      matchesIdentifier(['spaces']),
      matchesIdentifier(['ink']),
      matchesIdentifier(['edges']),
      matchesIdentifier(['box-decoration']),
    ])(normalizeNodeList(node, isMultiValue)),
  textEmphasis: (node, isMultiValue) =>
    validateUnorderedNodeList([
      validators.textEmphasisColor,
      validators.textEmphasisStyle,
    ])(normalizeNodeList(node, isMultiValue)),
  textEmphasisColor: isColor,
  textEmphasisPosition: (node, isMultiValue) =>
    isMultiValue &&
    node.length === 2 &&
    matchesIdentifier(['over', 'under'])(node[0]) &&
    matchesIdentifier(['left', 'right'])(node[1]),
  textEmphasisStyle: (node, isMultiValue) => {
    if (isMultiValue) {
      return validateUnorderedNodeList([
        matchesIdentifier(['filled', 'open']),
        matchesIdentifier([
          'dot',
          'circle',
          'double-circle',
          'triangle',
          'sesame',
        ]),
      ])(node)
    }

    return isStringLiteral(node)
  },
  textIndent: (node, isMultiValue) => {
    if (isMultiValue) {
      return validateUnorderedNodeList([
        isLengthPercentage,
        matchesIdentifier(['hanging']),
        matchesIdentifier(['each-line']),
      ])(node)
    }

    return isLengthPercentage(node)
  },
  textOverflow: (node, isMultiValue) => {
    if (isMultiValue && node.length === 2) {
      return validateNodeList(
        node => isStringLiteral(node) || matchesKeyword('textOverflow')(node)
      )
    }

    return isStringLiteral(node)
  },
  textShadow: (node, isMultiValue) => {
    if (isMultiValue) {
      if (node.length === 2) {
        return validateNodeList(isLength)(node)
      }

      if (node.length === 3) {
        return (
          validateNodeList(isLength)(node) ||
          (isLength(node[0]) && isLength(node[1]) && isColor(node[2]))
        )
      }

      if (node.length === 4) {
        return (
          isLength(node[0]) &&
          isLength(node[1]) &&
          isLength(node[2]) &&
          isColor(node[3])
        )
      }
    }
  },
  textSizeAdjust: isPercentage,
  textUnderlinePosition: (node, isMultiValue) =>
    isMultiValue &&
    validateUnorderedNodeList([
      matchesIdentifier(['under']),
      matchesIdentifier(['left', 'right']),
    ])(node),
  top: isLengthPercentage,
  touchAction: (node, isMultiValue) => {
    if (isMultiValue) {
      return validateUnorderedNodeList([
        matchesIdentifier(['pan-x', 'pan-left', 'pan-right']),
        matchesIdentifier(['pan-y', 'pan-up', 'pan-down']),
        matchesIdentifier(['pinch-zoom']),
      ])(node)
    }

    return matchesIdentifier([
      'pan-x',
      'pan-left',
      'pan-right',
      'pan-y',
      'pan-up',
      'pan-down',
      'pinch-zoom',
    ])(node)
  },
  transformOrigin: (node, isMultiValue) => {
    if (isMultiValue) {
      return (
        (node.length === 2 &&
          validateUnorderedNodeList([
            node =>
              isLengthPercentage(node) ||
              matchesIdentifier(['left', 'center', 'right'])(node),
            node =>
              isLengthPercentage(node) ||
              matchesIdentifier(['top', 'center', 'bottom'])(node),
          ])(node)) ||
        (node.length === 3 &&
          validateUnorderedNodeList([
            node =>
              isLengthPercentage(node) ||
              matchesIdentifier(['left', 'center', 'right'])(node),
            node =>
              isLengthPercentage(node) ||
              matchesIdentifier(['top', 'center', 'bottom'])(node),
          ])([node[0], node[1]]) &&
          isLengthPercentage(node[2]))
      )
    }

    return isLengthPercentage(node)
  },
  transition: (node, isMultiValue) =>
    validateUnorderedNodeList([
      validators.transitionDelay,
      validators.transitionDuration,
      node =>
        validators.transitionTimingFunction(node) ||
        matchesKeyword('transitionTimingFunction')(node),
      validators.transitionProperty,
    ])(normalizeNodeList(node, isMultiValue)),
  transitionDelay: isTime,
  transitionDuration: isTime,
  transitionProperty: isIdentifier,
  transitionTimingFunction: node =>
    isCubicBezier(node) || isFrames(node) || isSteps(node),
  verticalAlign: isLengthPercentage,
  voiceBalance: isNumber,
  voiceDuration: isTime,
  widows: isInteger,
  width: isSize,
  willChange: (node, isMultiValue, isValueList) =>
    isValueList
      ? isIdentifier(node) && node.value !== 'auto'
      : matchesIdentifier(['auto'])(node),
  wordSpacing: isLengthPercentage,
  zIndex: isInteger,
  zoom: node => isNumber(node) || isPercentage(node),
}

export default validators