All files / postcss index.js

95.24% Statements 60/63
71.43% Branches 15/21
100% Functions 17/17
94.83% Lines 55/58
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 1101x 1x 1x 1x 1x             1x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x   6x 6x       6x             6x       6x 6x               6x 6x 5x 2x 2x       6x 1x 1x 1x 1x         6x 6x     6x   6x     6x     6x 4x   2x   6x           6x       14x   6x 5x 1x   1x   1x   1x 6x  
const postcss = require('postcss');
const parser = require('postcss-selector-parser');
const _ = require('lodash/fp');
const parseAttributeNodes = require('./parse-attribute-nodes');
const appendAttr = require('./append-attr');
 
/**
 * @param {Object} options
 * @param {function} options.onComponents
 * @param {string} options.id
 */
module.exports = postcss.plugin('stylesheet', ({ onComponents, id }) => {
  return (root, result) => {
    let allComponents = {};
    result.root.walkRules(/\b[A-Z]/, rule => {
      const blockComponents = new Set();
      let components = {};
      const parseComponentTagsAndAttributesSelectors = selectorRoot => {
        for (const selector of selectorRoot.nodes) {
          for (const tag of selector.nodes.filter(matchComponentTags)) {
            const tagIndex = selector.nodes.indexOf(tag);
            const componentName = tag.value;
            const componentClassName = `${ componentName }_${ id }`;
            let nextCombinatorIndex = findNextCombinatorIndexFrom(tagIndex, selector.nodes);
            Eif (nextCombinatorIndex === -1) {
              nextCombinatorIndex = selector.nodes.length;
              blockComponents.add(componentName);
            }
            const attributeNodes = getAttributeNodes(tagIndex, nextCombinatorIndex, selector.nodes);
            components = _.update(
              componentName,
              _.flow([
                _.set('className', componentClassName),
                _.update('attributes', (attributes = []) => [
                  ...attributes,
                  ...parseAttributeNodes(id, componentName, attributeNodes),
                ]),
              ]),
              components
            );
            tag.replaceWith(parser.className({ value: componentClassName }));
          }
        }
      };
      try {
        rule.selector = parser(parseComponentTagsAndAttributesSelectors).process(
          rule.selector
        ).result;
      } catch (err) {
        if (err.message !== 'Expected pseudo-class or pseudo-element') {
          throw err;
        }
      }
      Eif (blockComponents.size) {
        rule.walkDecls(decl => {
          if (isAttr(decl.value)) {
            for (const component of blockComponents) {
              components = appendAttr(components, component, rule, decl);
            }
          }
        });
        rule.walkAtRules('apply', atRule => {
          Eif (isElementBase(atRule)) {
            for (const component of blockComponents) {
              components = _.set([component, 'base'], atRule.params, components);
              atRule.remove();
            }
          }
        });
      }
      for (const [componentName, component] of Object.entries(components)) {
        allComponents = _.update(
          componentName,
          _.flow([
            _.update('className', className => className || component.className),
            _.update('attributes', (attributes = []) => {
              Iif (!component.attributes) {
                return attributes;
              }
              return attributes.concat(component.attributes)
            }),
            _.update('attrs', (attrs = []) => {
              if (!component.attrs) {
                return attrs;
              }
              return attrs.concat(component.attrs)
            }),
            _.update('base', base => base || component.base),
          ]),
          allComponents
        );
      }
    });
    onComponents(allComponents);
  };
});
 
const and = _.curry((predicates, value) => _.every(predicate => predicate(value), predicates));
 
const isComponentElement = ({ value }) => value.search(/[A-Z]/) === 0;
const isAttr = value => value.search(/attr\(.+?\)/) !== -1;
const isElementBase = ({ params }) => params.search(/[A-z]/) === 0;
 
const matchComponentTags = and([_.matches({ type: 'tag' }), isComponentElement]);
 
const findNextCombinatorIndexFrom = _.findIndexFrom({ type: 'combinator' });
 
const getAttributeNodes = (tagIndex, nextCombinatorIndex, nodes) =>
  _.flow([_.slice(tagIndex, nextCombinatorIndex), _.filter({ type: 'attribute' })])(nodes);