All files / src/parser fragment.js

100% Statements 22/22
100% Branches 10/10
100% Functions 4/4
100% Lines 22/22
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                        27x                     27x       85x                                   724x           85x 724x   724x 1525x 1525x 257x     1268x 544x 544x     724x     724x     724x 367x   921x 921x 544x   377x         357x      
/**
 * Tag template literal for parsing stand alone statements
 *
 * For expressions, wrap the source string with parens. For example
 * an binary expression is not a valid statement (1 + 1), but wrapping
 * it with parens IS.
 *
 * 1 + 1 -> ERROR!
 * (1 + 1); -> OK!
 *
 */
// @flow
import { mapNode } from 'walt-parser-tools/map-node';
import type { NodeType } from '../flow/types';
 
type Parser = string => NodeType;
 
/**
 * Fragment tag template literal factory. All input strings must be valid
 * statements.
 *
 * @return {Function} tag template literal
 */
export const makeFragment = (parser: Parser) => {
  // For fragments we must wrap the source in a function
  // otherwise the parser will fail as it's not a valid
  // place for an expression
  const parse = src => {
    if (process.env.NODE_ENV === 'development') {
      try {
        // 1st node is a function.
        // 3rd node of a function is a block, containing a single expression
        return parser(`function fragment() {
        ${src}
      }`).params[0].params[2].params[0];
      } catch (e) {
        throw new Error(
          `PANIC - Invalid fragment input:
 
${src}
 
Parse Error: ${e.stack}`
        );
      }
    } else {
      return parser(`function fragment() {
        ${src}
      }`).params[0].params[2].params[0];
    }
  };
 
  return (template: string[], ...replacements: Array<string | NodeType>) => {
    let expandNodes = false;
    // Build out a placeholder source string which will be compiled
    const source = template.reduce((a, v, i) => {
      const rep = replacements[i];
      if (rep != null && typeof rep !== 'object') {
        return (a += v + String(rep));
      }
 
      if (rep != null) {
        expandNodes = true;
        return (a += v + `$$rep_${i}`);
      }
 
      return (a += v);
    }, '');
 
    const node = parse(source);
 
    // Expand any Node objects if necessary
    if (expandNodes) {
      return mapNode({
        Identifier(id) {
          const { value: name } = id;
          if (!name.indexOf('$$rep_')) {
            return replacements[Number(name.replace('$$rep_', ''))];
          }
          return id;
        },
      })(node);
    }
 
    return node;
  };
};