jsx-sort-props.js 2.65 KB
/**
 * @fileoverview Enforce props alphabetical sorting
 * @author Ilya Volodin, Yannick Croissant
 */
'use strict';

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

function isCallbackPropName(propName) {
  return /^on[A-Z]/.test(propName);
}

module.exports = function(context) {

  var configuration = context.options[0] || {};
  var ignoreCase = configuration.ignoreCase || false;
  var callbacksLast = configuration.callbacksLast || false;
  var shorthandFirst = configuration.shorthandFirst || false;

  return {
    JSXOpeningElement: function(node) {
      node.attributes.reduce(function(memo, decl, idx, attrs) {
        if (decl.type === 'JSXSpreadAttribute') {
          return attrs[idx + 1];
        }

        var previousPropName = memo.name.name;
        var currentPropName = decl.name.name;
        var previousValue = memo.value;
        var currentValue = decl.value;
        var previousIsCallback = isCallbackPropName(previousPropName);
        var currentIsCallback = isCallbackPropName(currentPropName);

        if (ignoreCase) {
          previousPropName = previousPropName.toLowerCase();
          currentPropName = currentPropName.toLowerCase();
        }

        if (callbacksLast) {
          if (!previousIsCallback && currentIsCallback) {
            // Entering the callback prop section
            return decl;
          }
          if (previousIsCallback && !currentIsCallback) {
            // Encountered a non-callback prop after a callback prop
            context.report(memo, 'Callbacks must be listed after all other props');
            return memo;
          }
        }

        if (shorthandFirst) {
          if (currentValue && !previousValue) {
            return decl;
          }
          if (!currentValue && previousValue) {
            context.report(memo, 'Shorthand props must be listed before all other props');
            return memo;
          }
        }

        if (currentPropName < previousPropName) {
          context.report(decl, 'Props should be sorted alphabetically');
          return memo;
        }

        return decl;
      }, node.attributes[0]);
    }
  };
};

module.exports.schema = [{
  type: 'object',
  properties: {
    // Whether callbacks (prefixed with "on") should be listed at the very end,
    // after all other props.
    callbacksLast: {
      type: 'boolean'
    },
    // Whether shorthand properties (without a value) should be listed first
    shorthandFirst: {
      type: 'boolean'
    },
    ignoreCase: {
      type: 'boolean'
    }
  },
  additionalProperties: false
}];