@@ -36,15 +36,15 @@ namespace ts.Completions {
36
36
) : CompletionInfo | undefined {
37
37
if ( isInReferenceComment ( sourceFile , position ) ) {
38
38
const entries = PathCompletions . getTripleSlashReferenceCompletion ( sourceFile , position , compilerOptions , host ) ;
39
- return entries && pathCompletionsInfo ( entries ) ;
39
+ return entries && convertPathCompletions ( entries ) ;
40
40
}
41
41
42
42
const contextToken = findPrecedingToken ( position , sourceFile ) ;
43
43
44
44
if ( isInString ( sourceFile , position , contextToken ) ) {
45
45
return ! contextToken || ! isStringLiteral ( contextToken ) && ! isNoSubstitutionTemplateLiteral ( contextToken )
46
46
? undefined
47
- : getStringLiteralCompletionEntries ( sourceFile , contextToken , position , typeChecker , compilerOptions , host , log ) ;
47
+ : convertStringLiteralCompletions ( getStringLiteralCompletionEntries ( sourceFile , contextToken , position , typeChecker , compilerOptions , host ) , sourceFile , typeChecker , log ) ;
48
48
}
49
49
50
50
if ( contextToken && isBreakOrContinueStatement ( contextToken . parent )
@@ -73,6 +73,34 @@ namespace ts.Completions {
73
73
}
74
74
}
75
75
76
+ function convertStringLiteralCompletions ( completion : StringLiteralCompletion | undefined , sourceFile : SourceFile , checker : TypeChecker , log : Log ) : CompletionInfo | undefined {
77
+ if ( completion === undefined ) {
78
+ return undefined ;
79
+ }
80
+ switch ( completion . kind ) {
81
+ case StringLiteralCompletionKind . Paths :
82
+ return convertPathCompletions ( completion . paths ) ;
83
+ case StringLiteralCompletionKind . Properties : {
84
+ const entries : CompletionEntry [ ] = [ ] ;
85
+ getCompletionEntriesFromSymbols ( completion . symbols , entries , sourceFile , sourceFile , checker , ScriptTarget . ESNext , log , CompletionKind . String ) ; // Target will not be used, so arbitrary
86
+ return { isGlobalCompletion : false , isMemberCompletion : true , isNewIdentifierLocation : true , entries } ;
87
+ }
88
+ case StringLiteralCompletionKind . Types : {
89
+ const entries = completion . types . map ( type => ( { name : type . value , kindModifiers : ScriptElementKindModifier . none , kind : ScriptElementKind . variableElement , sortText : "0" } ) ) ;
90
+ return { isGlobalCompletion : false , isMemberCompletion : false , isNewIdentifierLocation : true , entries } ;
91
+ }
92
+ default :
93
+ return Debug . assertNever ( completion ) ;
94
+ }
95
+ }
96
+
97
+ function convertPathCompletions ( pathCompletions : ReadonlyArray < PathCompletions . PathCompletion > ) : CompletionInfo {
98
+ const isGlobalCompletion = false ; // We don't want the editor to offer any other completions, such as snippets, inside a comment.
99
+ const isNewIdentifierLocation = true ; // The user may type in a path that doesn't yet exist, creating a "new identifier" with respect to the collection of identifiers the server is aware of.
100
+ const entries = pathCompletions . map ( ( { name, kind, span } ) => ( { name, kind, kindModifiers : ScriptElementKindModifier . none , sortText : "0" , replacementSpan : span } ) ) ;
101
+ return { isGlobalCompletion, isMemberCompletion : false , isNewIdentifierLocation, entries } ;
102
+ }
103
+
76
104
function jsdocCompletionInfo ( entries : CompletionEntry [ ] ) : CompletionInfo {
77
105
return { isGlobalCompletion : false , isMemberCompletion : false , isNewIdentifierLocation : false , entries } ;
78
106
}
@@ -294,7 +322,38 @@ namespace ts.Completions {
294
322
}
295
323
}
296
324
297
- function getStringLiteralCompletionEntries ( sourceFile : SourceFile , node : StringLiteralLike , position : number , typeChecker : TypeChecker , compilerOptions : CompilerOptions , host : LanguageServiceHost , log : Log ) : CompletionInfo | undefined {
325
+ function getLabelStatementCompletions ( node : Node ) : CompletionEntry [ ] {
326
+ const entries : CompletionEntry [ ] = [ ] ;
327
+ const uniques = createMap < true > ( ) ;
328
+ let current = node ;
329
+
330
+ while ( current ) {
331
+ if ( isFunctionLike ( current ) ) {
332
+ break ;
333
+ }
334
+ if ( isLabeledStatement ( current ) ) {
335
+ const name = current . label . text ;
336
+ if ( ! uniques . has ( name ) ) {
337
+ uniques . set ( name , true ) ;
338
+ entries . push ( {
339
+ name,
340
+ kindModifiers : ScriptElementKindModifier . none ,
341
+ kind : ScriptElementKind . label ,
342
+ sortText : "0"
343
+ } ) ;
344
+ }
345
+ }
346
+ current = current . parent ;
347
+ }
348
+ return entries ;
349
+ }
350
+
351
+ const enum StringLiteralCompletionKind { Paths , Properties , Types }
352
+ type StringLiteralCompletion =
353
+ | { readonly kind : StringLiteralCompletionKind . Paths , readonly paths : ReadonlyArray < PathCompletions . PathCompletion > }
354
+ | { readonly kind : StringLiteralCompletionKind . Properties , readonly symbols : ReadonlyArray < Symbol > }
355
+ | { readonly kind : StringLiteralCompletionKind . Types , readonly types : ReadonlyArray < StringLiteralType > } ;
356
+ function getStringLiteralCompletionEntries ( sourceFile : SourceFile , node : StringLiteralLike , position : number , typeChecker : TypeChecker , compilerOptions : CompilerOptions , host : LanguageServiceHost ) : StringLiteralCompletion | undefined {
298
357
switch ( node . parent . kind ) {
299
358
case SyntaxKind . LiteralType :
300
359
switch ( node . parent . parent . kind ) {
@@ -308,15 +367,13 @@ namespace ts.Completions {
308
367
// bar: string;
309
368
// }
310
369
// let x: Foo["/*completion position*/"]
311
- const type = typeChecker . getTypeFromTypeNode ( ( node . parent . parent as IndexedAccessTypeNode ) . objectType ) ;
312
- return getStringLiteralCompletionEntriesFromElementAccessOrIndexedAccess ( node , sourceFile , type , typeChecker , compilerOptions . target , log ) ;
370
+ return { kind : StringLiteralCompletionKind . Properties , symbols : typeChecker . getTypeFromTypeNode ( ( node . parent . parent as IndexedAccessTypeNode ) . objectType ) . getApparentProperties ( ) } ;
313
371
default :
314
372
return undefined ;
315
373
}
316
374
317
375
case SyntaxKind . PropertyAssignment :
318
- if ( node . parent . parent . kind === SyntaxKind . ObjectLiteralExpression &&
319
- ( < PropertyAssignment > node . parent ) . name === node ) {
376
+ if ( isObjectLiteralExpression ( node . parent . parent ) && ( < PropertyAssignment > node . parent ) . name === node ) {
320
377
// Get quoted name of properties of the object literal expression
321
378
// i.e. interface ConfigFiles {
322
379
// 'jspm:dev': string
@@ -329,7 +386,8 @@ namespace ts.Completions {
329
386
// foo({
330
387
// '/*completion position*/'
331
388
// });
332
- return getStringLiteralCompletionEntriesFromPropertyAssignment ( < PropertyAssignment > node . parent , sourceFile , typeChecker , compilerOptions . target , log ) ;
389
+ const type = typeChecker . getContextualType ( node . parent . parent ) ;
390
+ return { kind : StringLiteralCompletionKind . Properties , symbols : type && type . getApparentProperties ( ) } ;
333
391
}
334
392
return fromContextualType ( ) ;
335
393
@@ -342,10 +400,9 @@ namespace ts.Completions {
342
400
// }
343
401
// let a: A;
344
402
// a['/*completion position*/']
345
- const type = typeChecker . getTypeAtLocation ( expression ) ;
346
- return getStringLiteralCompletionEntriesFromElementAccessOrIndexedAccess ( node , sourceFile , type , typeChecker , compilerOptions . target , log ) ;
403
+ return { kind : StringLiteralCompletionKind . Properties , symbols : typeChecker . getTypeAtLocation ( expression ) . getApparentProperties ( ) } ;
347
404
}
348
- break ;
405
+ return undefined ;
349
406
}
350
407
351
408
case SyntaxKind . CallExpression :
@@ -355,9 +412,15 @@ namespace ts.Completions {
355
412
// Get string literal completions from specialized signatures of the target
356
413
// i.e. declare function f(a: 'A');
357
414
// f("/*completion position*/")
358
- return argumentInfo ? getStringLiteralCompletionEntriesFromCallExpression ( argumentInfo , typeChecker ) : fromContextualType ( ) ;
415
+ if ( argumentInfo ) {
416
+ const candidates : Signature [ ] = [ ] ;
417
+ typeChecker . getResolvedSignature ( argumentInfo . invocation , candidates , argumentInfo . argumentCount ) ;
418
+ const uniques = createMap < true > ( ) ;
419
+ return { kind : StringLiteralCompletionKind . Types , types : flatMap ( candidates , candidate => getStringLiteralTypes ( typeChecker . getParameterType ( candidate , argumentInfo . argumentIndex ) , typeChecker , uniques ) ) } ;
420
+ }
421
+ return fromContextualType ( ) ;
359
422
}
360
- // falls through
423
+ // falls through (is `require("")` or `import("")`)
361
424
362
425
case SyntaxKind . ImportDeclaration :
363
426
case SyntaxKind . ExportDeclaration :
@@ -368,132 +431,28 @@ namespace ts.Completions {
368
431
// import x = require("/*completion position*/");
369
432
// var y = require("/*completion position*/");
370
433
// export * from "/*completion position*/";
371
- return pathCompletionsInfo ( PathCompletions . getStringLiteralCompletionsFromModuleNames ( sourceFile , node , compilerOptions , host , typeChecker ) ) ;
434
+ return { kind : StringLiteralCompletionKind . Paths , paths : PathCompletions . getStringLiteralCompletionsFromModuleNames ( sourceFile , node , compilerOptions , host , typeChecker ) } ;
372
435
373
436
default :
374
437
return fromContextualType ( ) ;
375
438
}
376
439
377
- function fromContextualType ( ) : CompletionInfo {
440
+ function fromContextualType ( ) : StringLiteralCompletion {
378
441
// Get completion for string literal from string literal type
379
442
// i.e. var x: "hi" | "hello" = "/*completion position*/"
380
- return getStringLiteralCompletionEntriesFromType ( getContextualTypeFromParent ( node , typeChecker ) , typeChecker ) ;
443
+ return { kind : StringLiteralCompletionKind . Types , types : getStringLiteralTypes ( getContextualTypeFromParent ( node , typeChecker ) , typeChecker ) } ;
381
444
}
382
445
}
383
446
384
- function pathCompletionsInfo ( entries : CompletionEntry [ ] ) : CompletionInfo {
385
- return {
386
- // We don't want the editor to offer any other completions, such as snippets, inside a comment.
387
- isGlobalCompletion : false ,
388
- isMemberCompletion : false ,
389
- // The user may type in a path that doesn't yet exist, creating a "new identifier"
390
- // with respect to the collection of identifiers the server is aware of.
391
- isNewIdentifierLocation : true ,
392
- entries,
393
- } ;
394
- }
395
-
396
- function getStringLiteralCompletionEntriesFromPropertyAssignment ( element : ObjectLiteralElement , sourceFile : SourceFile , typeChecker : TypeChecker , target : ScriptTarget , log : Log ) : CompletionInfo | undefined {
397
- const type = typeChecker . getContextualType ( ( < ObjectLiteralExpression > element . parent ) ) ;
398
- const entries : CompletionEntry [ ] = [ ] ;
399
- if ( type ) {
400
- getCompletionEntriesFromSymbols ( type . getApparentProperties ( ) , entries , element , sourceFile , typeChecker , target , log , CompletionKind . String ) ;
401
- if ( entries . length ) {
402
- return { isGlobalCompletion : false , isMemberCompletion : true , isNewIdentifierLocation : true , entries } ;
403
- }
404
- }
405
- }
406
-
407
- function getStringLiteralCompletionEntriesFromCallExpression ( argumentInfo : SignatureHelp . ArgumentListInfo , typeChecker : TypeChecker ) : CompletionInfo | undefined {
408
- const candidates : Signature [ ] = [ ] ;
409
- const entries : CompletionEntry [ ] = [ ] ;
410
- const uniques = createMap < true > ( ) ;
411
-
412
- typeChecker . getResolvedSignature ( argumentInfo . invocation , candidates , argumentInfo . argumentCount ) ;
413
-
414
- for ( const candidate of candidates ) {
415
- addStringLiteralCompletionsFromType ( typeChecker . getParameterType ( candidate , argumentInfo . argumentIndex ) , entries , typeChecker , uniques ) ;
416
- }
417
-
418
- if ( entries . length ) {
419
- return { isGlobalCompletion : false , isMemberCompletion : false , isNewIdentifierLocation : true , entries } ;
420
- }
421
-
422
- return undefined ;
423
- }
424
-
425
- function getStringLiteralCompletionEntriesFromElementAccessOrIndexedAccess ( stringLiteralNode : StringLiteral | NoSubstitutionTemplateLiteral , sourceFile : SourceFile , type : Type , typeChecker : TypeChecker , target : ScriptTarget , log : Log ) : CompletionInfo | undefined {
426
- const entries : CompletionEntry [ ] = [ ] ;
427
- if ( type ) {
428
- getCompletionEntriesFromSymbols ( type . getApparentProperties ( ) , entries , stringLiteralNode , sourceFile , typeChecker , target , log , CompletionKind . String ) ;
429
- if ( entries . length ) {
430
- return { isGlobalCompletion : false , isMemberCompletion : true , isNewIdentifierLocation : true , entries } ;
431
- }
432
- }
433
- return undefined ;
434
- }
435
-
436
- function getStringLiteralCompletionEntriesFromType ( type : Type , typeChecker : TypeChecker ) : CompletionInfo | undefined {
437
- if ( type ) {
438
- const entries : CompletionEntry [ ] = [ ] ;
439
- addStringLiteralCompletionsFromType ( type , entries , typeChecker ) ;
440
- if ( entries . length ) {
441
- return { isGlobalCompletion : false , isMemberCompletion : false , isNewIdentifierLocation : false , entries } ;
442
- }
443
- }
444
- return undefined ;
445
- }
446
-
447
- function getLabelStatementCompletions ( node : Node ) : CompletionEntry [ ] {
448
- const entries : CompletionEntry [ ] = [ ] ;
449
- const uniques = createMap < true > ( ) ;
450
- let current = node ;
451
-
452
- while ( current ) {
453
- if ( isFunctionLike ( current ) ) {
454
- break ;
455
- }
456
- if ( isLabeledStatement ( current ) ) {
457
- const name = current . label . text ;
458
- if ( ! uniques . has ( name ) ) {
459
- uniques . set ( name , true ) ;
460
- entries . push ( {
461
- name,
462
- kindModifiers : ScriptElementKindModifier . none ,
463
- kind : ScriptElementKind . label ,
464
- sortText : "0"
465
- } ) ;
466
- }
467
- }
468
- current = current . parent ;
469
- }
470
- return entries ;
471
- }
472
-
473
- function addStringLiteralCompletionsFromType ( type : Type , result : Push < CompletionEntry > , typeChecker : TypeChecker , uniques = createMap < true > ( ) ) : void {
447
+ function getStringLiteralTypes ( type : Type , typeChecker : TypeChecker , uniques = createMap < true > ( ) ) : ReadonlyArray < StringLiteralType > {
474
448
if ( type && type . flags & TypeFlags . TypeParameter ) {
475
- type = typeChecker . getBaseConstraintOfType ( type ) ;
476
- }
477
- if ( ! type ) {
478
- return ;
479
- }
480
- if ( type . flags & TypeFlags . Union ) {
481
- for ( const t of ( < UnionType > type ) . types ) {
482
- addStringLiteralCompletionsFromType ( t , result , typeChecker , uniques ) ;
483
- }
484
- }
485
- else if ( type . flags & TypeFlags . StringLiteral && ! ( type . flags & TypeFlags . EnumLiteral ) ) {
486
- const name = ( < StringLiteralType > type ) . value ;
487
- if ( ! uniques . has ( name ) ) {
488
- uniques . set ( name , true ) ;
489
- result . push ( {
490
- name,
491
- kindModifiers : ScriptElementKindModifier . none ,
492
- kind : ScriptElementKind . variableElement ,
493
- sortText : "0"
494
- } ) ;
495
- }
449
+ type = type . getConstraint ( ) ;
496
450
}
451
+ return type && type . flags & TypeFlags . Union
452
+ ? flatMap ( ( < UnionType > type ) . types , t => getStringLiteralTypes ( t , typeChecker , uniques ) )
453
+ : type && type . flags & TypeFlags . StringLiteral && ! ( type . flags & TypeFlags . EnumLiteral ) && addToSeen ( uniques , ( type as StringLiteralType ) . value )
454
+ ? [ type as StringLiteralType ]
455
+ : emptyArray ;
497
456
}
498
457
499
458
interface SymbolCompletion {
0 commit comments