Skip to content

Commit 85511ad

Browse files
committed
Merge pull request #511 from KevinGrandon/closing_bracket_autofix
Add auto fix for jsx-closing-bracket-location.
2 parents 57a59cd + deb8e4e commit 85511ad

File tree

2 files changed

+129
-3
lines changed

2 files changed

+129
-3
lines changed

lib/rules/jsx-closing-bracket-location.js

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,25 @@ module.exports = function(context) {
142142
};
143143
}
144144

145+
var lastAttributeEndPos;
146+
var lastAttributeStartPos;
147+
145148
return {
146-
JSXOpeningElement: function(node) {
149+
JSXAttribute: function(node) {
150+
lastAttributeEndPos = node.end;
151+
lastAttributeStartPos = node.start;
152+
},
153+
154+
'JSXOpeningElement:exit': function(node) {
155+
var cachedLastAttributeEndPos = lastAttributeEndPos;
156+
var cachedLastAttributeStartPos = lastAttributeStartPos;
157+
var expectedNextLine;
147158
var tokens = getTokensLocations(node);
148159
var expectedLocation = getExpectedLocation(tokens);
160+
161+
lastAttributeStartPos = null;
162+
lastAttributeEndPos = null;
163+
149164
if (hasCorrectLocation(tokens, expectedLocation)) {
150165
return;
151166
}
@@ -154,7 +169,7 @@ module.exports = function(context) {
154169
var correctColumn = getCorrectColumn(tokens, expectedLocation);
155170

156171
if (correctColumn !== null) {
157-
var expectedNextLine = tokens.lastProp &&
172+
expectedNextLine = tokens.lastProp &&
158173
(tokens.lastProp.line === tokens.closing.line);
159174
data.details = ' (expected column ' + (correctColumn + 1) +
160175
(expectedNextLine ? ' on the next line)' : ')');
@@ -164,7 +179,46 @@ module.exports = function(context) {
164179
node: node,
165180
loc: tokens.closing,
166181
message: MESSAGE,
167-
data: data
182+
data: data,
183+
fix: function(fixer) {
184+
var closingTag = tokens.selfClosing ? '/>' : '>';
185+
switch (expectedLocation) {
186+
case 'after-tag':
187+
if (cachedLastAttributeEndPos) {
188+
return fixer.replaceTextRange([cachedLastAttributeEndPos, node.end],
189+
(expectedNextLine ? '\n' : '') + closingTag);
190+
}
191+
return fixer.replaceTextRange([node.name.loc.end.column + 1, node.end],
192+
(expectedNextLine ? '\n' : '') + closingTag);
193+
case 'after-props':
194+
return fixer.replaceTextRange([cachedLastAttributeEndPos, node.end],
195+
(expectedNextLine ? '\n' : '') + closingTag);
196+
case 'props-aligned':
197+
var spaces = new Array(cachedLastAttributeEndPos - cachedLastAttributeStartPos);
198+
return fixer.replaceTextRange([cachedLastAttributeEndPos, node.end],
199+
'\n' + spaces.join(' ') + closingTag);
200+
case 'tag-aligned':
201+
var tagSpaces = new Array(node.start);
202+
return fixer.replaceTextRange([cachedLastAttributeEndPos, node.end],
203+
'\n' + tagSpaces.join(' ') + closingTag);
204+
case 'line-aligned':
205+
var walkNode = node;
206+
var lineSpaces = 0;
207+
while ((walkNode = walkNode.parent)) {
208+
if (walkNode.type === 'VariableDeclaration' ||
209+
walkNode.type === 'ReturnStatement' ||
210+
walkNode.type === 'ExpressionStatement') {
211+
lineSpaces = walkNode.loc.start.column + 1;
212+
break;
213+
}
214+
}
215+
lineSpaces = new Array(lineSpaces);
216+
return fixer.replaceTextRange([cachedLastAttributeEndPos, node.end],
217+
'\n' + lineSpaces.join(' ') + closingTag);
218+
default:
219+
return true;
220+
}
221+
}
168222
});
169223
}
170224
};

tests/lib/rules/jsx-closing-bracket-location.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,27 +358,41 @@ ruleTester.run('jsx-closing-bracket-location', rule, {
358358
'<App ',
359359
'/>'
360360
].join('\n'),
361+
output: [
362+
'<App />'
363+
].join('\n'),
361364
parserOptions: parserOptions,
362365
errors: MESSAGE_AFTER_TAG
363366
}, {
364367
code: [
365368
'<App foo ',
366369
'/>'
367370
].join('\n'),
371+
output: [
372+
'<App foo/>'
373+
].join('\n'),
368374
parserOptions: parserOptions,
369375
errors: MESSAGE_AFTER_PROPS
370376
}, {
371377
code: [
372378
'<App foo',
373379
'></App>'
374380
].join('\n'),
381+
output: [
382+
'<App foo></App>'
383+
].join('\n'),
375384
parserOptions: parserOptions,
376385
errors: MESSAGE_AFTER_PROPS
377386
}, {
378387
code: [
379388
'<App ',
380389
' foo />'
381390
].join('\n'),
391+
output: [
392+
'<App ',
393+
' foo',
394+
' />'
395+
].join('\n'),
382396
options: [{location: 'props-aligned'}],
383397
parserOptions: parserOptions,
384398
errors: [{
@@ -391,6 +405,11 @@ ruleTester.run('jsx-closing-bracket-location', rule, {
391405
'<App ',
392406
' foo />'
393407
].join('\n'),
408+
output: [
409+
'<App ',
410+
' foo',
411+
'/>'
412+
].join('\n'),
394413
options: [{location: 'tag-aligned'}],
395414
parserOptions: parserOptions,
396415
errors: [{
@@ -403,6 +422,11 @@ ruleTester.run('jsx-closing-bracket-location', rule, {
403422
'<App ',
404423
' foo />'
405424
].join('\n'),
425+
output: [
426+
'<App ',
427+
' foo',
428+
'/>'
429+
].join('\n'),
406430
options: [{location: 'line-aligned'}],
407431
parserOptions: parserOptions,
408432
errors: [{
@@ -416,6 +440,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, {
416440
' foo',
417441
'/>'
418442
].join('\n'),
443+
output: [
444+
'<App ',
445+
' foo/>'
446+
].join('\n'),
419447
options: [{location: 'after-props'}],
420448
parserOptions: parserOptions,
421449
errors: MESSAGE_AFTER_PROPS
@@ -425,6 +453,11 @@ ruleTester.run('jsx-closing-bracket-location', rule, {
425453
' foo',
426454
'/>'
427455
].join('\n'),
456+
output: [
457+
'<App ',
458+
' foo',
459+
' />'
460+
].join('\n'),
428461
options: [{location: 'props-aligned'}],
429462
parserOptions: parserOptions,
430463
errors: [{
@@ -438,6 +471,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, {
438471
' foo',
439472
' />'
440473
].join('\n'),
474+
output: [
475+
'<App ',
476+
' foo/>'
477+
].join('\n'),
441478
options: [{location: 'after-props'}],
442479
parserOptions: parserOptions,
443480
errors: MESSAGE_AFTER_PROPS
@@ -447,6 +484,11 @@ ruleTester.run('jsx-closing-bracket-location', rule, {
447484
' foo',
448485
' />'
449486
].join('\n'),
487+
output: [
488+
'<App ',
489+
' foo',
490+
'/>'
491+
].join('\n'),
450492
options: [{location: 'tag-aligned'}],
451493
parserOptions: parserOptions,
452494
errors: [{
@@ -460,6 +502,11 @@ ruleTester.run('jsx-closing-bracket-location', rule, {
460502
' foo',
461503
' />'
462504
].join('\n'),
505+
output: [
506+
'<App ',
507+
' foo',
508+
'/>'
509+
].join('\n'),
463510
options: [{location: 'line-aligned'}],
464511
parserOptions: parserOptions,
465512
errors: [{
@@ -473,6 +520,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, {
473520
' foo',
474521
'></App>'
475522
].join('\n'),
523+
output: [
524+
'<App',
525+
' foo></App>'
526+
].join('\n'),
476527
options: [{location: 'after-props'}],
477528
parserOptions: parserOptions,
478529
errors: MESSAGE_AFTER_PROPS
@@ -482,6 +533,11 @@ ruleTester.run('jsx-closing-bracket-location', rule, {
482533
' foo',
483534
'></App>'
484535
].join('\n'),
536+
output: [
537+
'<App',
538+
' foo',
539+
' ></App>'
540+
].join('\n'),
485541
options: [{location: 'props-aligned'}],
486542
parserOptions: parserOptions,
487543
errors: [{
@@ -495,6 +551,10 @@ ruleTester.run('jsx-closing-bracket-location', rule, {
495551
' foo',
496552
' ></App>'
497553
].join('\n'),
554+
output: [
555+
'<App',
556+
' foo></App>'
557+
].join('\n'),
498558
options: [{location: 'after-props'}],
499559
parserOptions: parserOptions,
500560
errors: MESSAGE_AFTER_PROPS
@@ -596,6 +656,13 @@ ruleTester.run('jsx-closing-bracket-location', rule, {
596656
' />',
597657
'}'
598658
].join('\n'),
659+
output: [
660+
'var x = function() {',
661+
' return <App',
662+
' foo',
663+
' />',
664+
'}'
665+
].join('\n'),
599666
options: [{location: 'line-aligned'}],
600667
parserOptions: parserOptions,
601668
errors: [{
@@ -609,6 +676,11 @@ ruleTester.run('jsx-closing-bracket-location', rule, {
609676
' foo',
610677
' />'
611678
].join('\n'),
679+
output: [
680+
'var x = <App',
681+
' foo',
682+
'/>'
683+
].join('\n'),
612684
options: [{location: 'line-aligned'}],
613685
parserOptions: parserOptions,
614686
errors: [{

0 commit comments

Comments
 (0)