@@ -23,83 +23,111 @@ module.exports = {
23
23
create : function ( context ) {
24
24
const sourceCode = context . getSourceCode ( ) ;
25
25
26
- function generateFixFunction ( elementInLine ) {
27
- const nodeText = sourceCode . getText ( elementInLine ) ;
28
-
29
- return function ( fixer ) {
30
- return fixer . replaceText ( elementInLine , `\n${ nodeText } ` ) ;
31
- } ;
32
- }
33
-
34
- function elementDoesOpenOrCloseOnLine ( element , line ) {
35
- const reportableLines = [ element . openingElement , element . closingElement ] . reduce ( ( lines , el ) => {
36
- if ( ! el ) {
37
- return lines ;
38
- }
39
-
40
- return lines . concat ( [ el . loc . start . line , el . loc . end . line ] ) ;
41
- } , [ ] ) ;
42
-
43
-
44
- return reportableLines . indexOf ( line ) !== - 1 ;
45
- }
46
-
47
- function anyElementDoesOpenOrCloseOnLine ( elements , line ) {
48
- return elements . some ( element => elementDoesOpenOrCloseOnLine ( element , line ) ) ;
49
- }
50
-
51
26
return {
52
- JSXOpeningElement : function ( node ) {
53
- if ( ! node . parent ) {
27
+ JSXElement : function ( node ) {
28
+ const children = node . children ;
29
+
30
+ if ( ! children || ! children . length ) {
54
31
return ;
55
32
}
56
33
57
- const openingStartLine = node . loc . start . line ;
34
+ const openingElement = node . openingElement ;
35
+ const closingElement = node . closingElement ;
36
+ const openingElementEndLine = openingElement . loc . end . line ;
37
+ const closingElementStartLine = closingElement . loc . start . line ;
58
38
59
- // Parent
60
- if ( node . parent . parent && node . parent . parent . openingElement ) {
61
- const parentOpeningStartLine = node . parent . parent . openingElement . loc . end . line ;
39
+ const childrenGroupedByLine = { } ;
62
40
63
- if ( parentOpeningStartLine === openingStartLine ) {
64
- context . report ( {
65
- node : node ,
66
- message : `Opening tag for Element \`${ node . name . name } \` must be placed on a new line` ,
67
- fix : generateFixFunction ( node )
68
- } ) ;
69
- }
70
- }
41
+ children . forEach ( child => {
42
+ let countNewLinesBeforeContent = 0 ;
43
+ let countNewLinesAfterContent = 0 ;
71
44
72
- // Siblings
73
- if ( node . parent . parent && node . parent . parent . children && node . parent . parent . children . length ) {
74
- const firstSiblingOnLine = node . parent . parent . children . find ( sibling => (
75
- sibling . type === 'JSXElement' && elementDoesOpenOrCloseOnLine ( sibling , openingStartLine )
76
- ) ) ;
45
+ if ( child . type === 'Literal' ) {
46
+ if ( child . value . match ( / ^ \s * $ / ) ) {
47
+ return ;
48
+ }
77
49
78
- if ( firstSiblingOnLine !== node . parent ) {
79
- context . report ( {
80
- node : node ,
81
- message : `Opening tag for Element \`${ node . name . name } \` must be placed on a new line` ,
82
- fix : generateFixFunction ( node )
83
- } ) ;
50
+ countNewLinesBeforeContent = ( child . raw . match ( / ^ * \n / g) || [ ] ) . length ;
51
+ countNewLinesAfterContent = ( child . raw . match ( / \n * $ / g) || [ ] ) . length ;
84
52
}
85
- }
86
- } ,
87
-
88
- JSXClosingElement : function ( node ) {
89
- if ( ! node . parent || ! node . parent . children . length ) {
90
- return ;
91
- }
92
53
93
- const closingElementStartLine = node . loc . end . line ;
54
+ const startLine = child . loc . start . line + countNewLinesBeforeContent ;
55
+ const endLine = child . loc . end . line - countNewLinesAfterContent ;
56
+
57
+ if ( startLine === endLine ) {
58
+ if ( ! childrenGroupedByLine [ startLine ] ) {
59
+ childrenGroupedByLine [ startLine ] = [ ] ;
60
+ }
61
+ childrenGroupedByLine [ startLine ] . push ( child ) ;
62
+ } else {
63
+ if ( ! childrenGroupedByLine [ startLine ] ) {
64
+ childrenGroupedByLine [ startLine ] = [ ] ;
65
+ }
66
+ childrenGroupedByLine [ startLine ] . push ( child ) ;
67
+ if ( ! childrenGroupedByLine [ endLine ] ) {
68
+ childrenGroupedByLine [ endLine ] = [ ] ;
69
+ }
70
+ childrenGroupedByLine [ endLine ] . push ( child ) ;
71
+ }
72
+ } ) ;
94
73
95
- if ( ! anyElementDoesOpenOrCloseOnLine ( node . parent . children , closingElementStartLine ) ) {
96
- return ;
97
- }
74
+ Object . keys ( childrenGroupedByLine ) . forEach ( line => {
75
+ line = parseInt ( line , 10 ) ;
76
+ const firstIndex = 0 ;
77
+ const lastIndex = childrenGroupedByLine [ line ] . length - 1 ;
78
+
79
+ childrenGroupedByLine [ line ] . forEach ( ( child , i ) => {
80
+ let prevChild ;
81
+ if ( i === firstIndex ) {
82
+ if ( line === openingElementEndLine ) {
83
+ prevChild = openingElement ;
84
+ }
85
+ } else {
86
+ prevChild = childrenGroupedByLine [ line ] [ i - 1 ] ;
87
+ }
88
+ let nextChild ;
89
+ if ( i === lastIndex ) {
90
+ if ( line === closingElementStartLine ) {
91
+ nextChild = closingElement ;
92
+ }
93
+ } else {
94
+ // We don't need to append a trailing because the next child will prepend a leading.
95
+ // nextChild = childrenGroupedByLine[line][i + 1];
96
+ }
97
+
98
+ function spaceBetweenPrev ( ) {
99
+ return ( prevChild . type === 'Literal' && prevChild . raw . match ( / $ / ) ) ||
100
+ ( child . type === 'Literal' && child . raw . match ( / ^ / ) ) ||
101
+ sourceCode . isSpaceBetweenTokens ( prevChild , child ) ;
102
+ }
103
+ function spaceBetweenNext ( ) {
104
+ return ( nextChild . type === 'Literal' && nextChild . raw . match ( / ^ / ) ) ||
105
+ ( child . type === 'Literal' && child . raw . match ( / $ / ) ) ||
106
+ sourceCode . isSpaceBetweenTokens ( child , nextChild ) ;
107
+ }
108
+ const leadingSpace = prevChild && spaceBetweenPrev ( ) ? '\n{\' \'}' : '' ;
109
+ const trailingSpace = nextChild && spaceBetweenNext ( ) ? '{\' \'}\n' : '' ;
110
+ const leadingNewLine = prevChild ? '\n' : '' ;
111
+ const trailingNewLine = nextChild ? '\n' : '' ;
112
+
113
+ if ( ! prevChild && ! nextChild ) {
114
+ return ;
115
+ }
116
+
117
+ const source = sourceCode . getText ( child ) ;
118
+
119
+ function nodeDescriptor ( n ) {
120
+ return n . openingElement ? n . openingElement . name . name : source . replace ( / \n / g, '' ) ;
121
+ }
98
122
99
- context . report ( {
100
- node : node ,
101
- message : `Closing tag for Element \`${ node . name . name } \` must be placed on a new line` ,
102
- fix : generateFixFunction ( node )
123
+ context . report ( {
124
+ node : child ,
125
+ message : `\`${ nodeDescriptor ( child ) } \` must be placed on a new line` ,
126
+ fix : function ( fixer ) {
127
+ return fixer . replaceText ( child , `${ leadingSpace } ${ leadingNewLine } ${ source } ${ trailingNewLine } ${ trailingSpace } ` ) ;
128
+ }
129
+ } ) ;
130
+ } ) ;
103
131
} ) ;
104
132
}
105
133
} ;
0 commit comments