first commit
This commit is contained in:
343
app_vue/node_modules/eslint-plugin-vue/lib/rules/order-in-components.js
generated
vendored
Normal file
343
app_vue/node_modules/eslint-plugin-vue/lib/rules/order-in-components.js
generated
vendored
Normal file
@ -0,0 +1,343 @@
|
||||
/**
|
||||
* @fileoverview Keep order of properties in components
|
||||
* @author Michał Sajnóg
|
||||
*/
|
||||
'use strict'
|
||||
|
||||
const utils = require('../utils')
|
||||
const traverseNodes = require('vue-eslint-parser').AST.traverseNodes
|
||||
|
||||
/**
|
||||
* @typedef {import('eslint-visitor-keys').VisitorKeys} VisitorKeys
|
||||
*/
|
||||
|
||||
const defaultOrder = [
|
||||
// Side Effects (triggers effects outside the component)
|
||||
'el',
|
||||
|
||||
// Global Awareness (requires knowledge beyond the component)
|
||||
'name',
|
||||
'key', // for Nuxt
|
||||
'parent',
|
||||
|
||||
// Component Type (changes the type of the component)
|
||||
'functional',
|
||||
|
||||
// Template Modifiers (changes the way templates are compiled)
|
||||
['delimiters', 'comments'],
|
||||
|
||||
// Template Dependencies (assets used in the template)
|
||||
['components', 'directives', 'filters'],
|
||||
|
||||
// Composition (merges properties into the options)
|
||||
'extends',
|
||||
'mixins',
|
||||
['provide', 'inject'], // for Vue.js 2.2.0+
|
||||
|
||||
// Page Options (component rendered as a router page)
|
||||
'ROUTER_GUARDS', // for Vue Router
|
||||
'layout', // for Nuxt
|
||||
'middleware', // for Nuxt
|
||||
'validate', // for Nuxt
|
||||
'scrollToTop', // for Nuxt
|
||||
'transition', // for Nuxt
|
||||
'loading', // for Nuxt
|
||||
|
||||
// Interface (the interface to the component)
|
||||
'inheritAttrs',
|
||||
'model',
|
||||
['props', 'propsData'],
|
||||
'emits', // for Vue.js 3.x
|
||||
|
||||
// Note:
|
||||
// The `setup` option is included in the "Composition" category,
|
||||
// but the behavior of the `setup` option requires the definition of "Interface",
|
||||
// so we prefer to put the `setup` option after the "Interface".
|
||||
'setup', // for Vue 3.x
|
||||
|
||||
// Local State (local reactive properties)
|
||||
'asyncData', // for Nuxt
|
||||
'data',
|
||||
'fetch', // for Nuxt
|
||||
'head', // for Nuxt
|
||||
'computed',
|
||||
|
||||
// Events (callbacks triggered by reactive events)
|
||||
'watch',
|
||||
'watchQuery', // for Nuxt
|
||||
'LIFECYCLE_HOOKS',
|
||||
|
||||
// Non-Reactive Properties (instance properties independent of the reactivity system)
|
||||
'methods',
|
||||
|
||||
// Rendering (the declarative description of the component output)
|
||||
['template', 'render'],
|
||||
'renderError'
|
||||
]
|
||||
|
||||
/** @type { { [key: string]: string[] } } */
|
||||
const groups = {
|
||||
LIFECYCLE_HOOKS: [
|
||||
'beforeCreate',
|
||||
'created',
|
||||
'beforeMount',
|
||||
'mounted',
|
||||
'beforeUpdate',
|
||||
'updated',
|
||||
'activated',
|
||||
'deactivated',
|
||||
'beforeUnmount', // for Vue.js 3.x
|
||||
'unmounted', // for Vue.js 3.x
|
||||
'beforeDestroy',
|
||||
'destroyed',
|
||||
'renderTracked', // for Vue.js 3.x
|
||||
'renderTriggered', // for Vue.js 3.x
|
||||
'errorCaptured' // for Vue.js 2.5.0+
|
||||
],
|
||||
ROUTER_GUARDS: ['beforeRouteEnter', 'beforeRouteUpdate', 'beforeRouteLeave']
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(string | string[])[]} order
|
||||
*/
|
||||
function getOrderMap(order) {
|
||||
/** @type {Map<string, number>} */
|
||||
const orderMap = new Map()
|
||||
|
||||
order.forEach((property, i) => {
|
||||
if (Array.isArray(property)) {
|
||||
property.forEach((p) => orderMap.set(p, i))
|
||||
} else {
|
||||
orderMap.set(property, i)
|
||||
}
|
||||
})
|
||||
|
||||
return orderMap
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Token} node
|
||||
*/
|
||||
function isComma(node) {
|
||||
return node.type === 'Punctuator' && node.value === ','
|
||||
}
|
||||
|
||||
const ARITHMETIC_OPERATORS = ['+', '-', '*', '/', '%', '**' /* es2016 */]
|
||||
const BITWISE_OPERATORS = ['&', '|', '^', '~', '<<', '>>', '>>>']
|
||||
const COMPARISON_OPERATORS = ['==', '!=', '===', '!==', '>', '>=', '<', '<=']
|
||||
const RELATIONAL_OPERATORS = ['in', 'instanceof']
|
||||
const ALL_BINARY_OPERATORS = [
|
||||
...ARITHMETIC_OPERATORS,
|
||||
...BITWISE_OPERATORS,
|
||||
...COMPARISON_OPERATORS,
|
||||
...RELATIONAL_OPERATORS
|
||||
]
|
||||
const LOGICAL_OPERATORS = ['&&', '||', '??' /* es2020 */]
|
||||
|
||||
/**
|
||||
* Result `true` if the node is sure that there are no side effects
|
||||
*
|
||||
* Currently known side effects types
|
||||
*
|
||||
* node.type === 'CallExpression'
|
||||
* node.type === 'NewExpression'
|
||||
* node.type === 'UpdateExpression'
|
||||
* node.type === 'AssignmentExpression'
|
||||
* node.type === 'TaggedTemplateExpression'
|
||||
* node.type === 'UnaryExpression' && node.operator === 'delete'
|
||||
*
|
||||
* @param {ASTNode} node target node
|
||||
* @param {VisitorKeys} visitorKeys sourceCode.visitorKey
|
||||
* @returns {boolean} no side effects
|
||||
*/
|
||||
function isNotSideEffectsNode(node, visitorKeys) {
|
||||
let result = true
|
||||
/** @type {ASTNode | null} */
|
||||
let skipNode = null
|
||||
traverseNodes(node, {
|
||||
visitorKeys,
|
||||
/** @param {ASTNode} node */
|
||||
enterNode(node) {
|
||||
if (!result || skipNode) {
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
// no side effects node
|
||||
node.type === 'FunctionExpression' ||
|
||||
node.type === 'Identifier' ||
|
||||
node.type === 'Literal' ||
|
||||
// es2015
|
||||
node.type === 'ArrowFunctionExpression' ||
|
||||
node.type === 'TemplateElement'
|
||||
) {
|
||||
skipNode = node
|
||||
} else if (
|
||||
node.type !== 'Property' &&
|
||||
node.type !== 'ObjectExpression' &&
|
||||
node.type !== 'ArrayExpression' &&
|
||||
(node.type !== 'UnaryExpression' ||
|
||||
!['!', '~', '+', '-', 'typeof'].includes(node.operator)) &&
|
||||
(node.type !== 'BinaryExpression' ||
|
||||
!ALL_BINARY_OPERATORS.includes(node.operator)) &&
|
||||
(node.type !== 'LogicalExpression' ||
|
||||
!LOGICAL_OPERATORS.includes(node.operator)) &&
|
||||
node.type !== 'MemberExpression' &&
|
||||
node.type !== 'ConditionalExpression' &&
|
||||
// es2015
|
||||
node.type !== 'SpreadElement' &&
|
||||
node.type !== 'TemplateLiteral' &&
|
||||
// es2020
|
||||
node.type !== 'ChainExpression'
|
||||
) {
|
||||
// Can not be sure that a node has no side effects
|
||||
result = false
|
||||
}
|
||||
},
|
||||
/** @param {ASTNode} node */
|
||||
leaveNode(node) {
|
||||
if (skipNode === node) {
|
||||
skipNode = null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description: 'enforce order of properties in components',
|
||||
categories: ['vue3-recommended', 'recommended'],
|
||||
url: 'https://eslint.vuejs.org/rules/order-in-components.html'
|
||||
},
|
||||
fixable: 'code', // null or "code" or "whitespace"
|
||||
schema: [
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
order: {
|
||||
type: 'array'
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
]
|
||||
},
|
||||
/** @param {RuleContext} context */
|
||||
create(context) {
|
||||
const options = context.options[0] || {}
|
||||
/** @type {(string|string[])[]} */
|
||||
const order = options.order || defaultOrder
|
||||
/** @type {(string|string[])[]} */
|
||||
const extendedOrder = order.map(
|
||||
(property) =>
|
||||
(typeof property === 'string' && groups[property]) || property
|
||||
)
|
||||
const orderMap = getOrderMap(extendedOrder)
|
||||
const sourceCode = context.getSourceCode()
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
function getOrderPosition(name) {
|
||||
const num = orderMap.get(name)
|
||||
return num == null ? -1 : num
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(Property | SpreadElement)[]} propertiesNodes
|
||||
*/
|
||||
function checkOrder(propertiesNodes) {
|
||||
const properties = propertiesNodes
|
||||
.filter(utils.isProperty)
|
||||
.map((property) => {
|
||||
return {
|
||||
node: property,
|
||||
name:
|
||||
utils.getStaticPropertyName(property) ||
|
||||
(property.key.type === 'Identifier' && property.key.name) ||
|
||||
''
|
||||
}
|
||||
})
|
||||
|
||||
properties.forEach((property, i) => {
|
||||
const orderPos = getOrderPosition(property.name)
|
||||
if (orderPos < 0) {
|
||||
return
|
||||
}
|
||||
const propertiesAbove = properties.slice(0, i)
|
||||
const unorderedProperties = propertiesAbove
|
||||
.filter(
|
||||
(p) => getOrderPosition(p.name) > getOrderPosition(property.name)
|
||||
)
|
||||
.sort((p1, p2) =>
|
||||
getOrderPosition(p1.name) > getOrderPosition(p2.name) ? 1 : -1
|
||||
)
|
||||
|
||||
const firstUnorderedProperty = unorderedProperties[0]
|
||||
|
||||
if (firstUnorderedProperty) {
|
||||
const line = firstUnorderedProperty.node.loc.start.line
|
||||
context.report({
|
||||
node: property.node,
|
||||
message: `The "{{name}}" property should be above the "{{firstUnorderedPropertyName}}" property on line {{line}}.`,
|
||||
data: {
|
||||
name: property.name,
|
||||
firstUnorderedPropertyName: firstUnorderedProperty.name,
|
||||
line
|
||||
},
|
||||
*fix(fixer) {
|
||||
const propertyNode = property.node
|
||||
const firstUnorderedPropertyNode = firstUnorderedProperty.node
|
||||
const hasSideEffectsPossibility = propertiesNodes
|
||||
.slice(
|
||||
propertiesNodes.indexOf(firstUnorderedPropertyNode),
|
||||
propertiesNodes.indexOf(propertyNode) + 1
|
||||
)
|
||||
.some(
|
||||
(property) =>
|
||||
!isNotSideEffectsNode(property, sourceCode.visitorKeys)
|
||||
)
|
||||
if (hasSideEffectsPossibility) {
|
||||
return
|
||||
}
|
||||
const afterComma = sourceCode.getTokenAfter(propertyNode)
|
||||
const hasAfterComma = isComma(afterComma)
|
||||
|
||||
const beforeComma = sourceCode.getTokenBefore(propertyNode)
|
||||
const codeStart = beforeComma.range[1] // to include comments
|
||||
const codeEnd = hasAfterComma
|
||||
? afterComma.range[1]
|
||||
: propertyNode.range[1]
|
||||
|
||||
const removeStart = hasAfterComma
|
||||
? codeStart
|
||||
: beforeComma.range[0]
|
||||
yield fixer.removeRange([removeStart, codeEnd])
|
||||
|
||||
const propertyCode =
|
||||
sourceCode.text.slice(codeStart, codeEnd) +
|
||||
(hasAfterComma ? '' : ',')
|
||||
const insertTarget = sourceCode.getTokenBefore(
|
||||
firstUnorderedPropertyNode
|
||||
)
|
||||
|
||||
yield fixer.insertTextAfter(insertTarget, propertyCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return utils.executeOnVue(context, (obj) => {
|
||||
checkOrder(obj.properties)
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user