mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge
This commit is contained in:
@@ -722,5 +722,5 @@ namespace ts.BreakpointResolver {
|
||||
return spanInNode(node.parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +188,10 @@ namespace ts.NavigationBar {
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
let moduleDeclaration = <ModuleDeclaration>node;
|
||||
topLevelNodes.push(node);
|
||||
addTopLevelNodes((<Block>getInnermostModule(moduleDeclaration).body).statements, topLevelNodes);
|
||||
const inner = getInnermostModule(moduleDeclaration);
|
||||
if (inner.body) {
|
||||
addTopLevelNodes((<Block>inner.body).statements, topLevelNodes);
|
||||
}
|
||||
break;
|
||||
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
@@ -453,7 +456,8 @@ namespace ts.NavigationBar {
|
||||
function createModuleItem(node: ModuleDeclaration): NavigationBarItem {
|
||||
const moduleName = getModuleName(node);
|
||||
|
||||
const childItems = getItemsWorker(getChildNodes((<Block>getInnermostModule(node).body).statements), createChildItem);
|
||||
const body = <Block>getInnermostModule(node).body;
|
||||
const childItems = body ? getItemsWorker(getChildNodes(body.statements), createChildItem) : [];
|
||||
|
||||
return getNavigationBarItem(moduleName,
|
||||
ts.ScriptElementKind.moduleElement,
|
||||
@@ -611,7 +615,7 @@ namespace ts.NavigationBar {
|
||||
}
|
||||
|
||||
function getInnermostModule(node: ModuleDeclaration): ModuleDeclaration {
|
||||
while (node.body.kind === SyntaxKind.ModuleDeclaration) {
|
||||
while (node.body && node.body.kind === SyntaxKind.ModuleDeclaration) {
|
||||
node = <ModuleDeclaration>node.body;
|
||||
}
|
||||
|
||||
|
||||
+177
-33
@@ -406,37 +406,56 @@ namespace ts {
|
||||
const sourceFileOfDeclaration = getSourceFileOfNode(declaration);
|
||||
// If it is parameter - try and get the jsDoc comment with @param tag from function declaration's jsDoc comments
|
||||
if (canUseParsedParamTagComments && declaration.kind === SyntaxKind.Parameter) {
|
||||
ts.forEach(getJsDocCommentTextRange(declaration.parent, sourceFileOfDeclaration), jsDocCommentTextRange => {
|
||||
const cleanedParamJsDocComment = getCleanedParamJsDocComment(jsDocCommentTextRange.pos, jsDocCommentTextRange.end, sourceFileOfDeclaration);
|
||||
if (cleanedParamJsDocComment) {
|
||||
addRange(jsDocCommentParts, cleanedParamJsDocComment);
|
||||
}
|
||||
});
|
||||
if ((declaration.parent.kind === SyntaxKind.FunctionExpression || declaration.parent.kind === SyntaxKind.ArrowFunction) &&
|
||||
declaration.parent.parent.kind === SyntaxKind.VariableDeclaration) {
|
||||
addCommentParts(declaration.parent.parent.parent, sourceFileOfDeclaration, getCleanedParamJsDocComment);
|
||||
}
|
||||
addCommentParts(declaration.parent, sourceFileOfDeclaration, getCleanedParamJsDocComment);
|
||||
}
|
||||
|
||||
// If this is left side of dotted module declaration, there is no doc comments associated with this node
|
||||
if (declaration.kind === SyntaxKind.ModuleDeclaration && (<ModuleDeclaration>declaration).body.kind === SyntaxKind.ModuleDeclaration) {
|
||||
if (declaration.kind === SyntaxKind.ModuleDeclaration && (<ModuleDeclaration>declaration).body && (<ModuleDeclaration>declaration).body.kind === SyntaxKind.ModuleDeclaration) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((declaration.kind === SyntaxKind.FunctionExpression || declaration.kind === SyntaxKind.ArrowFunction) &&
|
||||
declaration.parent.kind === SyntaxKind.VariableDeclaration) {
|
||||
addCommentParts(declaration.parent.parent, sourceFileOfDeclaration, getCleanedJsDocComment);
|
||||
}
|
||||
|
||||
// If this is dotted module name, get the doc comments from the parent
|
||||
while (declaration.kind === SyntaxKind.ModuleDeclaration && declaration.parent.kind === SyntaxKind.ModuleDeclaration) {
|
||||
declaration = <ModuleDeclaration>declaration.parent;
|
||||
}
|
||||
addCommentParts(declaration.kind === SyntaxKind.VariableDeclaration ? declaration.parent.parent : declaration,
|
||||
sourceFileOfDeclaration,
|
||||
getCleanedJsDocComment);
|
||||
|
||||
// Get the cleaned js doc comment text from the declaration
|
||||
ts.forEach(getJsDocCommentTextRange(
|
||||
declaration.kind === SyntaxKind.VariableDeclaration ? declaration.parent.parent : declaration, sourceFileOfDeclaration), jsDocCommentTextRange => {
|
||||
const cleanedJsDocComment = getCleanedJsDocComment(jsDocCommentTextRange.pos, jsDocCommentTextRange.end, sourceFileOfDeclaration);
|
||||
if (cleanedJsDocComment) {
|
||||
addRange(jsDocCommentParts, cleanedJsDocComment);
|
||||
}
|
||||
});
|
||||
if (declaration.kind === SyntaxKind.VariableDeclaration) {
|
||||
const init = (declaration as VariableDeclaration).initializer;
|
||||
if (init && (init.kind === SyntaxKind.FunctionExpression || init.kind === SyntaxKind.ArrowFunction)) {
|
||||
// Get the cleaned js doc comment text from the initializer
|
||||
addCommentParts(init, sourceFileOfDeclaration, getCleanedJsDocComment);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return jsDocCommentParts;
|
||||
|
||||
function addCommentParts(commented: Node,
|
||||
sourceFileOfDeclaration: SourceFile,
|
||||
getCommentPart: (pos: number, end: number, file: SourceFile) => SymbolDisplayPart[]): void {
|
||||
const ranges = getJsDocCommentTextRange(commented, sourceFileOfDeclaration);
|
||||
// Get the cleaned js doc comment text from the declaration
|
||||
ts.forEach(ranges, jsDocCommentTextRange => {
|
||||
const cleanedComment = getCommentPart(jsDocCommentTextRange.pos, jsDocCommentTextRange.end, sourceFileOfDeclaration);
|
||||
if (cleanedComment) {
|
||||
addRange(jsDocCommentParts, cleanedComment);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getJsDocCommentTextRange(node: Node, sourceFile: SourceFile): TextRange[] {
|
||||
return ts.map(getJsDocComments(node, sourceFile),
|
||||
jsDocComment => {
|
||||
@@ -1045,10 +1064,10 @@ namespace ts {
|
||||
getCancellationToken?(): HostCancellationToken;
|
||||
getCurrentDirectory(): string;
|
||||
getDefaultLibFileName(options: CompilerOptions): string;
|
||||
log? (s: string): void;
|
||||
trace? (s: string): void;
|
||||
error? (s: string): void;
|
||||
useCaseSensitiveFileNames? (): boolean;
|
||||
log?(s: string): void;
|
||||
trace?(s: string): void;
|
||||
error?(s: string): void;
|
||||
useCaseSensitiveFileNames?(): boolean;
|
||||
|
||||
/*
|
||||
* LS host can optionally implement this method if it wants to be completely in charge of module name resolution.
|
||||
@@ -1921,6 +1940,40 @@ namespace ts {
|
||||
sourceMapText?: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
let commandLineOptions_stringToEnum: CommandLineOptionOfCustomType[];
|
||||
|
||||
/** JS users may pass in string values for enum compiler options (such as ModuleKind), so convert. */
|
||||
function fixupCompilerOptions(options: CompilerOptions, diagnostics: Diagnostic[]): CompilerOptions {
|
||||
// Lazily create this value to fix module loading errors.
|
||||
commandLineOptions_stringToEnum = commandLineOptions_stringToEnum || <CommandLineOptionOfCustomType[]>filter(optionDeclarations, o =>
|
||||
typeof o.type === "object" && !forEachValue(<Map<any>> o.type, v => typeof v !== "number"));
|
||||
|
||||
options = clone(options);
|
||||
|
||||
for (const opt of commandLineOptions_stringToEnum) {
|
||||
if (!hasProperty(options, opt.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = options[opt.name];
|
||||
// Value should be a key of opt.type
|
||||
if (typeof value === "string") {
|
||||
// If value is not a string, this will fail
|
||||
options[opt.name] = parseCustomTypeOption(opt, value, diagnostics);
|
||||
}
|
||||
else {
|
||||
if (!forEachValue(opt.type, v => v === value)) {
|
||||
// Supplied value isn't a valid enum value.
|
||||
diagnostics.push(createCompilerDiagnosticForInvalidCustomType(opt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function will compile source text from 'input' argument using specified compiler options.
|
||||
* If not options are provided - it will use a set of default compiler options.
|
||||
@@ -1931,7 +1984,9 @@ namespace ts {
|
||||
* - noResolve = true
|
||||
*/
|
||||
export function transpileModule(input: string, transpileOptions: TranspileOptions): TranspileOutput {
|
||||
const options = transpileOptions.compilerOptions ? clone(transpileOptions.compilerOptions) : getDefaultCompilerOptions();
|
||||
const diagnostics: Diagnostic[] = [];
|
||||
|
||||
const options: CompilerOptions = transpileOptions.compilerOptions ? fixupCompilerOptions(transpileOptions.compilerOptions, diagnostics) : getDefaultCompilerOptions();
|
||||
|
||||
options.isolatedModules = true;
|
||||
|
||||
@@ -1990,9 +2045,7 @@ namespace ts {
|
||||
|
||||
const program = createProgram([inputFileName], options, compilerHost);
|
||||
|
||||
let diagnostics: Diagnostic[];
|
||||
if (transpileOptions.reportDiagnostics) {
|
||||
diagnostics = [];
|
||||
addRange(/*to*/ diagnostics, /*from*/ program.getSyntacticDiagnostics(sourceFile));
|
||||
addRange(/*to*/ diagnostics, /*from*/ program.getOptionsDiagnostics());
|
||||
}
|
||||
@@ -2482,7 +2535,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
// should be start of dependency list
|
||||
if (token !== SyntaxKind.OpenBracketToken) {
|
||||
if (token !== SyntaxKind.OpenBracketToken) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2921,6 +2974,7 @@ namespace ts {
|
||||
const changesInCompilationSettingsAffectSyntax = oldSettings &&
|
||||
(oldSettings.target !== newSettings.target ||
|
||||
oldSettings.module !== newSettings.module ||
|
||||
oldSettings.moduleResolution !== newSettings.moduleResolution ||
|
||||
oldSettings.noResolve !== newSettings.noResolve ||
|
||||
oldSettings.jsx !== newSettings.jsx ||
|
||||
oldSettings.allowJs !== newSettings.allowJs);
|
||||
@@ -4016,10 +4070,15 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getCompletionsAtPosition(fileName: string, position: number): CompletionInfo {
|
||||
synchronizeHostData();
|
||||
|
||||
const sourceFile = getValidSourceFile(fileName);
|
||||
|
||||
if (isInString(sourceFile, position)) {
|
||||
return getStringLiteralCompletionEntries(sourceFile, position);
|
||||
}
|
||||
|
||||
const completionData = getCompletionData(fileName, position);
|
||||
if (!completionData) {
|
||||
return undefined;
|
||||
@@ -4032,12 +4091,10 @@ namespace ts {
|
||||
return { isMemberCompletion: false, isNewIdentifierLocation: false, entries: getAllJsDocCompletionEntries() };
|
||||
}
|
||||
|
||||
const sourceFile = getValidSourceFile(fileName);
|
||||
|
||||
const entries: CompletionEntry[] = [];
|
||||
|
||||
if (isSourceFileJavaScript(sourceFile)) {
|
||||
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries);
|
||||
const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ false);
|
||||
addRange(entries, getJavaScriptCompletionEntries(sourceFile, location.pos, uniqueNames));
|
||||
}
|
||||
else {
|
||||
@@ -4061,7 +4118,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
getCompletionEntriesFromSymbols(symbols, entries);
|
||||
getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true);
|
||||
}
|
||||
|
||||
// Add keywords if this is not a member completion list
|
||||
@@ -4111,11 +4168,11 @@ namespace ts {
|
||||
}));
|
||||
}
|
||||
|
||||
function createCompletionEntry(symbol: Symbol, location: Node): CompletionEntry {
|
||||
function createCompletionEntry(symbol: Symbol, location: Node, performCharacterChecks: boolean): CompletionEntry {
|
||||
// Try to get a valid display name for this symbol, if we could not find one, then ignore it.
|
||||
// We would like to only show things that can be added after a dot, so for instance numeric properties can
|
||||
// not be accessed with a dot (a.1 <- invalid)
|
||||
const displayName = getCompletionEntryDisplayNameForSymbol(symbol, program.getCompilerOptions().target, /*performCharacterChecks*/ true, location);
|
||||
const displayName = getCompletionEntryDisplayNameForSymbol(symbol, program.getCompilerOptions().target, performCharacterChecks, location);
|
||||
if (!displayName) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -4136,12 +4193,12 @@ namespace ts {
|
||||
};
|
||||
}
|
||||
|
||||
function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: CompletionEntry[]): Map<string> {
|
||||
function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: CompletionEntry[], location: Node, performCharacterChecks: boolean): Map<string> {
|
||||
const start = new Date().getTime();
|
||||
const uniqueNames: Map<string> = {};
|
||||
if (symbols) {
|
||||
for (const symbol of symbols) {
|
||||
const entry = createCompletionEntry(symbol, location);
|
||||
const entry = createCompletionEntry(symbol, location, performCharacterChecks);
|
||||
if (entry) {
|
||||
const id = escapeIdentifier(entry.name);
|
||||
if (!lookUp(uniqueNames, id)) {
|
||||
@@ -4155,6 +4212,93 @@ namespace ts {
|
||||
log("getCompletionsAtPosition: getCompletionEntriesFromSymbols: " + (new Date().getTime() - start));
|
||||
return uniqueNames;
|
||||
}
|
||||
|
||||
function getStringLiteralCompletionEntries(sourceFile: SourceFile, position: number) {
|
||||
const node = findPrecedingToken(position, sourceFile);
|
||||
if (!node || node.kind !== SyntaxKind.StringLiteral) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const argumentInfo = SignatureHelp.getContainingArgumentInfo(node, position, sourceFile);
|
||||
if (argumentInfo) {
|
||||
// Get string literal completions from specialized signatures of the target
|
||||
return getStringLiteralCompletionEntriesFromCallExpression(argumentInfo);
|
||||
}
|
||||
else if (isElementAccessExpression(node.parent) && node.parent.argumentExpression === node) {
|
||||
// Get all names of properties on the expression
|
||||
return getStringLiteralCompletionEntriesFromElementAccess(node.parent);
|
||||
}
|
||||
else {
|
||||
// Otherwise, get the completions from the contextual type if one exists
|
||||
return getStringLiteralCompletionEntriesFromContextualType(<StringLiteral>node);
|
||||
}
|
||||
}
|
||||
|
||||
function getStringLiteralCompletionEntriesFromCallExpression(argumentInfo: SignatureHelp.ArgumentListInfo) {
|
||||
const typeChecker = program.getTypeChecker();
|
||||
const candidates: Signature[] = [];
|
||||
const entries: CompletionEntry[] = [];
|
||||
|
||||
typeChecker.getResolvedSignature(argumentInfo.invocation, candidates);
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (candidate.parameters.length > argumentInfo.argumentIndex) {
|
||||
const parameter = candidate.parameters[argumentInfo.argumentIndex];
|
||||
addStringLiteralCompletionsFromType(typeChecker.getTypeAtLocation(parameter.valueDeclaration), entries);
|
||||
}
|
||||
}
|
||||
|
||||
if (entries.length) {
|
||||
return { isMemberCompletion: false, isNewIdentifierLocation: true, entries };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getStringLiteralCompletionEntriesFromElementAccess(node: ElementAccessExpression) {
|
||||
const typeChecker = program.getTypeChecker();
|
||||
const type = typeChecker.getTypeAtLocation(node.expression);
|
||||
const entries: CompletionEntry[] = [];
|
||||
if (type) {
|
||||
getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, node, /*performCharacterChecks*/false);
|
||||
if (entries.length) {
|
||||
return { isMemberCompletion: true, isNewIdentifierLocation: true, entries };
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getStringLiteralCompletionEntriesFromContextualType(node: StringLiteral) {
|
||||
const typeChecker = program.getTypeChecker();
|
||||
const type = typeChecker.getContextualType(node);
|
||||
if (type) {
|
||||
const entries: CompletionEntry[] = [];
|
||||
addStringLiteralCompletionsFromType(type, entries);
|
||||
if (entries.length) {
|
||||
return { isMemberCompletion: false, isNewIdentifierLocation: false, entries };
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function addStringLiteralCompletionsFromType(type: Type, result: CompletionEntry[]): void {
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
if (type.flags & TypeFlags.Union) {
|
||||
forEach((<UnionType>type).types, t => addStringLiteralCompletionsFromType(t, result));
|
||||
}
|
||||
else {
|
||||
if (type.flags & TypeFlags.StringLiteral) {
|
||||
result.push({
|
||||
name: (<StringLiteralType>type).text,
|
||||
kindModifiers: ScriptElementKindModifier.none,
|
||||
kind: ScriptElementKind.variableElement,
|
||||
sortText: "0"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails {
|
||||
@@ -4319,7 +4463,7 @@ namespace ts {
|
||||
// try get the call/construct signature from the type if it matches
|
||||
let callExpression: CallExpression;
|
||||
if (location.kind === SyntaxKind.CallExpression || location.kind === SyntaxKind.NewExpression) {
|
||||
callExpression = <CallExpression> location;
|
||||
callExpression = <CallExpression>location;
|
||||
}
|
||||
else if (isCallExpressionTarget(location) || isNewExpressionTarget(location)) {
|
||||
callExpression = <CallExpression>location.parent;
|
||||
|
||||
+379
-376
@@ -162,15 +162,16 @@ namespace ts.SignatureHelp {
|
||||
// // Did not find matching token
|
||||
// return null;
|
||||
// }
|
||||
|
||||
const emptyArray: any[] = [];
|
||||
|
||||
const enum ArgumentListKind {
|
||||
export const enum ArgumentListKind {
|
||||
TypeArguments,
|
||||
CallArguments,
|
||||
TaggedTemplateArguments
|
||||
}
|
||||
|
||||
interface ArgumentListInfo {
|
||||
export interface ArgumentListInfo {
|
||||
kind: ArgumentListKind;
|
||||
invocation: CallLikeExpression;
|
||||
argumentsSpan: TextSpan;
|
||||
@@ -188,7 +189,8 @@ namespace ts.SignatureHelp {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const argumentInfo = getContainingArgumentInfo(startingToken);
|
||||
const argumentInfo = getContainingArgumentInfo(startingToken, position, sourceFile);
|
||||
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
|
||||
// Semantic filtering of signature help
|
||||
@@ -205,431 +207,432 @@ namespace ts.SignatureHelp {
|
||||
// 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);
|
||||
return createJavaScriptSignatureHelpItems(argumentInfo, program);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return createSignatureHelpItems(candidates, resolvedSignature, argumentInfo);
|
||||
return createSignatureHelpItems(candidates, resolvedSignature, argumentInfo, typeChecker);
|
||||
}
|
||||
|
||||
function createJavaScriptSignatureHelpItems(argumentInfo: ArgumentListInfo): SignatureHelpItems {
|
||||
if (argumentInfo.invocation.kind !== SyntaxKind.CallExpression) {
|
||||
return undefined;
|
||||
}
|
||||
function createJavaScriptSignatureHelpItems(argumentInfo: ArgumentListInfo, program: Program): SignatureHelpItems {
|
||||
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 = <CallExpression>argumentInfo.invocation;
|
||||
const expression = callExpression.expression;
|
||||
const name = expression.kind === SyntaxKind.Identifier
|
||||
? <Identifier> expression
|
||||
: expression.kind === SyntaxKind.PropertyAccessExpression
|
||||
? (<PropertyAccessExpression>expression).name
|
||||
: undefined;
|
||||
// See if we can find some symbol with the call expression name that has call signatures.
|
||||
const callExpression = <CallExpression>argumentInfo.invocation;
|
||||
const expression = callExpression.expression;
|
||||
const name = expression.kind === SyntaxKind.Identifier
|
||||
? <Identifier>expression
|
||||
: expression.kind === SyntaxKind.PropertyAccessExpression
|
||||
? (<PropertyAccessExpression>expression).name
|
||||
: undefined;
|
||||
|
||||
if (!name || !name.text) {
|
||||
return undefined;
|
||||
}
|
||||
if (!name || !name.text) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const typeChecker = program.getTypeChecker();
|
||||
for (const sourceFile of program.getSourceFiles()) {
|
||||
const nameToDeclarations = sourceFile.getNamedDeclarations();
|
||||
const declarations = getProperty(nameToDeclarations, name.text);
|
||||
const typeChecker = program.getTypeChecker();
|
||||
for (const sourceFile of program.getSourceFiles()) {
|
||||
const nameToDeclarations = sourceFile.getNamedDeclarations();
|
||||
const declarations = getProperty(nameToDeclarations, name.text);
|
||||
|
||||
if (declarations) {
|
||||
for (const declaration of declarations) {
|
||||
const symbol = declaration.symbol;
|
||||
if (symbol) {
|
||||
const type = typeChecker.getTypeOfSymbolAtLocation(symbol, declaration);
|
||||
if (type) {
|
||||
const callSignatures = type.getCallSignatures();
|
||||
if (callSignatures && callSignatures.length) {
|
||||
return createSignatureHelpItems(callSignatures, callSignatures[0], argumentInfo);
|
||||
}
|
||||
if (declarations) {
|
||||
for (const declaration of declarations) {
|
||||
const symbol = declaration.symbol;
|
||||
if (symbol) {
|
||||
const type = typeChecker.getTypeOfSymbolAtLocation(symbol, declaration);
|
||||
if (type) {
|
||||
const callSignatures = type.getCallSignatures();
|
||||
if (callSignatures && callSignatures.length) {
|
||||
return createSignatureHelpItems(callSignatures, callSignatures[0], argumentInfo, typeChecker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns relevant information for the argument list and the current argument if we are
|
||||
* in the argument of an invocation; returns undefined otherwise.
|
||||
*/
|
||||
function getImmediatelyContainingArgumentInfo(node: Node): ArgumentListInfo {
|
||||
if (node.parent.kind === SyntaxKind.CallExpression || node.parent.kind === SyntaxKind.NewExpression) {
|
||||
const callExpression = <CallExpression>node.parent;
|
||||
// There are 3 cases to handle:
|
||||
// 1. The token introduces a list, and should begin a sig help session
|
||||
// 2. The token is either not associated with a list, or ends a list, so the session should end
|
||||
// 3. The token is buried inside a list, and should give sig help
|
||||
//
|
||||
// The following are examples of each:
|
||||
//
|
||||
// Case 1:
|
||||
// foo<#T, U>(#a, b) -> The token introduces a list, and should begin a sig help session
|
||||
// Case 2:
|
||||
// fo#o<T, U>#(a, b)# -> The token is either not associated with a list, or ends a list, so the session should end
|
||||
// Case 3:
|
||||
// foo<T#, U#>(a#, #b#) -> The token is buried inside a list, and should give sig help
|
||||
// Find out if 'node' is an argument, a type argument, or neither
|
||||
if (node.kind === SyntaxKind.LessThanToken ||
|
||||
node.kind === SyntaxKind.OpenParenToken) {
|
||||
// Find the list that starts right *after* the < or ( token.
|
||||
// If the user has just opened a list, consider this item 0.
|
||||
const list = getChildListThatStartsWithOpenerToken(callExpression, node, sourceFile);
|
||||
const isTypeArgList = callExpression.typeArguments && callExpression.typeArguments.pos === list.pos;
|
||||
Debug.assert(list !== undefined);
|
||||
return {
|
||||
kind: isTypeArgList ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments,
|
||||
invocation: callExpression,
|
||||
argumentsSpan: getApplicableSpanForArguments(list),
|
||||
argumentIndex: 0,
|
||||
argumentCount: getArgumentCount(list)
|
||||
};
|
||||
}
|
||||
|
||||
// findListItemInfo can return undefined if we are not in parent's argument list
|
||||
// or type argument list. This includes cases where the cursor is:
|
||||
// - To the right of the closing paren, non-substitution template, or template tail.
|
||||
// - Between the type arguments and the arguments (greater than token)
|
||||
// - On the target of the call (parent.func)
|
||||
// - On the 'new' keyword in a 'new' expression
|
||||
const listItemInfo = findListItemInfo(node);
|
||||
if (listItemInfo) {
|
||||
const list = listItemInfo.list;
|
||||
const isTypeArgList = callExpression.typeArguments && callExpression.typeArguments.pos === list.pos;
|
||||
|
||||
const argumentIndex = getArgumentIndex(list, node);
|
||||
const argumentCount = getArgumentCount(list);
|
||||
|
||||
Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount,
|
||||
`argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`);
|
||||
|
||||
return {
|
||||
kind: isTypeArgList ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments,
|
||||
invocation: callExpression,
|
||||
argumentsSpan: getApplicableSpanForArguments(list),
|
||||
argumentIndex: argumentIndex,
|
||||
argumentCount: argumentCount
|
||||
};
|
||||
}
|
||||
}
|
||||
else if (node.kind === SyntaxKind.NoSubstitutionTemplateLiteral && node.parent.kind === SyntaxKind.TaggedTemplateExpression) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
else if (node.kind === SyntaxKind.TemplateHead && node.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) {
|
||||
const templateExpression = <TemplateExpression>node.parent;
|
||||
const tagExpression = <TaggedTemplateExpression>templateExpression.parent;
|
||||
Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression);
|
||||
|
||||
const argumentIndex = isInsideTemplateLiteral(<LiteralExpression>node, position) ? 0 : 1;
|
||||
|
||||
return getArgumentListInfoForTemplate(tagExpression, argumentIndex);
|
||||
}
|
||||
else if (node.parent.kind === SyntaxKind.TemplateSpan && node.parent.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) {
|
||||
const templateSpan = <TemplateSpan>node.parent;
|
||||
const templateExpression = <TemplateExpression>templateSpan.parent;
|
||||
const tagExpression = <TaggedTemplateExpression>templateExpression.parent;
|
||||
Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression);
|
||||
|
||||
// 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 argumentIndex = getArgumentIndexForTemplatePiece(spanIndex, node);
|
||||
|
||||
return getArgumentListInfoForTemplate(tagExpression, argumentIndex);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getArgumentIndex(argumentsList: Node, node: Node) {
|
||||
// The list we got back can include commas. In the presence of errors it may
|
||||
// also just have nodes without commas. For example "Foo(a b c)" will have 3
|
||||
// args without commas. We want to find what index we're at. So we count
|
||||
// forward until we hit ourselves, only incrementing the index if it isn't a
|
||||
// comma.
|
||||
/**
|
||||
* Returns relevant information for the argument list and the current argument if we are
|
||||
* in the argument of an invocation; returns undefined otherwise.
|
||||
*/
|
||||
function getImmediatelyContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo {
|
||||
if (node.parent.kind === SyntaxKind.CallExpression || node.parent.kind === SyntaxKind.NewExpression) {
|
||||
const callExpression = <CallExpression>node.parent;
|
||||
// There are 3 cases to handle:
|
||||
// 1. The token introduces a list, and should begin a sig help session
|
||||
// 2. The token is either not associated with a list, or ends a list, so the session should end
|
||||
// 3. The token is buried inside a list, and should give sig help
|
||||
//
|
||||
// Note: the subtlety around trailing commas (in getArgumentCount) does not apply
|
||||
// here. That's because we're only walking forward until we hit the node we're
|
||||
// on. In that case, even if we're after the trailing comma, we'll still see
|
||||
// that trailing comma in the list, and we'll have generated the appropriate
|
||||
// arg index.
|
||||
let argumentIndex = 0;
|
||||
const listChildren = argumentsList.getChildren();
|
||||
for (const child of listChildren) {
|
||||
if (child === node) {
|
||||
break;
|
||||
}
|
||||
if (child.kind !== SyntaxKind.CommaToken) {
|
||||
argumentIndex++;
|
||||
}
|
||||
// The following are examples of each:
|
||||
//
|
||||
// Case 1:
|
||||
// foo<#T, U>(#a, b) -> The token introduces a list, and should begin a sig help session
|
||||
// Case 2:
|
||||
// fo#o<T, U>#(a, b)# -> The token is either not associated with a list, or ends a list, so the session should end
|
||||
// Case 3:
|
||||
// foo<T#, U#>(a#, #b#) -> The token is buried inside a list, and should give sig help
|
||||
// Find out if 'node' is an argument, a type argument, or neither
|
||||
if (node.kind === SyntaxKind.LessThanToken ||
|
||||
node.kind === SyntaxKind.OpenParenToken) {
|
||||
// Find the list that starts right *after* the < or ( token.
|
||||
// If the user has just opened a list, consider this item 0.
|
||||
const list = getChildListThatStartsWithOpenerToken(callExpression, node, sourceFile);
|
||||
const isTypeArgList = callExpression.typeArguments && callExpression.typeArguments.pos === list.pos;
|
||||
Debug.assert(list !== undefined);
|
||||
return {
|
||||
kind: isTypeArgList ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments,
|
||||
invocation: callExpression,
|
||||
argumentsSpan: getApplicableSpanForArguments(list, sourceFile),
|
||||
argumentIndex: 0,
|
||||
argumentCount: getArgumentCount(list)
|
||||
};
|
||||
}
|
||||
|
||||
return argumentIndex;
|
||||
}
|
||||
// findListItemInfo can return undefined if we are not in parent's argument list
|
||||
// or type argument list. This includes cases where the cursor is:
|
||||
// - To the right of the closing paren, non-substitution template, or template tail.
|
||||
// - Between the type arguments and the arguments (greater than token)
|
||||
// - On the target of the call (parent.func)
|
||||
// - On the 'new' keyword in a 'new' expression
|
||||
const listItemInfo = findListItemInfo(node);
|
||||
if (listItemInfo) {
|
||||
const list = listItemInfo.list;
|
||||
const isTypeArgList = callExpression.typeArguments && callExpression.typeArguments.pos === list.pos;
|
||||
|
||||
function getArgumentCount(argumentsList: Node) {
|
||||
// The argument count for a list is normally the number of non-comma children it has.
|
||||
// For example, if you have "Foo(a,b)" then there will be three children of the arg
|
||||
// list 'a' '<comma>' 'b'. So, in this case the arg count will be 2. However, there
|
||||
// is a small subtlety. If you have "Foo(a,)", then the child list will just have
|
||||
// 'a' '<comma>'. So, in the case where the last child is a comma, we increase the
|
||||
// arg count by one to compensate.
|
||||
//
|
||||
// Note: this subtlety only applies to the last comma. If you had "Foo(a,," then
|
||||
// we'll have: 'a' '<comma>' '<missing>'
|
||||
// That will give us 2 non-commas. We then add one for the last comma, givin us an
|
||||
// arg count of 3.
|
||||
const listChildren = argumentsList.getChildren();
|
||||
const argumentIndex = getArgumentIndex(list, node);
|
||||
const argumentCount = getArgumentCount(list);
|
||||
|
||||
let argumentCount = countWhere(listChildren, arg => arg.kind !== SyntaxKind.CommaToken);
|
||||
if (listChildren.length > 0 && lastOrUndefined(listChildren).kind === SyntaxKind.CommaToken) {
|
||||
argumentCount++;
|
||||
}
|
||||
Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount,
|
||||
`argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`);
|
||||
|
||||
return argumentCount;
|
||||
}
|
||||
|
||||
// spanIndex is either the index for a given template span.
|
||||
// This does not give appropriate results for a NoSubstitutionTemplateLiteral
|
||||
function getArgumentIndexForTemplatePiece(spanIndex: number, node: Node): number {
|
||||
// Because the TemplateStringsArray is the first argument, we have to offset each substitution expression by 1.
|
||||
// There are three cases we can encounter:
|
||||
// 1. We are precisely in the template literal (argIndex = 0).
|
||||
// 2. We are in or to the right of the substitution expression (argIndex = spanIndex + 1).
|
||||
// 3. We are directly to the right of the template literal, but because we look for the token on the left,
|
||||
// not enough to put us in the substitution expression; we should consider ourselves part of
|
||||
// the *next* span's expression by offsetting the index (argIndex = (spanIndex + 1) + 1).
|
||||
//
|
||||
// Example: f `# abcd $#{# 1 + 1# }# efghi ${ #"#hello"# } # `
|
||||
// ^ ^ ^ ^ ^ ^ ^ ^ ^
|
||||
// Case: 1 1 3 2 1 3 2 2 1
|
||||
Debug.assert(position >= node.getStart(), "Assumed 'position' could not occur before node.");
|
||||
if (isTemplateLiteralKind(node.kind)) {
|
||||
if (isInsideTemplateLiteral(<LiteralExpression>node, position)) {
|
||||
return 0;
|
||||
}
|
||||
return spanIndex + 2;
|
||||
}
|
||||
return spanIndex + 1;
|
||||
}
|
||||
|
||||
function getArgumentListInfoForTemplate(tagExpression: TaggedTemplateExpression, argumentIndex: number): ArgumentListInfo {
|
||||
// argumentCount is either 1 or (numSpans + 1) to account for the template strings array argument.
|
||||
const argumentCount = tagExpression.template.kind === SyntaxKind.NoSubstitutionTemplateLiteral
|
||||
? 1
|
||||
: (<TemplateExpression>tagExpression.template).templateSpans.length + 1;
|
||||
|
||||
Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`);
|
||||
|
||||
return {
|
||||
kind: ArgumentListKind.TaggedTemplateArguments,
|
||||
invocation: tagExpression,
|
||||
argumentsSpan: getApplicableSpanForTaggedTemplate(tagExpression),
|
||||
argumentIndex: argumentIndex,
|
||||
argumentCount: argumentCount
|
||||
};
|
||||
}
|
||||
|
||||
function getApplicableSpanForArguments(argumentsList: Node): TextSpan {
|
||||
// We use full start and skip trivia on the end because we want to include trivia on
|
||||
// both sides. For example,
|
||||
//
|
||||
// foo( /*comment */ a, b, c /*comment*/ )
|
||||
// | |
|
||||
//
|
||||
// The applicable span is from the first bar to the second bar (inclusive,
|
||||
// but not including parentheses)
|
||||
const applicableSpanStart = argumentsList.getFullStart();
|
||||
const applicableSpanEnd = skipTrivia(sourceFile.text, argumentsList.getEnd(), /*stopAfterLineBreak*/ false);
|
||||
return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart);
|
||||
}
|
||||
|
||||
function getApplicableSpanForTaggedTemplate(taggedTemplate: TaggedTemplateExpression): TextSpan {
|
||||
const template = taggedTemplate.template;
|
||||
const applicableSpanStart = template.getStart();
|
||||
let applicableSpanEnd = template.getEnd();
|
||||
|
||||
// We need to adjust the end position for the case where the template does not have a tail.
|
||||
// Otherwise, we will not show signature help past the expression.
|
||||
// For example,
|
||||
//
|
||||
// ` ${ 1 + 1 foo(10)
|
||||
// | |
|
||||
//
|
||||
// This is because a Missing node has no width. However, what we actually want is to include trivia
|
||||
// leading up to the next token in case the user is about to type in a TemplateMiddle or TemplateTail.
|
||||
if (template.kind === SyntaxKind.TemplateExpression) {
|
||||
const lastSpan = lastOrUndefined((<TemplateExpression>template).templateSpans);
|
||||
if (lastSpan.literal.getFullWidth() === 0) {
|
||||
applicableSpanEnd = skipTrivia(sourceFile.text, applicableSpanEnd, /*stopAfterLineBreak*/ false);
|
||||
}
|
||||
}
|
||||
|
||||
return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart);
|
||||
}
|
||||
|
||||
function getContainingArgumentInfo(node: Node): ArgumentListInfo {
|
||||
for (let n = node; n.kind !== SyntaxKind.SourceFile; n = n.parent) {
|
||||
if (isFunctionBlock(n)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
const argumentInfo = getImmediatelyContainingArgumentInfo(n);
|
||||
if (argumentInfo) {
|
||||
return argumentInfo;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Handle generic call with incomplete syntax
|
||||
return {
|
||||
kind: isTypeArgList ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments,
|
||||
invocation: callExpression,
|
||||
argumentsSpan: getApplicableSpanForArguments(list, sourceFile),
|
||||
argumentIndex: argumentIndex,
|
||||
argumentCount: argumentCount
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node, sourceFile: SourceFile): Node {
|
||||
const children = parent.getChildren(sourceFile);
|
||||
const indexOfOpenerToken = children.indexOf(openerToken);
|
||||
Debug.assert(indexOfOpenerToken >= 0 && children.length > indexOfOpenerToken + 1);
|
||||
return children[indexOfOpenerToken + 1];
|
||||
else if (node.kind === SyntaxKind.NoSubstitutionTemplateLiteral && node.parent.kind === SyntaxKind.TaggedTemplateExpression) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
else if (node.kind === SyntaxKind.TemplateHead && node.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) {
|
||||
const templateExpression = <TemplateExpression>node.parent;
|
||||
const tagExpression = <TaggedTemplateExpression>templateExpression.parent;
|
||||
Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression);
|
||||
|
||||
/**
|
||||
* The selectedItemIndex could be negative for several reasons.
|
||||
* 1. There are too many arguments for all of the overloads
|
||||
* 2. None of the overloads were type compatible
|
||||
* The solution here is to try to pick the best overload by picking
|
||||
* either the first one that has an appropriate number of parameters,
|
||||
* or the one with the most parameters.
|
||||
*/
|
||||
function selectBestInvalidOverloadIndex(candidates: Signature[], argumentCount: number): number {
|
||||
let maxParamsSignatureIndex = -1;
|
||||
let maxParams = -1;
|
||||
for (let i = 0; i < candidates.length; i++) {
|
||||
const candidate = candidates[i];
|
||||
const argumentIndex = isInsideTemplateLiteral(<LiteralExpression>node, position) ? 0 : 1;
|
||||
|
||||
if (candidate.hasRestParameter || candidate.parameters.length >= argumentCount) {
|
||||
return i;
|
||||
}
|
||||
return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile);
|
||||
}
|
||||
else if (node.parent.kind === SyntaxKind.TemplateSpan && node.parent.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) {
|
||||
const templateSpan = <TemplateSpan>node.parent;
|
||||
const templateExpression = <TemplateExpression>templateSpan.parent;
|
||||
const tagExpression = <TaggedTemplateExpression>templateExpression.parent;
|
||||
Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression);
|
||||
|
||||
if (candidate.parameters.length > maxParams) {
|
||||
maxParams = candidate.parameters.length;
|
||||
maxParamsSignatureIndex = i;
|
||||
}
|
||||
// If we're just after a template tail, don't show signature help.
|
||||
if (node.kind === SyntaxKind.TemplateTail && !isInsideTemplateLiteral(<LiteralExpression>node, position)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return maxParamsSignatureIndex;
|
||||
const spanIndex = templateExpression.templateSpans.indexOf(templateSpan);
|
||||
const argumentIndex = getArgumentIndexForTemplatePiece(spanIndex, node, position);
|
||||
|
||||
return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile);
|
||||
}
|
||||
|
||||
function createSignatureHelpItems(candidates: Signature[], bestSignature: Signature, argumentListInfo: ArgumentListInfo): SignatureHelpItems {
|
||||
const applicableSpan = argumentListInfo.argumentsSpan;
|
||||
const isTypeParameterList = argumentListInfo.kind === ArgumentListKind.TypeArguments;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const invocation = argumentListInfo.invocation;
|
||||
const callTarget = getInvokedExpression(invocation);
|
||||
const callTargetSymbol = typeChecker.getSymbolAtLocation(callTarget);
|
||||
const callTargetDisplayParts = callTargetSymbol && symbolToDisplayParts(typeChecker, callTargetSymbol, /*enclosingDeclaration*/ undefined, /*meaning*/ undefined);
|
||||
const items: SignatureHelpItem[] = map(candidates, candidateSignature => {
|
||||
let signatureHelpParameters: SignatureHelpParameter[];
|
||||
const prefixDisplayParts: SymbolDisplayPart[] = [];
|
||||
const suffixDisplayParts: SymbolDisplayPart[] = [];
|
||||
function getArgumentIndex(argumentsList: Node, node: Node) {
|
||||
// The list we got back can include commas. In the presence of errors it may
|
||||
// also just have nodes without commas. For example "Foo(a b c)" will have 3
|
||||
// args without commas. We want to find what index we're at. So we count
|
||||
// forward until we hit ourselves, only incrementing the index if it isn't a
|
||||
// comma.
|
||||
//
|
||||
// Note: the subtlety around trailing commas (in getArgumentCount) does not apply
|
||||
// here. That's because we're only walking forward until we hit the node we're
|
||||
// on. In that case, even if we're after the trailing comma, we'll still see
|
||||
// that trailing comma in the list, and we'll have generated the appropriate
|
||||
// arg index.
|
||||
let argumentIndex = 0;
|
||||
const listChildren = argumentsList.getChildren();
|
||||
for (const child of listChildren) {
|
||||
if (child === node) {
|
||||
break;
|
||||
}
|
||||
if (child.kind !== SyntaxKind.CommaToken) {
|
||||
argumentIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (callTargetDisplayParts) {
|
||||
addRange(prefixDisplayParts, callTargetDisplayParts);
|
||||
}
|
||||
return argumentIndex;
|
||||
}
|
||||
|
||||
if (isTypeParameterList) {
|
||||
prefixDisplayParts.push(punctuationPart(SyntaxKind.LessThanToken));
|
||||
const typeParameters = candidateSignature.typeParameters;
|
||||
signatureHelpParameters = typeParameters && typeParameters.length > 0 ? map(typeParameters, createSignatureHelpParameterForTypeParameter) : emptyArray;
|
||||
suffixDisplayParts.push(punctuationPart(SyntaxKind.GreaterThanToken));
|
||||
const parameterParts = mapToDisplayParts(writer =>
|
||||
typeChecker.getSymbolDisplayBuilder().buildDisplayForParametersAndDelimiters(candidateSignature.thisType, candidateSignature.parameters, writer, invocation));
|
||||
addRange(suffixDisplayParts, parameterParts);
|
||||
}
|
||||
else {
|
||||
const typeParameterParts = mapToDisplayParts(writer =>
|
||||
typeChecker.getSymbolDisplayBuilder().buildDisplayForTypeParametersAndDelimiters(candidateSignature.typeParameters, writer, invocation));
|
||||
addRange(prefixDisplayParts, typeParameterParts);
|
||||
prefixDisplayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
|
||||
function getArgumentCount(argumentsList: Node) {
|
||||
// The argument count for a list is normally the number of non-comma children it has.
|
||||
// For example, if you have "Foo(a,b)" then there will be three children of the arg
|
||||
// list 'a' '<comma>' 'b'. So, in this case the arg count will be 2. However, there
|
||||
// is a small subtlety. If you have "Foo(a,)", then the child list will just have
|
||||
// 'a' '<comma>'. So, in the case where the last child is a comma, we increase the
|
||||
// arg count by one to compensate.
|
||||
//
|
||||
// Note: this subtlety only applies to the last comma. If you had "Foo(a,," then
|
||||
// we'll have: 'a' '<comma>' '<missing>'
|
||||
// That will give us 2 non-commas. We then add one for the last comma, givin us an
|
||||
// arg count of 3.
|
||||
const listChildren = argumentsList.getChildren();
|
||||
|
||||
const parameters = candidateSignature.parameters;
|
||||
signatureHelpParameters = parameters.length > 0 ? map(parameters, createSignatureHelpParameterForParameter) : emptyArray;
|
||||
suffixDisplayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
|
||||
}
|
||||
let argumentCount = countWhere(listChildren, arg => arg.kind !== SyntaxKind.CommaToken);
|
||||
if (listChildren.length > 0 && lastOrUndefined(listChildren).kind === SyntaxKind.CommaToken) {
|
||||
argumentCount++;
|
||||
}
|
||||
|
||||
const returnTypeParts = mapToDisplayParts(writer =>
|
||||
typeChecker.getSymbolDisplayBuilder().buildReturnTypeDisplay(candidateSignature, writer, invocation));
|
||||
addRange(suffixDisplayParts, returnTypeParts);
|
||||
return argumentCount;
|
||||
}
|
||||
|
||||
return {
|
||||
isVariadic: candidateSignature.hasRestParameter,
|
||||
prefixDisplayParts,
|
||||
suffixDisplayParts,
|
||||
separatorDisplayParts: [punctuationPart(SyntaxKind.CommaToken), spacePart()],
|
||||
parameters: signatureHelpParameters,
|
||||
documentation: candidateSignature.getDocumentationComment()
|
||||
};
|
||||
});
|
||||
// spanIndex is either the index for a given template span.
|
||||
// This does not give appropriate results for a NoSubstitutionTemplateLiteral
|
||||
function getArgumentIndexForTemplatePiece(spanIndex: number, node: Node, position: number): number {
|
||||
// Because the TemplateStringsArray is the first argument, we have to offset each substitution expression by 1.
|
||||
// There are three cases we can encounter:
|
||||
// 1. We are precisely in the template literal (argIndex = 0).
|
||||
// 2. We are in or to the right of the substitution expression (argIndex = spanIndex + 1).
|
||||
// 3. We are directly to the right of the template literal, but because we look for the token on the left,
|
||||
// not enough to put us in the substitution expression; we should consider ourselves part of
|
||||
// the *next* span's expression by offsetting the index (argIndex = (spanIndex + 1) + 1).
|
||||
//
|
||||
// Example: f `# abcd $#{# 1 + 1# }# efghi ${ #"#hello"# } # `
|
||||
// ^ ^ ^ ^ ^ ^ ^ ^ ^
|
||||
// Case: 1 1 3 2 1 3 2 2 1
|
||||
Debug.assert(position >= node.getStart(), "Assumed 'position' could not occur before node.");
|
||||
if (isTemplateLiteralKind(node.kind)) {
|
||||
if (isInsideTemplateLiteral(<LiteralExpression>node, position)) {
|
||||
return 0;
|
||||
}
|
||||
return spanIndex + 2;
|
||||
}
|
||||
return spanIndex + 1;
|
||||
}
|
||||
|
||||
const argumentIndex = argumentListInfo.argumentIndex;
|
||||
function getArgumentListInfoForTemplate(tagExpression: TaggedTemplateExpression, argumentIndex: number, sourceFile: SourceFile): ArgumentListInfo {
|
||||
// argumentCount is either 1 or (numSpans + 1) to account for the template strings array argument.
|
||||
const argumentCount = tagExpression.template.kind === SyntaxKind.NoSubstitutionTemplateLiteral
|
||||
? 1
|
||||
: (<TemplateExpression>tagExpression.template).templateSpans.length + 1;
|
||||
|
||||
// argumentCount is the *apparent* number of arguments.
|
||||
const argumentCount = argumentListInfo.argumentCount;
|
||||
Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`);
|
||||
|
||||
let selectedItemIndex = candidates.indexOf(bestSignature);
|
||||
if (selectedItemIndex < 0) {
|
||||
selectedItemIndex = selectBestInvalidOverloadIndex(candidates, argumentCount);
|
||||
return {
|
||||
kind: ArgumentListKind.TaggedTemplateArguments,
|
||||
invocation: tagExpression,
|
||||
argumentsSpan: getApplicableSpanForTaggedTemplate(tagExpression, sourceFile),
|
||||
argumentIndex: argumentIndex,
|
||||
argumentCount: argumentCount
|
||||
};
|
||||
}
|
||||
|
||||
function getApplicableSpanForArguments(argumentsList: Node, sourceFile: SourceFile): TextSpan {
|
||||
// We use full start and skip trivia on the end because we want to include trivia on
|
||||
// both sides. For example,
|
||||
//
|
||||
// foo( /*comment */ a, b, c /*comment*/ )
|
||||
// | |
|
||||
//
|
||||
// The applicable span is from the first bar to the second bar (inclusive,
|
||||
// but not including parentheses)
|
||||
const applicableSpanStart = argumentsList.getFullStart();
|
||||
const applicableSpanEnd = skipTrivia(sourceFile.text, argumentsList.getEnd(), /*stopAfterLineBreak*/ false);
|
||||
return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart);
|
||||
}
|
||||
|
||||
function getApplicableSpanForTaggedTemplate(taggedTemplate: TaggedTemplateExpression, sourceFile: SourceFile): TextSpan {
|
||||
const template = taggedTemplate.template;
|
||||
const applicableSpanStart = template.getStart();
|
||||
let applicableSpanEnd = template.getEnd();
|
||||
|
||||
// We need to adjust the end position for the case where the template does not have a tail.
|
||||
// Otherwise, we will not show signature help past the expression.
|
||||
// For example,
|
||||
//
|
||||
// ` ${ 1 + 1 foo(10)
|
||||
// | |
|
||||
//
|
||||
// This is because a Missing node has no width. However, what we actually want is to include trivia
|
||||
// leading up to the next token in case the user is about to type in a TemplateMiddle or TemplateTail.
|
||||
if (template.kind === SyntaxKind.TemplateExpression) {
|
||||
const lastSpan = lastOrUndefined((<TemplateExpression>template).templateSpans);
|
||||
if (lastSpan.literal.getFullWidth() === 0) {
|
||||
applicableSpanEnd = skipTrivia(sourceFile.text, applicableSpanEnd, /*stopAfterLineBreak*/ false);
|
||||
}
|
||||
}
|
||||
|
||||
return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart);
|
||||
}
|
||||
|
||||
export function getContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo {
|
||||
for (let n = node; n.kind !== SyntaxKind.SourceFile; n = n.parent) {
|
||||
if (isFunctionBlock(n)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`);
|
||||
// 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);
|
||||
}
|
||||
|
||||
const argumentInfo = getImmediatelyContainingArgumentInfo(n, position, sourceFile);
|
||||
if (argumentInfo) {
|
||||
return argumentInfo;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Handle generic call with incomplete syntax
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node, sourceFile: SourceFile): Node {
|
||||
const children = parent.getChildren(sourceFile);
|
||||
const indexOfOpenerToken = children.indexOf(openerToken);
|
||||
Debug.assert(indexOfOpenerToken >= 0 && children.length > indexOfOpenerToken + 1);
|
||||
return children[indexOfOpenerToken + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* The selectedItemIndex could be negative for several reasons.
|
||||
* 1. There are too many arguments for all of the overloads
|
||||
* 2. None of the overloads were type compatible
|
||||
* The solution here is to try to pick the best overload by picking
|
||||
* either the first one that has an appropriate number of parameters,
|
||||
* or the one with the most parameters.
|
||||
*/
|
||||
function selectBestInvalidOverloadIndex(candidates: Signature[], argumentCount: number): number {
|
||||
let maxParamsSignatureIndex = -1;
|
||||
let maxParams = -1;
|
||||
for (let i = 0; i < candidates.length; i++) {
|
||||
const candidate = candidates[i];
|
||||
|
||||
if (candidate.hasRestParameter || candidate.parameters.length >= argumentCount) {
|
||||
return i;
|
||||
}
|
||||
|
||||
if (candidate.parameters.length > maxParams) {
|
||||
maxParams = candidate.parameters.length;
|
||||
maxParamsSignatureIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
return maxParamsSignatureIndex;
|
||||
}
|
||||
|
||||
function createSignatureHelpItems(candidates: Signature[], bestSignature: Signature, argumentListInfo: ArgumentListInfo, typeChecker: TypeChecker): SignatureHelpItems {
|
||||
const applicableSpan = argumentListInfo.argumentsSpan;
|
||||
const isTypeParameterList = argumentListInfo.kind === ArgumentListKind.TypeArguments;
|
||||
|
||||
const invocation = argumentListInfo.invocation;
|
||||
const callTarget = getInvokedExpression(invocation);
|
||||
const callTargetSymbol = typeChecker.getSymbolAtLocation(callTarget);
|
||||
const callTargetDisplayParts = callTargetSymbol && symbolToDisplayParts(typeChecker, callTargetSymbol, /*enclosingDeclaration*/ undefined, /*meaning*/ undefined);
|
||||
const items: SignatureHelpItem[] = map(candidates, candidateSignature => {
|
||||
let signatureHelpParameters: SignatureHelpParameter[];
|
||||
const prefixDisplayParts: SymbolDisplayPart[] = [];
|
||||
const suffixDisplayParts: SymbolDisplayPart[] = [];
|
||||
|
||||
if (callTargetDisplayParts) {
|
||||
addRange(prefixDisplayParts, callTargetDisplayParts);
|
||||
}
|
||||
|
||||
if (isTypeParameterList) {
|
||||
prefixDisplayParts.push(punctuationPart(SyntaxKind.LessThanToken));
|
||||
const typeParameters = candidateSignature.typeParameters;
|
||||
signatureHelpParameters = typeParameters && typeParameters.length > 0 ? map(typeParameters, createSignatureHelpParameterForTypeParameter) : emptyArray;
|
||||
suffixDisplayParts.push(punctuationPart(SyntaxKind.GreaterThanToken));
|
||||
const parameterParts = mapToDisplayParts(writer =>
|
||||
typeChecker.getSymbolDisplayBuilder().buildDisplayForParametersAndDelimiters(candidateSignature.thisType, candidateSignature.parameters, writer, invocation));
|
||||
addRange(suffixDisplayParts, parameterParts);
|
||||
}
|
||||
else {
|
||||
const typeParameterParts = mapToDisplayParts(writer =>
|
||||
typeChecker.getSymbolDisplayBuilder().buildDisplayForTypeParametersAndDelimiters(candidateSignature.typeParameters, writer, invocation));
|
||||
addRange(prefixDisplayParts, typeParameterParts);
|
||||
prefixDisplayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
|
||||
|
||||
const parameters = candidateSignature.parameters;
|
||||
signatureHelpParameters = parameters.length > 0 ? map(parameters, createSignatureHelpParameterForParameter) : emptyArray;
|
||||
suffixDisplayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
|
||||
}
|
||||
|
||||
const returnTypeParts = mapToDisplayParts(writer =>
|
||||
typeChecker.getSymbolDisplayBuilder().buildReturnTypeDisplay(candidateSignature, writer, invocation));
|
||||
addRange(suffixDisplayParts, returnTypeParts);
|
||||
|
||||
return {
|
||||
items,
|
||||
applicableSpan,
|
||||
selectedItemIndex,
|
||||
argumentIndex,
|
||||
argumentCount
|
||||
isVariadic: candidateSignature.hasRestParameter,
|
||||
prefixDisplayParts,
|
||||
suffixDisplayParts,
|
||||
separatorDisplayParts: [punctuationPart(SyntaxKind.CommaToken), spacePart()],
|
||||
parameters: signatureHelpParameters,
|
||||
documentation: candidateSignature.getDocumentationComment()
|
||||
};
|
||||
});
|
||||
|
||||
function createSignatureHelpParameterForParameter(parameter: Symbol): SignatureHelpParameter {
|
||||
const displayParts = mapToDisplayParts(writer =>
|
||||
typeChecker.getSymbolDisplayBuilder().buildParameterDisplay(parameter, writer, invocation));
|
||||
const argumentIndex = argumentListInfo.argumentIndex;
|
||||
|
||||
return {
|
||||
name: parameter.name,
|
||||
documentation: parameter.getDocumentationComment(),
|
||||
displayParts,
|
||||
isOptional: typeChecker.isOptionalParameter(<ParameterDeclaration>parameter.valueDeclaration)
|
||||
};
|
||||
}
|
||||
// argumentCount is the *apparent* number of arguments.
|
||||
const argumentCount = argumentListInfo.argumentCount;
|
||||
|
||||
function createSignatureHelpParameterForTypeParameter(typeParameter: TypeParameter): SignatureHelpParameter {
|
||||
const displayParts = mapToDisplayParts(writer =>
|
||||
typeChecker.getSymbolDisplayBuilder().buildTypeParameterDisplay(typeParameter, writer, invocation));
|
||||
let selectedItemIndex = candidates.indexOf(bestSignature);
|
||||
if (selectedItemIndex < 0) {
|
||||
selectedItemIndex = selectBestInvalidOverloadIndex(candidates, argumentCount);
|
||||
}
|
||||
|
||||
return {
|
||||
name: typeParameter.symbol.name,
|
||||
documentation: emptyArray,
|
||||
displayParts,
|
||||
isOptional: false
|
||||
};
|
||||
}
|
||||
Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`);
|
||||
|
||||
return {
|
||||
items,
|
||||
applicableSpan,
|
||||
selectedItemIndex,
|
||||
argumentIndex,
|
||||
argumentCount
|
||||
};
|
||||
|
||||
function createSignatureHelpParameterForParameter(parameter: Symbol): SignatureHelpParameter {
|
||||
const displayParts = mapToDisplayParts(writer =>
|
||||
typeChecker.getSymbolDisplayBuilder().buildParameterDisplay(parameter, writer, invocation));
|
||||
|
||||
return {
|
||||
name: parameter.name,
|
||||
documentation: parameter.getDocumentationComment(),
|
||||
displayParts,
|
||||
isOptional: typeChecker.isOptionalParameter(<ParameterDeclaration>parameter.valueDeclaration)
|
||||
};
|
||||
}
|
||||
|
||||
function createSignatureHelpParameterForTypeParameter(typeParameter: TypeParameter): SignatureHelpParameter {
|
||||
const displayParts = mapToDisplayParts(writer =>
|
||||
typeChecker.getSymbolDisplayBuilder().buildTypeParameterDisplay(typeParameter, writer, invocation));
|
||||
|
||||
return {
|
||||
name: typeParameter.symbol.name,
|
||||
documentation: emptyArray,
|
||||
displayParts,
|
||||
isOptional: false
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -426,9 +426,27 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
export function isInString(sourceFile: SourceFile, position: number) {
|
||||
const token = getTokenAtPosition(sourceFile, position);
|
||||
return token && (token.kind === SyntaxKind.StringLiteral || token.kind === SyntaxKind.StringLiteralType) && position > token.getStart(sourceFile);
|
||||
export function isInString(sourceFile: SourceFile, position: number): boolean {
|
||||
const previousToken = findPrecedingToken(position, sourceFile);
|
||||
if (previousToken &&
|
||||
(previousToken.kind === SyntaxKind.StringLiteral || previousToken.kind === SyntaxKind.StringLiteralType)) {
|
||||
const start = previousToken.getStart();
|
||||
const end = previousToken.getEnd();
|
||||
|
||||
// To be "in" one of these literals, the position has to be:
|
||||
// 1. entirely within the token text.
|
||||
// 2. at the end position of an unterminated token.
|
||||
// 3. at the end of a regular expression (due to trailing flags like '/foo/g').
|
||||
if (start < position && position < end) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (position === end) {
|
||||
return !!(<LiteralExpression>previousToken).isUnterminated;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isInComment(sourceFile: SourceFile, position: number) {
|
||||
|
||||
Reference in New Issue
Block a user