blob: 3efd5cfd00401711a3d939ab6b2a67b2a83b8f60 [file] [log] [blame]
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
var _utilities = require('../utilities');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
var defaults = {
caseSensitive: true,
natural: false
};
var schema = [{
enum: ['asc', 'desc'],
type: 'string'
}, {
additionalProperties: false,
properties: {
caseSensitive: {
type: 'boolean'
},
natural: {
type: 'boolean'
}
},
type: 'object'
}];
/**
* Functions to compare the order of two strings
*
* Based on a similar function from eslint's sort-keys rule.
* https://github.com/eslint/eslint/blob/master/lib/rules/sort-keys.js
*
* @private
*/
var isValidOrders = {
asc(str1, str2) {
return str1 <= str2;
},
ascI(str1, str2) {
return str1.toLowerCase() <= str2.toLowerCase();
},
ascIN(str1, str2) {
return isValidOrders.naturalCompare(str1.toLowerCase(), str2.toLowerCase()) <= 0;
},
ascN(str1, str2) {
return isValidOrders.naturalCompare(str1, str2) <= 0;
},
desc(str1, str2) {
return isValidOrders.asc(str2, str1);
},
descI(str1, str2) {
return isValidOrders.ascI(str2, str1);
},
descIN(str1, str2) {
return isValidOrders.ascIN(str2, str1);
},
descN(str1, str2) {
return isValidOrders.ascN(str2, str1);
},
naturalCompare(str1, str2) {
return str1.localeCompare(str2, 'en-US', { numeric: true });
}
};
var generateOrderedList = function generateOrderedList(context, sort, properties) {
var source = context.getSourceCode();
var items = properties.map(function (property) {
var name = (0, _utilities.getParameterName)(property, context);
var commentsBefore = source.getCommentsBefore(property);
var startIndex = commentsBefore.length > 0 ? commentsBefore[0].start : property.start;
if (property.type === 'ObjectTypeSpreadProperty' || !property.value) {
// NOTE: It could but currently does not fix recursive generic type arguments in GenericTypeAnnotation within ObjectTypeSpreadProperty.
// Maintain everything between the start of property including leading comments and the nextPunctuator `,` or `}`:
var nextPunctuator = source.getTokenAfter(property, {
filter: function filter(token) {
return token.type === 'Punctuator';
}
});
var beforePunctuator = source.getTokenBefore(nextPunctuator, {
includeComments: true
});
var text = source.getText().substring(startIndex, beforePunctuator.end);
return [property, text];
}
var colonToken = source.getTokenBefore(property.value, {
filter: function filter(token) {
return token.value === ':';
}
});
// Preserve all code until the colon verbatim:
var key = source.getText().substring(startIndex, colonToken.start);
var value = void 0;
if (property.value.type === 'ObjectTypeAnnotation') {
// eslint-disable-next-line no-use-before-define
value = ' ' + generateFix(property.value, context, sort);
} else {
// NOTE: It could but currently does not fix recursive generic type arguments in GenericTypeAnnotation.
// Maintain everything between the `:` and the next Punctuator `,` or `}`:
var _nextPunctuator = source.getTokenAfter(property, {
filter: function filter(token) {
return token.type === 'Punctuator';
}
});
var _beforePunctuator = source.getTokenBefore(_nextPunctuator, {
includeComments: true
});
var _text = source.getText().substring(colonToken.end, _beforePunctuator.end);
value = _text;
}
return [property, name, key, value];
});
var itemGroups = [[]];
var itemGroupIndex = 0;
items.forEach(function (item) {
if (item[0].type === 'ObjectTypeSpreadProperty') {
++itemGroupIndex;
itemGroups[itemGroupIndex] = [item];
++itemGroupIndex;
itemGroups[itemGroupIndex] = [];
} else {
itemGroups[itemGroupIndex].push(item);
}
});
var orderedList = [];
itemGroups.forEach(function (itemGroup) {
if (itemGroup[0] && itemGroup[0].type !== 'ObjectTypeSpreadProperty') {
itemGroup.sort(function (first, second) {
return sort(first[1], second[1]) ? -1 : 1;
});
}
orderedList.push.apply(orderedList, _toConsumableArray(itemGroup.map(function (item) {
if (item.length === 2) {
return item[1];
}
return item[2] + ':' + item[3];
})));
});
return orderedList;
};
var generateFix = function generateFix(node, context, sort) {
// this could be done much more cleanly in ESLint >=4
// as we can apply multiple fixes. That also means we can
// maintain code style in a much nicer way
var nodeText = void 0;
var newTypes = generateOrderedList(context, sort, node.properties);
var source = context.getSourceCode(node);
var originalSubstring = source.getText(node);
nodeText = originalSubstring;
node.properties.forEach(function (property, index) {
var nextPunctuator = source.getTokenAfter(property, {
filter: function filter(token) {
return token.type === 'Punctuator';
}
});
var beforePunctuator = source.getTokenBefore(nextPunctuator, {
includeComments: true
});
var commentsBefore = source.getCommentsBefore(property);
var startIndex = commentsBefore.length > 0 ? commentsBefore[0].start : property.start;
var subString = source.getText().substring(startIndex, beforePunctuator.end);
nodeText = nodeText.replace(subString, '$' + index);
});
newTypes.forEach(function (item, index) {
nodeText = nodeText.replace('$' + index, item);
});
return nodeText;
};
var create = function create(context) {
var order = _lodash2.default.get(context, ['options', 0], 'asc');
var _$get = _lodash2.default.get(context, ['options', 1], defaults),
natural = _$get.natural,
caseSensitive = _$get.caseSensitive;
var insensitive = caseSensitive === false;
var prev = void 0;
var checkKeyOrder = function checkKeyOrder(node) {
prev = null;
_lodash2.default.forEach(node.properties, function (identifierNode) {
var current = (0, _utilities.getParameterName)(identifierNode, context);
var last = prev;
// keep track of the last token
prev = current || last;
if (!last || !current) {
return;
}
var isValidOrder = isValidOrders[order + (insensitive ? 'I' : '') + (natural ? 'N' : '')];
if (isValidOrder(last, current) === false) {
context.report({
data: {
current,
insensitive: insensitive ? 'insensitive ' : '',
last,
natural: natural ? 'natural ' : '',
order
},
fix(fixer) {
var nodeText = generateFix(node, context, isValidOrder);
return fixer.replaceText(node, nodeText);
},
loc: identifierNode.loc,
message: 'Expected type annotations to be in {{natural}}{{insensitive}}{{order}}ending order. "{{current}}" should be before "{{last}}".',
node: identifierNode
});
}
});
};
return {
ObjectTypeAnnotation: checkKeyOrder
};
};
exports.default = {
create,
schema
};
module.exports = exports.default;