All files / lib/formulas convert-bind-attr.js

100% Statements 78/78
93.75% Branches 15/16
100% Functions 8/8
100% Lines 76/76
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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 1491x       1x 1x       1x           1x     21x       36x       13x 13x 13x 13x   13x 13x 10x   3x 3x     13x           13x       24x 24x 24x 24x   24x 24x 16x 8x 8x   24x 24x   24x 24x 24x 24x 24x 24x       25x 25x 25x 36x 36x 36x 13x   23x   36x     25x 25x   72x 36x 36x 25x 25x 25x         11x 11x   11x   11x       11x 11x 11x   11x 11x       25x   25x 25x 25x   25x 36x       18x   18x 81x 25x 25x 21x 21x 20x           18x              
import {
  Walker,
  builders,
} from 'glimmer-engine/dist/node_modules/glimmer-syntax';
import _ from 'underscore';
import {
  locDiff,
  addOffsets,
} from '../utils/location';
import {
  nodeIndex,
  offsetNode,
  removeNode,
  sizeOfNodes,
} from '../utils/node';
import classStringParser from '../class-string-parser';
 
function isBindAttr(modifier) {
  return modifier.path.original === 'bind-attr';
}
 
function isClassBinding(pair) {
  return pair.key === 'class';
}
 
function classBindingToAttribute(pair) {
  const classString = pair.value.value;
  const nodes = classStringParser(classString, { spaces: true });
  const size = sizeOfNodes(nodes);
  let node;
 
  let quoteSize = 0;
  if (nodes.length === 1) {
    node = nodes[0];
  } else {
    node = builders.concat(nodes);
    quoteSize = 2;
  }
 
  const loc = builders.loc(
    1,
    0,
    1 + size.line,
    size.column + 6 + quoteSize + 1, // "class=" 6
  );
  return builders.attr('class', node, loc);
}
 
function attributeBindingToAttribute(pair) {
  const attrKey = pair.key;
  const startAttr = 0;
  const equalCol = startAttr + attrKey.length;
  const startMust = equalCol + 1; // "="  1
 
  let value;
  if (pair.value.type === 'PathExpression') {
    value = pair.value.original;
  } else Eif (pair.value.type === 'StringLiteral') {
    value = pair.value.value;
  }
  const valueLength = value.length;
  const endMust = startMust + 2 + valueLength + 2;
 
  const node = builders.mustache(value);
  node.loc = builders.loc(1, startMust, 1, endMust);
  node.path.loc = builders.loc(1, startMust + 2, 1, endMust - 2);
  const newAttr = builders.attr(pair.key, node);
  newAttr.loc = builders.loc(1, startAttr, 1, endMust);
  return newAttr;
}
 
function removeBindAttr(modifier, node, ast) {
  const pairs = modifier.hash.pairs;
  const attrs = [];
  for (let j = 0; j < pairs.length; j += 1) {
    const pair = pairs[j];
    let attr;
    if (isClassBinding(pair)) {
      attr = classBindingToAttribute(pair);
    } else {
      attr = attributeBindingToAttribute(pair);
    }
    attrs.push({ pair, attr });
  }
 
  let firstAttrOffset;
  let prevAttrsOffset = { line: 0, column: 0 };
  // set locations of new attributes
  for (let i = 0; i < attrs.length; i += 1) {
    const { attr, pair } = attrs[i];
    if (i === 0) {
      firstAttrOffset = locDiff(attr.loc, modifier.loc).start;
      offsetNode(attr, firstAttrOffset, { recursive: true, both: true });
      prevAttrsOffset = addOffsets(prevAttrsOffset, sizeOfNodes(attr));
    } else {
      // find old offset to previous value
      const {
        pair: prevPair,
      } = attrs[i - 1];
      offsetNode(attr, firstAttrOffset, { recursive: true, both: true });
 
      let offset = { line: 0, column: 0 };
 
      const prevPairDiff = {
        line: pair.loc.start.line - prevPair.loc.end.line,
        column: pair.loc.start.column - prevPair.loc.end.column,
      };
      offset = addOffsets(offset, prevPairDiff);
      offset = addOffsets(offset, prevAttrsOffset);
      offsetNode(attr, offset, { recursive: true, both: false });
 
      prevAttrsOffset = addOffsets(prevAttrsOffset, sizeOfNodes(attr));
      prevAttrsOffset = addOffsets(prevAttrsOffset, prevPairDiff);
    }
  }
 
  removeNode(modifier, ast);
 
  const startingAt = modifier.loc.start;
  const offset = prevAttrsOffset;
  offsetNode(ast, offset, { recursive: true, startingAt });
 
  const bindAttrIndex = nodeIndex(modifier, node.attributes);
  attrs.forEach((a, i) => node.attributes.splice(bindAttrIndex + i, 0, a.attr));
}
 
export default function convertBindAttr(ast) {
  const walker = new Walker(ast);
 
  walker.visit(ast, (node) => {
    if (node.type === 'ElementNode' && node.modifiers) {
      const modifiers = _.clone(node.modifiers);
      for (let i = 0; i < modifiers.length; i += 1) {
        const modifier = modifiers[i];
        if (isBindAttr(modifier)) {
          removeBindAttr(modifier, node, ast);
        }
      }
    }
  });
 
  return ast;
}
 
export {
  attributeBindingToAttribute,
  removeBindAttr,
};