Allow types as well as values in possibly type argument location

Fixes #22369
This commit is contained in:
Sheetal Nandi
2018-03-08 16:06:26 -08:00
parent 17b10dc2a9
commit e1a89e7011
5 changed files with 162 additions and 7 deletions
+11 -6
View File
@@ -994,6 +994,7 @@ namespace ts.Completions {
// Since this is qualified name check its a type node location
const isTypeLocation = insideJsDocTagTypeExpression || isPartOfTypeNode(node.parent);
const isRhsOfImportDeclaration = isInRightSideOfInternalImportEqualsDeclaration(node);
const allowTypeOrValue = isRhsOfImportDeclaration || (!isTypeLocation && isPossiblyTypeArgumentPosition(contextToken, sourceFile));
if (isEntityName(node)) {
let symbol = typeChecker.getSymbolAtLocation(node);
if (symbol) {
@@ -1004,7 +1005,7 @@ namespace ts.Completions {
const exportedSymbols = Debug.assertEachDefined(typeChecker.getExportsOfModule(symbol), "getExportsOfModule() should all be defined");
const isValidValueAccess = (symbol: Symbol) => typeChecker.isValidPropertyAccess(<PropertyAccessExpression>(node.parent), symbol.name);
const isValidTypeAccess = (symbol: Symbol) => symbolCanBeReferencedAtTypeLocation(symbol);
const isValidAccess = isRhsOfImportDeclaration ?
const isValidAccess = allowTypeOrValue ?
// Any kind is allowed when dotting off namespace in internal import equals declaration
(symbol: Symbol) => isValidTypeAccess(symbol) || isValidValueAccess(symbol) :
isTypeLocation ? isValidTypeAccess : isValidValueAccess;
@@ -1173,8 +1174,9 @@ namespace ts.Completions {
}
function filterGlobalCompletion(symbols: Symbol[]): void {
const isTypeCompletion = insideJsDocTagTypeExpression || !isContextTokenValueLocation(contextToken) && (isPartOfTypeNode(location) || isContextTokenTypeLocation(contextToken));
if (isTypeCompletion) keywordFilters = KeywordCompletionFilters.TypeKeywords;
const isTypeOnlyCompletion = insideJsDocTagTypeExpression || !isContextTokenValueLocation(contextToken) && (isPartOfTypeNode(location) || isContextTokenTypeLocation(contextToken));
const allowTypes = isTypeOnlyCompletion || !isContextTokenValueLocation(contextToken) && isPossiblyTypeArgumentPosition(contextToken, sourceFile);
if (isTypeOnlyCompletion) keywordFilters = KeywordCompletionFilters.TypeKeywords;
filterMutate(symbols, symbol => {
if (!isSourceFile(location)) {
@@ -1190,9 +1192,12 @@ namespace ts.Completions {
return !!(symbol.flags & SymbolFlags.Namespace);
}
if (isTypeCompletion) {
if (allowTypes) {
// Its a type, but you can reach it by namespace.type as well
return symbolCanBeReferencedAtTypeLocation(symbol);
const symbolAllowedAsType = symbolCanBeReferencedAtTypeLocation(symbol);
if (symbolAllowedAsType || isTypeOnlyCompletion) {
return symbolAllowedAsType;
}
}
}
@@ -1204,7 +1209,7 @@ namespace ts.Completions {
function isContextTokenValueLocation(contextToken: Node) {
return contextToken &&
contextToken.kind === SyntaxKind.TypeOfKeyword &&
contextToken.parent.kind === SyntaxKind.TypeQuery;
(contextToken.parent.kind === SyntaxKind.TypeQuery || isTypeOfExpression(contextToken.parent));
}
function isContextTokenTypeLocation(contextToken: Node): boolean {
+102
View File
@@ -890,6 +890,108 @@ namespace ts {
return isTemplateLiteralKind(token.kind) && position > token.getStart(sourceFile);
}
export function findPrecedingMatchingToken(token: Node, matchingTokenKind: SyntaxKind, sourceFile: SourceFile) {
const tokenKind = token.kind;
let remainingMatchingTokens = 0;
while (true) {
token = findPrecedingToken(token.getFullStart(), sourceFile);
if (!token) {
return undefined;
}
if (token.kind === matchingTokenKind) {
if (remainingMatchingTokens === 0) {
return token;
}
remainingMatchingTokens--;
}
else if (token.kind === tokenKind) {
remainingMatchingTokens++;
}
}
}
export function isPossiblyTypeArgumentPosition(token: Node, sourceFile: SourceFile) {
let remainingLessThanTokens = 0;
while (token) {
switch (token.kind) {
case SyntaxKind.LessThanToken:
// Found the beginning of the generic argument expression
token = findPrecedingToken(token.getFullStart(), sourceFile);
const tokenIsIdentifier = token && isIdentifier(token);
if (!remainingLessThanTokens || !tokenIsIdentifier) {
return tokenIsIdentifier;
}
remainingLessThanTokens--;
break;
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
remainingLessThanTokens++;
// falls through
case SyntaxKind.GreaterThanGreaterThanToken:
remainingLessThanTokens++;
// falls through
case SyntaxKind.GreaterThanToken:
remainingLessThanTokens++;
break;
case SyntaxKind.CloseBraceToken:
// This can be object type, skip untill we find the matching open brace token
// Skip untill the matching open brace token
token = findPrecedingMatchingToken(token, SyntaxKind.OpenBraceToken, sourceFile);
if (!token) return false;
break;
case SyntaxKind.CloseParenToken:
// This can be object type, skip untill we find the matching open brace token
// Skip untill the matching open brace token
token = findPrecedingMatchingToken(token, SyntaxKind.OpenParenToken, sourceFile);
if (!token) return false;
break;
case SyntaxKind.CloseBracketToken:
// This can be object type, skip untill we find the matching open brace token
// Skip untill the matching open brace token
token = findPrecedingMatchingToken(token, SyntaxKind.OpenBracketToken, sourceFile);
if (!token) return false;
break;
// Valid tokens in a type name. Skip.
case SyntaxKind.CommaToken:
case SyntaxKind.EqualsGreaterThanToken:
case SyntaxKind.Identifier:
case SyntaxKind.StringLiteral:
case SyntaxKind.NumericLiteral:
case SyntaxKind.TrueKeyword:
case SyntaxKind.FalseKeyword:
case SyntaxKind.TypeOfKeyword:
case SyntaxKind.ExtendsKeyword:
case SyntaxKind.KeyOfKeyword:
case SyntaxKind.DotToken:
case SyntaxKind.BarToken:
case SyntaxKind.QuestionToken:
case SyntaxKind.ColonToken:
break;
default:
if (isTypeNode(token)) {
break;
}
// Invalid token in type
return false;
}
token = findPrecedingToken(token.getFullStart(), sourceFile);
}
return false;
}
/**
* Returns true if the cursor at position in sourceFile is within a comment.
*