@@ -385,6 +385,15 @@ func getCompletionData(program *compiler.Program, typeChecker *checker.Checker,
385
385
386
386
if contextToken != nil {
387
387
// !!! import completions
388
+ // Bail out if this is a known invalid completion location.
389
+ // !!! if (!importStatementCompletionInfo.replacementSpan && ...)
390
+ if isCompletionListBlocker (contextToken , previousToken , location , file , position , typeChecker ) {
391
+ if keywordFilters != KeywordCompletionFiltersNone {
392
+ isNewIdentifierLocation , _ := computeCommitCharactersAndIsNewIdentifier (contextToken , file , position )
393
+ return keywordCompletionData (keywordFilters , isJSOnlyLocation , isNewIdentifierLocation )
394
+ }
395
+ return nil
396
+ }
388
397
389
398
parent := contextToken .Parent
390
399
if contextToken .Kind == ast .KindDotToken || contextToken .Kind == ast .KindQuestionDotToken {
@@ -4200,3 +4209,228 @@ func (l *LanguageService) getLabelStatementCompletions(
4200
4209
}
4201
4210
return items
4202
4211
}
4212
+
4213
+ func isCompletionListBlocker (
4214
+ contextToken * ast.Node ,
4215
+ previousToken * ast.Node ,
4216
+ location * ast.Node ,
4217
+ file * ast.SourceFile ,
4218
+ position int ,
4219
+ typeChecker * checker.Checker ,
4220
+ ) bool {
4221
+ return isInStringOrRegularExpressionOrTemplateLiteral (contextToken , position ) ||
4222
+ isSolelyIdentifierDefinitionLocation (contextToken , previousToken , file , position , typeChecker ) ||
4223
+ isDotOfNumericLiteral (contextToken , file ) ||
4224
+ isInJsxText (contextToken , location ) ||
4225
+ ast .IsBigIntLiteral (contextToken )
4226
+ }
4227
+
4228
+ func isInStringOrRegularExpressionOrTemplateLiteral (contextToken * ast.Node , position int ) bool {
4229
+ // To be "in" one of these literals, the position has to be:
4230
+ // 1. entirely within the token text.
4231
+ // 2. at the end position of an unterminated token.
4232
+ // 3. at the end of a regular expression (due to trailing flags like '/foo/g').
4233
+ return (ast .IsRegularExpressionLiteral (contextToken ) || ast .IsStringTextContainingNode (contextToken )) &&
4234
+ (contextToken .Loc .ContainsExclusive (position )) ||
4235
+ position == contextToken .End () &&
4236
+ (ast .IsUnterminatedNode (contextToken ) || ast .IsRegularExpressionLiteral (contextToken ))
4237
+ }
4238
+
4239
+ // true if we are certain that the currently edited location must define a new location; false otherwise.
4240
+ func isSolelyIdentifierDefinitionLocation (
4241
+ contextToken * ast.Node ,
4242
+ previousToken * ast.Node ,
4243
+ file * ast.SourceFile ,
4244
+ position int ,
4245
+ typeChecker * checker.Checker ,
4246
+ ) bool {
4247
+ parent := contextToken .Parent
4248
+ containingNodeKind := parent .Kind
4249
+ switch contextToken .Kind {
4250
+ case ast .KindCommaToken :
4251
+ return containingNodeKind == ast .KindVariableDeclaration ||
4252
+ isVariableDeclarationListButNotTypeArgument (contextToken , file , typeChecker ) ||
4253
+ containingNodeKind == ast .KindVariableStatement ||
4254
+ containingNodeKind == ast .KindEnumDeclaration || // enum a { foo, |
4255
+ isFunctionLikeButNotConstructor (containingNodeKind ) ||
4256
+ containingNodeKind == ast .KindInterfaceDeclaration || // interface A<T, |
4257
+ containingNodeKind == ast .KindArrayBindingPattern || // var [x, y|
4258
+ containingNodeKind == ast .KindTypeAliasDeclaration || // type Map, K, |
4259
+ // class A<T, |
4260
+ // var C = class D<T, |
4261
+ (ast .IsClassLike (parent ) && parent .TypeParameterList () != nil && parent .TypeParameterList ().End () >= contextToken .Pos ())
4262
+ case ast .KindDotToken :
4263
+ return containingNodeKind == ast .KindArrayBindingPattern // var [.|
4264
+ case ast .KindColonToken :
4265
+ return containingNodeKind == ast .KindBindingElement // var {x :html|
4266
+ case ast .KindOpenBracketToken :
4267
+ return containingNodeKind == ast .KindArrayBindingPattern // var [x|
4268
+ case ast .KindOpenParenToken :
4269
+ return containingNodeKind == ast .KindCatchClause || isFunctionLikeButNotConstructor (containingNodeKind )
4270
+ case ast .KindOpenBraceToken :
4271
+ return containingNodeKind == ast .KindEnumDeclaration // enum a { |
4272
+ case ast .KindLessThanToken :
4273
+ return containingNodeKind == ast .KindClassDeclaration || // class A< |
4274
+ containingNodeKind == ast .KindClassExpression || // var C = class D< |
4275
+ containingNodeKind == ast .KindInterfaceDeclaration || // interface A< |
4276
+ containingNodeKind == ast .KindTypeAliasDeclaration || // type List< |
4277
+ ast .IsFunctionLikeKind (containingNodeKind )
4278
+ case ast .KindStaticKeyword :
4279
+ return containingNodeKind == ast .KindPropertyDeclaration &&
4280
+ ! ast .IsClassLike (parent .Parent )
4281
+ case ast .KindDotDotDotToken :
4282
+ return containingNodeKind == ast .KindParameter ||
4283
+ (parent .Parent != nil && parent .Parent .Kind == ast .KindArrayBindingPattern ) // var [...z|
4284
+ case ast .KindPublicKeyword , ast .KindPrivateKeyword , ast .KindProtectedKeyword :
4285
+ return containingNodeKind == ast .KindParameter && ! ast .IsConstructorDeclaration (parent .Parent )
4286
+ case ast .KindAsKeyword :
4287
+ return containingNodeKind == ast .KindImportSpecifier ||
4288
+ containingNodeKind == ast .KindExportSpecifier ||
4289
+ containingNodeKind == ast .KindNamespaceImport
4290
+ case ast .KindGetKeyword , ast .KindSetKeyword :
4291
+ return ! isFromObjectTypeDeclaration (contextToken )
4292
+ case ast .KindIdentifier :
4293
+ if (containingNodeKind == ast .KindImportSpecifier || containingNodeKind == ast .KindExportSpecifier ) &&
4294
+ contextToken == parent .Name () &&
4295
+ contextToken .Text () == "type" {
4296
+ // import { type | }
4297
+ return false
4298
+ }
4299
+ ancestorVariableDeclaration := ast .FindAncestor (parent , ast .IsVariableDeclaration )
4300
+ if ancestorVariableDeclaration != nil && getLineOfPosition (file , contextToken .End ()) < position {
4301
+ // let a
4302
+ // |
4303
+ return false
4304
+ }
4305
+ case ast .KindClassKeyword , ast .KindEnumKeyword , ast .KindInterfaceKeyword , ast .KindFunctionKeyword ,
4306
+ ast .KindVarKeyword , ast .KindImportKeyword , ast .KindLetKeyword , ast .KindConstKeyword , ast .KindInferKeyword :
4307
+ return true
4308
+ case ast .KindTypeKeyword :
4309
+ // import { type foo| }
4310
+ return containingNodeKind != ast .KindImportSpecifier
4311
+ case ast .KindAsteriskToken :
4312
+ return ast .IsFunctionLike (parent ) && ! ast .IsMethodDeclaration (parent )
4313
+ }
4314
+
4315
+ // If the previous token is keyword corresponding to class member completion keyword
4316
+ // there will be completion available here
4317
+ if isClassMemberCompletionKeyword (keywordForNode (contextToken )) && isFromObjectTypeDeclaration (contextToken ) {
4318
+ return false
4319
+ }
4320
+
4321
+ if isConstructorParameterCompletion (contextToken ) {
4322
+ // constructor parameter completion is available only if
4323
+ // - its modifier of the constructor parameter or
4324
+ // - its name of the parameter and not being edited
4325
+ // eg. constructor(a |<- this shouldnt show completion
4326
+ if ! ast .IsIdentifier (contextToken ) ||
4327
+ ast .IsParameterPropertyModifier (keywordForNode (contextToken )) ||
4328
+ isCurrentlyEditingNode (contextToken , file , position ) {
4329
+ return false
4330
+ }
4331
+ }
4332
+
4333
+ // Previous token may have been a keyword that was converted to an identifier.
4334
+ switch keywordForNode (contextToken ) {
4335
+ case ast .KindAbstractKeyword , ast .KindClassKeyword , ast .KindConstKeyword , ast .KindDeclareKeyword ,
4336
+ ast .KindEnumKeyword , ast .KindFunctionKeyword , ast .KindInterfaceKeyword , ast .KindLetKeyword ,
4337
+ ast .KindPrivateKeyword , ast .KindProtectedKeyword , ast .KindPublicKeyword ,
4338
+ ast .KindStaticKeyword , ast .KindVarKeyword :
4339
+ return true
4340
+ case ast .KindAsyncKeyword :
4341
+ return ast .IsPropertyDeclaration (contextToken .Parent )
4342
+ }
4343
+
4344
+ // If we are inside a class declaration, and `constructor` is totally not present,
4345
+ // but we request a completion manually at a whitespace...
4346
+ ancestorClassLike := ast .FindAncestor (parent , ast .IsClassLike )
4347
+ if ancestorClassLike != nil && contextToken == previousToken &&
4348
+ isPreviousPropertyDeclarationTerminated (contextToken , file , position ) {
4349
+ // Don't block completions.
4350
+ return false
4351
+ }
4352
+
4353
+ ancestorPropertyDeclaration := ast .FindAncestor (parent , ast .IsPropertyDeclaration )
4354
+ // If we are inside a class declaration and typing `constructor` after property declaration...
4355
+ if ancestorPropertyDeclaration != nil && contextToken != previousToken &&
4356
+ ast .IsClassLike (previousToken .Parent .Parent ) &&
4357
+ // And the cursor is at the token...
4358
+ position <= previousToken .End () {
4359
+ // If we are sure that the previous property declaration is terminated according to newline or semicolon...
4360
+ if isPreviousPropertyDeclarationTerminated (contextToken , file , previousToken .End ()) {
4361
+ // Don't block completions.
4362
+ return false
4363
+ } else if contextToken .Kind != ast .KindEqualsToken &&
4364
+ // Should not block: `class C { blah = c/**/ }`
4365
+ // But should block: `class C { blah = somewhat c/**/ }` and `class C { blah: SomeType c/**/ }`
4366
+ (ast .IsInitializedProperty (ancestorPropertyDeclaration ) || ancestorPropertyDeclaration .Type () != nil ) {
4367
+ return true
4368
+ }
4369
+ }
4370
+
4371
+ return ast .IsDeclarationName (contextToken ) &&
4372
+ ! ast .IsShorthandPropertyAssignment (parent ) &&
4373
+ ! ast .IsJsxAttribute (parent ) &&
4374
+ // Don't block completions if we're in `class C /**/`, `interface I /**/` or `<T /**/>` ,
4375
+ // because we're *past* the end of the identifier and might want to complete `extends`.
4376
+ // If `contextToken !== previousToken`, this is `class C ex/**/`, `interface I ex/**/` or `<T ex/**/>`.
4377
+ ! ((ast .IsClassLike (parent ) || ast .IsInterfaceDeclaration (parent ) || ast .IsTypeParameterDeclaration (parent )) &&
4378
+ (contextToken != previousToken || position > previousToken .End ()))
4379
+ }
4380
+
4381
+ func isVariableDeclarationListButNotTypeArgument (node * ast.Node , file * ast.SourceFile , typeChecker * checker.Checker ) bool {
4382
+ return node .Parent .Kind == ast .KindVariableDeclarationList &&
4383
+ ! isPossiblyTypeArgumentPosition (node , file , typeChecker )
4384
+ }
4385
+
4386
+ func isFunctionLikeButNotConstructor (kind ast.Kind ) bool {
4387
+ return ast .IsFunctionLikeKind (kind ) && kind != ast .KindConstructor
4388
+ }
4389
+
4390
+ func isPreviousPropertyDeclarationTerminated (contextToken * ast.Node , file * ast.SourceFile , position int ) bool {
4391
+ return contextToken .Kind != ast .KindEqualsToken &&
4392
+ (contextToken .Kind == ast .KindSemicolonToken ||
4393
+ getLineOfPosition (file , contextToken .End ()) != getLineOfPosition (file , position ))
4394
+ }
4395
+
4396
+ func isDotOfNumericLiteral (contextToken * ast.Node , file * ast.SourceFile ) bool {
4397
+ if contextToken .Kind == ast .KindNumericLiteral {
4398
+ text := file .Text ()[contextToken .Pos ():contextToken .End ()]
4399
+ r , _ := utf8 .DecodeLastRuneInString (text )
4400
+ return r == '.'
4401
+ }
4402
+
4403
+ return false
4404
+ }
4405
+
4406
+ func isInJsxText (contextToken * ast.Node , location * ast.Node ) bool {
4407
+ if contextToken .Kind == ast .KindJsxText {
4408
+ return true
4409
+ }
4410
+
4411
+ if contextToken .Kind == ast .KindGreaterThanToken && contextToken .Parent != nil {
4412
+ // <Component<string> /**/ />
4413
+ // <Component<string> /**/ ><Component>
4414
+ // - contextToken: GreaterThanToken (before cursor)
4415
+ // - location: JsxSelfClosingElement or JsxOpeningElement
4416
+ // - contextToken.parent === location
4417
+ if location == contextToken .Parent && ast .IsJsxOpeningLikeElement (location ) {
4418
+ return false
4419
+ }
4420
+
4421
+ if contextToken .Parent .Kind == ast .KindJsxOpeningElement {
4422
+ // <div>/**/
4423
+ // - contextToken: GreaterThanToken (before cursor)
4424
+ // - location: JSXElement
4425
+ // - different parents (JSXOpeningElement, JSXElement)
4426
+ return location .Parent .Kind != ast .KindJsxOpeningElement
4427
+ }
4428
+
4429
+ if contextToken .Parent .Kind == ast .KindJsxClosingElement ||
4430
+ contextToken .Parent .Kind == ast .KindJsxSelfClosingElement {
4431
+ return contextToken .Parent .Parent != nil && contextToken .Parent .Parent .Kind == ast .KindJsxElement
4432
+ }
4433
+ }
4434
+
4435
+ return false
4436
+ }
0 commit comments