55'use strict'
66
77const utils = require ( '../utils' )
8+ const Traverser = require ( 'eslint/lib/util/traverser' )
89
910const 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