From c178ce7044b5c19db2f4aabfdbe58003db5062fd Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 4 Mar 2022 02:46:05 +0100 Subject: [PATCH] fix: extend the autofix range in comma-dangle to ensure the last element (#15669) Fixes #15660 --- lib/rules/comma-dangle.js | 28 +++++++++-- tests/lib/rules/comma-dangle.js | 84 +++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/lib/rules/comma-dangle.js b/lib/rules/comma-dangle.js index 232b27cb39c..063e1cb697f 100644 --- a/lib/rules/comma-dangle.js +++ b/lib/rules/comma-dangle.js @@ -243,8 +243,18 @@ module.exports = { node: lastItem, loc: trailingToken.loc, messageId: "unexpected", - fix(fixer) { - return fixer.remove(trailingToken); + *fix(fixer) { + yield fixer.remove(trailingToken); + + /* + * Extend the range of the fix to include surrounding tokens to ensure + * that the element after which the comma is removed stays _last_. + * This intentionally makes conflicts in fix ranges with rules that may be + * adding or removing elements in the same autofix pass. + * https://github.com/eslint/eslint/issues/15660 + */ + yield fixer.insertTextBefore(sourceCode.getTokenBefore(trailingToken), ""); + yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), ""); } }); } @@ -282,8 +292,18 @@ module.exports = { end: astUtils.getNextLocation(sourceCode, trailingToken.loc.end) }, messageId: "missing", - fix(fixer) { - return fixer.insertTextAfter(trailingToken, ","); + *fix(fixer) { + yield fixer.insertTextAfter(trailingToken, ","); + + /* + * Extend the range of the fix to include surrounding tokens to ensure + * that the element after which the comma is inserted stays _last_. + * This intentionally makes conflicts in fix ranges with rules that may be + * adding or removing elements in the same autofix pass. + * https://github.com/eslint/eslint/issues/15660 + */ + yield fixer.insertTextBefore(trailingToken, ""); + yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), ""); } }); } diff --git a/tests/lib/rules/comma-dangle.js b/tests/lib/rules/comma-dangle.js index 2ef7a008e2e..b02a74a5528 100644 --- a/tests/lib/rules/comma-dangle.js +++ b/tests/lib/rules/comma-dangle.js @@ -10,6 +10,7 @@ //------------------------------------------------------------------------------ const path = require("path"), + { unIndent } = require("../../_utils"), rule = require("../../../lib/rules/comma-dangle"), { RuleTester } = require("../../../lib/rule-tester"); @@ -35,6 +36,29 @@ function parser(name) { const ruleTester = new RuleTester(); +ruleTester.defineRule("add-named-import", { + meta: { + fixable: "code" + }, + create(context) { + return { + ImportDeclaration(node) { + const sourceCode = context.getSourceCode(); + const closingBrace = sourceCode.getLastToken(node, token => token.value === "}"); + const addComma = sourceCode.getTokenBefore(closingBrace).value !== ","; + + context.report({ + message: "Add I18nManager.", + node, + fix(fixer) { + return fixer.insertTextBefore(closingBrace, `${addComma ? "," : ""}I18nManager`); + } + }); + } + }; + } +}); + ruleTester.run("comma-dangle", rule, { valid: [ "var foo = { bar: 'baz' }", @@ -1766,6 +1790,66 @@ let d = 0;export {d,}; output: "foo(a)", parserOptions: { ecmaVersion: 8 }, errors: [{ messageId: "unexpected" }] + }, + + // https://github.com/eslint/eslint/issues/15660 + { + code: unIndent` + /*eslint add-named-import:1*/ + import { + StyleSheet, + View, + TextInput, + ImageBackground, + Image, + TouchableOpacity, + SafeAreaView + } from 'react-native'; + `, + output: unIndent` + /*eslint add-named-import:1*/ + import { + StyleSheet, + View, + TextInput, + ImageBackground, + Image, + TouchableOpacity, + SafeAreaView, + } from 'react-native'; + `, + options: [{ imports: "always-multiline" }], + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: 2 + }, + { + code: unIndent` + /*eslint add-named-import:1*/ + import { + StyleSheet, + View, + TextInput, + ImageBackground, + Image, + TouchableOpacity, + SafeAreaView, + } from 'react-native'; + `, + output: unIndent` + /*eslint add-named-import:1*/ + import { + StyleSheet, + View, + TextInput, + ImageBackground, + Image, + TouchableOpacity, + SafeAreaView + } from 'react-native'; + `, + options: [{ imports: "never" }], + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: 2 } ] });