123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- /**
- * @fileoverview enforce sort-keys in a manner that is compatible with order-in-components
- * @author Loren Klingman
- * Original ESLint sort-keys by Toru Nagashima
- */
- 'use strict'
- // ------------------------------------------------------------------------------
- // Requirements
- // ------------------------------------------------------------------------------
- const naturalCompare = require('natural-compare')
- const utils = require('../utils')
- // ------------------------------------------------------------------------------
- // Helpers
- // ------------------------------------------------------------------------------
- /**
- * Gets the property name of the given `Property` node.
- *
- * - If the property's key is an `Identifier` node, this returns the key's name
- * whether it's a computed property or not.
- * - If the property has a static name, this returns the static name.
- * - Otherwise, this returns null.
- * @param {ASTNode} node The `Property` node to get.
- * @returns {string|null} The property name or null.
- * @private
- */
- function getPropertyName (node) {
- const staticName = utils.getStaticPropertyName(node)
- if (staticName !== null) {
- return staticName
- }
- return node.key.name || null
- }
- /**
- * Functions which check that the given 2 names are in specific order.
- *
- * Postfix `I` is meant insensitive.
- * Postfix `N` is meant natual.
- * @private
- */
- const isValidOrders = {
- asc (a, b) {
- return a <= b
- },
- ascI (a, b) {
- return a.toLowerCase() <= b.toLowerCase()
- },
- ascN (a, b) {
- return naturalCompare(a, b) <= 0
- },
- ascIN (a, b) {
- return naturalCompare(a.toLowerCase(), b.toLowerCase()) <= 0
- },
- desc (a, b) {
- return isValidOrders.asc(b, a)
- },
- descI (a, b) {
- return isValidOrders.ascI(b, a)
- },
- descN (a, b) {
- return isValidOrders.ascN(b, a)
- },
- descIN (a, b) {
- return isValidOrders.ascIN(b, a)
- }
- }
- // ------------------------------------------------------------------------------
- // Rule Definition
- // ------------------------------------------------------------------------------
- module.exports = {
- meta: {
- type: 'suggestion',
- docs: {
- description: 'enforce sort-keys in a manner that is compatible with order-in-components',
- category: null,
- recommended: false,
- url: 'https://eslint.vuejs.org/rules/sort-keys.html'
- },
- fixable: null,
- schema: [
- {
- enum: ['asc', 'desc']
- },
- {
- type: 'object',
- properties: {
- caseSensitive: {
- type: 'boolean',
- default: true
- },
- ignoreChildrenOf: {
- type: 'array'
- },
- ignoreGrandchildrenOf: {
- type: 'array'
- },
- minKeys: {
- type: 'integer',
- minimum: 2,
- default: 2
- },
- natural: {
- type: 'boolean',
- default: false
- },
- runOutsideVue: {
- type: 'boolean',
- default: true
- }
- },
- additionalProperties: false
- }
- ]
- },
- create (context) {
- // Parse options.
- const options = context.options[1]
- const order = context.options[0] || 'asc'
- const ignoreGrandchildrenOf = (options && options.ignoreGrandchildrenOf) || ['computed', 'directives', 'inject', 'props', 'watch']
- const ignoreChildrenOf = (options && options.ignoreChildrenOf) || ['model']
- const insensitive = options && options.caseSensitive === false
- const minKeys = options && options.minKeys
- const natual = options && options.natural
- const isValidOrder = isValidOrders[
- order + (insensitive ? 'I' : '') + (natual ? 'N' : '')
- ]
- // The stack to save the previous property's name for each object literals.
- let stack = null
- let errors = []
- const names = {}
- const reportErrors = (isVue) => {
- if (isVue) {
- errors = errors.filter((error) => {
- let parentIsRoot = !error.hasUpper
- let grandParentIsRoot = !error.grandparent
- let greatGrandparentIsRoot = !error.greatGrandparent
- const stackPrevChar = stack && stack.prevChar
- if (stackPrevChar) {
- parentIsRoot = stackPrevChar === error.parent
- grandParentIsRoot = stackPrevChar === error.grandparent
- greatGrandparentIsRoot = stackPrevChar === error.greatGrandparent
- }
- if (parentIsRoot) {
- return false
- } else if (grandParentIsRoot) {
- return !error.parentIsProperty || !ignoreChildrenOf.includes(names[error.parent])
- } else if (greatGrandparentIsRoot) {
- return !error.parentIsProperty || !ignoreGrandchildrenOf.includes(names[error.grandparent])
- }
- return true
- })
- }
- errors.forEach((error) => error.errors.forEach((e) => context.report(e)))
- errors = []
- }
- const sortTests = {
- ObjectExpression (node) {
- if (!stack) {
- reportErrors(false)
- }
- stack = {
- upper: stack,
- prevChar: null,
- prevName: null,
- numKeys: node.properties.length,
- parentIsProperty: node.parent.type === 'Property',
- errors: []
- }
- },
- 'ObjectExpression:exit' (node) {
- errors.push({
- errors: stack.errors,
- hasUpper: !!stack.upper,
- parentIsProperty: node.parent.type === 'Property',
- parent: stack.upper && stack.upper.prevChar,
- grandparent: stack.upper && stack.upper.upper && stack.upper.upper.prevChar,
- greatGrandparent: stack.upper && stack.upper.upper && stack.upper.upper.upper && stack.upper.upper.upper.prevChar
- })
- stack = stack.upper
- },
- SpreadElement (node) {
- if (node.parent.type === 'ObjectExpression') {
- stack.prevName = null
- stack.prevChar = null
- }
- },
- 'Program:exit' () {
- reportErrors(false)
- },
- Property (node) {
- if (node.parent.type === 'ObjectPattern') {
- return
- }
- const prevName = stack.prevName
- const numKeys = stack.numKeys
- const thisName = getPropertyName(node)
- if (thisName !== null) {
- stack.prevName = thisName
- stack.prevChar = node.range[0]
- if (Object.prototype.hasOwnProperty.call(names, node.range[0])) {
- throw new Error('Name clash')
- }
- names[node.range[0]] = thisName
- }
- if (prevName === null || thisName === null || numKeys < minKeys) {
- return
- }
- if (!isValidOrder(prevName, thisName)) {
- stack.errors.push({
- node,
- loc: node.key.loc,
- message: "Expected object keys to be in {{natual}}{{insensitive}}{{order}}ending order. '{{thisName}}' should be before '{{prevName}}'.",
- data: {
- thisName,
- prevName,
- order,
- insensitive: insensitive ? 'insensitive ' : '',
- natual: natual ? 'natural ' : ''
- }
- })
- }
- }
- }
- const execOnVue = utils.executeOnVue(context, (obj) => {
- reportErrors(true)
- })
- const result = { ...sortTests }
- Object.keys(execOnVue).forEach((key) => {
- // Ensure we call both the callback from sortTests and execOnVue if they both use the same key
- if (Object.prototype.hasOwnProperty.call(sortTests, key)) {
- result[key] = (node) => {
- sortTests[key](node)
- execOnVue[key](node)
- }
- } else {
- result[key] = execOnVue[key]
- }
- })
- return result
- }
- }
|