@@ -24,44 +24,75 @@ function splitLines(text) {
2424 * @param {RuleContext } context
2525 * @param {VElement } tag
2626 * @param {VElement } sibling
27+ * @param {number } lineDifference
2728 */
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+ }
3755}
3856
3957/**
4058 * @param {RuleContext } context
4159 * @param {VEndTag | VStartTag } endTag
4260 * @param {VElement } sibling
61+ * @param {number } lineDifference
4362 */
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 } ` )
6092 }
61- }
62- return fixer . replaceTextRange ( [ start , end ] , `${ newTextBetween } ` )
93+ } )
6394 }
64- } )
95+ }
6596}
6697
6798// ------------------------------------------------------------------------------
@@ -72,11 +103,19 @@ function removeExcessLines(context, endTag, sibling) {
72103 * @param {RuleContext } context
73104 */
74105function checkNewline ( context ) {
75- /** @type {Array<{blankLine: "always" | "never", prev: string, next: string}> } */
106+ /** @type {Array<{blankLine: "always" | "never" | "consistent" , prev: string, next: string}> } */
76107 const configureList = context . options [ 0 ] || [
77108 { blankLine : 'always' , prev : '*' , next : '*' }
78109 ]
79110
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+
80119 /**
81120 * @param {VElement } block
82121 */
@@ -99,53 +138,36 @@ function checkNewline(context) {
99138
100139 const closestSibling = /** @type {VElement } */ ( lowerSiblings [ 0 ] )
101140
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 ) =>
105143 ( configure . prev === '*' || block . name === configure . prev ) &&
106144 ( configure . next === '*' || closestSibling . name === configure . next )
145+ )
107146
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
148163 }
164+ blankLine = firstConsistentBlankLine
165+ }
166+
167+ if ( blankLine === 'always' ) {
168+ insertNewLine ( context , block , closestSibling , lineDifference )
169+ } else {
170+ removeExcessLines ( context , endTag , closestSibling , lineDifference )
149171 }
150172 }
151173}
@@ -166,7 +188,7 @@ module.exports = {
166188 items : {
167189 type : 'object' ,
168190 properties : {
169- blankLine : { enum : [ 'always' , 'never' ] } ,
191+ blankLine : { enum : [ 'always' , 'never' , 'consistent' ] } ,
170192 prev : { type : 'string' } ,
171193 next : { type : 'string' }
172194 } ,
0 commit comments