mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-11 16:06:39 +03:00
Improve completion context detection, respect sig help doc kind, helpers (#1160)
This commit is contained in:
parent
cc15c141bd
commit
54be42d245
@ -2963,3 +2963,17 @@ export class YieldFinder extends ParseTreeWalker {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class ReturnFinder extends ParseTreeWalker {
|
||||
private _containsReturn = false;
|
||||
|
||||
checkContainsReturn(node: ParseNode) {
|
||||
this.walk(node);
|
||||
return this._containsReturn;
|
||||
}
|
||||
|
||||
visitReturn(node: ReturnNode): boolean {
|
||||
this._containsReturn = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -550,7 +550,11 @@ export function getEnclosingClassOrFunction(node: ParseNode): FunctionNode | Cla
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getEnclosingSuiteOrModule(node: ParseNode): SuiteNode | ModuleNode | undefined {
|
||||
export function getEnclosingSuiteOrModule(
|
||||
node: ParseNode,
|
||||
stopAtFunction = false,
|
||||
stopAtLambda = true
|
||||
): SuiteNode | ModuleNode | undefined {
|
||||
let curNode = node.parent;
|
||||
while (curNode) {
|
||||
if (curNode.nodeType === ParseNodeType.Suite) {
|
||||
@ -562,7 +566,15 @@ export function getEnclosingSuiteOrModule(node: ParseNode): SuiteNode | ModuleNo
|
||||
}
|
||||
|
||||
if (curNode.nodeType === ParseNodeType.Lambda) {
|
||||
return undefined;
|
||||
if (stopAtLambda) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (curNode.nodeType === ParseNodeType.Function) {
|
||||
if (stopAtFunction) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
curNode = curNode.parent;
|
||||
|
@ -1330,6 +1330,7 @@ export class Program {
|
||||
getSignatureHelpForPosition(
|
||||
filePath: string,
|
||||
position: Position,
|
||||
format: MarkupKind,
|
||||
token: CancellationToken
|
||||
): SignatureHelpResults | undefined {
|
||||
return this._runEvaluatorWithCancellationToken(token, () => {
|
||||
@ -1344,6 +1345,7 @@ export class Program {
|
||||
position,
|
||||
this._lookUpImport,
|
||||
this._evaluator!,
|
||||
format,
|
||||
token
|
||||
);
|
||||
});
|
||||
|
@ -13,7 +13,6 @@ import {
|
||||
CancellationToken,
|
||||
CompletionItem,
|
||||
DocumentSymbol,
|
||||
SymbolInformation,
|
||||
} from 'vscode-languageserver';
|
||||
import { TextDocumentContentChangeEvent } from 'vscode-languageserver-textdocument';
|
||||
import {
|
||||
@ -57,6 +56,7 @@ import { BackgroundAnalysisProgram, BackgroundAnalysisProgramFactory } from './b
|
||||
import { ImportedModuleDescriptor, ImportResolver, ImportResolverFactory } from './importResolver';
|
||||
import { MaxAnalysisTime } from './program';
|
||||
import { findPythonSearchPaths, getPythonPathFromPythonInterpreter } from './pythonPathUtils';
|
||||
import { TypeEvaluator } from './typeEvaluator';
|
||||
|
||||
export const configFileNames = ['pyrightconfig.json', 'mspythonconfig.json'];
|
||||
|
||||
@ -285,9 +285,10 @@ export class AnalyzerService {
|
||||
getSignatureHelpForPosition(
|
||||
filePath: string,
|
||||
position: Position,
|
||||
format: MarkupKind,
|
||||
token: CancellationToken
|
||||
): SignatureHelpResults | undefined {
|
||||
return this._program.getSignatureHelpForPosition(filePath, position, token);
|
||||
return this._program.getSignatureHelpForPosition(filePath, position, format, token);
|
||||
}
|
||||
|
||||
getCompletionsForPosition(
|
||||
@ -309,6 +310,10 @@ export class AnalyzerService {
|
||||
);
|
||||
}
|
||||
|
||||
getEvaluator(): TypeEvaluator | undefined {
|
||||
return this._program.evaluator;
|
||||
}
|
||||
|
||||
resolveCompletionItem(
|
||||
filePath: string,
|
||||
completionItem: CompletionItem,
|
||||
|
@ -771,6 +771,7 @@ export class SourceFile {
|
||||
position: Position,
|
||||
importLookup: ImportLookup,
|
||||
evaluator: TypeEvaluator,
|
||||
format: MarkupKind,
|
||||
token: CancellationToken
|
||||
): SignatureHelpResults | undefined {
|
||||
// If we have no completed analysis job, there's nothing to do.
|
||||
@ -778,7 +779,13 @@ export class SourceFile {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return SignatureHelpProvider.getSignatureHelpForPosition(this._parseResults, position, evaluator, token);
|
||||
return SignatureHelpProvider.getSignatureHelpForPosition(
|
||||
this._parseResults,
|
||||
position,
|
||||
evaluator,
|
||||
format,
|
||||
token
|
||||
);
|
||||
}
|
||||
|
||||
getCompletionsForPosition(
|
||||
|
@ -7,10 +7,9 @@
|
||||
* Collection of functions that operate on Symbol objects.
|
||||
*/
|
||||
|
||||
import { ParseNodeType } from '../parser/parseNodes';
|
||||
import { Declaration, DeclarationType } from './declaration';
|
||||
import { isFinalVariableDeclaration } from './declarationUtils';
|
||||
import { Symbol, SymbolTable } from './symbol';
|
||||
import { Symbol } from './symbol';
|
||||
|
||||
export function getLastTypedDeclaredForSymbol(symbol: Symbol): Declaration | undefined {
|
||||
const typedDecls = symbol.getTypedDeclarations();
|
||||
|
@ -173,6 +173,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
|
||||
protected _hasHierarchicalDocumentSymbolCapability = false;
|
||||
protected _hoverContentFormat: MarkupKind = MarkupKind.PlainText;
|
||||
protected _completionDocFormat: MarkupKind = MarkupKind.PlainText;
|
||||
protected _signatureDocFormat: MarkupKind = MarkupKind.PlainText;
|
||||
protected _supportsUnnecessaryDiagnosticTag = false;
|
||||
protected _defaultClientConfig: any;
|
||||
|
||||
@ -589,6 +590,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
|
||||
const signatureHelpResults = workspace.serviceInstance.getSignatureHelpForPosition(
|
||||
filePath,
|
||||
position,
|
||||
this._signatureDocFormat,
|
||||
token
|
||||
);
|
||||
if (!signatureHelpResults) {
|
||||
@ -606,7 +608,8 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
|
||||
);
|
||||
}
|
||||
|
||||
const sigInfo = SignatureInformation.create(sig.label, sig.documentation, ...paramInfo);
|
||||
const sigInfo = SignatureInformation.create(sig.label, undefined, ...paramInfo);
|
||||
sigInfo.documentation = sig.documentation;
|
||||
sigInfo.activeParameter = sig.activeParameter;
|
||||
return sigInfo;
|
||||
});
|
||||
@ -934,6 +937,9 @@ export abstract class LanguageServerBase implements LanguageServerInterface {
|
||||
this._completionDocFormat = this._getCompatibleMarkupKind(
|
||||
capabilities.textDocument?.completion?.completionItem?.documentationFormat
|
||||
);
|
||||
this._signatureDocFormat = this._getCompatibleMarkupKind(
|
||||
capabilities.textDocument?.signatureHelp?.signatureInformation?.documentationFormat
|
||||
);
|
||||
const supportedDiagnosticTags = capabilities.textDocument?.publishDiagnostics?.tagSupport?.valueSet || [];
|
||||
this._supportsUnnecessaryDiagnosticTag = supportedDiagnosticTags.some(
|
||||
(tag) => tag === DiagnosticTag.Unnecessary
|
||||
|
@ -72,6 +72,7 @@ import { comparePositions, Position } from '../common/textRange';
|
||||
import { TextRange } from '../common/textRange';
|
||||
import { TextRangeCollection } from '../common/textRangeCollection';
|
||||
import {
|
||||
DecoratorNode,
|
||||
ErrorExpressionCategory,
|
||||
ErrorNode,
|
||||
ExpressionNode,
|
||||
@ -356,26 +357,12 @@ export class CompletionProvider {
|
||||
}
|
||||
|
||||
if (curNode.nodeType === ParseNodeType.Name) {
|
||||
// Are we within a "from X import Y as Z" statement and
|
||||
// more specifically within the "Y"?
|
||||
if (curNode.parent && curNode.parent.nodeType === ParseNodeType.ModuleName) {
|
||||
return this._getImportModuleCompletions(curNode.parent);
|
||||
} else if (curNode.parent && curNode.parent.nodeType === ParseNodeType.ImportFromAs) {
|
||||
const parentNode = curNode.parent.parent;
|
||||
|
||||
if (parentNode && parentNode.nodeType === ParseNodeType.ImportFrom) {
|
||||
if (curNode.parent.name === curNode) {
|
||||
return this._getImportFromCompletions(parentNode, priorWord);
|
||||
} else {
|
||||
return this._getImportFromCompletions(parentNode, '');
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
curNode.parent &&
|
||||
curNode.parent.nodeType === ParseNodeType.MemberAccess &&
|
||||
curNode === curNode.parent.memberName
|
||||
) {
|
||||
return this._getMemberAccessCompletions(curNode.parent.leftExpression, priorWord);
|
||||
// This condition is little different than others since it does its own
|
||||
// tree walk up to find context and let outer tree walk up to proceed if it can't find
|
||||
// one to show completion.
|
||||
const result = this._tryGetNameCompletions(curNode, offset, priorWord);
|
||||
if (result || result === undefined) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@ -387,10 +374,47 @@ export class CompletionProvider {
|
||||
return this._getExpressionCompletions(curNode, priorWord, priorText, postText);
|
||||
}
|
||||
|
||||
if (curNode.nodeType === ParseNodeType.Suite || curNode.nodeType === ParseNodeType.Module) {
|
||||
if (curNode.nodeType === ParseNodeType.Suite) {
|
||||
if (
|
||||
curNode.parent &&
|
||||
curNode.parent.nodeType === ParseNodeType.Except &&
|
||||
!curNode.parent.name &&
|
||||
curNode.parent.typeExpression &&
|
||||
TextRange.getEnd(curNode.parent.typeExpression) < offset &&
|
||||
offset <= curNode.parent.exceptSuite.start
|
||||
) {
|
||||
// except Exception as [<empty>]
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
curNode.parent &&
|
||||
curNode.parent.nodeType === ParseNodeType.Class &&
|
||||
(!curNode.parent.name || !curNode.parent.name.value) &&
|
||||
curNode.parent.arguments.length === 0 &&
|
||||
offset <= curNode.parent.suite.start
|
||||
) {
|
||||
// class [<empty>]
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this._getStatementCompletions(curNode, priorWord, priorText, postText);
|
||||
}
|
||||
|
||||
if (curNode.nodeType === ParseNodeType.Module) {
|
||||
return this._getStatementCompletions(curNode, priorWord, priorText, postText);
|
||||
}
|
||||
|
||||
if (
|
||||
curNode.nodeType === ParseNodeType.Parameter &&
|
||||
curNode.length === 0 &&
|
||||
curNode.parent &&
|
||||
curNode.parent.nodeType === ParseNodeType.Lambda
|
||||
) {
|
||||
// lambda [<empty>] or lambda x, [<empty>]
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!curNode.parent) {
|
||||
break;
|
||||
}
|
||||
@ -446,6 +470,96 @@ export class CompletionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private _tryGetNameCompletions(curNode: NameNode, offset: number, priorWord: string) {
|
||||
if (!curNode.parent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (curNode.parent.nodeType === ParseNodeType.ImportAs && curNode.parent.alias === curNode) {
|
||||
// Are we within a "import Y as [Z]"?
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (curNode.parent.nodeType === ParseNodeType.ModuleName) {
|
||||
// Are we within a "import Y as [<empty>]"?
|
||||
if (
|
||||
curNode.parent.parent &&
|
||||
curNode.parent.parent.nodeType === ParseNodeType.ImportAs &&
|
||||
!curNode.parent.parent.alias &&
|
||||
TextRange.getEnd(curNode.parent.parent) < offset
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Are we within a "from X import Y as Z" statement and
|
||||
// more specifically within the "Y"?
|
||||
return this._getImportModuleCompletions(curNode.parent);
|
||||
}
|
||||
|
||||
if (curNode.parent.nodeType === ParseNodeType.ImportFromAs) {
|
||||
if (curNode.parent.alias === curNode) {
|
||||
// Are we within a "from X import Y as [Z]"?
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parentNode = curNode.parent.parent;
|
||||
if (parentNode && parentNode.nodeType === ParseNodeType.ImportFrom) {
|
||||
// Are we within a "from X import Y as [<empty>]"?
|
||||
if (!curNode.parent.alias && TextRange.getEnd(curNode.parent) < offset) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (curNode.parent.name === curNode) {
|
||||
return this._getImportFromCompletions(parentNode, priorWord);
|
||||
}
|
||||
|
||||
return this._getImportFromCompletions(parentNode, '');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (curNode.parent.nodeType === ParseNodeType.MemberAccess && curNode === curNode.parent.memberName) {
|
||||
return this._getMemberAccessCompletions(curNode.parent.leftExpression, priorWord);
|
||||
}
|
||||
|
||||
if (curNode.parent.nodeType === ParseNodeType.Except && curNode === curNode.parent.name) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (curNode.parent.nodeType === ParseNodeType.Function && curNode === curNode.parent.name) {
|
||||
if (curNode.parent.decorators?.some((d) => this._isOverload(d))) {
|
||||
return this._getMethodOverloadsCompletions(curNode);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (curNode.parent.nodeType === ParseNodeType.Parameter && curNode === curNode.parent.name) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (curNode.parent.nodeType === ParseNodeType.Class && curNode === curNode.parent.name) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
curNode.parent.nodeType === ParseNodeType.For &&
|
||||
TextRange.contains(curNode.parent.targetExpression, curNode.start)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
curNode.parent.nodeType === ParseNodeType.ListComprehensionFor &&
|
||||
TextRange.contains(curNode.parent.targetExpression, curNode.start)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private _isWithinComment(offset: number): boolean {
|
||||
const token = getTokenAfter(offset, this._parseResults.tokenizerOutput.tokens);
|
||||
if (!token) {
|
||||
@ -523,6 +637,10 @@ export class CompletionProvider {
|
||||
|
||||
case ErrorExpressionCategory.MissingFunctionParameterList: {
|
||||
if (node.child && node.child.nodeType === ParseNodeType.Name) {
|
||||
if (node.decorators?.some((d) => this._isOverload(d))) {
|
||||
return this._getMethodOverloadsCompletions(node.child);
|
||||
}
|
||||
|
||||
// Determine if the partial name is a method that's overriding
|
||||
// a method in a base class.
|
||||
return this._getMethodOverrideCompletions(node.child);
|
||||
@ -534,6 +652,10 @@ export class CompletionProvider {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _isOverload(d: DecoratorNode): unknown {
|
||||
return d.expression.nodeType === ParseNodeType.Name && d.expression.value === 'overload';
|
||||
}
|
||||
|
||||
private _createSingleKeywordCompletionList(keyword: string): CompletionResults {
|
||||
const completionItem = CompletionItem.create(keyword);
|
||||
completionItem.kind = CompletionItemKind.Keyword;
|
||||
@ -542,6 +664,75 @@ export class CompletionProvider {
|
||||
return { completionList };
|
||||
}
|
||||
|
||||
private _getMethodOverloadsCompletions(partialName: NameNode): CompletionResults | undefined {
|
||||
const symbolTable = getSymbolTable(this._evaluator, partialName);
|
||||
if (!symbolTable) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const completionList = CompletionList.create();
|
||||
|
||||
const enclosingFunc = ParseTreeUtils.getEnclosingFunction(partialName);
|
||||
symbolTable.forEach((symbol, name) => {
|
||||
const decl = getLastTypedDeclaredForSymbol(symbol);
|
||||
if (!decl || decl.type !== DeclarationType.Function) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!decl.node.decorators.some((d) => this._isOverload(d))) {
|
||||
// Only consider ones that have overload decorator.
|
||||
return;
|
||||
}
|
||||
|
||||
const decls = symbol.getDeclarations();
|
||||
if (decls.length === 1 && decls.some((d) => d.node === enclosingFunc)) {
|
||||
// Don't show itself.
|
||||
return;
|
||||
}
|
||||
|
||||
const isSimilar = StringUtils.computeCompletionSimilarity(partialName.value, name) > similarityLimit;
|
||||
if (isSimilar) {
|
||||
const range: Range = {
|
||||
start: { line: this._position.line, character: this._position.character - partialName.length },
|
||||
end: { line: this._position.line, character: this._position.character },
|
||||
};
|
||||
|
||||
const textEdit = TextEdit.replace(range, decl.node.name.value);
|
||||
this._addSymbol(name, symbol, partialName.value, completionList, { edits: { textEdit } });
|
||||
}
|
||||
});
|
||||
|
||||
return { completionList };
|
||||
|
||||
function getSymbolTable(evaluator: TypeEvaluator, partialName: NameNode) {
|
||||
const enclosingClass = ParseTreeUtils.getEnclosingClass(partialName, false);
|
||||
if (enclosingClass) {
|
||||
const classResults = evaluator.getTypeOfClass(enclosingClass);
|
||||
if (!classResults) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const symbolTable = new Map<string, Symbol>();
|
||||
for (const mroClass of classResults.classType.details.mro) {
|
||||
if (isClass(mroClass)) {
|
||||
getMembersForClass(mroClass, symbolTable, false);
|
||||
}
|
||||
}
|
||||
|
||||
return symbolTable;
|
||||
}
|
||||
|
||||
// For function overload, we only care about top level functions
|
||||
const moduleNode = ParseTreeUtils.getEnclosingModule(partialName);
|
||||
if (moduleNode) {
|
||||
const moduleScope = AnalyzerNodeInfo.getScope(moduleNode);
|
||||
return moduleScope?.symbolTable;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _getMethodOverrideCompletions(partialName: NameNode): CompletionResults | undefined {
|
||||
const enclosingClass = ParseTreeUtils.getEnclosingClass(partialName, true);
|
||||
if (!enclosingClass) {
|
||||
@ -928,7 +1119,13 @@ export class CompletionProvider {
|
||||
this._addLiteralValuesForTargetType(declaredTypeOfTarget, priorText, postText, completionList);
|
||||
}
|
||||
} else {
|
||||
this._addCallArgumentCompletions(parseNode, priorWord, priorText, postText, completionList);
|
||||
// Make sure we are not inside of the string literal.
|
||||
debug.assert(parseNode.nodeType === ParseNodeType.String);
|
||||
|
||||
const offset = convertPositionToOffset(this._position, this._parseResults.tokenizerOutput.lines)!;
|
||||
if (offset <= parentNode.start || TextRange.getEnd(parseNode) <= offset) {
|
||||
this._addCallArgumentCompletions(parseNode, priorWord, priorText, postText, completionList);
|
||||
}
|
||||
}
|
||||
|
||||
return { completionList };
|
||||
|
@ -54,7 +54,7 @@ export class ReferencesResult {
|
||||
}
|
||||
}
|
||||
|
||||
class FindReferencesTreeWalker extends ParseTreeWalker {
|
||||
export class FindReferencesTreeWalker extends ParseTreeWalker {
|
||||
private readonly _locationsFound: DocumentRange[] = [];
|
||||
|
||||
constructor(
|
||||
@ -68,8 +68,8 @@ class FindReferencesTreeWalker extends ParseTreeWalker {
|
||||
super();
|
||||
}
|
||||
|
||||
findReferences() {
|
||||
this.walk(this._parseResults.parseTree);
|
||||
findReferences(rootNode = this._parseResults.parseTree) {
|
||||
this.walk(rootNode);
|
||||
|
||||
return this._locationsFound;
|
||||
}
|
||||
|
@ -9,8 +9,9 @@
|
||||
* arguments for the call.
|
||||
*/
|
||||
|
||||
import { CancellationToken } from 'vscode-languageserver';
|
||||
import { CancellationToken, MarkupContent, MarkupKind } from 'vscode-languageserver';
|
||||
|
||||
import { convertDocStringToMarkdown, convertDocStringToPlainText } from '../analyzer/docStringConversion';
|
||||
import { extractParameterDocumentation } from '../analyzer/docStringUtils';
|
||||
import * as ParseTreeUtils from '../analyzer/parseTreeUtils';
|
||||
import { CallSignature, TypeEvaluator } from '../analyzer/typeEvaluator';
|
||||
@ -28,7 +29,7 @@ export interface ParamInfo {
|
||||
|
||||
export interface SignatureInfo {
|
||||
label: string;
|
||||
documentation?: string;
|
||||
documentation?: MarkupContent;
|
||||
parameters?: ParamInfo[];
|
||||
activeParameter?: number;
|
||||
}
|
||||
@ -43,6 +44,7 @@ export class SignatureHelpProvider {
|
||||
parseResults: ParseResults,
|
||||
position: Position,
|
||||
evaluator: TypeEvaluator,
|
||||
format: MarkupKind,
|
||||
token: CancellationToken
|
||||
): SignatureHelpResults | undefined {
|
||||
throwIfCancellationRequested(token);
|
||||
@ -80,7 +82,7 @@ export class SignatureHelpProvider {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const signatures = callSignatureInfo.signatures.map((sig) => this._makeSignature(sig, evaluator));
|
||||
const signatures = callSignatureInfo.signatures.map((sig) => this._makeSignature(sig, evaluator, format));
|
||||
const callHasParameters = !!callSignatureInfo.callNode.arguments?.length;
|
||||
|
||||
return {
|
||||
@ -89,7 +91,11 @@ export class SignatureHelpProvider {
|
||||
};
|
||||
}
|
||||
|
||||
private static _makeSignature(signature: CallSignature, evaluator: TypeEvaluator): SignatureInfo {
|
||||
private static _makeSignature(
|
||||
signature: CallSignature,
|
||||
evaluator: TypeEvaluator,
|
||||
format: MarkupKind
|
||||
): SignatureInfo {
|
||||
const functionType = signature.type;
|
||||
const stringParts = evaluator.printFunctionParts(functionType);
|
||||
const parameters: ParamInfo[] = [];
|
||||
@ -124,10 +130,23 @@ export class SignatureHelpProvider {
|
||||
const sigInfo: SignatureInfo = {
|
||||
label,
|
||||
parameters,
|
||||
documentation: functionDocString,
|
||||
activeParameter,
|
||||
};
|
||||
|
||||
if (functionDocString) {
|
||||
if (format === MarkupKind.Markdown) {
|
||||
sigInfo.documentation = {
|
||||
kind: MarkupKind.Markdown,
|
||||
value: convertDocStringToMarkdown(functionDocString),
|
||||
};
|
||||
} else {
|
||||
sigInfo.documentation = {
|
||||
kind: MarkupKind.PlainText,
|
||||
value: convertDocStringToPlainText(functionDocString),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return sigInfo;
|
||||
}
|
||||
}
|
||||
|
@ -701,10 +701,16 @@ export interface ErrorNode extends ParseNodeBase {
|
||||
readonly nodeType: ParseNodeType.Error;
|
||||
readonly category: ErrorExpressionCategory;
|
||||
readonly child?: ExpressionNode;
|
||||
readonly decorators?: DecoratorNode[];
|
||||
}
|
||||
|
||||
export namespace ErrorNode {
|
||||
export function create(initialRange: TextRange, category: ErrorExpressionCategory, child?: ExpressionNode) {
|
||||
export function create(
|
||||
initialRange: TextRange,
|
||||
category: ErrorExpressionCategory,
|
||||
child?: ExpressionNode,
|
||||
decorators?: DecoratorNode[]
|
||||
) {
|
||||
const node: ErrorNode = {
|
||||
start: initialRange.start,
|
||||
length: initialRange.length,
|
||||
@ -712,6 +718,7 @@ export namespace ErrorNode {
|
||||
id: _nextNodeId++,
|
||||
category,
|
||||
child,
|
||||
decorators,
|
||||
};
|
||||
|
||||
if (child) {
|
||||
@ -719,6 +726,16 @@ export namespace ErrorNode {
|
||||
extendRange(node, child);
|
||||
}
|
||||
|
||||
if (decorators) {
|
||||
decorators.forEach((decorator) => {
|
||||
decorator.parent = node;
|
||||
});
|
||||
|
||||
if (decorators.length > 0) {
|
||||
extendRange(node, decorators[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
@ -720,7 +720,12 @@ export class Parser {
|
||||
const nameToken = this._getTokenIfIdentifier();
|
||||
if (!nameToken) {
|
||||
this._addError(Localizer.Diagnostic.expectedFunctionName(), defToken);
|
||||
return ErrorNode.create(defToken, ErrorExpressionCategory.MissingFunctionParameterList);
|
||||
return ErrorNode.create(
|
||||
defToken,
|
||||
ErrorExpressionCategory.MissingFunctionParameterList,
|
||||
undefined,
|
||||
decorators
|
||||
);
|
||||
}
|
||||
|
||||
if (!this._consumeTokenIfType(TokenType.OpenParenthesis)) {
|
||||
@ -728,7 +733,8 @@ export class Parser {
|
||||
return ErrorNode.create(
|
||||
nameToken,
|
||||
ErrorExpressionCategory.MissingFunctionParameterList,
|
||||
NameNode.create(nameToken)
|
||||
NameNode.create(nameToken),
|
||||
decorators
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,35 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @filename: test.py
|
||||
//// class [|/*marker1*/|]
|
||||
////
|
||||
|
||||
// @filename: test1.py
|
||||
//// class c[|/*marker2*/|]
|
||||
////
|
||||
|
||||
// @filename: test2.py
|
||||
//// class c1[|/*marker3*/|]():
|
||||
//// pass
|
||||
////
|
||||
|
||||
// @filename: test3.py
|
||||
//// class c1([|/*marker4*/|]):
|
||||
//// pass
|
||||
////
|
||||
|
||||
{
|
||||
helper.openFiles(helper.getMarkers().map((m) => m.fileName));
|
||||
|
||||
// @ts-ignore
|
||||
await helper.verifyCompletion('exact', 'markdown', {
|
||||
marker1: { completions: [] },
|
||||
marker2: { completions: [] },
|
||||
marker3: { completions: [] },
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
await helper.verifyCompletion('included', 'markdown', {
|
||||
marker4: { completions: [{ label: 'Exception', kind: Consts.CompletionItemKind.Class }] },
|
||||
});
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @filename: test.py
|
||||
//// try:
|
||||
//// pass
|
||||
//// except ZeroDivisionError as d[|/*marker1*/|]:
|
||||
//// pass
|
||||
////
|
||||
//// try:
|
||||
//// pass
|
||||
//// except ZeroDivisionError as [|/*marker2*/|]:
|
||||
//// pass
|
||||
|
||||
// @ts-ignore
|
||||
await helper.verifyCompletion('exact', 'markdown', {
|
||||
marker1: { completions: [] },
|
||||
marker2: { completions: [] },
|
||||
});
|
@ -0,0 +1,30 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @filename: test1.py
|
||||
//// for [|/*marker1*/|]
|
||||
////
|
||||
|
||||
// @filename: test2.py
|
||||
//// for c[|/*marker2*/|]
|
||||
////
|
||||
|
||||
// @filename: test3.py
|
||||
//// for c1[|/*marker3*/|] in [1, 2]:
|
||||
//// pass
|
||||
////
|
||||
|
||||
// @filename: test4.py
|
||||
//// [c for c[|/*marker4*/|] in [1, 2]]
|
||||
////
|
||||
|
||||
{
|
||||
helper.openFiles(helper.getMarkers().map((m) => m.fileName));
|
||||
|
||||
// @ts-ignore
|
||||
await helper.verifyCompletion('exact', 'markdown', {
|
||||
marker1: { completions: [{ label: 'in', kind: Consts.CompletionItemKind.Keyword }] },
|
||||
marker2: { completions: [] },
|
||||
marker3: { completions: [] },
|
||||
marker4: { completions: [] },
|
||||
});
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @filename: test.py
|
||||
//// import os as o[|/*marker1*/|]
|
||||
//// import os as [|/*marker2*/|]
|
||||
//// from os import path as p[|/*marker3*/|]
|
||||
//// from os import path as [|/*marker4*/|]
|
||||
|
||||
// @ts-ignore
|
||||
await helper.verifyCompletion('exact', 'markdown', {
|
||||
marker1: { completions: [] },
|
||||
marker2: { completions: [] },
|
||||
marker3: { completions: [] },
|
||||
marker4: { completions: [] },
|
||||
});
|
@ -0,0 +1,45 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @filename: test.py
|
||||
//// lambda [|/*marker1*/|]
|
||||
|
||||
// @filename: test1.py
|
||||
//// lambda x[|/*marker2*/|]
|
||||
|
||||
// @filename: test2.py
|
||||
//// lambda x[|/*marker3*/|]:
|
||||
|
||||
// @filename: test3.py
|
||||
//// lambda x, [|/*marker4*/|]
|
||||
|
||||
// @filename: test4.py
|
||||
//// lambda x, [|/*marker5*/|]:
|
||||
|
||||
// @filename: test5.py
|
||||
//// lambda x, y[|/*marker6*/|]
|
||||
|
||||
// @filename: test6.py
|
||||
//// lambda x, y[|/*marker7*/|]:
|
||||
|
||||
// @filename: test7.py
|
||||
//// lambda x: [|/*marker8*/|]
|
||||
|
||||
{
|
||||
helper.openFiles(helper.getMarkers().map((m) => m.fileName));
|
||||
|
||||
// @ts-ignore
|
||||
await helper.verifyCompletion('exact', 'markdown', {
|
||||
marker1: { completions: [] },
|
||||
marker2: { completions: [] },
|
||||
marker3: { completions: [] },
|
||||
marker4: { completions: [] },
|
||||
marker5: { completions: [] },
|
||||
marker6: { completions: [] },
|
||||
marker7: { completions: [] },
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
await helper.verifyCompletion('included', 'markdown', {
|
||||
marker8: { completions: [{ label: 'str', kind: Consts.CompletionItemKind.Class }] },
|
||||
});
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @filename: test.py
|
||||
//// def [|/*marker1*/|]
|
||||
////
|
||||
//// def d[|/*marker2*/|]
|
||||
////
|
||||
//// def d1[|/*marker3*/|]():
|
||||
//// pass
|
||||
////
|
||||
//// async def [|/*marker4*/|]
|
||||
////
|
||||
//// async def a[|/*marker5*/|]
|
||||
////
|
||||
//// async def a1[|/*marker6*/|]():
|
||||
//// pass
|
||||
////
|
||||
//// def method(p[|/*marker7*/|]):
|
||||
//// pass
|
||||
//// def method(p:[|/*marker8*/|]):
|
||||
//// pass
|
||||
////
|
||||
//// def method(p, p2[|/*marker9*/|]):
|
||||
//// pass
|
||||
//// def method(p, p2:[|/*marker10*/|]):
|
||||
//// pass
|
||||
|
||||
// @filename: test1.py
|
||||
//// class A:
|
||||
//// def a1[|/*marker11*/|]
|
||||
////
|
||||
//// def a2[|/*marker12*/|]():
|
||||
//// pass
|
||||
////
|
||||
//// def method(p[|/*marker13*/|]):
|
||||
//// pass
|
||||
//// def method(p:[|/*marker14*/|]):
|
||||
//// pass
|
||||
////
|
||||
//// def method(p, p2[|/*marker15*/|]):
|
||||
//// pass
|
||||
//// def method(p, p2:[|/*marker16*/|]):
|
||||
//// pass
|
||||
|
||||
{
|
||||
helper.openFiles(helper.getMarkers().map((m) => m.fileName));
|
||||
|
||||
// @ts-ignore
|
||||
await helper.verifyCompletion('exact', 'markdown', {
|
||||
marker1: { completions: [] },
|
||||
marker2: { completions: [] },
|
||||
marker3: { completions: [] },
|
||||
marker4: { completions: [] },
|
||||
marker5: { completions: [] },
|
||||
marker6: { completions: [] },
|
||||
marker7: { completions: [] },
|
||||
marker9: { completions: [] },
|
||||
marker11: { completions: [] },
|
||||
marker12: { completions: [] },
|
||||
marker13: { completions: [] },
|
||||
marker15: { completions: [] },
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
await helper.verifyCompletion('included', 'markdown', {
|
||||
marker8: { completions: [{ label: 'str', kind: Consts.CompletionItemKind.Class }] },
|
||||
marker10: { completions: [{ label: 'str', kind: Consts.CompletionItemKind.Class }] },
|
||||
marker14: { completions: [{ label: 'str', kind: Consts.CompletionItemKind.Class }] },
|
||||
marker16: { completions: [{ label: 'str', kind: Consts.CompletionItemKind.Class }] },
|
||||
});
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @filename: test1.py
|
||||
//// from typing import overload
|
||||
////
|
||||
//// class A:
|
||||
//// @overload
|
||||
//// def met[|/*marker1*/|]
|
||||
|
||||
// @filename: test2.py
|
||||
//// from typing import overload
|
||||
////
|
||||
//// class A:
|
||||
//// @overload
|
||||
//// def met[|/*marker2*/|]()
|
||||
|
||||
// @filename: test3.py
|
||||
//// from typing import overload
|
||||
////
|
||||
//// class A:
|
||||
//// @overload
|
||||
//// def met[|/*marker3*/|]():
|
||||
//// pass
|
||||
|
||||
// @filename: test4.py
|
||||
//// from typing import overload
|
||||
////
|
||||
//// class A:
|
||||
//// @overload
|
||||
//// def method(self):
|
||||
//// pass
|
||||
//// @overload
|
||||
//// def met[|/*marker4*/|]
|
||||
|
||||
// @filename: test5.py
|
||||
//// from typing import overload
|
||||
////
|
||||
//// class A:
|
||||
//// @overload
|
||||
//// def method(self):
|
||||
//// pass
|
||||
//// @overload
|
||||
//// def method2(self):
|
||||
//// pass
|
||||
//// @overload
|
||||
//// def met[|/*marker5*/|]
|
||||
|
||||
// @filename: test6.py
|
||||
//// from typing import overload
|
||||
////
|
||||
//// class A:
|
||||
//// @overload
|
||||
//// def method(self):
|
||||
//// pass
|
||||
//// @overload
|
||||
//// def method2(self):
|
||||
//// pass
|
||||
//// @overload
|
||||
//// def diff[|/*marker6*/|]
|
||||
|
||||
// @filename: test7.py
|
||||
//// from typing import overload
|
||||
////
|
||||
//// class A:
|
||||
//// @overload
|
||||
//// def method(self):
|
||||
//// pass
|
||||
//// @overload
|
||||
//// def method2(self):
|
||||
//// pass
|
||||
////
|
||||
//// class B(A):
|
||||
//// @overload
|
||||
//// def method3(self):
|
||||
//// pass
|
||||
//// @overload
|
||||
//// def met[|/*marker7*/|]
|
||||
|
||||
// @filename: test8.py
|
||||
//// from typing import overload
|
||||
////
|
||||
//// class A:
|
||||
//// @overload
|
||||
//// def method(self):
|
||||
//// pass
|
||||
//// @overload
|
||||
//// def method[|/*marker8*/|](self, a):
|
||||
//// pass
|
||||
|
||||
{
|
||||
helper.openFiles(helper.getMarkers().map((m) => m.fileName));
|
||||
|
||||
// @ts-ignore
|
||||
await helper.verifyCompletion('exact', 'markdown', {
|
||||
marker1: { completions: [] },
|
||||
marker2: { completions: [] },
|
||||
marker3: { completions: [] },
|
||||
marker4: { completions: [{ label: 'method', kind: Consts.CompletionItemKind.Method }] },
|
||||
marker5: {
|
||||
completions: [
|
||||
{ label: 'method', kind: Consts.CompletionItemKind.Method },
|
||||
{ label: 'method2', kind: Consts.CompletionItemKind.Method },
|
||||
],
|
||||
},
|
||||
marker6: { completions: [] },
|
||||
marker7: {
|
||||
completions: [
|
||||
{ label: 'method', kind: Consts.CompletionItemKind.Method },
|
||||
{ label: 'method2', kind: Consts.CompletionItemKind.Method },
|
||||
{ label: 'method3', kind: Consts.CompletionItemKind.Method },
|
||||
],
|
||||
},
|
||||
marker8: { completions: [{ label: 'method', kind: Consts.CompletionItemKind.Method }] },
|
||||
});
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @filename: test1.py
|
||||
//// from typing import overload
|
||||
////
|
||||
//// @overload
|
||||
//// def met[|/*marker1*/|]
|
||||
|
||||
// @filename: test2.py
|
||||
//// from typing import overload
|
||||
////
|
||||
//// @overload
|
||||
//// def met[|/*marker2*/|]()
|
||||
|
||||
// @filename: test3.py
|
||||
//// from typing import overload
|
||||
////
|
||||
//// @overload
|
||||
//// def met[|/*marker3*/|]():
|
||||
//// pass
|
||||
|
||||
// @filename: test4.py
|
||||
//// from typing import overload
|
||||
////
|
||||
//// @overload
|
||||
//// def method(self):
|
||||
//// pass
|
||||
//// @overload
|
||||
//// def met[|/*marker4*/|]
|
||||
|
||||
// @filename: test5.py
|
||||
//// from typing import overload
|
||||
////
|
||||
//// @overload
|
||||
//// def method(self):
|
||||
//// pass
|
||||
//// @overload
|
||||
//// def method2(self):
|
||||
//// pass
|
||||
//// @overload
|
||||
//// def met[|/*marker5*/|]
|
||||
|
||||
// @filename: test6.py
|
||||
//// from typing import overload
|
||||
////
|
||||
//// @overload
|
||||
//// def method(self):
|
||||
//// pass
|
||||
//// @overload
|
||||
//// def method2(self):
|
||||
//// pass
|
||||
//// @overload
|
||||
//// def diff[|/*marker6*/|]
|
||||
|
||||
// @filename: test7.py
|
||||
//// from typing import overload
|
||||
////
|
||||
//// @overload
|
||||
//// def method(self):
|
||||
//// pass
|
||||
//// @overload
|
||||
//// def method2(self):
|
||||
//// pass
|
||||
|
||||
// @filename: test7-1.py
|
||||
//// from typing import overload
|
||||
//// from test7 import method, method2
|
||||
////
|
||||
//// @overload
|
||||
//// def method3(self):
|
||||
//// pass
|
||||
//// @overload
|
||||
//// def met[|/*marker7*/|]
|
||||
|
||||
// @filename: test8.py
|
||||
//// from typing import overload
|
||||
////
|
||||
//// @overload
|
||||
//// def method(self):
|
||||
//// pass
|
||||
//// @overload
|
||||
//// def method[|/*marker8*/|](self, a):
|
||||
//// pass
|
||||
|
||||
{
|
||||
helper.openFiles(helper.getMarkers().map((m) => m.fileName));
|
||||
|
||||
// @ts-ignore
|
||||
await helper.verifyCompletion('exact', 'markdown', {
|
||||
marker1: { completions: [] },
|
||||
marker2: { completions: [] },
|
||||
marker3: { completions: [] },
|
||||
marker4: { completions: [{ label: 'method', kind: Consts.CompletionItemKind.Function }] },
|
||||
marker5: {
|
||||
completions: [
|
||||
{ label: 'method', kind: Consts.CompletionItemKind.Function },
|
||||
{ label: 'method2', kind: Consts.CompletionItemKind.Function },
|
||||
],
|
||||
},
|
||||
marker6: { completions: [] },
|
||||
marker7: {
|
||||
completions: [{ label: 'method3', kind: Consts.CompletionItemKind.Function }],
|
||||
},
|
||||
marker8: { completions: [{ label: 'method', kind: Consts.CompletionItemKind.Function }] },
|
||||
});
|
||||
}
|
@ -12,6 +12,7 @@
|
||||
//// '''another function docs'''
|
||||
//// pass
|
||||
//// some_fun[|/*marker3*/|]
|
||||
//// unknownVariable.[|/*marker4*/|]
|
||||
|
||||
// @ts-ignore
|
||||
await helper.verifyCompletion('exact', 'markdown', {
|
||||
@ -31,4 +32,5 @@ await helper.verifyCompletion('exact', 'markdown', {
|
||||
},
|
||||
],
|
||||
},
|
||||
marker4: { completions: [] },
|
||||
});
|
||||
|
@ -0,0 +1,21 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @filename: test.py
|
||||
//// msg = "gekki"
|
||||
////
|
||||
//// a = f"{[|/*marker1*/|]}"
|
||||
//// b = f"{msg.c[|/*marker2*/|]}"
|
||||
//// b = f"{msg.[|/*marker3*/|]}"
|
||||
|
||||
// @ts-ignore
|
||||
await helper.verifyCompletion('included', 'markdown', {
|
||||
marker1: {
|
||||
completions: [{ label: 'msg', kind: Consts.CompletionItemKind.Variable }],
|
||||
},
|
||||
marker2: {
|
||||
completions: [{ label: 'count', kind: Consts.CompletionItemKind.Method }],
|
||||
},
|
||||
marker3: {
|
||||
completions: [{ label: 'capitalize', kind: Consts.CompletionItemKind.Method }],
|
||||
},
|
||||
});
|
@ -0,0 +1,23 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @filename: test.py
|
||||
//// def Method(a, b, c):
|
||||
//// pass
|
||||
////
|
||||
//// Method([|/*marker1*/|]"[|/*marker2*/|]hello[|/*marker3*/|]"[|/*marker4*/|])
|
||||
|
||||
// @ts-ignore
|
||||
await helper.verifyCompletion('included', 'markdown', {
|
||||
marker1: {
|
||||
completions: [{ label: 'a=', kind: Consts.CompletionItemKind.Variable }],
|
||||
},
|
||||
marker4: {
|
||||
completions: [{ label: 'b=', kind: Consts.CompletionItemKind.Variable }],
|
||||
},
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
await helper.verifyCompletion('exact', 'markdown', {
|
||||
marker2: { completions: [] },
|
||||
marker3: { completions: [] },
|
||||
});
|
@ -0,0 +1,37 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @filename: test.py
|
||||
//// import os
|
||||
//// from typing import Literal, TypedDict, Union
|
||||
////
|
||||
//// def method(a, b, c):
|
||||
//// pass
|
||||
////
|
||||
//// method("os.[|/*marker1*/|]")
|
||||
////
|
||||
//// class Movie(TypedDict):
|
||||
//// name: str
|
||||
//// age: int
|
||||
////
|
||||
//// m = Movie(name="hello", age=10)
|
||||
//// m["[|/*marker2*/|]"]
|
||||
////
|
||||
//// a: Union[Literal["hello"], Literal["hallo"]]
|
||||
//// a = "[|/*marker3*/|]"
|
||||
|
||||
// @ts-ignore
|
||||
await helper.verifyCompletion('exact', 'markdown', {
|
||||
marker1: { completions: [] },
|
||||
marker2: {
|
||||
completions: [
|
||||
{ label: '"name"', kind: Consts.CompletionItemKind.Text },
|
||||
{ label: '"age"', kind: Consts.CompletionItemKind.Text },
|
||||
],
|
||||
},
|
||||
marker3: {
|
||||
completions: [
|
||||
{ label: '"hello"', kind: Consts.CompletionItemKind.Text },
|
||||
{ label: '"hallo"', kind: Consts.CompletionItemKind.Text },
|
||||
],
|
||||
},
|
||||
});
|
@ -136,6 +136,8 @@ declare namespace _ {
|
||||
importName: string;
|
||||
}
|
||||
|
||||
type MarkupKind = 'markdown' | 'plaintext';
|
||||
|
||||
interface Fourslash {
|
||||
getDocumentHighlightKind(m?: Marker): DocumentHighlightKind | undefined;
|
||||
|
||||
@ -166,7 +168,8 @@ declare namespace _ {
|
||||
|
||||
moveCaretRight(count: number): void;
|
||||
|
||||
openFile(indexOrName: number | string, content?: string): void;
|
||||
openFile(indexOrName: number | string): void;
|
||||
openFiles(indexOrNames: (number | string)[]): void;
|
||||
|
||||
verifyDiagnostics(map?: { [marker: string]: { category: string; message: string } }): void;
|
||||
verifyCodeActions(
|
||||
@ -185,7 +188,7 @@ declare namespace _ {
|
||||
verifyHover(kind: string, map: { [marker: string]: string }): void;
|
||||
verifyCompletion(
|
||||
verifyMode: FourSlashCompletionVerificationMode,
|
||||
docFormat: string,
|
||||
docFormat: MarkupKind,
|
||||
map: {
|
||||
[marker: string]: {
|
||||
completions: FourSlashCompletionItem[];
|
||||
@ -198,17 +201,21 @@ declare namespace _ {
|
||||
},
|
||||
abbrMap?: { [abbr: string]: AbbreviationInfo }
|
||||
): Promise<void>;
|
||||
verifySignature(map: {
|
||||
[marker: string]: {
|
||||
noSig?: boolean;
|
||||
signatures?: {
|
||||
label: string;
|
||||
parameters: string[];
|
||||
}[];
|
||||
activeParameters?: (number | undefined)[];
|
||||
callHasParameters?: boolean;
|
||||
};
|
||||
}): void;
|
||||
verifySignature(
|
||||
docFormat: MarkupKind,
|
||||
map: {
|
||||
[marker: string]: {
|
||||
noSig?: boolean;
|
||||
signatures?: {
|
||||
label: string;
|
||||
parameters: string[];
|
||||
documentation?: string;
|
||||
}[];
|
||||
activeParameters?: (number | undefined)[];
|
||||
callHasParameters?: boolean;
|
||||
};
|
||||
}
|
||||
): void;
|
||||
verifyFindAllReferences(map: {
|
||||
[marker: string]: {
|
||||
references: DocumentRange[];
|
||||
|
@ -44,7 +44,7 @@
|
||||
},
|
||||
];
|
||||
|
||||
helper.verifySignature({
|
||||
helper.verifySignature('plaintext', {
|
||||
init: {
|
||||
signatures: xInitSignatures,
|
||||
activeParameters: [0],
|
||||
|
@ -0,0 +1,43 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @filename: docstrings.py
|
||||
//// from typing import overload
|
||||
////
|
||||
//// def repeat(a: str, b: int) -> str:
|
||||
//// """Repeat the string ``a`` ``b`` times.
|
||||
////
|
||||
//// >>> repeat('foo', 3)
|
||||
//// 'foofoofoo'
|
||||
//// """
|
||||
////
|
||||
//// return a * b
|
||||
////
|
||||
//// repeat([|/*marker1*/|])
|
||||
|
||||
{
|
||||
helper.verifySignature('plaintext', {
|
||||
marker1: {
|
||||
signatures: [
|
||||
{
|
||||
label: '(a: str, b: int) -> str',
|
||||
parameters: ['a: str', 'b: int'],
|
||||
documentation: "Repeat the string ``a`` ``b`` times.\n\n>>> repeat('foo', 3)\n'foofoofoo'",
|
||||
},
|
||||
],
|
||||
activeParameters: [0],
|
||||
},
|
||||
});
|
||||
|
||||
helper.verifySignature('markdown', {
|
||||
marker1: {
|
||||
signatures: [
|
||||
{
|
||||
label: '(a: str, b: int) -> str',
|
||||
parameters: ['a: str', 'b: int'],
|
||||
documentation: "Repeat the string `a` `b` times.\n\n```\n>>> repeat('foo', 3)\n'foofoofoo'\n```",
|
||||
},
|
||||
],
|
||||
activeParameters: [0],
|
||||
},
|
||||
});
|
||||
}
|
@ -38,7 +38,7 @@
|
||||
},
|
||||
];
|
||||
|
||||
helper.verifySignature({
|
||||
helper.verifySignature('plaintext', {
|
||||
o1: {
|
||||
signatures: overloadedSignatures,
|
||||
activeParameters: [undefined, 0, 0],
|
||||
|
@ -21,7 +21,7 @@
|
||||
},
|
||||
];
|
||||
|
||||
helper.verifySignature({
|
||||
helper.verifySignature('plaintext', {
|
||||
s1: {
|
||||
signatures: simpleSignatures,
|
||||
activeParameters: [0],
|
||||
|
@ -355,7 +355,7 @@ export class TestState {
|
||||
}
|
||||
|
||||
// Opens a file given its 0-based index or fileName
|
||||
openFile(indexOrName: number | string, content?: string): void {
|
||||
openFile(indexOrName: number | string): void {
|
||||
const fileToOpen: FourSlashFile = this._findFile(indexOrName);
|
||||
fileToOpen.fileName = normalizeSlashes(fileToOpen.fileName);
|
||||
this.activeFile = fileToOpen;
|
||||
@ -363,6 +363,12 @@ export class TestState {
|
||||
this.program.setFileOpened(this.activeFile.fileName, 1, [{ text: fileToOpen.content }]);
|
||||
}
|
||||
|
||||
openFiles(indexOrNames: (number | string)[]): void {
|
||||
for (const indexOrName of indexOrNames) {
|
||||
this.openFile(indexOrName);
|
||||
}
|
||||
}
|
||||
|
||||
printCurrentFileState(showWhitespace: boolean, makeCaretVisible: boolean) {
|
||||
for (const file of this.testData.files) {
|
||||
const active = this.activeFile === file;
|
||||
@ -663,6 +669,7 @@ export class TestState {
|
||||
);
|
||||
}
|
||||
}
|
||||
return commandResult;
|
||||
}
|
||||
|
||||
async verifyInvokeCodeAction(
|
||||
@ -936,17 +943,21 @@ export class TestState {
|
||||
}
|
||||
}
|
||||
|
||||
verifySignature(map: {
|
||||
[marker: string]: {
|
||||
noSig?: boolean;
|
||||
signatures?: {
|
||||
label: string;
|
||||
parameters: string[];
|
||||
}[];
|
||||
activeParameters?: (number | undefined)[];
|
||||
callHasParameters?: boolean;
|
||||
};
|
||||
}): void {
|
||||
verifySignature(
|
||||
docFormat: MarkupKind,
|
||||
map: {
|
||||
[marker: string]: {
|
||||
noSig?: boolean;
|
||||
signatures?: {
|
||||
label: string;
|
||||
parameters: string[];
|
||||
documentation?: string;
|
||||
}[];
|
||||
activeParameters?: (number | undefined)[];
|
||||
callHasParameters?: boolean;
|
||||
};
|
||||
}
|
||||
): void {
|
||||
this._analyze();
|
||||
|
||||
for (const marker of this.getMarkers()) {
|
||||
@ -960,7 +971,12 @@ export class TestState {
|
||||
const expected = map[name];
|
||||
const position = this.convertOffsetToPosition(fileName, marker.position);
|
||||
|
||||
const actual = this.program.getSignatureHelpForPosition(fileName, position, CancellationToken.None);
|
||||
const actual = this.program.getSignatureHelpForPosition(
|
||||
fileName,
|
||||
position,
|
||||
docFormat,
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
if (expected.noSig) {
|
||||
assert.equal(actual, undefined);
|
||||
@ -984,6 +1000,15 @@ export class TestState {
|
||||
});
|
||||
|
||||
assert.deepEqual(actualParameters, expectedSig.parameters);
|
||||
|
||||
if (expectedSig.documentation === undefined) {
|
||||
assert.equal(sig.documentation, undefined);
|
||||
} else {
|
||||
assert.deepEqual(sig.documentation, {
|
||||
kind: docFormat,
|
||||
value: expectedSig.documentation,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
|
Loading…
Reference in New Issue
Block a user