From deb8e4e9dfa7a04077b21541fb008fc38de65a6b Mon Sep 17 00:00:00 2001 From: Kevin Grandon Date: Mon, 21 Mar 2016 15:53:51 -0700 Subject: [PATCH] Autofix for jsx-closing-bracket-location. --- lib/rules/jsx-closing-bracket-location.js | 60 +++++++++++++++- .../lib/rules/jsx-closing-bracket-location.js | 72 +++++++++++++++++++ 2 files changed, 129 insertions(+), 3 deletions(-) diff --git a/lib/rules/jsx-closing-bracket-location.js b/lib/rules/jsx-closing-bracket-location.js index 36859a3900..51fc3cf9b2 100644 --- a/lib/rules/jsx-closing-bracket-location.js +++ b/lib/rules/jsx-closing-bracket-location.js @@ -142,10 +142,25 @@ module.exports = function(context) { }; } + var lastAttributeEndPos; + var lastAttributeStartPos; + return { - JSXOpeningElement: function(node) { + JSXAttribute: function(node) { + lastAttributeEndPos = node.end; + lastAttributeStartPos = node.start; + }, + + 'JSXOpeningElement:exit': function(node) { + var cachedLastAttributeEndPos = lastAttributeEndPos; + var cachedLastAttributeStartPos = lastAttributeStartPos; + var expectedNextLine; var tokens = getTokensLocations(node); var expectedLocation = getExpectedLocation(tokens); + + lastAttributeStartPos = null; + lastAttributeEndPos = null; + if (hasCorrectLocation(tokens, expectedLocation)) { return; } @@ -154,7 +169,7 @@ module.exports = function(context) { var correctColumn = getCorrectColumn(tokens, expectedLocation); if (correctColumn !== null) { - var expectedNextLine = tokens.lastProp && + expectedNextLine = tokens.lastProp && (tokens.lastProp.line === tokens.closing.line); data.details = ' (expected column ' + (correctColumn + 1) + (expectedNextLine ? ' on the next line)' : ')'); @@ -164,7 +179,46 @@ module.exports = function(context) { node: node, loc: tokens.closing, message: MESSAGE, - data: data + data: data, + fix: function(fixer) { + var closingTag = tokens.selfClosing ? '/>' : '>'; + switch (expectedLocation) { + case 'after-tag': + if (cachedLastAttributeEndPos) { + return fixer.replaceTextRange([cachedLastAttributeEndPos, node.end], + (expectedNextLine ? '\n' : '') + closingTag); + } + return fixer.replaceTextRange([node.name.loc.end.column + 1, node.end], + (expectedNextLine ? '\n' : '') + closingTag); + case 'after-props': + return fixer.replaceTextRange([cachedLastAttributeEndPos, node.end], + (expectedNextLine ? '\n' : '') + closingTag); + case 'props-aligned': + var spaces = new Array(cachedLastAttributeEndPos - cachedLastAttributeStartPos); + return fixer.replaceTextRange([cachedLastAttributeEndPos, node.end], + '\n' + spaces.join(' ') + closingTag); + case 'tag-aligned': + var tagSpaces = new Array(node.start); + return fixer.replaceTextRange([cachedLastAttributeEndPos, node.end], + '\n' + tagSpaces.join(' ') + closingTag); + case 'line-aligned': + var walkNode = node; + var lineSpaces = 0; + while ((walkNode = walkNode.parent)) { + if (walkNode.type === 'VariableDeclaration' || + walkNode.type === 'ReturnStatement' || + walkNode.type === 'ExpressionStatement') { + lineSpaces = walkNode.loc.start.column + 1; + break; + } + } + lineSpaces = new Array(lineSpaces); + return fixer.replaceTextRange([cachedLastAttributeEndPos, node.end], + '\n' + lineSpaces.join(' ') + closingTag); + default: + return true; + } + } }); } }; diff --git a/tests/lib/rules/jsx-closing-bracket-location.js b/tests/lib/rules/jsx-closing-bracket-location.js index 18b2eb0208..8c4e374235 100644 --- a/tests/lib/rules/jsx-closing-bracket-location.js +++ b/tests/lib/rules/jsx-closing-bracket-location.js @@ -358,6 +358,9 @@ ruleTester.run('jsx-closing-bracket-location', rule, { '' ].join('\n'), + output: [ + '' + ].join('\n'), parserOptions: parserOptions, errors: MESSAGE_AFTER_TAG }, { @@ -365,6 +368,9 @@ ruleTester.run('jsx-closing-bracket-location', rule, { '' ].join('\n'), + output: [ + '' + ].join('\n'), parserOptions: parserOptions, errors: MESSAGE_AFTER_PROPS }, { @@ -372,6 +378,9 @@ ruleTester.run('jsx-closing-bracket-location', rule, { '' ].join('\n'), + output: [ + '' + ].join('\n'), parserOptions: parserOptions, errors: MESSAGE_AFTER_PROPS }, { @@ -379,6 +388,11 @@ ruleTester.run('jsx-closing-bracket-location', rule, { '' ].join('\n'), + output: [ + '' + ].join('\n'), options: [{location: 'props-aligned'}], parserOptions: parserOptions, errors: [{ @@ -391,6 +405,11 @@ ruleTester.run('jsx-closing-bracket-location', rule, { '' ].join('\n'), + output: [ + '' + ].join('\n'), options: [{location: 'tag-aligned'}], parserOptions: parserOptions, errors: [{ @@ -403,6 +422,11 @@ ruleTester.run('jsx-closing-bracket-location', rule, { '' ].join('\n'), + output: [ + '' + ].join('\n'), options: [{location: 'line-aligned'}], parserOptions: parserOptions, errors: [{ @@ -416,6 +440,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, { ' foo', '/>' ].join('\n'), + output: [ + '' + ].join('\n'), options: [{location: 'after-props'}], parserOptions: parserOptions, errors: MESSAGE_AFTER_PROPS @@ -425,6 +453,11 @@ ruleTester.run('jsx-closing-bracket-location', rule, { ' foo', '/>' ].join('\n'), + output: [ + '' + ].join('\n'), options: [{location: 'props-aligned'}], parserOptions: parserOptions, errors: [{ @@ -438,6 +471,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, { ' foo', ' />' ].join('\n'), + output: [ + '' + ].join('\n'), options: [{location: 'after-props'}], parserOptions: parserOptions, errors: MESSAGE_AFTER_PROPS @@ -447,6 +484,11 @@ ruleTester.run('jsx-closing-bracket-location', rule, { ' foo', ' />' ].join('\n'), + output: [ + '' + ].join('\n'), options: [{location: 'tag-aligned'}], parserOptions: parserOptions, errors: [{ @@ -460,6 +502,11 @@ ruleTester.run('jsx-closing-bracket-location', rule, { ' foo', ' />' ].join('\n'), + output: [ + '' + ].join('\n'), options: [{location: 'line-aligned'}], parserOptions: parserOptions, errors: [{ @@ -473,6 +520,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, { ' foo', '>' ].join('\n'), + output: [ + '' + ].join('\n'), options: [{location: 'after-props'}], parserOptions: parserOptions, errors: MESSAGE_AFTER_PROPS @@ -482,6 +533,11 @@ ruleTester.run('jsx-closing-bracket-location', rule, { ' foo', '>' ].join('\n'), + output: [ + '' + ].join('\n'), options: [{location: 'props-aligned'}], parserOptions: parserOptions, errors: [{ @@ -495,6 +551,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, { ' foo', ' >' ].join('\n'), + output: [ + '' + ].join('\n'), options: [{location: 'after-props'}], parserOptions: parserOptions, errors: MESSAGE_AFTER_PROPS @@ -596,6 +656,13 @@ ruleTester.run('jsx-closing-bracket-location', rule, { ' />', '}' ].join('\n'), + output: [ + 'var x = function() {', + ' return ', + '}' + ].join('\n'), options: [{location: 'line-aligned'}], parserOptions: parserOptions, errors: [{ @@ -609,6 +676,11 @@ ruleTester.run('jsx-closing-bracket-location', rule, { ' foo', ' />' ].join('\n'), + output: [ + 'var x = ' + ].join('\n'), options: [{location: 'line-aligned'}], parserOptions: parserOptions, errors: [{