@@ -20,9 +20,13 @@ const MESSAGE_IDS = {
2020 EXTRA_BETWEEN : "unexpectedBetween" ,
2121 EXTRA_AFTER : "unexpectedAfter" ,
2222 EXTRA_BEFORE : "unexpectedBefore" ,
23+ EXTRA_BEFORE_CLOSE : "unexpectedBeforeClose" ,
2324 MISSING_BEFORE : "missingBefore" ,
2425 MISSING_BEFORE_SELF_CLOSE : "missingBeforeSelfClose" ,
2526 EXTRA_BEFORE_SELF_CLOSE : "unexpectedBeforeSelfClose" ,
27+ EXTRA_TAB_BEFORE : "unexpectedTabBefore" ,
28+ EXTRA_TAB_BEFORE_SELF_CLOSE : "unexpectedTabBeforeSelfClose" ,
29+ EXTRA_TAB_BETWEEN : "unexpectedTabBetween" ,
2630} ;
2731
2832/**
@@ -46,6 +50,9 @@ module.exports = {
4650 disallowMissing : {
4751 type : "boolean" ,
4852 } ,
53+ disallowTabs : {
54+ type : "boolean" ,
55+ } ,
4956 enforceBeforeSelfClose : {
5057 type : "boolean" ,
5158 } ,
@@ -56,17 +63,27 @@ module.exports = {
5663 [ MESSAGE_IDS . EXTRA_BETWEEN ] : "Unexpected space between attributes" ,
5764 [ MESSAGE_IDS . EXTRA_AFTER ] : "Unexpected space after attribute" ,
5865 [ MESSAGE_IDS . EXTRA_BEFORE ] : "Unexpected space before attribute" ,
66+ [ MESSAGE_IDS . EXTRA_BEFORE_CLOSE ] : "Unexpected space before closing" ,
5967 [ MESSAGE_IDS . MISSING_BEFORE_SELF_CLOSE ] :
6068 "Missing space before self closing" ,
6169 [ MESSAGE_IDS . EXTRA_BEFORE_SELF_CLOSE ] :
6270 "Unexpected extra spaces before self closing" ,
6371 [ MESSAGE_IDS . MISSING_BEFORE ] : "Missing space before attribute" ,
72+ [ MESSAGE_IDS . EXTRA_TAB_BEFORE ] :
73+ "Unexpected tab before attribute; use space instead" ,
74+ [ MESSAGE_IDS . EXTRA_TAB_BEFORE_SELF_CLOSE ] :
75+ "Unexpected tab before self closing; use space instead" ,
76+ [ MESSAGE_IDS . EXTRA_TAB_BETWEEN ] :
77+ "Unexpected tab between attributes; use space instead" ,
6478 } ,
6579 } ,
6680 create ( context ) {
6781 const enforceBeforeSelfClose = ! ! ( context . options [ 0 ] || { } )
6882 . enforceBeforeSelfClose ;
6983 const disallowMissing = ! ! ( context . options [ 0 ] || { } ) . disallowMissing ;
84+ const disallowTabs = ! ! ( context . options [ 0 ] || { } ) . disallowTabs ;
85+
86+ const sourceCode = context . getSourceCode ( ) . text ;
7087
7188 /**
7289 * @param {AttributeNode[] } attrs
@@ -98,48 +115,23 @@ module.exports = {
98115 return fixer . insertTextAfter ( current , " " ) ;
99116 } ,
100117 } ) ;
118+ } else if ( disallowTabs ) {
119+ if ( sourceCode [ current . range [ 1 ] ] === `\t` ) {
120+ context . report ( {
121+ loc : getLocBetween ( current , after ) ,
122+ messageId : MESSAGE_IDS . EXTRA_TAB_BETWEEN ,
123+ fix ( fixer ) {
124+ return fixer . replaceTextRange (
125+ [ current . range [ 1 ] , current . range [ 1 ] + 1 ] ,
126+ ` `
127+ ) ;
128+ } ,
129+ } ) ;
130+ }
101131 }
102132 } ) ;
103133 }
104134
105- /**
106- * @param {OpenTagEndNode | OpenScriptTagEndNode | OpenStyleTagEndNode } openEnd
107- * @param {AttributeNode } lastAttr
108- * @param {boolean } isSelfClosed
109- * @returns {void }
110- */
111- function checkExtraSpaceAfter ( openEnd , lastAttr , isSelfClosed ) {
112- if ( openEnd . loc . end . line !== lastAttr . loc . end . line ) {
113- // skip the attribute on the different line with the start tag
114- return ;
115- }
116- const limit = isSelfClosed && enforceBeforeSelfClose ? 1 : 0 ;
117- const spacesBetween = openEnd . loc . start . column - lastAttr . loc . end . column ;
118-
119- if ( spacesBetween > limit ) {
120- context . report ( {
121- loc : getLocBetween ( lastAttr , openEnd ) ,
122- messageId : MESSAGE_IDS . EXTRA_AFTER ,
123- fix ( fixer ) {
124- return fixer . removeRange ( [
125- lastAttr . range [ 1 ] ,
126- lastAttr . range [ 1 ] + spacesBetween - limit ,
127- ] ) ;
128- } ,
129- } ) ;
130- }
131-
132- if ( isSelfClosed && enforceBeforeSelfClose && spacesBetween < 1 ) {
133- context . report ( {
134- loc : getLocBetween ( lastAttr , openEnd ) ,
135- messageId : MESSAGE_IDS . MISSING_BEFORE_SELF_CLOSE ,
136- fix ( fixer ) {
137- return fixer . insertTextAfter ( lastAttr , " " ) ;
138- } ,
139- } ) ;
140- }
141- }
142-
143135 /**
144136 * @param {OpenScriptTagStartNode | OpenTagStartNode | OpenStyleTagStartNode } node
145137 * @param {AttributeNode } firstAttr
@@ -164,42 +156,19 @@ module.exports = {
164156 ] ) ;
165157 } ,
166158 } ) ;
167- }
168- }
169-
170- /**
171- * @param {AnyNode } beforeSelfClosing
172- * @param {OpenTagEndNode | OpenScriptTagEndNode | OpenStyleTagEndNode } openEnd
173- * @returns
174- */
175- function checkSpaceBeforeSelfClosing ( beforeSelfClosing , openEnd ) {
176- if ( beforeSelfClosing . loc . start . line !== openEnd . loc . start . line ) {
177- // skip the attribute on the different line with the start tag
178- return ;
179- }
180- const spacesBetween =
181- openEnd . loc . start . column - beforeSelfClosing . loc . end . column ;
182- const locBetween = getLocBetween ( beforeSelfClosing , openEnd ) ;
183-
184- if ( spacesBetween > 1 ) {
185- context . report ( {
186- loc : locBetween ,
187- messageId : MESSAGE_IDS . EXTRA_BEFORE_SELF_CLOSE ,
188- fix ( fixer ) {
189- return fixer . removeRange ( [
190- beforeSelfClosing . range [ 1 ] + 1 ,
191- openEnd . range [ 0 ] ,
192- ] ) ;
193- } ,
194- } ) ;
195- } else if ( spacesBetween < 1 ) {
196- context . report ( {
197- loc : locBetween ,
198- messageId : MESSAGE_IDS . MISSING_BEFORE_SELF_CLOSE ,
199- fix ( fixer ) {
200- return fixer . insertTextAfter ( beforeSelfClosing , " " ) ;
201- } ,
202- } ) ;
159+ } else if ( disallowTabs ) {
160+ if ( sourceCode [ firstAttr . range [ 0 ] - 1 ] === `\t` ) {
161+ context . report ( {
162+ loc : firstAttr . loc ,
163+ messageId : MESSAGE_IDS . EXTRA_TAB_BEFORE ,
164+ fix ( fixer ) {
165+ return fixer . replaceTextRange (
166+ [ firstAttr . range [ 0 ] - 1 , firstAttr . range [ 0 ] ] ,
167+ ` `
168+ ) ;
169+ } ,
170+ } ) ;
171+ }
203172 }
204173 }
205174
@@ -216,24 +185,89 @@ module.exports = {
216185 if ( node . attributes . length ) {
217186 checkExtraSpaceBefore ( node . openStart , node . attributes [ 0 ] ) ;
218187 }
188+
219189 if ( node . openEnd ) {
190+ checkExtraSpacesBetweenAttrs ( node . attributes ) ;
191+
192+ const lastAttr = node . attributes [ node . attributes . length - 1 ] ;
193+ const nodeBeforeEnd =
194+ node . attributes . length === 0 ? node . openStart : lastAttr ;
195+
196+ if ( nodeBeforeEnd . loc . end . line !== node . openEnd . loc . start . line ) {
197+ return ;
198+ }
199+
220200 const isSelfClosing = node . openEnd . value === "/>" ;
221201
222- if ( node . attributes && node . attributes . length > 0 ) {
223- checkExtraSpaceAfter (
224- node . openEnd ,
225- node . attributes [ node . attributes . length - 1 ] ,
226- isSelfClosing
227- ) ;
202+ const spacesBetween =
203+ node . openEnd . loc . start . column - nodeBeforeEnd . loc . end . column ;
204+ const locBetween = getLocBetween ( nodeBeforeEnd , node . openEnd ) ;
205+
206+ if ( isSelfClosing && enforceBeforeSelfClose ) {
207+ if ( spacesBetween < 1 ) {
208+ context . report ( {
209+ loc : locBetween ,
210+ messageId : MESSAGE_IDS . MISSING_BEFORE_SELF_CLOSE ,
211+ fix ( fixer ) {
212+ return fixer . insertTextAfter ( nodeBeforeEnd , " " ) ;
213+ } ,
214+ } ) ;
215+ } else if ( spacesBetween === 1 ) {
216+ if (
217+ disallowTabs &&
218+ sourceCode [ node . openEnd . range [ 0 ] - 1 ] === `\t`
219+ ) {
220+ context . report ( {
221+ loc : node . openEnd . loc ,
222+ messageId : MESSAGE_IDS . EXTRA_TAB_BEFORE_SELF_CLOSE ,
223+ fix ( fixer ) {
224+ return fixer . replaceTextRange (
225+ [ node . openEnd . range [ 0 ] - 1 , node . openEnd . range [ 0 ] ] ,
226+ ` `
227+ ) ;
228+ } ,
229+ } ) ;
230+ }
231+ } else {
232+ context . report ( {
233+ loc : locBetween ,
234+ messageId : MESSAGE_IDS . EXTRA_BEFORE_SELF_CLOSE ,
235+ fix ( fixer ) {
236+ return fixer . removeRange ( [
237+ nodeBeforeEnd . range [ 1 ] + 1 ,
238+ node . openEnd . range [ 0 ] ,
239+ ] ) ;
240+ } ,
241+ } ) ;
242+ }
243+
244+ return ;
228245 }
229246
230- checkExtraSpacesBetweenAttrs ( node . attributes ) ;
231- if (
232- node . attributes . length === 0 &&
233- isSelfClosing &&
234- enforceBeforeSelfClose
235- ) {
236- checkSpaceBeforeSelfClosing ( node . openStart , node . openEnd ) ;
247+ if ( spacesBetween > 0 ) {
248+ if ( node . attributes . length > 0 ) {
249+ context . report ( {
250+ loc : locBetween ,
251+ messageId : MESSAGE_IDS . EXTRA_AFTER ,
252+ fix ( fixer ) {
253+ return fixer . removeRange ( [
254+ lastAttr . range [ 1 ] ,
255+ node . openEnd . range [ 0 ] ,
256+ ] ) ;
257+ } ,
258+ } ) ;
259+ } else {
260+ context . report ( {
261+ loc : locBetween ,
262+ messageId : MESSAGE_IDS . EXTRA_BEFORE_CLOSE ,
263+ fix ( fixer ) {
264+ return fixer . removeRange ( [
265+ node . openStart . range [ 1 ] ,
266+ node . openEnd . range [ 0 ] ,
267+ ] ) ;
268+ } ,
269+ } ) ;
270+ }
237271 }
238272 }
239273 } ,
0 commit comments