All files cucumber_expression.js

100% Statements 57/57
100% Branches 12/12
100% Functions 18/18
100% Lines 52/52

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 1231x 1x 1x       1x             78x 105x 78x 1x 77x 1x   1x   1x               78x 78x   78x 78x 77x 75x 64x   64x       78x       78x 23x 3x   20x 19x         77x         7x 7x 5x 10x         3x   2x             75x 78x   77x 77x 67x 67x 66x 66x         60x       6x       4x       30x 3x           66x 38x     28x 56x     28x   1x  
const Argument = require('./argument')
const TreeRegexp = require('./tree_regexp')
const ParameterType = require('./parameter_type')
const {
  UndefinedParameterTypeError,
  CucumberExpressionError,
} = require('./errors')
 
// RegExps with the g flag are stateful in JavaScript. In order to be able
// to reuse them we have to wrap them in a function.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test
 
// Does not include (){} characters because they have special meaning
const ESCAPE_REGEXP = () => /([\\^[$.|?*+])/g
const PARAMETER_REGEXP = () => /(\\\\)?{([^}]*)}/g
const OPTIONAL_REGEXP = () => /(\\\\)?\(([^)]+)\)/g
const ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = () =>
  /([^\s^/]+)((\/[^\s^/]+)+)/g
const DOUBLE_ESCAPE = '\\\\'
const PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE =
  'Parameter types cannot be alternative: '
const PARAMETER_TYPES_CANNOT_BE_OPTIONAL =
  'Parameter types cannot be optional: '
 
class CucumberExpression {
  /**
   * @param expression
   * @param parameterTypeRegistry
   */
  constructor(expression, parameterTypeRegistry) {
    this._expression = expression
    this._parameterTypes = []
 
    expression = this.processEscapes(expression)
    expression = this.processOptional(expression)
    expression = this.processAlternation(expression)
    expression = this.processParameters(expression, parameterTypeRegistry)
    expression = `^${expression}$`
 
    this._treeRegexp = new TreeRegexp(expression)
  }
 
  processEscapes(expression) {
    return expression.replace(ESCAPE_REGEXP(), '\\$1')
  }
 
  processOptional(expression) {
    return expression.replace(OPTIONAL_REGEXP(), (match, p1, p2) => {
      if (p1 === DOUBLE_ESCAPE) {
        return `\\(${p2}\\)`
      }
      this._checkNoParameterType(p2, PARAMETER_TYPES_CANNOT_BE_OPTIONAL)
      return `(?:${p2})?`
    })
  }
 
  processAlternation(expression) {
    return expression.replace(
      ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP(),
      match => {
        // replace \/ with /
        // replace / with |
        const replacement = match.replace(/\//g, '|').replace(/\\\|/g, '/')
        if (replacement.indexOf('|') !== -1) {
          for (const part of replacement.split(/\|/)) {
            this._checkNoParameterType(
              part,
              PARAMETER_TYPES_CANNOT_BE_ALTERNATIVE
            )
          }
          return `(?:${replacement})`
        } else {
          return replacement
        }
      }
    )
  }
 
  processParameters(expression, parameterTypeRegistry) {
    return expression.replace(PARAMETER_REGEXP(), (match, p1, p2) => {
      if (p1 === DOUBLE_ESCAPE) return `\\{${p2}\\}`
 
      const typeName = p2
      ParameterType.checkParameterTypeName(typeName)
      const parameterType = parameterTypeRegistry.lookupByTypeName(typeName)
      if (!parameterType) throw new UndefinedParameterTypeError(typeName)
      this._parameterTypes.push(parameterType)
      return buildCaptureRegexp(parameterType.regexps)
    })
  }
 
  match(text) {
    return Argument.build(this._treeRegexp, text, this._parameterTypes)
  }
 
  get regexp() {
    return this._treeRegexp.regexp
  }
 
  get source() {
    return this._expression
  }
 
  _checkNoParameterType(s, message) {
    if (s.match(PARAMETER_REGEXP())) {
      throw new CucumberExpressionError(message + this.source)
    }
  }
}
 
function buildCaptureRegexp(regexps) {
  if (regexps.length === 1) {
    return `(${regexps[0]})`
  }
 
  const captureGroups = regexps.map(group => {
    return `(?:${group})`
  })
 
  return `(${captureGroups.join('|')})`
}
module.exports = CucumberExpression