@@ -24,44 +24,75 @@ function splitLines(text) {
24
24
* @param {RuleContext } context
25
25
* @param {VElement } tag
26
26
* @param {VElement } sibling
27
+ * @param {number } lineDifference
27
28
*/
28
- function insertNewLine ( context , tag , sibling ) {
29
- context . report ( {
30
- messageId : 'always' ,
31
- loc : sibling . loc ,
32
- // @ts -ignore
33
- fix ( fixer ) {
34
- return fixer . insertTextAfter ( tag , '\n' )
35
- }
36
- } )
29
+ function insertNewLine ( context , tag , sibling , lineDifference ) {
30
+ const endTag = tag . endTag || tag . startTag
31
+
32
+ if ( lineDifference === 1 ) {
33
+ context . report ( {
34
+ messageId : 'always' ,
35
+ loc : sibling . loc ,
36
+ // @ts -ignore
37
+ fix ( fixer ) {
38
+ return fixer . insertTextAfter ( tag , '\n' )
39
+ }
40
+ } )
41
+ } else if ( lineDifference === 0 ) {
42
+ context . report ( {
43
+ messageId : 'always' ,
44
+ loc : sibling . loc ,
45
+ // @ts -ignore
46
+ fix ( fixer ) {
47
+ const lastSpaces = /** @type {RegExpExecArray } */ (
48
+ / ^ \s * / . exec ( context . getSourceCode ( ) . lines [ endTag . loc . start . line - 1 ] )
49
+ ) [ 0 ]
50
+
51
+ return fixer . insertTextAfter ( endTag , `\n\n${ lastSpaces } ` )
52
+ }
53
+ } )
54
+ }
37
55
}
38
56
39
57
/**
40
58
* @param {RuleContext } context
41
59
* @param {VEndTag | VStartTag } endTag
42
60
* @param {VElement } sibling
61
+ * @param {number } lineDifference
43
62
*/
44
- function removeExcessLines ( context , endTag , sibling ) {
45
- context . report ( {
46
- messageId : 'never' ,
47
- loc : sibling . loc ,
48
- // @ts -ignore
49
- fix ( fixer ) {
50
- const start = endTag . range [ 1 ]
51
- const end = sibling . range [ 0 ]
52
- const paddingText = context . getSourceCode ( ) . text . slice ( start , end )
53
- const textBetween = splitLines ( paddingText )
54
- let newTextBetween = `\n${ textBetween . pop ( ) } `
55
- for ( let i = textBetween . length - 1 ; i >= 0 ; i -- ) {
56
- if ( ! / ^ \s * $ / . test ( textBetween [ i ] ) ) {
57
- newTextBetween = `${ i === 0 ? '' : '\n' } ${
58
- textBetween [ i ]
59
- } ${ newTextBetween } `
63
+ function removeExcessLines ( context , endTag , sibling , lineDifference ) {
64
+ if ( lineDifference > 1 ) {
65
+ let hasOnlyTextBetween = true
66
+ for (
67
+ let i = endTag . loc . start . line ;
68
+ i < sibling . loc . start . line - 1 && hasOnlyTextBetween ;
69
+ i ++
70
+ ) {
71
+ hasOnlyTextBetween = ! / ^ \s * $ / . test ( context . getSourceCode ( ) . lines [ i ] )
72
+ }
73
+ if ( ! hasOnlyTextBetween ) {
74
+ context . report ( {
75
+ messageId : 'never' ,
76
+ loc : sibling . loc ,
77
+ // @ts -ignore
78
+ fix ( fixer ) {
79
+ const start = endTag . range [ 1 ]
80
+ const end = sibling . range [ 0 ]
81
+ const paddingText = context . getSourceCode ( ) . text . slice ( start , end )
82
+ const textBetween = splitLines ( paddingText )
83
+ let newTextBetween = `\n${ textBetween . pop ( ) } `
84
+ for ( let i = textBetween . length - 1 ; i >= 0 ; i -- ) {
85
+ if ( ! / ^ \s * $ / . test ( textBetween [ i ] ) ) {
86
+ newTextBetween = `${ i === 0 ? '' : '\n' } ${
87
+ textBetween [ i ]
88
+ } ${ newTextBetween } `
89
+ }
90
+ }
91
+ return fixer . replaceTextRange ( [ start , end ] , `${ newTextBetween } ` )
60
92
}
61
- }
62
- return fixer . replaceTextRange ( [ start , end ] , `${ newTextBetween } ` )
93
+ } )
63
94
}
64
- } )
95
+ }
65
96
}
66
97
67
98
// ------------------------------------------------------------------------------
@@ -72,11 +103,19 @@ function removeExcessLines(context, endTag, sibling) {
72
103
* @param {RuleContext } context
73
104
*/
74
105
function checkNewline ( context ) {
75
- /** @type {Array<{blankLine: "always" | "never", prev: string, next: string}> } */
106
+ /** @type {Array<{blankLine: "always" | "never" | "consistent" , prev: string, next: string}> } */
76
107
const configureList = context . options [ 0 ] || [
77
108
{ blankLine : 'always' , prev : '*' , next : '*' }
78
109
]
79
110
111
+ const reverseConfigureList = [ ...configureList ] . reverse ( )
112
+
113
+ /**
114
+ * It has the style of the first `blankLine="consistent"`.
115
+ * @type {Map<VElement, "always" | "never"> }
116
+ */
117
+ const firstConsistentBlankLines = new Map ( )
118
+
80
119
/**
81
120
* @param {VElement } block
82
121
*/
@@ -99,53 +138,36 @@ function checkNewline(context) {
99
138
100
139
const closestSibling = /** @type {VElement } */ ( lowerSiblings [ 0 ] )
101
140
102
- for ( let i = configureList . length - 1 ; i >= 0 ; -- i ) {
103
- const configure = configureList [ i ]
104
- const matched =
141
+ const configure = reverseConfigureList . find (
142
+ ( configure ) =>
105
143
( configure . prev === '*' || block . name === configure . prev ) &&
106
144
( configure . next === '*' || closestSibling . name === configure . next )
145
+ )
107
146
108
- if ( matched ) {
109
- const lineDifference =
110
- closestSibling . loc . start . line - endTag . loc . end . line
111
- if ( configure . blankLine === 'always' ) {
112
- if ( lineDifference === 1 ) {
113
- insertNewLine ( context , block , closestSibling )
114
- } else if ( lineDifference === 0 ) {
115
- context . report ( {
116
- messageId : 'always' ,
117
- loc : closestSibling . loc ,
118
- // @ts -ignore
119
- fix ( fixer ) {
120
- const lastSpaces = /** @type {RegExpExecArray } */ (
121
- / ^ \s * / . exec (
122
- context . getSourceCode ( ) . lines [ endTag . loc . start . line - 1 ]
123
- )
124
- ) [ 0 ]
125
-
126
- return fixer . insertTextAfter ( endTag , `\n\n${ lastSpaces } ` )
127
- }
128
- } )
129
- }
130
- } else {
131
- if ( lineDifference > 1 ) {
132
- let hasOnlyTextBetween = true
133
- for (
134
- let i = endTag . loc . start . line ;
135
- i < closestSibling . loc . start . line - 1 && hasOnlyTextBetween ;
136
- i ++
137
- ) {
138
- hasOnlyTextBetween = ! / ^ \s * $ / . test (
139
- context . getSourceCode ( ) . lines [ i ]
140
- )
141
- }
142
- if ( ! hasOnlyTextBetween ) {
143
- removeExcessLines ( context , endTag , closestSibling )
144
- }
145
- }
146
- }
147
- break
147
+ if ( ! configure ) {
148
+ return
149
+ }
150
+ const lineDifference = closestSibling . loc . start . line - endTag . loc . end . line
151
+
152
+ let blankLine = configure . blankLine
153
+ if ( blankLine === 'consistent' ) {
154
+ const firstConsistentBlankLine = firstConsistentBlankLines . get (
155
+ block . parent
156
+ )
157
+ if ( firstConsistentBlankLine == null ) {
158
+ firstConsistentBlankLines . set (
159
+ block . parent ,
160
+ lineDifference > 1 ? 'always' : 'never'
161
+ )
162
+ return
148
163
}
164
+ blankLine = firstConsistentBlankLine
165
+ }
166
+
167
+ if ( blankLine === 'always' ) {
168
+ insertNewLine ( context , block , closestSibling , lineDifference )
169
+ } else {
170
+ removeExcessLines ( context , endTag , closestSibling , lineDifference )
149
171
}
150
172
}
151
173
}
@@ -166,7 +188,7 @@ module.exports = {
166
188
items : {
167
189
type : 'object' ,
168
190
properties : {
169
- blankLine : { enum : [ 'always' , 'never' ] } ,
191
+ blankLine : { enum : [ 'always' , 'never' , 'consistent' ] } ,
170
192
prev : { type : 'string' } ,
171
193
next : { type : 'string' }
172
194
} ,
0 commit comments