sort-keys.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. /**
  2. * @fileoverview enforce sort-keys in a manner that is compatible with order-in-components
  3. * @author Loren Klingman
  4. * Original ESLint sort-keys by Toru Nagashima
  5. */
  6. 'use strict'
  7. // ------------------------------------------------------------------------------
  8. // Requirements
  9. // ------------------------------------------------------------------------------
  10. const naturalCompare = require('natural-compare')
  11. const utils = require('../utils')
  12. // ------------------------------------------------------------------------------
  13. // Helpers
  14. // ------------------------------------------------------------------------------
  15. /**
  16. * Gets the property name of the given `Property` node.
  17. *
  18. * - If the property's key is an `Identifier` node, this returns the key's name
  19. * whether it's a computed property or not.
  20. * - If the property has a static name, this returns the static name.
  21. * - Otherwise, this returns null.
  22. * @param {ASTNode} node The `Property` node to get.
  23. * @returns {string|null} The property name or null.
  24. * @private
  25. */
  26. function getPropertyName (node) {
  27. const staticName = utils.getStaticPropertyName(node)
  28. if (staticName !== null) {
  29. return staticName
  30. }
  31. return node.key.name || null
  32. }
  33. /**
  34. * Functions which check that the given 2 names are in specific order.
  35. *
  36. * Postfix `I` is meant insensitive.
  37. * Postfix `N` is meant natual.
  38. * @private
  39. */
  40. const isValidOrders = {
  41. asc (a, b) {
  42. return a <= b
  43. },
  44. ascI (a, b) {
  45. return a.toLowerCase() <= b.toLowerCase()
  46. },
  47. ascN (a, b) {
  48. return naturalCompare(a, b) <= 0
  49. },
  50. ascIN (a, b) {
  51. return naturalCompare(a.toLowerCase(), b.toLowerCase()) <= 0
  52. },
  53. desc (a, b) {
  54. return isValidOrders.asc(b, a)
  55. },
  56. descI (a, b) {
  57. return isValidOrders.ascI(b, a)
  58. },
  59. descN (a, b) {
  60. return isValidOrders.ascN(b, a)
  61. },
  62. descIN (a, b) {
  63. return isValidOrders.ascIN(b, a)
  64. }
  65. }
  66. // ------------------------------------------------------------------------------
  67. // Rule Definition
  68. // ------------------------------------------------------------------------------
  69. module.exports = {
  70. meta: {
  71. type: 'suggestion',
  72. docs: {
  73. description: 'enforce sort-keys in a manner that is compatible with order-in-components',
  74. category: null,
  75. recommended: false,
  76. url: 'https://eslint.vuejs.org/rules/sort-keys.html'
  77. },
  78. fixable: null,
  79. schema: [
  80. {
  81. enum: ['asc', 'desc']
  82. },
  83. {
  84. type: 'object',
  85. properties: {
  86. caseSensitive: {
  87. type: 'boolean',
  88. default: true
  89. },
  90. ignoreChildrenOf: {
  91. type: 'array'
  92. },
  93. ignoreGrandchildrenOf: {
  94. type: 'array'
  95. },
  96. minKeys: {
  97. type: 'integer',
  98. minimum: 2,
  99. default: 2
  100. },
  101. natural: {
  102. type: 'boolean',
  103. default: false
  104. },
  105. runOutsideVue: {
  106. type: 'boolean',
  107. default: true
  108. }
  109. },
  110. additionalProperties: false
  111. }
  112. ]
  113. },
  114. create (context) {
  115. // Parse options.
  116. const options = context.options[1]
  117. const order = context.options[0] || 'asc'
  118. const ignoreGrandchildrenOf = (options && options.ignoreGrandchildrenOf) || ['computed', 'directives', 'inject', 'props', 'watch']
  119. const ignoreChildrenOf = (options && options.ignoreChildrenOf) || ['model']
  120. const insensitive = options && options.caseSensitive === false
  121. const minKeys = options && options.minKeys
  122. const natual = options && options.natural
  123. const isValidOrder = isValidOrders[
  124. order + (insensitive ? 'I' : '') + (natual ? 'N' : '')
  125. ]
  126. // The stack to save the previous property's name for each object literals.
  127. let stack = null
  128. let errors = []
  129. const names = {}
  130. const reportErrors = (isVue) => {
  131. if (isVue) {
  132. errors = errors.filter((error) => {
  133. let parentIsRoot = !error.hasUpper
  134. let grandParentIsRoot = !error.grandparent
  135. let greatGrandparentIsRoot = !error.greatGrandparent
  136. const stackPrevChar = stack && stack.prevChar
  137. if (stackPrevChar) {
  138. parentIsRoot = stackPrevChar === error.parent
  139. grandParentIsRoot = stackPrevChar === error.grandparent
  140. greatGrandparentIsRoot = stackPrevChar === error.greatGrandparent
  141. }
  142. if (parentIsRoot) {
  143. return false
  144. } else if (grandParentIsRoot) {
  145. return !error.parentIsProperty || !ignoreChildrenOf.includes(names[error.parent])
  146. } else if (greatGrandparentIsRoot) {
  147. return !error.parentIsProperty || !ignoreGrandchildrenOf.includes(names[error.grandparent])
  148. }
  149. return true
  150. })
  151. }
  152. errors.forEach((error) => error.errors.forEach((e) => context.report(e)))
  153. errors = []
  154. }
  155. const sortTests = {
  156. ObjectExpression (node) {
  157. if (!stack) {
  158. reportErrors(false)
  159. }
  160. stack = {
  161. upper: stack,
  162. prevChar: null,
  163. prevName: null,
  164. numKeys: node.properties.length,
  165. parentIsProperty: node.parent.type === 'Property',
  166. errors: []
  167. }
  168. },
  169. 'ObjectExpression:exit' (node) {
  170. errors.push({
  171. errors: stack.errors,
  172. hasUpper: !!stack.upper,
  173. parentIsProperty: node.parent.type === 'Property',
  174. parent: stack.upper && stack.upper.prevChar,
  175. grandparent: stack.upper && stack.upper.upper && stack.upper.upper.prevChar,
  176. greatGrandparent: stack.upper && stack.upper.upper && stack.upper.upper.upper && stack.upper.upper.upper.prevChar
  177. })
  178. stack = stack.upper
  179. },
  180. SpreadElement (node) {
  181. if (node.parent.type === 'ObjectExpression') {
  182. stack.prevName = null
  183. stack.prevChar = null
  184. }
  185. },
  186. 'Program:exit' () {
  187. reportErrors(false)
  188. },
  189. Property (node) {
  190. if (node.parent.type === 'ObjectPattern') {
  191. return
  192. }
  193. const prevName = stack.prevName
  194. const numKeys = stack.numKeys
  195. const thisName = getPropertyName(node)
  196. if (thisName !== null) {
  197. stack.prevName = thisName
  198. stack.prevChar = node.range[0]
  199. if (Object.prototype.hasOwnProperty.call(names, node.range[0])) {
  200. throw new Error('Name clash')
  201. }
  202. names[node.range[0]] = thisName
  203. }
  204. if (prevName === null || thisName === null || numKeys < minKeys) {
  205. return
  206. }
  207. if (!isValidOrder(prevName, thisName)) {
  208. stack.errors.push({
  209. node,
  210. loc: node.key.loc,
  211. message: "Expected object keys to be in {{natual}}{{insensitive}}{{order}}ending order. '{{thisName}}' should be before '{{prevName}}'.",
  212. data: {
  213. thisName,
  214. prevName,
  215. order,
  216. insensitive: insensitive ? 'insensitive ' : '',
  217. natual: natual ? 'natural ' : ''
  218. }
  219. })
  220. }
  221. }
  222. }
  223. const execOnVue = utils.executeOnVue(context, (obj) => {
  224. reportErrors(true)
  225. })
  226. const result = { ...sortTests }
  227. Object.keys(execOnVue).forEach((key) => {
  228. // Ensure we call both the callback from sortTests and execOnVue if they both use the same key
  229. if (Object.prototype.hasOwnProperty.call(sortTests, key)) {
  230. result[key] = (node) => {
  231. sortTests[key](node)
  232. execOnVue[key](node)
  233. }
  234. } else {
  235. result[key] = execOnVue[key]
  236. }
  237. })
  238. return result
  239. }
  240. }