Skip to content

Commit 9857496

Browse files
ota-meshimichalsnik
authored andcommitted
Update: make vue/order-in-components fixable (#381)
* [Update] Make `vue/order-in-components` fixable This Commit makes `vue/order-in-components` fixable. In case of `The "A" property should be above the "B" property` error, autofix will move A before B * [fix] fail test at [email protected] * [fix] If there is a possibility of a side effect, don't autofix * [fix] failed test at node v4 * [update] use Traverser * [fix] failed test [email protected] * [fix] I used `output: null` to specify "not fix"
1 parent 742bfd6 commit 9857496

File tree

2 files changed

+530
-1
lines changed

2 files changed

+530
-1
lines changed

Diff for: lib/rules/order-in-components.js

+101-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
'use strict'
66

77
const utils = require('../utils')
8+
const Traverser = require('eslint/lib/util/traverser')
89

910
const defaultOrder = [
1011
'el',
@@ -56,6 +57,75 @@ function getOrderMap (order) {
5657
return orderMap
5758
}
5859

60+
function isComma (node) {
61+
return node.type === 'Punctuator' && node.value === ','
62+
}
63+
64+
const ARITHMETIC_OPERATORS = ['+', '-', '*', '/', '%', '**']
65+
const BITWISE_OPERATORS = ['&', '|', '^', '~', '<<', '>>', '>>>']
66+
const COMPARISON_OPERATORS = ['==', '!=', '===', '!==', '>', '>=', '<', '<=']
67+
const RELATIONAL_OPERATORS = ['in', 'instanceof']
68+
const ALL_BINARY_OPERATORS = [].concat(
69+
ARITHMETIC_OPERATORS,
70+
BITWISE_OPERATORS,
71+
COMPARISON_OPERATORS,
72+
RELATIONAL_OPERATORS
73+
)
74+
const LOGICAL_OPERATORS = ['&&', '||']
75+
76+
/*
77+
* Result `true` if the node is sure that there are no side effects
78+
*
79+
* Currently known side effects types
80+
*
81+
* node.type === 'CallExpression'
82+
* node.type === 'NewExpression'
83+
* node.type === 'UpdateExpression'
84+
* node.type === 'AssignmentExpression'
85+
* node.type === 'TaggedTemplateExpression'
86+
* node.type === 'UnaryExpression' && node.operator === 'delete'
87+
*
88+
* @param {ASTNode} node target node
89+
* @param {Object} visitorKeys sourceCode.visitorKey
90+
* @returns {Boolean} no side effects
91+
*/
92+
function isNotSideEffectsNode (node, visitorKeys) {
93+
let result = true
94+
new Traverser().traverse(node, {
95+
visitorKeys,
96+
enter (node, parent) {
97+
if (
98+
node.type === 'FunctionExpression' ||
99+
node.type === 'Identifier' ||
100+
node.type === 'Literal' ||
101+
// es2015
102+
node.type === 'ArrowFunctionExpression' ||
103+
node.type === 'TemplateElement'
104+
) {
105+
// no side effects node
106+
this.skip()
107+
} else if (
108+
node.type !== 'Property' &&
109+
node.type !== 'ObjectExpression' &&
110+
node.type !== 'ArrayExpression' &&
111+
(node.type !== 'UnaryExpression' || ['!', '~', '+', '-', 'typeof'].indexOf(node.operator) < 0) &&
112+
(node.type !== 'BinaryExpression' || ALL_BINARY_OPERATORS.indexOf(node.operator) < 0) &&
113+
(node.type !== 'LogicalExpression' || LOGICAL_OPERATORS.indexOf(node.operator) < 0) &&
114+
node.type !== 'MemberExpression' &&
115+
node.type !== 'ConditionalExpression' &&
116+
// es2015
117+
node.type !== 'SpreadElement' &&
118+
node.type !== 'TemplateLiteral'
119+
) {
120+
// Can not be sure that a node has no side effects
121+
result = false
122+
this.break()
123+
}
124+
}
125+
})
126+
return result
127+
}
128+
59129
// ------------------------------------------------------------------------------
60130
// Rule Definition
61131
// ------------------------------------------------------------------------------
@@ -67,7 +137,7 @@ module.exports = {
67137
category: 'recommended',
68138
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.2.2/docs/rules/order-in-components.md'
69139
},
70-
fixable: null,
140+
fixable: 'code', // null or "code" or "whitespace"
71141
schema: [
72142
{
73143
type: 'object',
@@ -86,6 +156,7 @@ module.exports = {
86156
const order = options.order || defaultOrder
87157
const extendedOrder = order.map(property => groups[property] || property)
88158
const orderMap = getOrderMap(extendedOrder)
159+
const sourceCode = context.getSourceCode()
89160

90161
function checkOrder (propertiesNodes, orderMap) {
91162
const properties = propertiesNodes
@@ -109,6 +180,35 @@ module.exports = {
109180
name: property.name,
110181
firstUnorderedPropertyName: firstUnorderedProperty.name,
111182
line
183+
},
184+
fix (fixer) {
185+
const propertyNode = property.parent
186+
const firstUnorderedPropertyNode = firstUnorderedProperty.parent
187+
const hasSideEffectsPossibility = propertiesNodes
188+
.slice(
189+
propertiesNodes.indexOf(firstUnorderedPropertyNode),
190+
propertiesNodes.indexOf(propertyNode) + 1
191+
)
192+
.some((property) => !isNotSideEffectsNode(property, sourceCode.visitorKeys))
193+
if (hasSideEffectsPossibility) {
194+
return undefined
195+
}
196+
const comma = sourceCode.getTokenAfter(propertyNode)
197+
const hasAfterComma = isComma(comma)
198+
199+
const codeStart = sourceCode.getTokenBefore(propertyNode).range[1] // to include comments
200+
const codeEnd = hasAfterComma ? comma.range[1] : propertyNode.range[1]
201+
202+
const propertyCode = sourceCode.text.slice(codeStart, codeEnd) + (hasAfterComma ? '' : ',')
203+
const insertTarget = sourceCode.getTokenBefore(firstUnorderedPropertyNode)
204+
// If we can upgrade requirements to `eslint@>4.1.0`, this code can be replaced by:
205+
// return [
206+
// fixer.removeRange([codeStart, codeEnd]),
207+
// fixer.insertTextAfter(insertTarget, propertyCode)
208+
// ]
209+
const insertStart = insertTarget.range[1]
210+
const newCode = propertyCode + sourceCode.text.slice(insertStart, codeStart)
211+
return fixer.replaceTextRange([insertStart, codeEnd], newCode)
112212
}
113213
})
114214
}

0 commit comments

Comments
 (0)