From bcdc21bcd70f7d79ea590968f87910fe48a5083c Mon Sep 17 00:00:00 2001 From: fisker Date: Sat, 23 Jan 2021 01:25:18 +0800 Subject: [PATCH 1/3] Fix conflicts with `arrow-body-style` --- eslint-plugin-prettier.js | 121 ++++++++++++++++++++++++++------------ test/invalid/19.txt | 39 ++++++++++++ test/prettier.js | 61 ++++++++++++++++++- 3 files changed, 180 insertions(+), 41 deletions(-) create mode 100644 test/invalid/19.txt diff --git a/eslint-plugin-prettier.js b/eslint-plugin-prettier.js index fd7e453c..e879e97c 100644 --- a/eslint-plugin-prettier.js +++ b/eslint-plugin-prettier.js @@ -33,71 +33,94 @@ let prettier; /** * Reports an "Insert ..." issue where text must be inserted. - * @param {RuleContext} context - The ESLint rule context. * @param {number} offset - The source offset where to insert text. * @param {string} text - The text to be inserted. - * @returns {void} + * @returns {{message: string, data: {code: string}, range: number[], fix: function}} */ -function reportInsert(context, offset, text) { - const pos = context.getSourceCode().getLocFromIndex(offset); +function parseInsert(offset, text) { const range = [offset, offset]; - context.report({ + return { message: 'Insert `{{ code }}`', data: { code: showInvisibles(text) }, - loc: { start: pos, end: pos }, - fix(fixer) { - return fixer.insertTextAfterRange(range, text); - } - }); + range, + fix: fixer => fixer.insertTextAfterRange(range, text) + }; } /** * Reports a "Delete ..." issue where text must be deleted. - * @param {RuleContext} context - The ESLint rule context. * @param {number} offset - The source offset where to delete text. * @param {string} text - The text to be deleted. - * @returns {void} + * @returns {{message: string, data: {code: string}, range: number[], fix: function}} */ -function reportDelete(context, offset, text) { - const start = context.getSourceCode().getLocFromIndex(offset); - const end = context.getSourceCode().getLocFromIndex(offset + text.length); +function parseDelete(offset, text) { const range = [offset, offset + text.length]; - context.report({ + return { message: 'Delete `{{ code }}`', data: { code: showInvisibles(text) }, - loc: { start, end }, - fix(fixer) { - return fixer.removeRange(range); - } - }); + range, + fix: fixer => fixer.removeRange(range) + }; } /** * Reports a "Replace ... with ..." issue where text must be replaced. - * @param {RuleContext} context - The ESLint rule context. * @param {number} offset - The source offset where to replace deleted text with inserted text. * @param {string} deleteText - The text to be deleted. * @param {string} insertText - The text to be inserted. - * @returns {void} + * @returns {{message: string, data: {deleteCode: string, insetCode: string}, range: number[], fix: function}} */ -function reportReplace(context, offset, deleteText, insertText) { - const start = context.getSourceCode().getLocFromIndex(offset); - const end = context - .getSourceCode() - .getLocFromIndex(offset + deleteText.length); +function parseReplace(offset, deleteText, insertText) { const range = [offset, offset + deleteText.length]; - context.report({ + return { message: 'Replace `{{ deleteCode }}` with `{{ insertCode }}`', data: { deleteCode: showInvisibles(deleteText), insertCode: showInvisibles(insertText) }, - loc: { start, end }, - fix(fixer) { - return fixer.replaceTextRange(range, insertText); + range, + fix: fixer => fixer.replaceTextRange(range, insertText) + }; +} + +/** + * Reports ESLint problems. + * @param {RuleContext} context - The ESLint rule context. + * @param {object} result - The parsed data. + * @param {function} [fixAll] - The function fixes whole file. + * @returns {void} + */ +function report(context, result, fixAll) { + const { message, data, range, fix } = result; + const [start, end] = range.map(index => + context.getSourceCode().getLocFromIndex(index) + ); + + const problem = { + message, + data, + loc: { + start, + end } - }); + }; + + if (fixAll) { + problem.message = `${message}(Apply fix in editor will fix all prettier/prettier errors)`; + problem.fix = fixAll; + problem.suggest = [ + { + desc: message, + data, + fix + } + ]; + } else { + problem.fix = fix; + } + + context.report(problem); } // ------------------------------------------------------------------------------ @@ -139,6 +162,10 @@ module.exports = { type: 'object', properties: {}, additionalProperties: true + }, + experimentalFix: { + type: 'boolean', + default: false } }, additionalProperties: true @@ -150,6 +177,8 @@ module.exports = { !context.options[1] || context.options[1].usePrettierrc !== false; const eslintFileInfoOptions = (context.options[1] && context.options[1].fileInfoOptions) || {}; + const experimentalFix = + context.options[1] && context.options[1].experimentalFix === true; const sourceCode = context.getSourceCode(); const filepath = context.getFilename(); const source = sourceCode.text; @@ -260,34 +289,48 @@ module.exports = { return; } + let fix; + if (experimentalFix) { + let result; + fix = fixer => { + if (!result) { + result = fixer.replaceTextRange( + [0, source.length], + prettierSource + ); + } + return result; + }; + } + if (source !== prettierSource) { const differences = generateDifferences(source, prettierSource); differences.forEach(difference => { + let result; switch (difference.operation) { case INSERT: - reportInsert( - context, + result = parseInsert( difference.offset, difference.insertText ); break; case DELETE: - reportDelete( - context, + result = parseDelete( difference.offset, difference.deleteText ); break; case REPLACE: - reportReplace( - context, + result = parseReplace( difference.offset, difference.deleteText, difference.insertText ); break; } + + report(context, result, fix); }); } } diff --git a/test/invalid/19.txt b/test/invalid/19.txt new file mode 100644 index 00000000..7d7fffa4 --- /dev/null +++ b/test/invalid/19.txt @@ -0,0 +1,39 @@ +CODE: +a = 1.0000; +b = 2.00000000; + +OUTPUT: +a = 1.0; +b = 2.0; + +OPTIONS: +[{}, {experimentalFix: true}] + +ERRORS: +[ + { + message: 'Delete `000`(Apply fix in editor will fix all prettier/prettier errors)', + line: 1, column: 8, endLine: 1, endColumn: 11, + suggestions: [ + { + desc: 'Delete `000`', + output: 'a = 1.0;\nb = 2.00000000;\n' + } + ] + }, + { + message: 'Delete `0000000`(Apply fix in editor will fix all prettier/prettier errors)', + line: 2, column: 7, endLine: 2, endColumn: 14, + suggestions: [ + { + desc: 'Delete `0000000`', + output: 'a = 1.0000;\nb = 2.0;\n' + } + ] + }, +] + + +FILENAME: +experimental-fix.js + diff --git a/test/prettier.js b/test/prettier.js index c5e8a6af..86954065 100644 --- a/test/prettier.js +++ b/test/prettier.js @@ -14,11 +14,13 @@ const fs = require('fs'); const path = require('path'); +const assert = require('assert'); const eslintPluginPrettier = require('..'); const rule = eslintPluginPrettier.rules.prettier; -const RuleTester = require('eslint').RuleTester; +const eslint = require('eslint'); +const RuleTester = eslint.RuleTester; // ------------------------------------------------------------------------------ // Tests @@ -87,7 +89,8 @@ ruleTester.run('prettier', rule, { '15', '16', '17', - '18' + '18', + '19' ].map(loadInvalidFixture) }); @@ -112,6 +115,60 @@ vueRuleTester.run('prettier', rule, { ] }); +it('Should fix conflicts with `arrow-body-style` rule', () => { + const linter = new eslint.Linter(); + linter.defineRules({ + 'arrow-body-style': require('eslint/lib/rules/arrow-body-style'), + 'prettier/prettier': rule + }); + const code = ` +const foo = a => a.map(b => { + return b.map( + c => { + return {}; + } + ); +}); +`; + const output = + ` +const foo = a => + a.map(b => + b.map(c => ({}) + ); +`.trim() + '\n'; + const outputWithExperimentalFix = + ` +const foo = a => a.map(b => b.map(c => ({}))); +`.trim() + '\n'; + const [result, resultWithExperimentalFix] = [false, true].map( + experimentalFix => + linter.verifyAndFix(code, { + parserOptions: { ecmaVersion: 2015 }, + rules: { + 'arrow-body-style': ['error'], + 'prettier/prettier': ['error', {}, { experimentalFix }] + } + }) + ); + assert.strictEqual(result.output, output); + assert.deepStrictEqual(result.messages, [ + { + column: 4, + fatal: true, + line: 4, + message: 'Parsing error: Unexpected token ;', + ruleId: null, + severity: 2 + } + ]); + assert.strictEqual( + resultWithExperimentalFix.output, + outputWithExperimentalFix + ); + assert.deepStrictEqual(resultWithExperimentalFix.messages, []); +}); + // ------------------------------------------------------------------------------ // Helpers // ------------------------------------------------------------------------------ From 10089645af2474d986fe95edbb69b214d17bb27e Mon Sep 17 00:00:00 2001 From: fisker Date: Sun, 24 Jan 2021 02:00:20 +0800 Subject: [PATCH 2/3] Another way to fix conflicts --- eslint-plugin-prettier.js | 149 ++++++++++++++++++-------------------- test/invalid/19.txt | 22 ++---- test/invalid/20.txt | 22 ++++++ 3 files changed, 97 insertions(+), 96 deletions(-) create mode 100644 test/invalid/20.txt diff --git a/eslint-plugin-prettier.js b/eslint-plugin-prettier.js index e879e97c..2205b096 100644 --- a/eslint-plugin-prettier.js +++ b/eslint-plugin-prettier.js @@ -31,96 +31,98 @@ let prettier; // Rule Definition // ------------------------------------------------------------------------------ +/** + * Extend fix range to prevent changes from other rules. + * @param {ruleFixer} fixer - The fixer to fix. + * @param {int[]} range - The extended range node. + */ +function* extendFixRange(fixer, range) { + yield fixer.insertTextBeforeRange(range, ''); + yield fixer.insertTextAfterRange(range, ''); +} + /** * Reports an "Insert ..." issue where text must be inserted. + * @param {RuleContext} context - The ESLint rule context. * @param {number} offset - The source offset where to insert text. * @param {string} text - The text to be inserted. - * @returns {{message: string, data: {code: string}, range: number[], fix: function}} + * @param {boolean} experimentalFix - Use the new way to fix code. + * @returns {void} */ -function parseInsert(offset, text) { +function reportInsert(context, offset, text, experimentalFix) { + const pos = context.getSourceCode().getLocFromIndex(offset); const range = [offset, offset]; - return { + context.report({ message: 'Insert `{{ code }}`', data: { code: showInvisibles(text) }, - range, - fix: fixer => fixer.insertTextAfterRange(range, text) - }; + loc: { start: pos, end: pos }, + *fix(fixer) { + yield fixer.insertTextAfterRange(range, text); + if (experimentalFix) + yield* extendFixRange(fixer, [0, context.getSourceCode().text.length]); + } + }); } /** * Reports a "Delete ..." issue where text must be deleted. + * @param {RuleContext} context - The ESLint rule context. * @param {number} offset - The source offset where to delete text. * @param {string} text - The text to be deleted. - * @returns {{message: string, data: {code: string}, range: number[], fix: function}} + * @param {boolean} experimentalFix - Use the new way to fix code. + * @returns {void} */ -function parseDelete(offset, text) { +function reportDelete(context, offset, text, experimentalFix) { + const start = context.getSourceCode().getLocFromIndex(offset); + const end = context.getSourceCode().getLocFromIndex(offset + text.length); const range = [offset, offset + text.length]; - return { + context.report({ message: 'Delete `{{ code }}`', data: { code: showInvisibles(text) }, - range, - fix: fixer => fixer.removeRange(range) - }; + loc: { start, end }, + *fix(fixer) { + yield fixer.removeRange(range); + if (experimentalFix) + yield* extendFixRange(fixer, [0, context.getSourceCode().text.length]); + } + }); } /** * Reports a "Replace ... with ..." issue where text must be replaced. + * @param {RuleContext} context - The ESLint rule context. * @param {number} offset - The source offset where to replace deleted text with inserted text. * @param {string} deleteText - The text to be deleted. * @param {string} insertText - The text to be inserted. - * @returns {{message: string, data: {deleteCode: string, insetCode: string}, range: number[], fix: function}} + * @param {boolean} experimentalFix - Use the new way to fix code. + * @returns {void} */ -function parseReplace(offset, deleteText, insertText) { +function reportReplace( + context, + offset, + deleteText, + insertText, + experimentalFix +) { + const start = context.getSourceCode().getLocFromIndex(offset); + const end = context + .getSourceCode() + .getLocFromIndex(offset + deleteText.length); const range = [offset, offset + deleteText.length]; - return { + context.report({ message: 'Replace `{{ deleteCode }}` with `{{ insertCode }}`', data: { deleteCode: showInvisibles(deleteText), insertCode: showInvisibles(insertText) }, - range, - fix: fixer => fixer.replaceTextRange(range, insertText) - }; -} - -/** - * Reports ESLint problems. - * @param {RuleContext} context - The ESLint rule context. - * @param {object} result - The parsed data. - * @param {function} [fixAll] - The function fixes whole file. - * @returns {void} - */ -function report(context, result, fixAll) { - const { message, data, range, fix } = result; - const [start, end] = range.map(index => - context.getSourceCode().getLocFromIndex(index) - ); - - const problem = { - message, - data, - loc: { - start, - end + loc: { start, end }, + *fix(fixer) { + yield fixer.replaceTextRange(range, insertText); + if (experimentalFix) + yield* extendFixRange(fixer, [0, context.getSourceCode().text.length]); } - }; - - if (fixAll) { - problem.message = `${message}(Apply fix in editor will fix all prettier/prettier errors)`; - problem.fix = fixAll; - problem.suggest = [ - { - desc: message, - data, - fix - } - ]; - } else { - problem.fix = fix; - } - - context.report(problem); + }); } // ------------------------------------------------------------------------------ @@ -289,48 +291,37 @@ module.exports = { return; } - let fix; - if (experimentalFix) { - let result; - fix = fixer => { - if (!result) { - result = fixer.replaceTextRange( - [0, source.length], - prettierSource - ); - } - return result; - }; - } - if (source !== prettierSource) { const differences = generateDifferences(source, prettierSource); differences.forEach(difference => { - let result; switch (difference.operation) { case INSERT: - result = parseInsert( + reportInsert( + context, difference.offset, - difference.insertText + difference.insertText, + experimentalFix ); break; case DELETE: - result = parseDelete( + reportDelete( + context, difference.offset, - difference.deleteText + difference.deleteText, + experimentalFix ); break; case REPLACE: - result = parseReplace( + reportReplace( + context, difference.offset, difference.deleteText, - difference.insertText + difference.insertText, + experimentalFix ); break; } - - report(context, result, fix); }); } } diff --git a/test/invalid/19.txt b/test/invalid/19.txt index 7d7fffa4..d0a4796a 100644 --- a/test/invalid/19.txt +++ b/test/invalid/19.txt @@ -4,7 +4,7 @@ b = 2.00000000; OUTPUT: a = 1.0; -b = 2.0; +b = 2.00000000; OPTIONS: [{}, {experimentalFix: true}] @@ -12,24 +12,12 @@ OPTIONS: ERRORS: [ { - message: 'Delete `000`(Apply fix in editor will fix all prettier/prettier errors)', - line: 1, column: 8, endLine: 1, endColumn: 11, - suggestions: [ - { - desc: 'Delete `000`', - output: 'a = 1.0;\nb = 2.00000000;\n' - } - ] + message: 'Delete `000`', + line: 1, column: 8, endLine: 1, endColumn: 11 }, { - message: 'Delete `0000000`(Apply fix in editor will fix all prettier/prettier errors)', - line: 2, column: 7, endLine: 2, endColumn: 14, - suggestions: [ - { - desc: 'Delete `0000000`', - output: 'a = 1.0000;\nb = 2.0;\n' - } - ] + message: 'Delete `0000000`', + line: 2, column: 7, endLine: 2, endColumn: 14 }, ] diff --git a/test/invalid/20.txt b/test/invalid/20.txt new file mode 100644 index 00000000..062cbcda --- /dev/null +++ b/test/invalid/20.txt @@ -0,0 +1,22 @@ +CODE: +a = 1.0; +b = 2.00000000; + +OUTPUT: +a = 1.0; +b = 2.0; + +OPTIONS: +[{}, {experimentalFix: true}] + +ERRORS: +[ + { + message: 'Delete `0000000`', + line: 2, column: 7, endLine: 2, endColumn: 14 + }, +] + + +FILENAME: +experimental-fix.js From 0d045926dee957be3aee52096f7bd13c343aec7b Mon Sep 17 00:00:00 2001 From: fisker Date: Sun, 24 Jan 2021 02:02:51 +0800 Subject: [PATCH 3/3] Fix test --- test/invalid/20.txt | 2 +- test/prettier.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/invalid/20.txt b/test/invalid/20.txt index 062cbcda..618723ec 100644 --- a/test/invalid/20.txt +++ b/test/invalid/20.txt @@ -13,7 +13,7 @@ ERRORS: [ { message: 'Delete `0000000`', - line: 2, column: 7, endLine: 2, endColumn: 14 + line: 2, column: 8, endLine: 2, endColumn: 15 }, ] diff --git a/test/prettier.js b/test/prettier.js index 86954065..612499c1 100644 --- a/test/prettier.js +++ b/test/prettier.js @@ -90,7 +90,8 @@ ruleTester.run('prettier', rule, { '16', '17', '18', - '19' + '19', + '20' ].map(loadInvalidFixture) });