Skip to content

Commit 5e509d0

Browse files
authored
fix(no-negated-v-if-condition): swap elements (#2941)
1 parent f9dcbda commit 5e509d0

File tree

3 files changed

+229
-90
lines changed

3 files changed

+229
-90
lines changed

.changeset/tired-emus-matter.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'eslint-plugin-vue': patch
3+
---
4+
5+
Fixed `no-negated-v-if-condition` rule to swap entire elements

lib/rules/no-negated-v-if-condition.js

Lines changed: 82 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,21 @@ function isDirectlyFollowedByElse(element) {
5353
return nextElement ? utils.hasDirective(nextElement, 'else') : false
5454
}
5555

56+
/**
57+
* @param {VElement} element
58+
*/
59+
function getDirective(element) {
60+
return /** @type {VIfDirective|undefined} */ (
61+
element.startTag.attributes.find(
62+
(attr) =>
63+
attr.directive &&
64+
attr.key.name &&
65+
attr.key.name.name &&
66+
['if', 'else-if', 'else'].includes(attr.key.name.name)
67+
)
68+
)
69+
}
70+
5671
module.exports = {
5772
meta: {
5873
type: 'suggestion',
@@ -73,9 +88,37 @@ module.exports = {
7388
/** @param {RuleContext} context */
7489
create(context) {
7590
const sourceCode = context.getSourceCode()
76-
const templateTokens =
77-
sourceCode.parserServices.getTemplateBodyTokenStore &&
78-
sourceCode.parserServices.getTemplateBodyTokenStore()
91+
92+
const processedPairs = new Set()
93+
94+
/**
95+
* @param {Expression} expression
96+
* @returns {string}
97+
*/
98+
function getConvertedCondition(expression) {
99+
if (
100+
expression.type === 'UnaryExpression' &&
101+
expression.operator === '!'
102+
) {
103+
return sourceCode.text.slice(
104+
expression.range[0] + 1,
105+
expression.range[1]
106+
)
107+
}
108+
109+
if (expression.type === 'BinaryExpression') {
110+
const left = sourceCode.getText(expression.left)
111+
const right = sourceCode.getText(expression.right)
112+
113+
if (expression.operator === '!=') {
114+
return `${left} == ${right}`
115+
} else if (expression.operator === '!==') {
116+
return `${left} === ${right}`
117+
}
118+
}
119+
120+
return sourceCode.getText(expression)
121+
}
79122

80123
/**
81124
* @param {VIfDirective} node
@@ -100,94 +143,67 @@ module.exports = {
100143
return
101144
}
102145

146+
const pairKey = `${element.range[0]}-${elseElement.range[0]}`
147+
if (processedPairs.has(pairKey)) {
148+
return
149+
}
150+
processedPairs.add(pairKey)
151+
103152
context.report({
104153
node: expression,
105154
messageId: 'negatedCondition',
106155
suggest: [
107156
{
108157
messageId: 'fixNegatedCondition',
109158
*fix(fixer) {
110-
yield* convertNegatedCondition(fixer, expression)
111-
yield* swapElementContents(fixer, element, elseElement)
159+
yield* swapElements(fixer, element, elseElement, expression)
112160
}
113161
}
114162
]
115163
})
116164
}
117165

118-
/**
119-
* @param {RuleFixer} fixer
120-
* @param {Expression} expression
121-
*/
122-
function* convertNegatedCondition(fixer, expression) {
123-
if (
124-
expression.type === 'UnaryExpression' &&
125-
expression.operator === '!'
126-
) {
127-
const token = templateTokens.getFirstToken(expression)
128-
if (token?.type === 'Punctuator' && token.value === '!') {
129-
yield fixer.remove(token)
130-
}
131-
return
132-
}
133-
134-
if (expression.type === 'BinaryExpression') {
135-
const operatorToken = templateTokens.getTokenAfter(
136-
expression.left,
137-
(token) =>
138-
token?.type === 'Punctuator' && token.value === expression.operator
139-
)
140-
141-
if (!operatorToken) return
142-
143-
if (expression.operator === '!=') {
144-
yield fixer.replaceText(operatorToken, '==')
145-
} else if (expression.operator === '!==') {
146-
yield fixer.replaceText(operatorToken, '===')
147-
}
148-
}
149-
}
150-
151-
/**
152-
* @param {VElement} element
153-
* @returns {string}
154-
*/
155-
function getElementContent(element) {
156-
if (element.children.length === 0 || !element.endTag) {
157-
return ''
158-
}
159-
160-
const contentStart = element.startTag.range[1]
161-
const contentEnd = element.endTag.range[0]
162-
163-
return sourceCode.text.slice(contentStart, contentEnd)
164-
}
165-
166166
/**
167167
* @param {RuleFixer} fixer
168168
* @param {VElement} ifElement
169169
* @param {VElement} elseElement
170+
* @param {Expression} expression
170171
*/
171-
function* swapElementContents(fixer, ifElement, elseElement) {
172-
if (!ifElement.endTag || !elseElement.endTag) {
173-
return
174-
}
172+
function* swapElements(fixer, ifElement, elseElement, expression) {
173+
const convertedCondition = getConvertedCondition(expression)
175174

176-
const ifContent = getElementContent(ifElement)
177-
const elseContent = getElementContent(elseElement)
175+
const ifDir = getDirective(ifElement)
176+
const elseDir = getDirective(elseElement)
178177

179-
if (ifContent === elseContent) {
178+
if (!ifDir || !elseDir) {
180179
return
181180
}
182181

183-
yield fixer.replaceTextRange(
184-
[ifElement.startTag.range[1], ifElement.endTag.range[0]],
185-
elseContent
182+
const ifDirectiveName = ifDir.key.name.name
183+
184+
const ifText = sourceCode.text.slice(
185+
ifElement.range[0],
186+
ifElement.range[1]
186187
)
187-
yield fixer.replaceTextRange(
188-
[elseElement.startTag.range[1], elseElement.endTag.range[0]],
189-
ifContent
188+
const elseText = sourceCode.text.slice(
189+
elseElement.range[0],
190+
elseElement.range[1]
190191
)
192+
193+
const newIfDirective = `v-${ifDirectiveName}="${convertedCondition}"`
194+
const newIfText =
195+
elseText.slice(0, elseDir.range[0] - elseElement.range[0]) +
196+
newIfDirective +
197+
elseText.slice(elseDir.range[1] - elseElement.range[0])
198+
199+
const newElseDirective = 'v-else'
200+
const newElseText =
201+
ifText.slice(0, ifDir.range[0] - ifElement.range[0]) +
202+
newElseDirective +
203+
ifText.slice(ifDir.range[1] - ifElement.range[0])
204+
205+
yield fixer.replaceTextRange(ifElement.range, newIfText)
206+
yield fixer.replaceTextRange(elseElement.range, newElseText)
191207
}
192208

193209
return utils.defineTemplateBodyVisitor(context, {

0 commit comments

Comments
 (0)