Support signature help for partially-filled-in type arguments f< (#24138)

* Support signature help for partially-filled-in type arguments `f<`

* Use `isPossiblyTypeArgumentPosition` and support new expressions
This commit is contained in:
Andy
2018-05-23 08:28:09 -07:00
committed by GitHub
parent 16d7f4c103
commit fb4caadaec
4 changed files with 174 additions and 82 deletions

View File

@@ -422,7 +422,7 @@ namespace ts.Completions {
case SyntaxKind.CallExpression:
case SyntaxKind.NewExpression:
if (!isRequireCall(node.parent, /*checkArgumentIsStringLiteralLike*/ false) && !isImportCall(node.parent)) {
const argumentInfo = SignatureHelp.getImmediatelyContainingArgumentInfo(node, position, sourceFile);
const argumentInfo = SignatureHelp.getArgumentInfoForCompletions(node, position, sourceFile);
// Get string literal completions from specialized signatures of the target
// i.e. declare function f(a: 'A');
// f("/*completion position*/")
@@ -452,7 +452,7 @@ namespace ts.Completions {
}
}
function getStringLiteralCompletionsFromSignature(argumentInfo: SignatureHelp.ArgumentListInfo, checker: TypeChecker): StringLiteralCompletionsFromTypes {
function getStringLiteralCompletionsFromSignature(argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes {
let isNewIdentifier = false;
const uniques = createMap<true>();
@@ -460,7 +460,7 @@ namespace ts.Completions {
checker.getResolvedSignature(argumentInfo.invocation, candidates, argumentInfo.argumentCount);
const types = flatMap(candidates, candidate => {
if (!candidate.hasRestParameter && argumentInfo.argumentCount > candidate.parameters.length) return;
const type = checker.getParameterType(candidate, argumentInfo.argumentIndex!); // TODO: GH#18217
const type = checker.getParameterType(candidate, argumentInfo.argumentIndex);
isNewIdentifier = isNewIdentifier || !!(type.flags & TypeFlags.String);
return getStringLiteralTypes(type, checker, uniques);
});
@@ -720,10 +720,10 @@ namespace ts.Completions {
case SyntaxKind.OpenBraceToken:
return isJsxExpression(parent) && parent.parent.kind !== SyntaxKind.JsxElement ? checker.getContextualTypeForJsxAttribute(parent.parent) : undefined;
default:
const argInfo = SignatureHelp.getImmediatelyContainingArgumentInfo(currentToken, position, sourceFile);
const argInfo = SignatureHelp.getArgumentInfoForCompletions(currentToken, position, sourceFile);
return argInfo
// At `,`, treat this as the next argument after the comma.
? checker.getContextualTypeForArgumentAtIndex(argInfo.invocation, argInfo.argumentIndex! + (currentToken.kind === SyntaxKind.CommaToken ? 1 : 0)) // TODO: GH#18217
? checker.getContextualTypeForArgumentAtIndex(argInfo.invocation, argInfo.argumentIndex + (currentToken.kind === SyntaxKind.CommaToken ? 1 : 0))
: isEqualityOperatorKind(currentToken.kind) && isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind)
// completion at `x ===/**/` should be for the right side
? checker.getTypeAtLocation(parent.left)

View File

@@ -1,17 +1,20 @@
/* @internal */
namespace ts.SignatureHelp {
export const enum ArgumentListKind {
const enum ArgumentListKind {
TypeArguments,
CallArguments,
TaggedTemplateArguments,
JSXAttributesArguments
}
export interface ArgumentListInfo {
const enum InvocationKind { Call, TypeArgs }
type Invocation = { kind: InvocationKind.Call, node: CallLikeExpression } | { kind: InvocationKind.TypeArgs, called: Expression };
interface ArgumentListInfo {
kind: ArgumentListKind;
invocation: CallLikeExpression;
invocation: Invocation;
argumentsSpan: TextSpan;
argumentIndex?: number;
argumentIndex: number;
/** argumentCount is the *apparent* number of arguments. */
argumentCount: number;
}
@@ -32,32 +35,39 @@ namespace ts.SignatureHelp {
cancellationToken.throwIfCancellationRequested();
// Semantic filtering of signature help
const call = argumentInfo.invocation;
const candidates: Signature[] = [];
const resolvedSignature = typeChecker.getResolvedSignature(call, candidates, argumentInfo.argumentCount);
const candidateInfo = getCandidateInfo(argumentInfo, typeChecker);
cancellationToken.throwIfCancellationRequested();
if (!candidates.length) {
if (!candidateInfo) {
// We didn't have any sig help items produced by the TS compiler. If this is a JS
// file, then see if we can figure out anything better.
if (isSourceFileJavaScript(sourceFile)) {
return createJavaScriptSignatureHelpItems(argumentInfo, program, cancellationToken);
}
return undefined;
}
return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => createSignatureHelpItems(candidates, resolvedSignature!, argumentInfo, typeChecker));
return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => createSignatureHelpItems(candidateInfo.candidates, candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker));
}
function getCandidateInfo(argumentInfo: ArgumentListInfo, checker: TypeChecker): { readonly candidates: ReadonlyArray<Signature>, readonly resolvedSignature: Signature } | undefined {
const { invocation } = argumentInfo;
if (invocation.kind === InvocationKind.Call) {
const candidates: Signature[] = [];
const resolvedSignature = checker.getResolvedSignature(invocation.node, candidates, argumentInfo.argumentCount)!; // TODO: GH#18217
return candidates.length === 0 ? undefined : { candidates, resolvedSignature };
}
else {
const type = checker.getTypeAtLocation(invocation.called)!; // TODO: GH#18217
const signatures = isNewExpression(invocation.called.parent) ? type.getConstructSignatures() : type.getCallSignatures();
const candidates = signatures.filter(candidate => !!candidate.typeParameters && candidate.typeParameters.length >= argumentInfo.argumentCount);
return candidates.length === 0 ? undefined : { candidates, resolvedSignature: first(candidates) };
}
}
function createJavaScriptSignatureHelpItems(argumentInfo: ArgumentListInfo, program: Program, cancellationToken: CancellationToken): SignatureHelpItems | undefined {
if (argumentInfo.invocation.kind !== SyntaxKind.CallExpression) {
return undefined;
}
// See if we can find some symbol with the call expression name that has call signatures.
const callExpression = argumentInfo.invocation;
const expression = callExpression.expression;
const expression = getExpressionFromInvocation(argumentInfo.invocation);
const name = isIdentifier(expression) ? expression : isPropertyAccessExpression(expression) ? expression.name : undefined;
if (!name || !name.escapedText) {
return undefined;
@@ -76,7 +86,7 @@ namespace ts.SignatureHelp {
if (type) {
const callSignatures = type.getCallSignatures();
if (callSignatures && callSignatures.length) {
return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => createSignatureHelpItems(callSignatures, callSignatures[0], argumentInfo, typeChecker));
return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => createSignatureHelpItems(callSignatures, callSignatures[0], argumentInfo, sourceFile, typeChecker));
}
}
}
@@ -85,13 +95,25 @@ namespace ts.SignatureHelp {
}
}
export interface ArgumentInfoForCompletions {
readonly invocation: CallLikeExpression;
readonly argumentIndex: number;
readonly argumentCount: number;
}
export function getArgumentInfoForCompletions(node: Node, position: number, sourceFile: SourceFile): ArgumentInfoForCompletions | undefined {
const info = getImmediatelyContainingArgumentInfo(node, position, sourceFile);
return !info || info.kind === ArgumentListKind.TypeArguments || info.invocation.kind === InvocationKind.TypeArgs ? undefined
: { invocation: info.invocation.node, argumentCount: info.argumentCount, argumentIndex: info.argumentIndex };
}
/**
* Returns relevant information for the argument list and the current argument if we are
* in the argument of an invocation; returns undefined otherwise.
*/
export function getImmediatelyContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo | undefined {
function getImmediatelyContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo | undefined {
const { parent } = node;
if (isCallOrNewExpression(parent)) {
const invocation = parent;
let list: Node | undefined;
let argumentIndex: number;
@@ -134,56 +156,63 @@ namespace ts.SignatureHelp {
Debug.assertLessThan(argumentIndex, argumentCount);
}
const argumentsSpan = getApplicableSpanForArguments(list, sourceFile);
return { kind, invocation: parent, argumentsSpan, argumentIndex, argumentCount };
return { kind, invocation: { kind: InvocationKind.Call, node: invocation }, argumentsSpan, argumentIndex, argumentCount };
}
else if (node.kind === SyntaxKind.NoSubstitutionTemplateLiteral && parent.kind === SyntaxKind.TaggedTemplateExpression) {
else if (isNoSubstitutionTemplateLiteral(node) && isTaggedTemplateExpression(parent)) {
// Check if we're actually inside the template;
// otherwise we'll fall out and return undefined.
if (isInsideTemplateLiteral(<LiteralExpression>node, position)) {
return getArgumentListInfoForTemplate(<TaggedTemplateExpression>node.parent, /*argumentIndex*/ 0, sourceFile);
if (isInsideTemplateLiteral(node, position)) {
return getArgumentListInfoForTemplate(parent, /*argumentIndex*/ 0, sourceFile);
}
}
else if (node.kind === SyntaxKind.TemplateHead && parent.parent.kind === SyntaxKind.TaggedTemplateExpression) {
const templateExpression = <TemplateExpression>node.parent;
else if (isTemplateHead(node) && parent.parent.kind === SyntaxKind.TaggedTemplateExpression) {
const templateExpression = <TemplateExpression>parent;
const tagExpression = <TaggedTemplateExpression>templateExpression.parent;
Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression);
const argumentIndex = isInsideTemplateLiteral(<LiteralExpression>node, position) ? 0 : 1;
const argumentIndex = isInsideTemplateLiteral(node, position) ? 0 : 1;
return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile);
}
else if (parent.kind === SyntaxKind.TemplateSpan && parent.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) {
const templateSpan = <TemplateSpan>node.parent;
const templateExpression = templateSpan.parent;
const tagExpression = <TaggedTemplateExpression>templateExpression.parent;
Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression);
else if (isTemplateSpan(parent) && isTaggedTemplateExpression(parent.parent.parent)) {
const templateSpan = parent;
const tagExpression = parent.parent.parent;
// If we're just after a template tail, don't show signature help.
if (node.kind === SyntaxKind.TemplateTail && !isInsideTemplateLiteral(<LiteralExpression>node, position)) {
return undefined;
}
const spanIndex = templateExpression.templateSpans.indexOf(templateSpan);
const spanIndex = templateSpan.parent.templateSpans.indexOf(templateSpan);
const argumentIndex = getArgumentIndexForTemplatePiece(spanIndex, node, position);
return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile);
}
else if (node.parent && isJsxOpeningLikeElement(node.parent)) {
else if (isJsxOpeningLikeElement(parent)) {
// Provide a signature help for JSX opening element or JSX self-closing element.
// This is not guarantee that JSX tag-name is resolved into stateless function component. (that is done in "getSignatureHelpItems")
// i.e
// export function MainButton(props: ButtonProps, context: any): JSX.Element { ... }
// <MainButton /*signatureHelp*/
const attributeSpanStart = node.parent.attributes.getFullStart();
const attributeSpanEnd = skipTrivia(sourceFile.text, node.parent.attributes.getEnd(), /*stopAfterLineBreak*/ false);
const attributeSpanStart = parent.attributes.pos;
const attributeSpanEnd = skipTrivia(sourceFile.text, parent.attributes.end, /*stopAfterLineBreak*/ false);
return {
kind: ArgumentListKind.JSXAttributesArguments,
invocation: node.parent,
invocation: { kind: InvocationKind.Call, node: parent },
argumentsSpan: createTextSpan(attributeSpanStart, attributeSpanEnd - attributeSpanStart),
argumentIndex: 0,
argumentCount: 1
};
}
else {
const typeArgInfo = isPossiblyTypeArgumentPosition(node, sourceFile);
if (typeArgInfo) {
const { called, nTypeArguments } = typeArgInfo;
const invocation: Invocation = { kind: InvocationKind.TypeArgs, called };
const argumentsSpan = createTextSpanFromBounds(called.getStart(sourceFile), node.end);
return { kind: ArgumentListKind.TypeArguments, invocation, argumentsSpan, argumentIndex: nTypeArguments, argumentCount: nTypeArguments + 1 };
}
}
return undefined;
}
@@ -269,7 +298,7 @@ namespace ts.SignatureHelp {
}
return {
kind: ArgumentListKind.TaggedTemplateArguments,
invocation: tagExpression,
invocation: { kind: InvocationKind.Call, node: tagExpression },
argumentsSpan: getApplicableSpanForTaggedTemplate(tagExpression, sourceFile),
argumentIndex,
argumentCount
@@ -313,25 +342,15 @@ namespace ts.SignatureHelp {
return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart);
}
export function getContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo | undefined {
for (let n = node; n.kind !== SyntaxKind.SourceFile; n = n.parent) {
if (isFunctionBlock(n)) {
return undefined;
}
function getContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo | undefined {
for (let n = node; !isBlock(n) && !isSourceFile(n); n = n.parent) {
// If the node is not a subspan of its parent, this is a big problem.
// There have been crashes that might be caused by this violation.
if (n.pos < n.parent.pos || n.end > n.parent.end) {
Debug.fail("Node of kind " + n.kind + " is not a subspan of its parent of kind " + n.parent.kind);
}
Debug.assert(rangeContainsRange(n.parent, n), "Not a subspan", () => `Child: ${Debug.showSyntaxKind(n)}, parent: ${Debug.showSyntaxKind(n.parent)}`);
const argumentInfo = getImmediatelyContainingArgumentInfo(n, position, sourceFile);
if (argumentInfo) {
return argumentInfo;
}
// TODO: Handle generic call with incomplete syntax
}
return undefined;
}
@@ -343,16 +362,20 @@ namespace ts.SignatureHelp {
return children[indexOfOpenerToken + 1];
}
function getExpressionFromInvocation(invocation: Invocation): Expression {
return invocation.kind === InvocationKind.Call ? getInvokedExpression(invocation.node) : invocation.called;
}
const signatureHelpNodeBuilderFlags = NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors;
function createSignatureHelpItems(candidates: Signature[], resolvedSignature: Signature, argumentListInfo: ArgumentListInfo, typeChecker: TypeChecker): SignatureHelpItems {
function createSignatureHelpItems(candidates: ReadonlyArray<Signature>, resolvedSignature: Signature, argumentListInfo: ArgumentListInfo, sourceFile: SourceFile, typeChecker: TypeChecker): SignatureHelpItems {
const { argumentCount, argumentsSpan: applicableSpan, invocation, argumentIndex } = argumentListInfo;
const isTypeParameterList = argumentListInfo.kind === ArgumentListKind.TypeArguments;
const callTarget = getInvokedExpression(invocation);
const callTargetSymbol = typeChecker.getSymbolAtLocation(callTarget);
const enclosingDeclaration = invocation.kind === InvocationKind.Call ? invocation.node : invocation.called;
const callTargetSymbol = typeChecker.getSymbolAtLocation(getExpressionFromInvocation(invocation));
const callTargetDisplayParts = callTargetSymbol && symbolToDisplayParts(typeChecker, callTargetSymbol, /*enclosingDeclaration*/ undefined, /*meaning*/ undefined);
const printer = createPrinter({ removeComments: true });
const items: SignatureHelpItem[] = map(candidates, candidateSignature => {
const items = candidates.map<SignatureHelpItem>(candidateSignature => {
let signatureHelpParameters: SignatureHelpParameter[];
const prefixDisplayParts: SymbolDisplayPart[] = [];
const suffixDisplayParts: SymbolDisplayPart[] = [];
@@ -369,9 +392,9 @@ namespace ts.SignatureHelp {
signatureHelpParameters = typeParameters && typeParameters.length > 0 ? map(typeParameters, createSignatureHelpParameterForTypeParameter) : emptyArray;
suffixDisplayParts.push(punctuationPart(SyntaxKind.GreaterThanToken));
const parameterParts = mapToDisplayParts(writer => {
const thisParameter = candidateSignature.thisParameter ? [typeChecker.symbolToParameterDeclaration(candidateSignature.thisParameter, invocation, signatureHelpNodeBuilderFlags)!] : [];
const params = createNodeArray([...thisParameter, ...map(candidateSignature.parameters, param => typeChecker.symbolToParameterDeclaration(param, invocation, signatureHelpNodeBuilderFlags)!)]);
printer.writeList(ListFormat.CallExpressionArguments, params, getSourceFileOfNode(getParseTreeNode(invocation)), writer);
const thisParameter = candidateSignature.thisParameter ? [typeChecker.symbolToParameterDeclaration(candidateSignature.thisParameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!] : [];
const params = createNodeArray([...thisParameter, ...candidateSignature.parameters.map(param => typeChecker.symbolToParameterDeclaration(param, enclosingDeclaration, signatureHelpNodeBuilderFlags)!)]);
printer.writeList(ListFormat.CallExpressionArguments, params, sourceFile, writer);
});
addRange(suffixDisplayParts, parameterParts);
}
@@ -379,8 +402,8 @@ namespace ts.SignatureHelp {
isVariadic = candidateSignature.hasRestParameter;
const typeParameterParts = mapToDisplayParts(writer => {
if (candidateSignature.typeParameters && candidateSignature.typeParameters.length) {
const args = createNodeArray(map(candidateSignature.typeParameters, p => typeChecker.typeParameterToDeclaration(p, invocation)!));
printer.writeList(ListFormat.TypeParameters, args, getSourceFileOfNode(getParseTreeNode(invocation)), writer);
const args = createNodeArray(candidateSignature.typeParameters.map(p => typeChecker.typeParameterToDeclaration(p, enclosingDeclaration)!));
printer.writeList(ListFormat.TypeParameters, args, sourceFile, writer);
}
});
addRange(prefixDisplayParts, typeParameterParts);
@@ -395,10 +418,10 @@ namespace ts.SignatureHelp {
writer.writeSpace(" ");
const predicate = typeChecker.getTypePredicateOfSignature(candidateSignature);
if (predicate) {
typeChecker.writeTypePredicate(predicate, invocation, /*flags*/ undefined, writer);
typeChecker.writeTypePredicate(predicate, enclosingDeclaration, /*flags*/ undefined, writer);
}
else {
typeChecker.writeType(typeChecker.getReturnTypeOfSignature(candidateSignature), invocation, /*flags*/ undefined, writer);
typeChecker.writeType(typeChecker.getReturnTypeOfSignature(candidateSignature), enclosingDeclaration, /*flags*/ undefined, writer);
}
});
addRange(suffixDisplayParts, returnTypeParts);
@@ -415,18 +438,18 @@ namespace ts.SignatureHelp {
});
if (argumentIndex !== 0) {
Debug.assertLessThan(argumentIndex!, argumentCount); // TODO: GH#18217
Debug.assertLessThan(argumentIndex, argumentCount);
}
const selectedItemIndex = candidates.indexOf(resolvedSignature);
Debug.assert(selectedItemIndex !== -1); // If candidates is non-empty it should always include bestSignature. We check for an empty candidates before calling this function.
return { items, applicableSpan, selectedItemIndex, argumentIndex: argumentIndex!, argumentCount }; // TODO: GH#18217
return { items, applicableSpan, selectedItemIndex, argumentIndex, argumentCount };
function createSignatureHelpParameterForParameter(parameter: Symbol): SignatureHelpParameter {
const displayParts = mapToDisplayParts(writer => {
const param = typeChecker.symbolToParameterDeclaration(parameter, invocation, signatureHelpNodeBuilderFlags)!;
printer.writeNode(EmitHint.Unspecified, param, getSourceFileOfNode(getParseTreeNode(invocation)), writer);
const param = typeChecker.symbolToParameterDeclaration(parameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!;
printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer);
});
return {
@@ -439,8 +462,8 @@ namespace ts.SignatureHelp {
function createSignatureHelpParameterForTypeParameter(typeParameter: TypeParameter): SignatureHelpParameter {
const displayParts = mapToDisplayParts(writer => {
const param = typeChecker.typeParameterToDeclaration(typeParameter, invocation)!;
printer.writeNode(EmitHint.Unspecified, param, getSourceFileOfNode(getParseTreeNode(invocation)), writer);
const param = typeChecker.typeParameterToDeclaration(typeParameter, enclosingDeclaration)!;
printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer);
});
return {

View File

@@ -920,7 +920,11 @@ namespace ts {
}
}
export function isPossiblyTypeArgumentPosition(tokenIn: Node, sourceFile: SourceFile): boolean {
export interface PossibleTypeArgumentInfo {
readonly called: Identifier;
readonly nTypeArguments: number;
}
export function isPossiblyTypeArgumentPosition(tokenIn: Node, sourceFile: SourceFile): PossibleTypeArgumentInfo | undefined {
let token: Node | undefined = tokenIn;
// This function determines if the node could be type argument position
// Since during editing, when type argument list is not complete,
@@ -928,15 +932,15 @@ namespace ts {
// scanning of the previous identifier followed by "<" before current node would give us better result
// Note that we also balance out the already provided type arguments, arrays, object literals while doing so
let remainingLessThanTokens = 0;
let nTypeArguments = 0;
while (token) {
switch (token.kind) {
case SyntaxKind.LessThanToken:
// Found the beginning of the generic argument expression
token = findPrecedingToken(token.getFullStart(), sourceFile);
if (!token) return false;
const tokenIsIdentifier = isIdentifier(token);
if (!remainingLessThanTokens || !tokenIsIdentifier) {
return tokenIsIdentifier;
if (!token || !isIdentifier(token)) return undefined;
if (!remainingLessThanTokens) {
return { called: token, nTypeArguments };
}
remainingLessThanTokens--;
break;
@@ -957,25 +961,28 @@ namespace ts {
// This can be object type, skip until we find the matching open brace token
// Skip until the matching open brace token
token = findPrecedingMatchingToken(token, SyntaxKind.OpenBraceToken, sourceFile);
if (!token) return false;
if (!token) return undefined;
break;
case SyntaxKind.CloseParenToken:
// This can be object type, skip until we find the matching open brace token
// Skip until the matching open brace token
token = findPrecedingMatchingToken(token, SyntaxKind.OpenParenToken, sourceFile);
if (!token) return false;
if (!token) return undefined;
break;
case SyntaxKind.CloseBracketToken:
// This can be object type, skip until we find the matching open brace token
// Skip until the matching open brace token
token = findPrecedingMatchingToken(token, SyntaxKind.OpenBracketToken, sourceFile);
if (!token) return false;
if (!token) return undefined;
break;
// Valid tokens in a type name. Skip.
case SyntaxKind.CommaToken:
nTypeArguments++;
break;
case SyntaxKind.EqualsGreaterThanToken:
case SyntaxKind.Identifier:
@@ -999,13 +1006,13 @@ namespace ts {
}
// Invalid token in type
return false;
return undefined;
}
token = findPrecedingToken(token.getFullStart(), sourceFile);
}
return false;
return undefined;
}
/**
@@ -1086,7 +1093,7 @@ namespace ts {
return SyntaxKind.FirstPunctuation <= kind && kind <= SyntaxKind.LastPunctuation;
}
export function isInsideTemplateLiteral(node: LiteralExpression, position: number) {
export function isInsideTemplateLiteral(node: LiteralExpression | TemplateHead, position: number) {
return isTemplateLiteralKind(node.kind)
&& (node.getStart() < position && position < node.getEnd()) || (!!node.isUnterminated && position === node.getEnd());
}