diff --git a/.changeset/tired-emus-matter.md b/.changeset/tired-emus-matter.md
new file mode 100644
index 000000000..6ca7123cb
--- /dev/null
+++ b/.changeset/tired-emus-matter.md
@@ -0,0 +1,5 @@
+---
+'eslint-plugin-vue': minor
+---
+
+Fixed `no-negated-v-if-condition` rule to swap entire elements
diff --git a/lib/rules/no-negated-v-if-condition.js b/lib/rules/no-negated-v-if-condition.js
index 73ee877c0..e0fec2546 100644
--- a/lib/rules/no-negated-v-if-condition.js
+++ b/lib/rules/no-negated-v-if-condition.js
@@ -53,6 +53,21 @@ function isDirectlyFollowedByElse(element) {
return nextElement ? utils.hasDirective(nextElement, 'else') : false
}
+/**
+ * @param {VElement} element
+ */
+function getDirective(element) {
+ return /** @type {VIfDirective|undefined} */ (
+ element.startTag.attributes.find(
+ (attr) =>
+ attr.directive &&
+ attr.key.name &&
+ attr.key.name.name &&
+ ['if', 'else-if', 'else'].includes(attr.key.name.name)
+ )
+ )
+}
+
module.exports = {
meta: {
type: 'suggestion',
@@ -73,9 +88,37 @@ module.exports = {
/** @param {RuleContext} context */
create(context) {
const sourceCode = context.getSourceCode()
- const templateTokens =
- sourceCode.parserServices.getTemplateBodyTokenStore &&
- sourceCode.parserServices.getTemplateBodyTokenStore()
+
+ const processedPairs = new Set()
+
+ /**
+ * @param {Expression} expression
+ * @returns {string}
+ */
+ function getConvertedCondition(expression) {
+ if (
+ expression.type === 'UnaryExpression' &&
+ expression.operator === '!'
+ ) {
+ return sourceCode.text.slice(
+ expression.range[0] + 1,
+ expression.range[1]
+ )
+ }
+
+ if (expression.type === 'BinaryExpression') {
+ const left = sourceCode.getText(expression.left)
+ const right = sourceCode.getText(expression.right)
+
+ if (expression.operator === '!=') {
+ return `${left} == ${right}`
+ } else if (expression.operator === '!==') {
+ return `${left} === ${right}`
+ }
+ }
+
+ return sourceCode.getText(expression)
+ }
/**
* @param {VIfDirective} node
@@ -100,6 +143,12 @@ module.exports = {
return
}
+ const pairKey = `${element.range[0]}-${elseElement.range[0]}`
+ if (processedPairs.has(pairKey)) {
+ return
+ }
+ processedPairs.add(pairKey)
+
context.report({
node: expression,
messageId: 'negatedCondition',
@@ -107,87 +156,54 @@ module.exports = {
{
messageId: 'fixNegatedCondition',
*fix(fixer) {
- yield* convertNegatedCondition(fixer, expression)
- yield* swapElementContents(fixer, element, elseElement)
+ yield* swapElements(fixer, element, elseElement, expression)
}
}
]
})
}
- /**
- * @param {RuleFixer} fixer
- * @param {Expression} expression
- */
- function* convertNegatedCondition(fixer, expression) {
- if (
- expression.type === 'UnaryExpression' &&
- expression.operator === '!'
- ) {
- const token = templateTokens.getFirstToken(expression)
- if (token?.type === 'Punctuator' && token.value === '!') {
- yield fixer.remove(token)
- }
- return
- }
-
- if (expression.type === 'BinaryExpression') {
- const operatorToken = templateTokens.getTokenAfter(
- expression.left,
- (token) =>
- token?.type === 'Punctuator' && token.value === expression.operator
- )
-
- if (!operatorToken) return
-
- if (expression.operator === '!=') {
- yield fixer.replaceText(operatorToken, '==')
- } else if (expression.operator === '!==') {
- yield fixer.replaceText(operatorToken, '===')
- }
- }
- }
-
- /**
- * @param {VElement} element
- * @returns {string}
- */
- function getElementContent(element) {
- if (element.children.length === 0 || !element.endTag) {
- return ''
- }
-
- const contentStart = element.startTag.range[1]
- const contentEnd = element.endTag.range[0]
-
- return sourceCode.text.slice(contentStart, contentEnd)
- }
-
/**
* @param {RuleFixer} fixer
* @param {VElement} ifElement
* @param {VElement} elseElement
+ * @param {Expression} expression
*/
- function* swapElementContents(fixer, ifElement, elseElement) {
- if (!ifElement.endTag || !elseElement.endTag) {
- return
- }
+ function* swapElements(fixer, ifElement, elseElement, expression) {
+ const convertedCondition = getConvertedCondition(expression)
- const ifContent = getElementContent(ifElement)
- const elseContent = getElementContent(elseElement)
+ const ifDir = getDirective(ifElement)
+ const elseDir = getDirective(elseElement)
- if (ifContent === elseContent) {
+ if (!ifDir || !elseDir) {
return
}
- yield fixer.replaceTextRange(
- [ifElement.startTag.range[1], ifElement.endTag.range[0]],
- elseContent
+ const ifDirectiveName = ifDir.key.name.name
+
+ const ifText = sourceCode.text.slice(
+ ifElement.range[0],
+ ifElement.range[1]
)
- yield fixer.replaceTextRange(
- [elseElement.startTag.range[1], elseElement.endTag.range[0]],
- ifContent
+ const elseText = sourceCode.text.slice(
+ elseElement.range[0],
+ elseElement.range[1]
)
+
+ const newIfDirective = `v-${ifDirectiveName}="${convertedCondition}"`
+ const newIfText =
+ elseText.slice(0, elseDir.range[0] - elseElement.range[0]) +
+ newIfDirective +
+ elseText.slice(elseDir.range[1] - elseElement.range[0])
+
+ const newElseDirective = 'v-else'
+ const newElseText =
+ ifText.slice(0, ifDir.range[0] - ifElement.range[0]) +
+ newElseDirective +
+ ifText.slice(ifDir.range[1] - ifElement.range[0])
+
+ yield fixer.replaceTextRange(ifElement.range, newIfText)
+ yield fixer.replaceTextRange(elseElement.range, newElseText)
}
return utils.defineTemplateBodyVisitor(context, {
diff --git a/tests/lib/rules/no-negated-v-if-condition.js b/tests/lib/rules/no-negated-v-if-condition.js
index ee60562ea..fc0e02d0d 100644
--- a/tests/lib/rules/no-negated-v-if-condition.js
+++ b/tests/lib/rules/no-negated-v-if-condition.js
@@ -149,8 +149,8 @@ tester.run('no-negated-v-if-condition', rule, {
filename: 'test.vue',
code: `
- Content
- Alternative
+ Content
+ Alternative
`,
errors: [
@@ -165,8 +165,8 @@ tester.run('no-negated-v-if-condition', rule, {
messageId: 'fixNegatedCondition',
output: `
- Alternative
- Content
+ Alternative
+ Content
`
}
@@ -178,8 +178,8 @@ tester.run('no-negated-v-if-condition', rule, {
filename: 'test.vue',
code: `
- Negated condition
- Otherwise
+ Negated condition
+ Otherwise
`,
errors: [
@@ -194,8 +194,8 @@ tester.run('no-negated-v-if-condition', rule, {
messageId: 'fixNegatedCondition',
output: `
- Otherwise
- Negated condition
+ Otherwise
+ Negated condition
`
}
@@ -265,26 +265,26 @@ tester.run('no-negated-v-if-condition', rule, {
filename: 'test.vue',
code: `
- First
- Second
- Default
+ First
+ Second
+ Default
`,
errors: [
{
messageId: 'negatedCondition',
line: 4,
- column: 25,
+ column: 26,
endLine: 4,
- endColumn: 29,
+ endColumn: 30,
suggestions: [
{
messageId: 'fixNegatedCondition',
output: `
- First
- Default
- Second
+ First
+ Default
+ Second
`
}
@@ -296,26 +296,144 @@ tester.run('no-negated-v-if-condition', rule, {
filename: 'test.vue',
code: `
- First
- Second
- Default
+ First
+ Second
+ Default
`,
errors: [
{
messageId: 'negatedCondition',
line: 4,
- column: 25,
+ column: 26,
endLine: 4,
- endColumn: 27,
+ endColumn: 28,
suggestions: [
{
messageId: 'fixNegatedCondition',
output: `
- First
- Default
- Second
+ First
+ Default
+ Second
+
+ `
+ }
+ ]
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ div contents
+ span contents
+
+ `,
+ errors: [
+ {
+ messageId: 'negatedCondition',
+ line: 3,
+ column: 20,
+ endLine: 3,
+ endColumn: 30,
+ suggestions: [
+ {
+ messageId: 'fixNegatedCondition',
+ output: `
+
+ span contents
+ div contents
+
+ `
+ }
+ ]
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
Inner if content
+
Inner else content
+
+
+ Nested if content
+ Nested else content
+
+
+ `,
+ errors: [
+ {
+ messageId: 'negatedCondition',
+ line: 3,
+ column: 20,
+ endLine: 3,
+ endColumn: 26,
+ suggestions: [
+ {
+ messageId: 'fixNegatedCondition',
+ output: `
+
+
+ Nested if content
+ Nested else content
+
+
+
Inner if content
+
Inner else content
+
+
+ `
+ }
+ ]
+ },
+ {
+ messageId: 'negatedCondition',
+ line: 4,
+ column: 23,
+ endLine: 4,
+ endColumn: 29,
+ suggestions: [
+ {
+ messageId: 'fixNegatedCondition',
+ output: `
+
+
+
Inner else content
+
Inner if content
+
+
+ Nested if content
+ Nested else content
+
+
+ `
+ }
+ ]
+ },
+ {
+ messageId: 'negatedCondition',
+ line: 8,
+ column: 23,
+ endLine: 8,
+ endColumn: 30,
+ suggestions: [
+ {
+ messageId: 'fixNegatedCondition',
+ output: `
+
+
+
Inner if content
+
Inner else content
+
+
+ Nested else content
+ Nested if content
+
`
}