| '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; |