@@ -2476,36 +2476,38 @@ module ts {
24762476 return undefined ;
24772477 }
24782478
2479- // The decision to provide completion depends on the previous token, so find it
2480- // Note: previousToken can be undefined if we are the beginning of the file
24812479 start = new Date ( ) . getTime ( ) ;
24822480 let previousToken = findPrecedingToken ( position , sourceFile ) ;
24832481 log ( "getCompletionData: Get previous token 1: " + ( new Date ( ) . getTime ( ) - start ) ) ;
24842482
2485- // The caret is at the end of an identifier; this is a partial identifier that we want to complete: e.g. a.toS|
2486- // Skip this partial identifier to the previous token
2487- if ( previousToken && position <= previousToken . end && previousToken . kind === SyntaxKind . Identifier ) {
2483+ // The decision to provide completion depends on the contextToken, which is determined through the previousToken.
2484+ // Note: 'previousToken' (and thus 'contextToken') can be undefined if we are the beginning of the file
2485+ let contextToken = previousToken ;
2486+
2487+ // Check if the caret is at the end of an identifier; this is a partial identifier that we want to complete: e.g. a.toS|
2488+ // Skip this partial identifier and adjust the contextToken to the token that precedes it.
2489+ if ( contextToken && position <= contextToken . end && isWord ( contextToken . kind ) ) {
24882490 let start = new Date ( ) . getTime ( ) ;
2489- previousToken = findPrecedingToken ( previousToken . pos , sourceFile ) ;
2491+ contextToken = findPrecedingToken ( contextToken . getFullStart ( ) , sourceFile ) ;
24902492 log ( "getCompletionData: Get previous token 2: " + ( new Date ( ) . getTime ( ) - start ) ) ;
24912493 }
24922494
24932495 // Check if this is a valid completion location
2494- if ( previousToken && isCompletionListBlocker ( previousToken ) ) {
2496+ if ( contextToken && isCompletionListBlocker ( contextToken ) ) {
24952497 log ( "Returning an empty list because completion was requested in an invalid position." ) ;
24962498 return undefined ;
24972499 }
24982500
24992501 // Find the node where completion is requested on, in the case of a completion after a dot, it is the member access expression
2500- // other wise , it is a request for all visible symbols in the scope, and the node is the current location
2502+ // otherwise , it is a request for all visible symbols in the scope, and the node is the current location
25012503 let node = currentToken ;
25022504 let isRightOfDot = false ;
2503- if ( previousToken && previousToken . kind === SyntaxKind . DotToken && previousToken . parent . kind === SyntaxKind . PropertyAccessExpression ) {
2504- node = ( < PropertyAccessExpression > previousToken . parent ) . expression ;
2505+ if ( contextToken && contextToken . kind === SyntaxKind . DotToken && contextToken . parent . kind === SyntaxKind . PropertyAccessExpression ) {
2506+ node = ( < PropertyAccessExpression > contextToken . parent ) . expression ;
25052507 isRightOfDot = true ;
25062508 }
2507- else if ( previousToken && previousToken . kind === SyntaxKind . DotToken && previousToken . parent . kind === SyntaxKind . QualifiedName ) {
2508- node = ( < QualifiedName > previousToken . parent ) . left ;
2509+ else if ( contextToken && contextToken . kind === SyntaxKind . DotToken && contextToken . parent . kind === SyntaxKind . QualifiedName ) {
2510+ node = ( < QualifiedName > contextToken . parent ) . left ;
25092511 isRightOfDot = true ;
25102512 }
25112513
@@ -2552,7 +2554,7 @@ module ts {
25522554 }
25532555 }
25542556 else {
2555- let containingObjectLiteral = getContainingObjectLiteralApplicableForCompletion ( previousToken ) ;
2557+ let containingObjectLiteral = getContainingObjectLiteralApplicableForCompletion ( contextToken ) ;
25562558 if ( containingObjectLiteral ) {
25572559 // Object literal expression, look up possible property names from contextual type
25582560 isMemberCompletion = true ;
@@ -2569,27 +2571,59 @@ module ts {
25692571 symbols = filterContextualMembersList ( contextualTypeMembers , containingObjectLiteral . properties ) ;
25702572 }
25712573 }
2572- else if ( getAncestor ( previousToken , SyntaxKind . ImportClause ) ) {
2574+ else if ( getAncestor ( contextToken , SyntaxKind . ImportClause ) ) {
25732575 // cursor is in import clause
25742576 // try to show exported member for imported module
25752577 isMemberCompletion = true ;
25762578 isNewIdentifierLocation = true ;
2577- if ( showCompletionsInImportsClause ( previousToken ) ) {
2578- let importDeclaration = < ImportDeclaration > getAncestor ( previousToken , SyntaxKind . ImportDeclaration ) ;
2579+ if ( showCompletionsInImportsClause ( contextToken ) ) {
2580+ let importDeclaration = < ImportDeclaration > getAncestor ( contextToken , SyntaxKind . ImportDeclaration ) ;
25792581 Debug . assert ( importDeclaration !== undefined ) ;
25802582 let exports = typeInfoResolver . getExportsOfExternalModule ( importDeclaration ) ;
25812583 symbols = filterModuleExports ( exports , importDeclaration ) ;
25822584 }
25832585 }
25842586 else {
2585- // Get scope members
2587+ // Get all entities in the current scope.
25862588 isMemberCompletion = false ;
2587- isNewIdentifierLocation = isNewIdentifierDefinitionLocation ( previousToken ) ;
2589+ isNewIdentifierLocation = isNewIdentifierDefinitionLocation ( contextToken ) ;
2590+
2591+ if ( previousToken !== contextToken ) {
2592+ Debug . assert ( ! ! previousToken , "Expected 'contextToken' to be defined when different from 'previousToken'." ) ;
2593+ }
2594+ // We need to find the node that will give us an appropriate scope to begin
2595+ // aggregating completion candidates. This is achieved in 'getScopeNode'
2596+ // by finding the first node that encompasses a position, accounting for whether a node
2597+ // is "complete" to decide whether a position belongs to the node.
2598+ //
2599+ // However, at the end of an identifier, we are interested in the scope of the identifier
2600+ // itself, but fall outside of the identifier. For instance:
2601+ //
2602+ // xyz => x$
2603+ //
2604+ // the cursor is outside of both the 'x' and the arrow function 'xyz => x',
2605+ // so 'xyz' is not returned in our results.
2606+ //
2607+ // We define 'adjustedPosition' so that we may appropriately account for
2608+ // being at the end of an identifier. The intention is that if requesting completion
2609+ // at the end of an identifier, it should be effectively equivalent to requesting completion
2610+ // anywhere inside/at the beginning of the identifier. So in the previous case, the
2611+ // 'adjustedPosition' will work as if requesting completion in the following:
2612+ //
2613+ // xyz => $x
2614+ //
2615+ // If previousToken !== contextToken, then
2616+ // - 'contextToken' was adjusted to the token prior to 'previousToken'
2617+ // because we were at the end of an identifier.
2618+ // - 'previousToken' is defined.
2619+ let adjustedPosition = previousToken !== contextToken ?
2620+ previousToken . getStart ( ) :
2621+ position ;
2622+
2623+ let scopeNode = getScopeNode ( contextToken , adjustedPosition , sourceFile ) ;
25882624
25892625 /// TODO filter meaning based on the current context
2590- let scopeNode = getScopeNode ( previousToken , position , sourceFile ) ;
25912626 let symbolMeanings = SymbolFlags . Type | SymbolFlags . Value | SymbolFlags . Namespace | SymbolFlags . Alias ;
2592-
25932627 symbols = typeInfoResolver . getSymbolsInScope ( scopeNode , symbolMeanings ) ;
25942628 }
25952629 }
0 commit comments