@@ -26,7 +26,6 @@ const OPTION_IGNORE_BOUND_CLASS_METHODS = "ignore-bound-class-methods";
26
26
const OPTION_IGNORE_INTERFACES = "ignore-interfaces" ;
27
27
28
28
interface Options {
29
- always : boolean ;
30
29
boundClassMethods : boolean ;
31
30
interfaces : boolean ;
32
31
}
@@ -78,100 +77,89 @@ export class Rule extends Lint.Rules.AbstractRule {
78
77
79
78
public apply ( sourceFile : ts . SourceFile ) : Lint . RuleFailure [ ] {
80
79
const options : Options = {
81
- always : this . ruleArguments . indexOf ( OPTION_NEVER ) === - 1 ,
82
80
boundClassMethods : this . ruleArguments . indexOf ( OPTION_IGNORE_BOUND_CLASS_METHODS ) === - 1 ,
83
81
interfaces : this . ruleArguments . indexOf ( OPTION_IGNORE_INTERFACES ) === - 1 ,
84
82
} ;
85
- return this . applyWithWalker ( new SemicolonWalker ( sourceFile , this . ruleName , options ) ) ;
83
+ const Walker = this . ruleArguments . indexOf ( OPTION_NEVER ) === - 1 ? SemicolonAlwaysWalker : SemicolonNeverWalker ;
84
+ return this . applyWithWalker ( new Walker ( sourceFile , this . ruleName , options ) ) ;
86
85
}
87
86
}
88
87
89
- class SemicolonWalker extends Lint . AbstractWalker < Options > {
90
- private scanner ?: ts . Scanner = undefined ;
88
+ abstract class SemicolonWalker extends Lint . AbstractWalker < Options > {
91
89
public walk ( sourceFile : ts . SourceFile ) {
92
90
const cb = ( node : ts . Node ) : void => {
93
- switch ( node . kind ) {
94
- case ts . SyntaxKind . VariableStatement :
95
- case ts . SyntaxKind . ExpressionStatement :
96
- case ts . SyntaxKind . ReturnStatement :
97
- case ts . SyntaxKind . BreakStatement :
98
- case ts . SyntaxKind . ContinueStatement :
99
- case ts . SyntaxKind . ThrowStatement :
100
- case ts . SyntaxKind . ImportEqualsDeclaration :
101
- case ts . SyntaxKind . DoStatement :
102
- case ts . SyntaxKind . ExportAssignment :
103
- this . checkSemicolonAt ( node as ts . Statement ) ;
104
- break ;
105
- case ts . SyntaxKind . TypeAliasDeclaration :
106
- case ts . SyntaxKind . ImportDeclaration :
107
- case ts . SyntaxKind . ExportDeclaration :
108
- case ts . SyntaxKind . DebuggerStatement :
109
- this . checkSemicolonOrLineBreak ( node ) ;
110
- break ;
111
- case ts . SyntaxKind . ModuleDeclaration :
112
- // shorthand module declaration
113
- if ( ( node as ts . ModuleDeclaration ) . body === undefined ) {
114
- this . checkSemicolonOrLineBreak ( node ) ;
115
- }
116
- break ;
117
- case ts . SyntaxKind . PropertyDeclaration :
118
- this . visitPropertyDeclaration ( node as ts . PropertyDeclaration ) ;
119
- break ;
120
- case ts . SyntaxKind . MethodDeclaration :
121
- case ts . SyntaxKind . FunctionDeclaration :
122
- if ( ( node as ts . FunctionLikeDeclaration ) . body === undefined ) {
123
- this . checkSemicolonOrLineBreak ( node ) ;
124
- }
125
- break ;
126
- case ts . SyntaxKind . InterfaceDeclaration :
127
- if ( this . options . interfaces ) {
128
- this . checkInterface ( node as ts . InterfaceDeclaration ) ;
129
- }
130
- break ;
131
- case ts . SyntaxKind . SemicolonClassElement :
132
- return this . reportUnnecessary ( node . end - 1 ) ;
133
- case ts . SyntaxKind . EmptyStatement :
134
- return this . checkEmptyStatement ( node ) ;
135
- default :
136
- }
91
+ this . visitNode ( node ) ;
137
92
return ts . forEachChild ( node , cb ) ;
138
93
} ;
139
94
return ts . forEachChild ( sourceFile , cb ) ;
140
95
}
141
96
142
- private visitPropertyDeclaration ( node : ts . PropertyDeclaration ) {
143
- // check if this is a multi-line arrow function
144
- if ( node . initializer !== undefined &&
145
- node . initializer . kind === ts . SyntaxKind . ArrowFunction &&
146
- ! utils . isSameLine ( this . sourceFile , node . getStart ( this . sourceFile ) , node . end ) ) {
147
- if ( this . options . boundClassMethods ) {
148
- if ( this . sourceFile . text [ node . end - 1 ] === ";" &&
149
- this . isFollowedByLineBreak ( node . end ) ) {
150
- this . reportUnnecessary ( node . end - 1 ) ;
151
- }
152
- }
153
- } else {
154
- this . checkSemicolonOrLineBreak ( node ) ;
97
+ protected visitNode ( node : ts . Node ) {
98
+ switch ( node . kind ) {
99
+ case ts . SyntaxKind . SemicolonClassElement :
100
+ return this . reportUnnecessary ( node . end ) ;
101
+ case ts . SyntaxKind . EmptyStatement :
102
+ return this . checkEmptyStatement ( node ) ;
103
+ case ts . SyntaxKind . PropertyDeclaration :
104
+ return this . visitPropertyDeclaration ( node as ts . PropertyDeclaration ) ;
155
105
}
156
106
}
157
107
158
- private isFollowedByLineBreak ( pos : number ) {
159
- const scanner = this . scanner !== undefined ? this . scanner :
160
- ( this . scanner = ts . createScanner ( this . sourceFile . languageVersion , true , this . sourceFile . languageVariant , this . sourceFile . text ) ) ;
161
- scanner . setTextPos ( pos ) ;
162
- return scanner . scan ( ) === ts . SyntaxKind . EndOfFileToken || scanner . hasPrecedingLineBreak ( ) ;
108
+ protected reportUnnecessary ( pos : number , noFix = false ) {
109
+ this . addFailure ( pos - 1 , pos , Rule . FAILURE_STRING_UNNECESSARY , noFix ? undefined : Lint . Replacement . deleteText ( pos - 1 , 1 ) ) ;
163
110
}
164
111
165
- private checkSemicolonOrLineBreak ( node : ts . Node ) {
166
- const hasSemicolon = this . sourceFile . text [ node . end - 1 ] === ";" ;
167
- if ( this . options . always && ! hasSemicolon ) {
168
- this . reportMissing ( node . end ) ;
169
- } else if ( ! this . options . always && hasSemicolon && this . isFollowedByLineBreak ( node . end ) ) {
170
- // semicolon can be removed if followed by line break;
171
- this . reportUnnecessary ( node . end - 1 ) ;
112
+ protected checkSemicolonOrLineBreak ( node : ts . Node ) {
113
+ if ( this . sourceFile . text [ node . end - 1 ] !== ";" ) {
114
+ return ;
115
+ }
116
+ const nextToken = utils . getNextToken ( node , this . sourceFile ) ! ;
117
+ switch ( nextToken . kind ) {
118
+ case ts . SyntaxKind . EndOfFileToken :
119
+ case ts . SyntaxKind . CloseBraceToken :
120
+ return this . reportUnnecessary ( node . end ) ;
121
+ default :
122
+ if ( ! utils . isSameLine ( this . sourceFile , node . end , nextToken . end ) ) {
123
+ this . reportUnnecessary ( node . end ) ;
124
+ }
125
+ }
126
+ }
127
+
128
+ protected checkUnnecessary ( node : ts . Node ) {
129
+ if ( this . sourceFile . text [ node . end - 1 ] !== ";" ) {
130
+ return ;
131
+ }
132
+ const lastToken = utils . getPreviousToken ( node . getLastToken ( this . sourceFile ) ! , this . sourceFile ) ! ;
133
+ // yield does not continue on the next line if there is no yielded expression
134
+ if ( lastToken . kind === ts . SyntaxKind . YieldKeyword && lastToken . parent ! . kind === ts . SyntaxKind . YieldExpression ||
135
+ // arrow functions with block as body don't continue on the next line
136
+ lastToken . kind === ts . SyntaxKind . CloseBraceToken && lastToken . parent ! . kind === ts . SyntaxKind . Block &&
137
+ lastToken . parent ! . parent ! . kind === ts . SyntaxKind . ArrowFunction ) {
138
+ return this . checkSemicolonOrLineBreak ( node ) ;
139
+ }
140
+ const nextToken = utils . getNextToken ( node , this . sourceFile ) ! ;
141
+ switch ( nextToken . kind ) {
142
+ case ts . SyntaxKind . OpenParenToken :
143
+ case ts . SyntaxKind . OpenBracketToken :
144
+ case ts . SyntaxKind . PlusToken :
145
+ case ts . SyntaxKind . MinusToken :
146
+ case ts . SyntaxKind . RegularExpressionLiteral :
147
+ case ts . SyntaxKind . LessThanToken :
148
+ case ts . SyntaxKind . NoSubstitutionTemplateLiteral :
149
+ case ts . SyntaxKind . TemplateHead :
150
+ break ;
151
+ case ts . SyntaxKind . CloseBraceToken :
152
+ case ts . SyntaxKind . EndOfFileToken :
153
+ return this . reportUnnecessary ( node . end ) ;
154
+ default :
155
+ if ( ! utils . isSameLine ( this . sourceFile , node . end , nextToken . end ) ) {
156
+ this . reportUnnecessary ( node . end ) ;
157
+ }
172
158
}
173
159
}
174
160
161
+ protected abstract checkPropertyDeclaration ( node : ts . PropertyDeclaration ) : void ;
162
+
175
163
private checkEmptyStatement ( node : ts . Node ) {
176
164
// An empty statement is only ever useful when it is the only statement inside a loop
177
165
if ( ! utils . isIterationStatement ( node . parent ! ) ) {
@@ -181,61 +169,165 @@ class SemicolonWalker extends Lint.AbstractWalker<Options> {
181
169
const noFix = parentKind === ts . SyntaxKind . IfStatement ||
182
170
parentKind === ts . SyntaxKind . LabeledStatement ||
183
171
parentKind === ts . SyntaxKind . WithStatement ;
184
- this . reportUnnecessary ( node . end - 1 , noFix ) ;
172
+ this . reportUnnecessary ( node . end , noFix ) ;
185
173
}
186
174
}
187
175
188
- private checkInterface ( node : ts . InterfaceDeclaration ) {
189
- for ( const member of node . members ) {
190
- const lastChar = this . sourceFile . text [ member . end - 1 ] ;
191
- const hasSemicolon = lastChar === ";" ;
192
- if ( this . options . always && ! hasSemicolon ) {
193
- if ( lastChar === "," ) {
194
- this . addFailureAt ( member . end - 1 , 1 , Rule . FAILURE_STRING_COMMA , new Lint . Replacement ( member . end - 1 , 1 , ";" ) ) ;
195
- } else {
196
- this . reportMissing ( member . end ) ;
197
- }
198
- } else if ( ! this . options . always && hasSemicolon &&
199
- ( member === node . members [ node . members . length - 1 ] || this . isFollowedByLineBreak ( member . end ) ) ) {
200
- this . reportUnnecessary ( member . end - 1 ) ;
176
+ private visitPropertyDeclaration ( node : ts . PropertyDeclaration ) {
177
+ // check if this is a multi-line arrow function
178
+ if ( node . initializer !== undefined &&
179
+ node . initializer . kind === ts . SyntaxKind . ArrowFunction &&
180
+ ! utils . isSameLine ( this . sourceFile , node . getStart ( this . sourceFile ) , node . end ) ) {
181
+ if ( this . options . boundClassMethods ) {
182
+ this . checkUnnecessary ( node ) ;
201
183
}
184
+ } else {
185
+ this . checkPropertyDeclaration ( node ) ;
202
186
}
203
187
}
188
+ }
204
189
205
- private reportMissing ( pos : number ) {
206
- this . addFailureAt ( pos , 0 , Rule . FAILURE_STRING_MISSING , Lint . Replacement . appendText ( pos , ";" ) ) ;
190
+ class SemicolonAlwaysWalker extends SemicolonWalker {
191
+ protected visitNode ( node : ts . Node ) {
192
+ switch ( node . kind ) {
193
+ case ts . SyntaxKind . VariableStatement :
194
+ case ts . SyntaxKind . ExpressionStatement :
195
+ case ts . SyntaxKind . ReturnStatement :
196
+ case ts . SyntaxKind . BreakStatement :
197
+ case ts . SyntaxKind . ContinueStatement :
198
+ case ts . SyntaxKind . ThrowStatement :
199
+ case ts . SyntaxKind . ImportEqualsDeclaration :
200
+ case ts . SyntaxKind . DoStatement :
201
+ case ts . SyntaxKind . ExportAssignment :
202
+ case ts . SyntaxKind . TypeAliasDeclaration :
203
+ case ts . SyntaxKind . ImportDeclaration :
204
+ case ts . SyntaxKind . ExportDeclaration :
205
+ case ts . SyntaxKind . DebuggerStatement :
206
+ return this . checkMissing ( node ) ;
207
+ case ts . SyntaxKind . ModuleDeclaration :
208
+ case ts . SyntaxKind . MethodDeclaration :
209
+ case ts . SyntaxKind . FunctionDeclaration :
210
+ // check shorthand module declarations and method / function signatures
211
+ if ( ( node as ts . FunctionLikeDeclaration | ts . ModuleDeclaration ) . body === undefined ) {
212
+ this . checkMissing ( node ) ;
213
+ }
214
+ break ;
215
+ case ts . SyntaxKind . InterfaceDeclaration :
216
+ if ( this . options . interfaces ) {
217
+ this . checkInterface ( node as ts . InterfaceDeclaration ) ;
218
+ }
219
+ break ;
220
+ default :
221
+ return super . visitNode ( node ) ;
222
+ }
207
223
}
208
224
209
- private reportUnnecessary ( pos : number , noFix ?: boolean ) {
210
- this . addFailureAt ( pos , 1 , Rule . FAILURE_STRING_UNNECESSARY , noFix === true ? undefined : Lint . Replacement . deleteText ( pos , 1 ) ) ;
225
+ protected checkPropertyDeclaration ( node : ts . PropertyDeclaration ) {
226
+ return this . checkMissing ( node ) ;
211
227
}
212
228
213
- private checkSemicolonAt ( node : ts . Statement ) {
214
- const hasSemicolon = this . sourceFile . text [ node . end - 1 ] === ";" ;
215
-
216
- if ( this . options . always && ! hasSemicolon ) {
229
+ private checkMissing ( node : ts . Node ) {
230
+ if ( this . sourceFile . text [ node . end - 1 ] !== ";" ) {
217
231
this . reportMissing ( node . end ) ;
218
- } else if ( ! this . options . always && hasSemicolon ) {
219
- switch ( utils . getNextToken ( node , this . sourceFile ) ! . kind ) {
220
- case ts . SyntaxKind . OpenParenToken :
221
- case ts . SyntaxKind . OpenBracketToken :
222
- case ts . SyntaxKind . PlusToken :
223
- case ts . SyntaxKind . MinusToken :
224
- case ts . SyntaxKind . RegularExpressionLiteral :
232
+ }
233
+ }
234
+
235
+ private reportMissing ( pos : number ) {
236
+ this . addFailureAt ( pos , 0 , Rule . FAILURE_STRING_MISSING , Lint . Replacement . appendText ( pos , ";" ) ) ;
237
+ }
238
+
239
+ private checkInterface ( node : ts . InterfaceDeclaration ) {
240
+ for ( const member of node . members ) {
241
+ switch ( this . sourceFile . text [ member . end - 1 ] ) {
242
+ case ";" : break ;
243
+ case "," :
244
+ this . addFailureAt ( member . end - 1 , 1 , Rule . FAILURE_STRING_COMMA , new Lint . Replacement ( member . end - 1 , 1 , ";" ) ) ;
225
245
break ;
226
246
default :
227
- if ( ! this . isFollowedByStatement ( node ) ) {
228
- this . reportUnnecessary ( node . end - 1 ) ;
229
- }
247
+ this . reportMissing ( member . end ) ;
230
248
}
231
249
}
232
250
}
251
+ }
233
252
234
- private isFollowedByStatement ( node : ts . Statement ) : boolean {
253
+ class SemicolonNeverWalker extends SemicolonWalker {
254
+ protected visitNode ( node : ts . Node ) {
255
+ switch ( node . kind ) {
256
+ case ts . SyntaxKind . ExpressionStatement :
257
+ case ts . SyntaxKind . ThrowStatement :
258
+ case ts . SyntaxKind . ExportAssignment :
259
+ return this . checkUnnecessary ( node as ts . Statement ) ;
260
+ case ts . SyntaxKind . VariableStatement :
261
+ return this . checkVariableStatement ( node as ts . VariableStatement ) ;
262
+ case ts . SyntaxKind . ReturnStatement :
263
+ if ( ( node as ts . ReturnStatement ) . expression === undefined ) {
264
+ // return does not continue on the next line if the is no returned expression
265
+ return this . checkSemicolonOrLineBreak ( node ) ;
266
+ }
267
+ return this . checkUnnecessary ( node ) ;
268
+ case ts . SyntaxKind . TypeAliasDeclaration :
269
+ case ts . SyntaxKind . ImportEqualsDeclaration :
270
+ case ts . SyntaxKind . ImportDeclaration :
271
+ case ts . SyntaxKind . ExportDeclaration :
272
+ case ts . SyntaxKind . DebuggerStatement :
273
+ case ts . SyntaxKind . BreakStatement :
274
+ case ts . SyntaxKind . ContinueStatement :
275
+ case ts . SyntaxKind . DoStatement :
276
+ return this . checkSemicolonOrLineBreak ( node ) ;
277
+ case ts . SyntaxKind . ModuleDeclaration :
278
+ // shorthand module declaration
279
+ if ( ( node as ts . ModuleDeclaration ) . body === undefined ) {
280
+ this . checkShorthandModuleDeclaration ( node as ts . ModuleDeclaration ) ;
281
+ }
282
+ break ;
283
+ case ts . SyntaxKind . MethodDeclaration :
284
+ // check method signature
285
+ if ( ( node as ts . MethodDeclaration ) . body === undefined ) {
286
+ this . checkSemicolonOrLineBreak ( node ) ;
287
+ }
288
+ break ;
289
+ case ts . SyntaxKind . FunctionDeclaration :
290
+ // check function signature
291
+ if ( ( node as ts . FunctionDeclaration ) . body === undefined ) {
292
+ this . checkSemicolonOrLineBreak ( node ) ;
293
+ }
294
+ break ;
295
+ case ts . SyntaxKind . InterfaceDeclaration :
296
+ if ( this . options . interfaces ) {
297
+ this . checkInterface ( node as ts . InterfaceDeclaration ) ;
298
+ }
299
+ break ;
300
+ default :
301
+ return super . visitNode ( node ) ;
302
+ }
303
+ }
304
+
305
+ protected checkPropertyDeclaration ( node : ts . PropertyDeclaration ) {
306
+ if ( node . initializer === undefined ) {
307
+ return this . checkSemicolonOrLineBreak ( node ) ;
308
+ }
309
+ return this . checkUnnecessary ( node ) ;
310
+ }
311
+
312
+ private checkVariableStatement ( node : ts . VariableStatement ) {
313
+ const declarations = node . declarationList . declarations ;
314
+ if ( declarations [ declarations . length - 1 ] . initializer === undefined ) {
315
+ // variable declaration does not continue on the next line if it has no initializer
316
+ return this . checkSemicolonOrLineBreak ( node ) ;
317
+ }
318
+ return this . checkUnnecessary ( node ) ;
319
+ }
320
+
321
+ private checkShorthandModuleDeclaration ( node : ts . ModuleDeclaration ) {
235
322
const nextStatement = utils . getNextStatement ( node ) ;
236
- if ( nextStatement === undefined ) {
237
- return false ;
323
+ if ( nextStatement === undefined || nextStatement . kind !== ts . SyntaxKind . Block ) {
324
+ this . checkSemicolonOrLineBreak ( node ) ;
325
+ }
326
+ }
327
+
328
+ private checkInterface ( node : ts . InterfaceDeclaration ) {
329
+ for ( const member of node . members ) {
330
+ this . checkSemicolonOrLineBreak ( member ) ;
238
331
}
239
- return utils . isSameLine ( this . sourceFile , node . end , nextStatement . getStart ( this . sourceFile ) ) ;
240
332
}
241
333
}
0 commit comments