<commit_before>import { PropTypes } from 'react';
import identity from 'lodash/identity';
import includes from 'lodash/includes';
import intersection from 'lodash/intersection';
import noop from 'lodash/noop';
import reduce from 'lodash/reduce';

export const CommonPropTypes = {
  className: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.array,
    PropTypes.object,
  ]),
  style: PropTypes.object,
  width: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
  ]),
  height: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
  ]),
};

export const CommonDefaultProps = {
  noop,
  identity,
};

export function exactlyOneOfProp(propTypes) {
  return (props, propName, componentName, location, propFullName) => {
    let error = null;
    const validProps = intersection(Object.keys(props), Object.keys(propTypes));
    if (validProps.length === 1) {
      if (validProps[0] === propName) {
        error = propTypes[propName](props, propName, componentName, location, propFullName);
      }
    } else {
      /* eslint-disable max-len */
      error = `Exactly one of prop [\`${Object.keys(propTypes).join('`,`')}\`] must be specified in \`${componentName}\`. Found: [${validProps.length ? `\`${validProps.join('`,`')}\`` : ''}].`;
      /* eslint-enable max-len */
    }
    return error && new Error(error);
  };
}

export function atLeastOneOfProp(propTypes) {
  return (props, propName, componentName, location, propFullName) => {
    let error = null;
    const validProps = intersection(Object.keys(props), Object.keys(propTypes));
    if (validProps.length > 0) {
      if (includes(validProps, propName)) {
        error = propTypes[propName](props, propName, componentName, location, propFullName);
      }
    } else {
      /* eslint-disable max-len */
      error = `At least one of prop [\`${Object.keys(propTypes).join('`,`')}\`] must be specified in \`${componentName}\`.`;
      /* eslint-enable max-len */
    }
    return error && new Error(error);
  };
}

export function propsChanged(prevProps, nextProps, propsToCompare, propsToOmit) {
  return !reduce(propsToCompare || Object.keys(nextProps), (acc, prop) => {
    return acc && (includes(propsToOmit, prop) || prevProps[prop] === nextProps[prop]);
  }, true);
}

export function stateFromPropUpdates(propUpdates, prevProps, nextProps, state) {
  return reduce(propUpdates, (acc, value, key) => {
    return value(acc, key, prevProps, nextProps);
  }, state);
}

export function updateFunc(func) {
  return (acc, key, prevProps = {}, nextProps) => {
    return prevProps[key] !== nextProps[key] ?
      { ...acc, ...func(nextProps[key], key, nextProps) } :
      acc;
  };
}<commit_msg>document new prop helpers<commit_after>import { PropTypes } from 'react';
import identity from 'lodash/identity';
import includes from 'lodash/includes';
import intersection from 'lodash/intersection';
import noop from 'lodash/noop';
import reduce from 'lodash/reduce';

export const CommonPropTypes = {
  className: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.array,
    PropTypes.object,
  ]),
  style: PropTypes.object,
  width: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
  ]),
  height: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
  ]),
};

export const CommonDefaultProps = {
  noop,
  identity,
};

export function exactlyOneOfProp(propTypes) {
  return (props, propName, componentName, location, propFullName) => {
    let error = null;
    const validProps = intersection(Object.keys(props), Object.keys(propTypes));
    if (validProps.length === 1) {
      if (validProps[0] === propName) {
        error = propTypes[propName](props, propName, componentName, location, propFullName);
      }
    } else {
      /* eslint-disable max-len */
      error = `Exactly one of prop [\`${Object.keys(propTypes).join('`,`')}\`] must be specified in \`${componentName}\`. Found: [${validProps.length ? `\`${validProps.join('`,`')}\`` : ''}].`;
      /* eslint-enable max-len */
    }
    return error && new Error(error);
  };
}

export function atLeastOneOfProp(propTypes) {
  return (props, propName, componentName, location, propFullName) => {
    let error = null;
    const validProps = intersection(Object.keys(props), Object.keys(propTypes));
    if (validProps.length > 0) {
      if (includes(validProps, propName)) {
        error = propTypes[propName](props, propName, componentName, location, propFullName);
      }
    } else {
      /* eslint-disable max-len */
      error = `At least one of prop [\`${Object.keys(propTypes).join('`,`')}\`] must be specified in \`${componentName}\`.`;
      /* eslint-enable max-len */
    }
    return error && new Error(error);
  };
}

export function propsChanged(prevProps, nextProps, propsToCompare, propsToOmit) {
  return !reduce(propsToCompare || Object.keys(nextProps), (acc, prop) => {
    return acc && (includes(propsToOmit, prop) || prevProps[prop] === nextProps[prop]);
  }, true);
}

/**
 * Helper function to build a state object from updated props.
 *
 * The `propUpdates` object will define the prop name and the comparison action. The comparison
 * action is a callback with signature (accumulator, propName, prevProps, nextProps), that
 * evaluates the prop changes, and determines what to add to the state. The return value will be
 * the new accumulated state. It is important to return the accumulator even if no relevant prop
 * changes are found.
 *
 * @param {{ propName: comparisonActionCallback }} propUpdates - prop name and comparison action function.
 * @param {Object} prevProps
 * @param {Object} nextProps
 * @param {Object} state
 * @returns {Object} accumulated state.
 */
export function stateFromPropUpdates(propUpdates, prevProps, nextProps, state) {
  return reduce(propUpdates, (acc, value, key) => {
    return value(acc, key, prevProps, nextProps);
  }, state);
}

/**
 * Helper function to plug into `stateFromPropUpdates` to provide simplified comparison and update
 * functionality.
 *
 * The `func` callback will be called if the immediate prop has changed. Its signature is
 * (nextProp, propName, nextProps), and it determines what to add to the state. The return value
 * will be the new accumulated state.
 *
 * @param {updateFuncCallback} func
 * @returns {comparisonActionCallback} comparison action callback.
 */
export function updateFunc(func) {
  return (acc, key, prevProps = {}, nextProps) => {
    return prevProps[key] !== nextProps[key] ?
      { ...acc, ...func(nextProps[key], key, nextProps) } :
      acc;
  };
}

/**
 * @callback comparisonActionCallback
 * @param {Object} state accumulator
 * @param {string} propName
 * @param {Object} prevProps
 * @param {Object} nextProps
 */

/**
 * @callback updateFuncCallback
 * @param {any} nextProp
 * @param {string} propName
 * @param {Object} nextProps
 * @returns {Object} accumulated state.
 */
{'fraction_non_alphanumeric': 0.1415775787344698, 'fraction_numerical': 0.0008668015024559376, 'mean_word_length': 3.882228490832158, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5480125', 'n_tokens_mistral': 2078, 'n_tokens_neox': 2060, 'n_words': 580}