All files index.js

100% Statements 36/36
94.74% Branches 18/19
100% Functions 8/8
100% Lines 35/35
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            1x                                           1x     1x     1x   1x 11x     11x 2x   9x       11x   11x   11x 12x     11x   44x   44x 11x   11x 11x   11x 11x   11x     11x     44x 35x     9x   44x   11x 6x     9x   11x   9x 6x     9x           11x      
import DocChomp from 'doc-chomp';
 
import getDisplayName from './get-display-name';
import consoleReporter from './console-reporter';
import verifyReact from './verify-react';
 
const SNOBBY_TESTS = [
  {
    name: 'ugly double quotes',
    regexp: /"/g,
    suggestion: '`“` or `”`'
  },
  {
    name: 'ugly single quotes',
    regexp: /'/g,
    suggestion: '`‘` or `’`'
  },
  {
    name: 'full-stops as ellipses',
    regexp: /\.{2,}/g,
    suggestion: '`…`'
  },
  {
    name: 'space preceding ellipses',
    regexp: /\s(?:\.{2,}|…)/g
  }
];
 
const CONTEXT_LENGTH = 20;
 
export default function typeSnob(React, reporter = consoleReporter) {
  verifyReact(React);
 
  // keep original `createElement` method, as we intend to proxy it!
  const createElement = React.createElement;
 
  React.createElement = function typeSnobCreateElement(type, rawProps, ...rawChildren) {
    const props = rawProps || {};
    let children;
 
    if (rawChildren.length === 0) {
      children = props.children || [];
    } else {
      children = rawChildren;
    }
 
    // proxy out to the real `createElement` early so we don't try to test any invalid states!
    const reactElement = createElement.call(this, type, rawProps, ...rawChildren);
 
    let textContent = '';
 
    React.Children.forEach(children, (child) => {
      textContent += React.isValidElement(child) ? '[React Element]' : child;
    });
 
    const errors = SNOBBY_TESTS
      .map((test) => {
        const contexts = [];
 
        textContent.replace(test.regexp, (match, ...args) => {
          const fullText = args.pop();
 
          const startOffset = args.pop();
          const endOffset = startOffset + match.length;
 
          const contextStartOffset = Math.max(startOffset - CONTEXT_LENGTH, 0);
          const contextEndOffset = Math.min(endOffset + CONTEXT_LENGTH, fullText.length);
 
          contexts.push(fullText.slice(contextStartOffset, contextEndOffset).replace(/\n/g, ' '));
 
          // *do not* replace, as we may need to do further processing
          return match;
        });
 
        if (contexts.length === 0) {
          return;
        }
 
        return Object.assign({ contexts }, test);
      })
      .filter((returnedThing) => returnedThing);
 
    if (errors.length > 0) {
      reporter(DocChomp`
        [React Type Snob] Problems detected in text content of \`${getDisplayName(type, rawProps)}\`${(reactElement._owner && reactElement._owner.getName) ? `. Please check the render method of \`${reactElement._owner.getName()}\`` : ''};
        ${errors.map(({ name, suggestion, contexts }) => {
          let report = `* Found ${name};`;
 
          contexts.forEach((context) => report += `\n   * \`${context}\``);
 
          if (suggestion) {
            report += `\n  Suggested replacements: ${suggestion}`;
          }
 
          return report;
        }).join('\n\n')
        }`
      );
    }
 
    return reactElement;
  };
}