diff --git a/lib/rules/jsx-closing-bracket-location.js b/lib/rules/jsx-closing-bracket-location.js index fb25c4f4db..9da181467e 100644 --- a/lib/rules/jsx-closing-bracket-location.js +++ b/lib/rules/jsx-closing-bracket-location.js @@ -146,6 +146,33 @@ module.exports = { } } + /** + * Get the characters used for indentation on the line to be matched + * @param {Object} tokens Locations of the opening bracket, closing bracket and last prop + * @param {String} expectedLocation Expected location for the closing bracket + * @param {Number} correctColumn Expected column for the closing bracket + * @return {String} The characters used for indentation + */ + function getIndentation(tokens, expectedLocation, correctColumn) { + var indentation, spaces = []; + switch (expectedLocation) { + case 'props-aligned': + indentation = /^\s*/.exec(sourceCode.lines[tokens.lastProp.firstLine - 1])[0]; + break; + case 'tag-aligned': + case 'line-aligned': + indentation = /^\s*/.exec(sourceCode.lines[tokens.opening.line - 1])[0]; + break; + default: + indentation = ''; + } + if (indentation.length + 1 < correctColumn) { + // Non-whitespace characters were included in the column offset + spaces = new Array(+correctColumn + 1 - indentation.length); + } + return indentation + spaces.join(' '); + } + /** * Get the locations of the opening bracket, closing bracket, last prop, and * start of opening line. @@ -244,9 +271,8 @@ module.exports = { case 'props-aligned': case 'tag-aligned': case 'line-aligned': - var spaces = new Array(+correctColumn + 1); return fixer.replaceTextRange([cachedLastAttributeEndPos, node.end], - '\n' + spaces.join(' ') + closingTag); + '\n' + getIndentation(tokens, expectedLocation, correctColumn) + 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 9a2f2a147d..7571a1e580 100644 --- a/tests/lib/rules/jsx-closing-bracket-location.js +++ b/tests/lib/rules/jsx-closing-bracket-location.js @@ -398,6 +398,334 @@ ruleTester.run('jsx-closing-bracket-location', rule, { ].join('\n'), options: [{location: 'tag-aligned'}], parserOptions: parserOptions + }, { + code: [ + '' + ].join('\n'), + parserOptions: parserOptions + }, { + code: [ + '' + ].join('\n'), + options: ['after-props'], + parserOptions: parserOptions + }, { + code: [ + '' + ].join('\n'), + options: ['props-aligned'], + parserOptions: parserOptions + }, { + code: [ + '' + ].join('\n'), + options: [{location: 'after-props'}], + parserOptions: parserOptions + }, { + code: [ + '' + ].join('\n'), + options: [{location: 'tag-aligned'}], + parserOptions: parserOptions + }, { + code: [ + '' + ].join('\n'), + options: [{location: 'line-aligned'}], + parserOptions: parserOptions + }, { + code: [ + '' + ].join('\n'), + options: [{location: 'props-aligned'}], + parserOptions: parserOptions + }, { + code: [ + '' + ].join('\n'), + options: [{location: 'tag-aligned'}], + parserOptions: parserOptions + }, { + code: [ + '' + ].join('\n'), + options: [{location: 'line-aligned'}], + parserOptions: parserOptions + }, { + code: [ + '' + ].join('\n'), + options: [{location: 'props-aligned'}], + parserOptions: parserOptions + }, { + code: [ + '' + ].join('\n'), + options: [{location: 'after-props'}], + parserOptions: parserOptions + }, { + code: [ + '' + ].join('\n'), + options: [{location: 'props-aligned'}], + parserOptions: parserOptions + }, { + code: [ + '' + ].join('\n'), + options: [{location: 'tag-aligned'}], + parserOptions: parserOptions + }, { + code: [ + '' + ].join('\n'), + options: [{location: 'line-aligned'}], + parserOptions: parserOptions + }, { + code: [ + '' + ].join('\n'), + options: [{location: 'after-props'}], + parserOptions: parserOptions + }, { + code: [ + '' + ].join('\n'), + options: [{location: 'props-aligned'}], + parserOptions: parserOptions + }, { + code: [ + '' + ].join('\n'), + options: [{location: 'tag-aligned'}], + parserOptions: parserOptions + }, { + code: [ + '' + ].join('\n'), + options: [{location: 'line-aligned'}], + parserOptions: parserOptions + }, { + code: [ + '', + '\t', + '' + ].join('\n'), + options: [{selfClosing: 'after-props'}], + parserOptions: parserOptions + }, { + code: [ + '', + '\t', + '' + ].join('\n'), + options: [{selfClosing: 'after-props'}], + parserOptions: parserOptions + }, { + code: [ + '', + '\t', + '' + ].join('\n'), + options: [{nonEmpty: 'after-props'}], + parserOptions: parserOptions + }, { + code: [ + '', + '\t', + '' + ].join('\n'), + options: [{selfClosing: 'props-aligned'}], + parserOptions: parserOptions + }, { + code: [ + '', + '\t', + '' + ].join('\n'), + options: [{nonEmpty: 'props-aligned'}], + parserOptions: parserOptions + }, { + code: [ + 'var x = function() {', + '\treturn ', + '\t\t\tbar', + '\t ', + '}' + ].join('\n'), + options: [{location: 'tag-aligned'}], + parserOptions: parserOptions + }, { + code: [ + 'var x = function() {', + '\treturn ', + '}' + ].join('\n'), + options: [{location: 'tag-aligned'}], + parserOptions: parserOptions + }, { + code: [ + 'var x = ' + ].join('\n'), + options: [{location: 'tag-aligned'}], + parserOptions: parserOptions + }, { + code: [ + 'var x = function() {', + '\treturn ', + '}' + ].join('\n'), + options: [{location: 'line-aligned'}], + parserOptions: parserOptions + }, { + code: [ + 'var x = ' + ].join('\n'), + options: [{location: 'line-aligned'}], + parserOptions: parserOptions + }, { + code: [ + '', + '\t', + '' + ].join('\n'), + options: [{location: 'line-aligned'}], + parserOptions: parserOptions + }, { + code: [ + '', + '\t{baz && }', + '' + ].join('\n'), + options: [{location: 'line-aligned'}], + parserOptions: parserOptions + }, { + code: [ + '', + '\t', + '\t', + '\t', + '' + ].join('\n'), + options: [{ + nonEmpty: false, + selfClosing: 'after-props' + }], + parserOptions: parserOptions + }, { + code: [ + '', + '\t', + '\t', + '\t', + '' + ].join('\n'), + options: [{ + nonEmpty: 'after-props', + selfClosing: false + }], + parserOptions: parserOptions + }, { + code: [ + '
', + '\tSome text', + '
' + ].join('\n'), + options: [{location: 'tag-aligned'}], + parserOptions: parserOptions }], invalid: [{ @@ -959,5 +1287,534 @@ ruleTester.run('jsx-closing-bracket-location', rule, { line: 4, column: 7 }] + }, { + code: [ + '' + ].join('\n'), + output: [ + '' + ].join('\n'), + options: [{location: 'props-aligned'}], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_PROPS_ALIGNED, 2, true), + line: 2, + column: 6 + }] + }, { + code: [ + '' + ].join('\n'), + output: [ + '' + ].join('\n'), + options: [{location: 'tag-aligned'}], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_TAG_ALIGNED, 1, true), + line: 2, + column: 6 + }] + }, { + code: [ + '' + ].join('\n'), + output: [ + '' + ].join('\n'), + options: [{location: 'line-aligned'}], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_LINE_ALIGNED, 1, true), + line: 2, + column: 6 + }] + }, { + code: [ + '' + ].join('\n'), + output: [ + '' + ].join('\n'), + options: [{location: 'after-props'}], + parserOptions: parserOptions, + errors: MESSAGE_AFTER_PROPS + }, { + code: [ + '' + ].join('\n'), + output: [ + '' + ].join('\n'), + options: [{location: 'props-aligned'}], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_PROPS_ALIGNED, 2, false), + line: 3, + column: 1 + }] + }, { + code: [ + '' + ].join('\n'), + output: [ + '' + ].join('\n'), + options: [{location: 'after-props'}], + parserOptions: parserOptions, + errors: MESSAGE_AFTER_PROPS + }, { + code: [ + '' + ].join('\n'), + output: [ + '' + ].join('\n'), + options: [{location: 'tag-aligned'}], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_TAG_ALIGNED, 1, false), + line: 3, + column: 2 + }] + }, { + code: [ + '' + ].join('\n'), + output: [ + '' + ].join('\n'), + options: [{location: 'line-aligned'}], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_LINE_ALIGNED, 1, false), + line: 3, + column: 2 + }] + }, { + code: [ + '' + ].join('\n'), + output: [ + '' + ].join('\n'), + options: [{location: 'after-props'}], + parserOptions: parserOptions, + errors: MESSAGE_AFTER_PROPS + }, { + code: [ + '' + ].join('\n'), + output: [ + '' + ].join('\n'), + options: [{location: 'props-aligned'}], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_PROPS_ALIGNED, 2, false), + line: 3, + column: 1 + }] + }, { + code: [ + '' + ].join('\n'), + output: [ + '' + ].join('\n'), + options: [{location: 'after-props'}], + parserOptions: parserOptions, + errors: MESSAGE_AFTER_PROPS + }, { + code: [ + '' + ].join('\n'), + output: [ + '' + ].join('\n'), + options: [{location: 'tag-aligned'}], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_TAG_ALIGNED, 1, false), + line: 3, + column: 2 + }] + }, { + code: [ + '' + ].join('\n'), + output: [ + '' + ].join('\n'), + options: [{location: 'line-aligned'}], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_LINE_ALIGNED, 1, false), + line: 3, + column: 2 + }] + }, { + code: [ + '', // <-- + '\t', + '' + ].join('\n'), + output: [ + '', + '\t', + '' + ].join('\n'), + options: [{selfClosing: 'props-aligned'}], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_TAG_ALIGNED, 1, true), + line: 2, + column: 7 + }] + }, { + code: [ + 'const Button = function(props) {', + '\treturn (', + '\t\t', + '\t\t\tButton Text', + '\t\t', + '\t);', + '};' + ].join('\n'), + output: [ + 'const Button = function(props) {', + '\treturn (', + '\t\t', + '\t\t\tButton Text', + '\t\t', + '\t);', + '};' + ].join('\n'), + options: ['props-aligned'], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_PROPS_ALIGNED, 4, false), + line: 6, + column: 19 + }] + }, { + code: [ + 'const Button = function(props) {', + '\treturn (', + '\t\t', + '\t\t\tButton Text', + '\t\t', + '\t);', + '};' + ].join('\n'), + output: [ + 'const Button = function(props) {', + '\treturn (', + '\t\t', + '\t\t\tButton Text', + '\t\t', + '\t);', + '};' + ].join('\n'), + options: ['tag-aligned'], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_TAG_ALIGNED, 3, false), + line: 6, + column: 19 + }] + }, { + code: [ + 'const Button = function(props) {', + '\treturn (', + '\t\t', + '\t\t\tButton Text', + '\t\t', + '\t);', + '};' + ].join('\n'), + output: [ + 'const Button = function(props) {', + '\treturn (', + '\t\t', + '\t\t\tButton Text', + '\t\t', + '\t);', + '};' + ].join('\n'), + options: ['line-aligned'], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_LINE_ALIGNED, 3, false), + line: 6, + column: 19 + }] + }, { + code: [ + '', + '\t', // <-- + '' + ].join('\n'), + output: [ + '', + '\t', + '' + ].join('\n'), + options: [{nonEmpty: 'props-aligned'}], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_TAG_ALIGNED, 2, false), + line: 6, + column: 3 + }] + }, { + code: [ + '', // <-- + '\t', + '' + ].join('\n'), + output: [ + '', + '\t', + '' + ].join('\n'), + options: [{selfClosing: 'after-props'}], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_TAG_ALIGNED, 1, true), + line: 2, + column: 7 + }] + }, { + code: [ + '', + '\t', // <-- + '' + ].join('\n'), + output: [ + '', + '\t', // <-- + '' + ].join('\n'), + options: [{nonEmpty: 'after-props'}], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_TAG_ALIGNED, 2, false), + line: 5, + column: 3 + }] + }, { + code: [ + 'var x = function() {', + '\treturn ', + '}' + ].join('\n'), + output: [ + 'var x = function() {', + '\treturn ', + '}' + ].join('\n'), + options: [{location: 'line-aligned'}], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_LINE_ALIGNED, 2, false), + line: 4, + column: 6 + }] + }, { + code: [ + 'var x = ' + ].join('\n'), + output: [ + 'var x = ' + ].join('\n'), + options: [{location: 'line-aligned'}], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_LINE_ALIGNED, 1, false), + line: 3, + column: 9 + }] + }, { + code: [ + 'var x = (', + '\t', + ')' + ].join('\n'), + output: [ + 'var x = (', + '\t', + ')' + ].join('\n'), + options: [{location: 'line-aligned'}], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_LINE_ALIGNED, 2, true), + line: 4, + column: 14 + }] + }, { + code: [ + 'var x = (', + '\t} />', + ')' + ].join('\n'), + output: [ + 'var x = (', + '\t}', + '\t/>', + ')' + ].join('\n'), + options: [{location: 'line-aligned'}], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_LINE_ALIGNED, 2, true), + line: 3, + column: 21 + }] + }, { + code: [ + 'var x = (', + '\t', + ')' + ].join('\n'), + output: [ + 'var x = (', + '\t', + ')' + ].join('\n'), + options: [{location: 'line-aligned'}], + parserOptions: parserOptions, + errors: [MESSAGE_AFTER_TAG] + }, { + code: [ + '
', + '\tSome text', + '
' + ].join('\n'), + output: [ + '
', + '\tSome text', + '
' + ].join('\n'), + options: [{location: 'tag-aligned'}], + parserOptions: parserOptions, + errors: [{ + message: messageWithDetails(MESSAGE_TAG_ALIGNED, 1, true), + line: 4, + column: 6 + }] }] });