@@ -2,17 +2,20 @@ import * as Lint from 'tslint';
2
2
import * as ts from 'typescript' ;
3
3
import { NgWalker } from './angular/ngWalker' ;
4
4
import * as ast from '@angular/compiler' ;
5
- import { BasicTemplateAstVisitor } from './angular/templates/basicTemplateAstVisitor' ;
6
- import { ExpTypes } from './angular/expressionTypes' ;
7
- import { Config } from './angular/config' ;
8
- import { RecursiveAngularExpressionVisitor } from './angular/templates/recursiveAngularExpressionVisitor' ;
5
+ import { BasicTemplateAstVisitor } from './angular/templates/basicTemplateAstVisitor' ;
6
+ import { ExpTypes } from './angular/expressionTypes' ;
7
+ import { Config } from './angular/config' ;
8
+ import { RecursiveAngularExpressionVisitor } from './angular/templates/recursiveAngularExpressionVisitor' ;
9
9
10
10
const InterpolationOpen = Config . interpolation [ 0 ] ;
11
11
const InterpolationClose = Config . interpolation [ 1 ] ;
12
12
const InterpolationNoWhitespaceRe = new RegExp ( `${ InterpolationOpen } \\S(.*?)\\S${ InterpolationClose } |${ InterpolationOpen } ` +
13
13
`\\s(.*?)\\S${ InterpolationClose } |${ InterpolationOpen } \\S(.*?)\\s${ InterpolationClose } ` ) ;
14
14
const InterpolationExtraWhitespaceRe =
15
15
new RegExp ( `${ InterpolationOpen } \\s\\s(.*?)\\s${ InterpolationClose } |${ InterpolationOpen } \\s(.*?)\\s\\s${ InterpolationClose } ` ) ;
16
+ const SemicolonNoWhitespaceNotInSimpleQuoteRe = new RegExp ( / ; \S (? ! [ ^ ' ] * ' ) / ) ;
17
+ const SemicolonNoWhitespaceNotInDoubleQuoteRe = new RegExp ( / ; \S (? ! [ ^ " ] * " ) / ) ;
18
+
16
19
17
20
const getReplacements = ( text : ast . BoundTextAst , absolutePosition : number ) => {
18
21
const expr : string = ( text . value as any ) . source ;
@@ -27,7 +30,16 @@ const getReplacements = (text: ast.BoundTextAst, absolutePosition: number) => {
27
30
] ;
28
31
} ;
29
32
30
- type Option = 'check-interpolation' | 'check-pipe' ;
33
+
34
+ const getSemicolonReplacements = ( text : ast . BoundDirectivePropertyAst , absolutePosition : number ) => {
35
+
36
+ return [
37
+ new Lint . Replacement ( absolutePosition , 1 , '; ' )
38
+ ] ;
39
+
40
+ } ;
41
+
42
+ type Option = 'check-interpolation' | 'check-pipe' | 'check-semicolon' ;
31
43
32
44
interface ConfigurableVisitor {
33
45
getOption ( ) : Option ;
@@ -66,9 +78,52 @@ class InterpolationWhitespaceVisitor extends BasicTemplateAstVisitor implements
66
78
}
67
79
}
68
80
81
+ class SemicolonTemplateVisitor extends BasicTemplateAstVisitor implements ConfigurableVisitor {
82
+
83
+ visitDirectiveProperty ( prop : ast . BoundDirectivePropertyAst , context : BasicTemplateAstVisitor ) : any {
84
+
85
+
86
+ if ( prop . sourceSpan ) {
87
+ const directive = ( < any > prop . sourceSpan ) . toString ( ) ;
88
+ const rawExpression = directive . split ( '=' ) [ 1 ] . trim ( ) ;
89
+ const expr = rawExpression . substring ( 1 , rawExpression . length - 1 ) . trim ( ) ;
90
+
91
+ const doubleQuote = rawExpression . substring ( 0 , 1 ) . indexOf ( '\"' ) === 0 ;
92
+
93
+ // Note that will not be reliable for different interpolation symbols
94
+ let error = null ;
95
+
96
+ if ( doubleQuote && SemicolonNoWhitespaceNotInSimpleQuoteRe . test ( expr ) ) {
97
+ error = 'Missing whitespace after semicolon; expecting \'; expr\'' ;
98
+ const internalStart = expr . search ( SemicolonNoWhitespaceNotInSimpleQuoteRe ) + 1 ;
99
+ const start = prop . sourceSpan . start . offset + internalStart + directive . length - directive . split ( '=' ) [ 1 ] . trim ( ) . length + 1 ;
100
+ const absolutePosition = context . getSourcePosition ( start - 1 ) ;
101
+ return context . addFailure ( context . createFailure ( start , 2 ,
102
+ error , getSemicolonReplacements ( prop , absolutePosition ) )
103
+ ) ;
104
+ } else if ( ! doubleQuote && SemicolonNoWhitespaceNotInDoubleQuoteRe . test ( expr ) ) {
105
+ error = 'Missing whitespace after semicolon; expecting \'; expr\'' ;
106
+ const internalStart = expr . search ( SemicolonNoWhitespaceNotInDoubleQuoteRe ) + 1 ;
107
+ const start = prop . sourceSpan . start . offset + internalStart + directive . length - directive . split ( '=' ) [ 1 ] . trim ( ) . length + 1 ;
108
+ const absolutePosition = context . getSourcePosition ( start - 1 ) ;
109
+ return context . addFailure ( context . createFailure ( start , 2 ,
110
+ error , getSemicolonReplacements ( prop , absolutePosition ) )
111
+ ) ;
112
+ }
113
+ }
114
+ }
115
+
116
+ getOption ( ) : Option {
117
+ return 'check-semicolon' ;
118
+ }
119
+
120
+ }
121
+
122
+
69
123
class WhitespaceTemplateVisitor extends BasicTemplateAstVisitor {
70
124
private visitors : ( BasicTemplateAstVisitor & ConfigurableVisitor ) [ ] = [
71
- new InterpolationWhitespaceVisitor ( this . getSourceFile ( ) , this . getOptions ( ) , this . context , this . templateStart )
125
+ new InterpolationWhitespaceVisitor ( this . getSourceFile ( ) , this . getOptions ( ) , this . context , this . templateStart ) ,
126
+ new SemicolonTemplateVisitor ( this . getSourceFile ( ) , this . getOptions ( ) , this . context , this . templateStart )
72
127
] ;
73
128
74
129
visitBoundText ( text : ast . BoundTextAst , context : any ) : any {
@@ -80,8 +135,21 @@ class WhitespaceTemplateVisitor extends BasicTemplateAstVisitor {
80
135
. forEach ( f => this . addFailure ( f ) ) ;
81
136
super . visitBoundText ( text , context ) ;
82
137
}
138
+
139
+ visitDirectiveProperty ( prop : ast . BoundDirectivePropertyAst , context : any ) : any {
140
+ const options = this . getOptions ( ) ;
141
+ this . visitors
142
+ . filter ( v => options . indexOf ( v . getOption ( ) ) >= 0 )
143
+ . map ( v => v . visitDirectiveProperty ( prop , this ) )
144
+ . filter ( f => ! ! f )
145
+ . forEach ( f => this . addFailure ( f ) ) ;
146
+ super . visitDirectiveProperty ( prop , context ) ;
147
+ }
148
+
149
+
83
150
}
84
151
152
+
85
153
/* Expression visitors */
86
154
87
155
class PipeWhitespaceVisitor extends RecursiveAngularExpressionVisitor implements ConfigurableVisitor {
@@ -123,8 +191,8 @@ class PipeWhitespaceVisitor extends RecursiveAngularExpressionVisitor implements
123
191
if ( replacements . length ) {
124
192
context . addFailure (
125
193
context . createFailure ( ast . exp . span . end - 1 , 3 ,
126
- 'The pipe operator should be surrounded by one space on each side, i.e. " | ".' ,
127
- replacements )
194
+ 'The pipe operator should be surrounded by one space on each side, i.e. " | ".' ,
195
+ replacements )
128
196
) ;
129
197
}
130
198
super . visitPipe ( ast , context ) ;
@@ -163,27 +231,30 @@ export class Rule extends Lint.Rules.AbstractRule {
163
231
description : `Ensures the proper formatting of Angular expressions.` ,
164
232
rationale : `Having whitespace in the right places in an Angular expression makes the template more readable.` ,
165
233
optionsDescription : Lint . Utils . dedent `
166
- One argument may be optionally provided:
167
- * \`"check-interpolation"\` checks for whitespace before and after the interpolation characters` ,
234
+ Arguments may be optionally provided:
235
+ * \`"check-interpolation"\` checks for whitespace before and after the interpolation characters
236
+ * \`"check-pipe"\` checks for whitespace before and after a pipe
237
+ * \`"check-semicolon"\` checks for whitespace after semicolon` ,
238
+
168
239
options : {
169
240
type : 'array' ,
170
241
items : {
171
242
type : 'string' ,
172
- enum : [ 'check-interpolation' ] ,
243
+ enum : [ 'check-interpolation' , 'check-pipe' , 'check-semicolon' ] ,
173
244
} ,
174
245
minLength : 0 ,
175
- maxLength : 1 ,
246
+ maxLength : 3 ,
176
247
} ,
177
248
optionExamples : [ '[true, "check-interpolation"]' ] ,
178
249
typescriptOnly : true ,
179
250
} ;
180
251
181
252
public apply ( sourceFile : ts . SourceFile ) : Lint . RuleFailure [ ] {
182
253
return this . applyWithWalker (
183
- new NgWalker ( sourceFile ,
184
- this . getOptions ( ) , {
185
- templateVisitorCtrl : WhitespaceTemplateVisitor ,
186
- expressionVisitorCtrl : TemplateExpressionVisitor
187
- } ) ) ;
254
+ new NgWalker ( sourceFile ,
255
+ this . getOptions ( ) , {
256
+ templateVisitorCtrl : WhitespaceTemplateVisitor ,
257
+ expressionVisitorCtrl : TemplateExpressionVisitor ,
258
+ } ) ) ;
188
259
}
189
260
}
0 commit comments