diff --git a/packages/pyright-internal/src/analyzer/codeFlowEngine.ts b/packages/pyright-internal/src/analyzer/codeFlowEngine.ts index 96e0927e6..c3f422ce1 100644 --- a/packages/pyright-internal/src/analyzer/codeFlowEngine.ts +++ b/packages/pyright-internal/src/analyzer/codeFlowEngine.ts @@ -11,6 +11,7 @@ * TypeScript compiler. */ +import { ConsoleInterface } from '../common/console'; import { assert, fail } from '../common/debug'; import { convertOffsetToPosition } from '../common/positionUtils'; import { ArgumentCategory, ExpressionNode, ParseNode, ParseNodeType } from '../parser/parseNodes'; @@ -99,6 +100,12 @@ export interface CodeFlowEngine { createCodeFlowAnalyzer: () => CodeFlowAnalyzer; isFlowNodeReachable: (flowNode: FlowNode, sourceFlowNode?: FlowNode, ignoreNoReturn?: boolean) => boolean; narrowConstrainedTypeVar: (flowNode: FlowNode, typeVar: TypeVarType) => Type | undefined; + printControlFlowGraph: ( + flowNode: FlowNode, + reference: CodeFlowReferenceExpressionNode | undefined, + callName: string, + logger: ConsoleInterface + ) => void; } interface CodeFlowTypeCache { @@ -1557,7 +1564,8 @@ export function getCodeFlowEngine( function printControlFlowGraph( flowNode: FlowNode, reference: CodeFlowReferenceExpressionNode | undefined, - callName: string + callName: string, + logger: ConsoleInterface = console ) { let referenceText = ''; if (reference) { @@ -1566,13 +1574,14 @@ export function getCodeFlowEngine( referenceText = `${printExpression(reference)}[${pos.line + 1}:${pos.character + 1}]`; } - console.log(`${callName}@${flowNode.id}: ${referenceText || '(none)'}`); - console.log(formatControlFlowGraph(flowNode)); + logger.log(`${callName}@${flowNode.id}: ${referenceText || '(none)'}`); + logger.log(formatControlFlowGraph(flowNode)); } return { createCodeFlowAnalyzer, isFlowNodeReachable, narrowConstrainedTypeVar, + printControlFlowGraph, }; } diff --git a/packages/pyright-internal/src/analyzer/typeEvaluator.ts b/packages/pyright-internal/src/analyzer/typeEvaluator.ts index 33732cd5b..b2c222320 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluator.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluator.ts @@ -20,6 +20,7 @@ import { Commands } from '../commands/commands'; import { throwIfCancellationRequested } from '../common/cancellationUtils'; import { appendArray } from '../common/collectionUtils'; import { DiagnosticLevel } from '../common/configOptions'; +import { ConsoleInterface } from '../common/console'; import { assert, assertNever, fail } from '../common/debug'; import { AddMissingOptionalToParamAction, DiagnosticAddendum } from '../common/diagnostic'; import { DiagnosticRule } from '../common/diagnosticRules'; @@ -832,6 +833,11 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions })?.type; } + // Reads the type of the node from the cache. + function getCachedType(node: ExpressionNode): Type | undefined { + return readTypeCache(node, EvaluatorFlags.None); + } + // Determines the expected type of a specified node based on surrounding // context. For example, if it's a subexpression of an argument expression, // the associated parameter type might inform the expected type. @@ -24051,9 +24057,19 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions return (range.start.line + 1).toString(); } + function printControlFlowGraph( + flowNode: FlowNode, + reference: CodeFlowReferenceExpressionNode | undefined, + callName: string, + logger: ConsoleInterface + ) { + return codeFlowEngine.printControlFlowGraph(flowNode, reference, callName, logger); + } + const evaluatorInterface: TypeEvaluator = { runWithCancellationToken, getType, + getCachedType, getTypeOfExpression, getTypeOfAnnotation, getTypeOfClass, @@ -24135,6 +24151,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions useSpeculativeMode, setTypeForNode, checkForCancellation, + printControlFlowGraph, }; const codeFlowEngine = getCodeFlowEngine(evaluatorInterface, speculativeTypeTracker); diff --git a/packages/pyright-internal/src/analyzer/typeEvaluatorTypes.ts b/packages/pyright-internal/src/analyzer/typeEvaluatorTypes.ts index b38bd321c..cfcac3739 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluatorTypes.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluatorTypes.ts @@ -11,6 +11,7 @@ import { CancellationToken } from 'vscode-languageserver-protocol'; import { DiagnosticLevel } from '../common/configOptions'; +import { ConsoleInterface } from '../common/console'; import { Diagnostic, DiagnosticAddendum } from '../common/diagnostic'; import { TextRange } from '../common/textRange'; import { @@ -31,6 +32,7 @@ import { } from '../parser/parseNodes'; import * as DeclarationUtils from './aliasDeclarationUtils'; import { AnalyzerFileInfo } from './analyzerFileInfo'; +import { CodeFlowReferenceExpressionNode, FlowNode } from './codeFlowTypes'; import { Declaration } from './declaration'; import { SymbolWithScope } from './scope'; import { Symbol } from './symbol'; @@ -312,6 +314,7 @@ export interface TypeEvaluator { runWithCancellationToken(token: CancellationToken, callback: () => T): T; getType: (node: ExpressionNode) => Type | undefined; + getCachedType: (node: ExpressionNode) => Type | undefined; getTypeOfExpression: (node: ExpressionNode, flags?: EvaluatorFlags, expectedType?: Type) => TypeResult; getTypeOfAnnotation: (node: ExpressionNode, options?: AnnotationTypeOptions) => Type; getTypeOfClass: (node: ClassNode) => ClassTypeResult | undefined; @@ -489,4 +492,10 @@ export interface TypeEvaluator { setTypeForNode: (node: ParseNode, type?: Type, flags?: EvaluatorFlags) => void; checkForCancellation: () => void; + printControlFlowGraph: ( + flowNode: FlowNode, + reference: CodeFlowReferenceExpressionNode | undefined, + callName: string, + logger: ConsoleInterface + ) => void; } diff --git a/packages/pyright-internal/src/commands/commandController.ts b/packages/pyright-internal/src/commands/commandController.ts index e1e5419c3..c6cbc8152 100644 --- a/packages/pyright-internal/src/commands/commandController.ts +++ b/packages/pyright-internal/src/commands/commandController.ts @@ -11,6 +11,7 @@ import { CancellationToken, ExecuteCommandParams, ResponseError } from 'vscode-l import { LanguageServerInterface } from '../languageServerBase'; import { Commands } from './commands'; import { CreateTypeStubCommand } from './createTypeStub'; +import { DumpFileDebugInfoCommand } from './dumpFileDebugInfoCommand'; import { QuickActionCommand } from './quickActionCommand'; import { RestartServerCommand } from './restartServer'; @@ -22,11 +23,13 @@ export class CommandController implements ServerCommand { private _createStub: CreateTypeStubCommand; private _restartServer: RestartServerCommand; private _quickAction: QuickActionCommand; + private _dumpFileDebugInfo: DumpFileDebugInfoCommand; constructor(ls: LanguageServerInterface) { this._createStub = new CreateTypeStubCommand(ls); this._restartServer = new RestartServerCommand(ls); this._quickAction = new QuickActionCommand(ls); + this._dumpFileDebugInfo = new DumpFileDebugInfoCommand(ls); } async execute(cmdParams: ExecuteCommandParams, token: CancellationToken): Promise { @@ -44,6 +47,10 @@ export class CommandController implements ServerCommand { return this._restartServer.execute(cmdParams); } + case Commands.dumpFileDebugInfo: { + return this._dumpFileDebugInfo.execute(cmdParams, token); + } + default: { return new ResponseError(1, 'Unsupported command'); } diff --git a/packages/pyright-internal/src/commands/commands.ts b/packages/pyright-internal/src/commands/commands.ts index 2dbc7b51a..caf908d82 100644 --- a/packages/pyright-internal/src/commands/commands.ts +++ b/packages/pyright-internal/src/commands/commands.ts @@ -13,4 +13,10 @@ export const enum Commands { orderImports = 'pyright.organizeimports', addMissingOptionalToParam = 'pyright.addoptionalforparam', unusedImport = 'pyright.unusedImport', + dumpFileDebugInfo = 'pyright.dumpFileDebugInfo', + dumpTokens = 'pyright.dumpTokens', + dumpNodes = 'pyright.dumpNodes', + dumpTypes = 'pyright.dumpTypes', + dumpCachedTypes = 'pyright.dumpCachedTypes', + dumpCodeFlowGraph = 'pyright.dumpCodeFlowGraph', } diff --git a/packages/pyright-internal/src/commands/dumpFileDebugInfoCommand.ts b/packages/pyright-internal/src/commands/dumpFileDebugInfoCommand.ts new file mode 100644 index 000000000..5f0a5477d --- /dev/null +++ b/packages/pyright-internal/src/commands/dumpFileDebugInfoCommand.ts @@ -0,0 +1,1259 @@ +/* + * dumpFileDebugInfoCommand.ts + * Copyright (c) Microsoft Corporation. + * + * Dump various token/node/type info + */ + +import { CancellationToken, ExecuteCommandParams } from 'vscode-languageserver'; + +import { getFlowNode } from '../analyzer/analyzerNodeInfo'; +import { findNodeByOffset, printParseNodeType } from '../analyzer/parseTreeUtils'; +import { ParseTreeWalker } from '../analyzer/parseTreeWalker'; +import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes'; +import { + ClassType, + ClassTypeFlags, + FunctionType, + FunctionTypeFlags, + ParamSpecEntry, + TypeBase, + TypeCategory, + TypeFlags, + TypeVarDetails, + TypeVarType, + Variance, +} from '../analyzer/types'; +import { throwIfCancellationRequested } from '../common/cancellationUtils'; +import { isNumber, isString } from '../common/core'; +import { convertOffsetsToRange, convertOffsetToPosition } from '../common/positionUtils'; +import { TextRange } from '../common/textRange'; +import { TextRangeCollection } from '../common/textRangeCollection'; +import { LanguageServerInterface } from '../languageServerBase'; +import { + ArgumentCategory, + ArgumentNode, + AssertNode, + AssignmentExpressionNode, + AssignmentNode, + AugmentedAssignmentNode, + AwaitNode, + BinaryOperationNode, + BreakNode, + CallNode, + ClassNode, + ConstantNode, + ContinueNode, + DecoratorNode, + DelNode, + DictionaryExpandEntryNode, + DictionaryKeyEntryNode, + DictionaryNode, + EllipsisNode, + ErrorExpressionCategory, + ErrorNode, + ExceptNode, + ExpressionNode, + FormatStringNode, + ForNode, + FunctionAnnotationNode, + FunctionNode, + GlobalNode, + IfNode, + ImportAsNode, + ImportFromAsNode, + ImportFromNode, + ImportNode, + IndexNode, + isExpressionNode, + LambdaNode, + ListComprehensionForNode, + ListComprehensionIfNode, + ListComprehensionNode, + ListNode, + MemberAccessNode, + ModuleNameNode, + ModuleNode, + NameNode, + NonlocalNode, + NumberNode, + ParameterCategory, + ParameterNode, + ParseNode, + ParseNodeType, + PassNode, + RaiseNode, + ReturnNode, + SetNode, + SliceNode, + StatementListNode, + StringListNode, + StringNode, + SuiteNode, + TernaryNode, + TryNode, + TupleNode, + TypeAnnotationNode, + UnaryOperationNode, + UnpackNode, + WhileNode, + WithItemNode, + WithNode, + YieldFromNode, + YieldNode, +} from '../parser/parseNodes'; +import { ParseResults } from '../parser/parser'; +import { KeywordType, NewLineType, OperatorType, StringTokenFlags, Token, TokenType } from '../parser/tokenizerTypes'; +import { ServerCommand } from './commandController'; + +export class DumpFileDebugInfoCommand implements ServerCommand { + constructor(private _ls: LanguageServerInterface) {} + + async execute(params: ExecuteCommandParams, token: CancellationToken): Promise { + throwIfCancellationRequested(token); + + if (!params.arguments || params.arguments.length < 2) { + return []; + } + + const filePath = params.arguments[0] as string; + const kind = params.arguments[1]; + + const workspace = await this._ls.getWorkspaceForFile(filePath); + const parseResults = workspace.serviceInstance.getParseResult(filePath); + if (!parseResults) { + return []; + } + + const output: string[] = []; + const collectingConsole = { + info: (m: string) => { + output.push(m); + }, + log: (m: string) => { + output.push(m); + }, + error: (m: string) => { + output.push(m); + }, + warn: (m: string) => { + output.push(m); + }, + }; + + collectingConsole.info(`* Dump debug info for '${filePath}'`); + + switch (kind) { + case 'tokens': { + collectingConsole.info(`* Token info (${parseResults.tokenizerOutput.tokens.count} tokens)`); + + for (let i = 0; i < parseResults.tokenizerOutput.tokens.count; i++) { + const token = parseResults.tokenizerOutput.tokens.getItemAt(i); + collectingConsole.info( + `[${i}] ${getTokenString(filePath, token, parseResults.tokenizerOutput.lines)}` + ); + } + break; + } + case 'nodes': { + collectingConsole.info(`* Node info`); + + const dumper = new TreeDumper(filePath, parseResults.tokenizerOutput.lines); + dumper.walk(parseResults.parseTree); + + collectingConsole.info(dumper.output); + break; + } + case 'types': { + const evaluator = workspace.serviceInstance.getEvaluator(); + const start = params.arguments[2] as number; + const end = params.arguments[3] as number; + if (!evaluator || !start || !end) { + return []; + } + + collectingConsole.info(`* Type info`); + collectingConsole.info(`${getTypeEvaluatorString(filePath, evaluator, parseResults, start, end)}`); + break; + } + case 'cachedtypes': { + const evaluator = workspace.serviceInstance.getEvaluator(); + const start = params.arguments[2] as number; + const end = params.arguments[3] as number; + if (!evaluator || !start || !end) { + return []; + } + + collectingConsole.info(`* Cached Type info`); + collectingConsole.info( + `${getTypeEvaluatorString(filePath, evaluator, parseResults, start, end, true)}` + ); + break; + } + + case 'codeflowgraph': { + const evaluator = workspace.serviceInstance.getEvaluator(); + const offset = params.arguments[2] as number; + if (!evaluator || offset === undefined) { + return []; + } + const node = findNodeByOffset(parseResults.parseTree, offset); + if (!node) { + return []; + } + const flowNode = getFlowNode(node); + if (!flowNode) { + return []; + } + collectingConsole.info(`* CodeFlow Graph`); + evaluator.printControlFlowGraph(flowNode, undefined, 'Dump CodeFlowGraph', collectingConsole); + } + } + + // Print all of the output in one message so the trace log is smaller. + this._ls.console.info(output.join('\n')); + } +} + +function stringify(value: any, replacer: (this: any, key: string, value: any) => any): string { + const json = JSON.stringify(value, replacer, 2); + + // Unescape any paths so VS code shows them as clickable. + return json.replace(/\\\\/g, '\\'); +} + +function getTypeEvaluatorString( + file: string, + evaluator: TypeEvaluator, + results: ParseResults, + start: number, + end: number, + cacheOnly?: boolean +) { + const dumper = new TreeDumper(file, results.tokenizerOutput.lines); + const node = findNodeByOffset(results.parseTree, start) ?? findNodeByOffset(results.parseTree, end); + if (!node) { + return 'N/A'; + } + + const set = new Set(); + + if (node.nodeType === ParseNodeType.Name) { + switch (node.parent?.nodeType) { + case ParseNodeType.Class: { + const result = cacheOnly + ? evaluator.getCachedType(node.parent.name) + : evaluator.getTypeOfClass(node.parent as ClassNode); + if (!result) { + return 'N/A'; + } + + return stringify(result, replacer); + } + case ParseNodeType.Function: { + const result = cacheOnly + ? evaluator.getCachedType(node.parent.name) + : evaluator.getTypeOfFunction(node.parent as FunctionNode); + if (!result) { + return 'N/A'; + } + + return stringify(result, replacer); + } + } + } + + const range = TextRange.fromBounds(start, end); + const expr = getExpressionNodeWithRange(node, range); + if (!expr) { + return 'N/A'; + } + + const sb = `Expression node found at ${getTextSpanString( + expr, + results.tokenizerOutput.lines + )} from the given span ${getTextSpanString(range, results.tokenizerOutput.lines)}\r\n`; + + const result = cacheOnly ? evaluator.getCachedType(expr) : evaluator.getType(expr); + if (!result) { + return sb + 'No result'; + } + + return sb + stringify(result, replacer); + + function getExpressionNodeWithRange(node: ParseNode, range: TextRange): ExpressionNode | undefined { + // find best expression node that contains both start and end. + let current: ParseNode | undefined = node; + while (current && !TextRange.containsRange(current, range)) { + current = current.parent; + } + + if (!current) { + return undefined; + } + + while (!isExpressionNode(current!)) { + current = current!.parent; + } + + return current; + } + + function replacer(this: any, key: string, value: any) { + if (value === undefined) { + return undefined; + } + + if (!isNumber(value) && !isString(value)) { + if (set.has(value)) { + if (isClassType(value)) { + return ` class '${value.details.fullName}' typeSourceId:${value.details.typeSourceId}`; + } + + if (isFunctionType(value)) { + return ` function '${value.details.fullName}' parameter count:${value.details.parameters.length}`; + } + + if (isTypeVarType(value)) { + return ` function '${value.details.name}' scope id:${value.nameWithScope}`; + } + + return undefined; + } else { + set.add(value); + } + } + + if (isTypeBase(this) && key === 'category') { + return getTypeCategoryString(value, this); + } + + if (isTypeBase(this) && key === 'flags') { + return getTypeFlagsString(value); + } + + if (isClassDetail(this) && key === 'flags') { + return getClassTypeFlagsString(value); + } + + if (isFunctionDetail(this) && key === 'flags') { + return getFunctionTypeFlagsString(value); + } + + if (isTypeVarDetails(this) && key === 'variance') { + return getVarianceString(value); + } + + if (isParamSpecEntry(this) && key === 'category') { + return getParameterCategoryString(value); + } + + if (value.nodeType && value.id) { + dumper.visitNode(value as ParseNode); + + const output = dumper.output; + dumper.reset(); + return output; + } + + return value; + } + + function isTypeBase(type: any): boolean { + return type.category && type.flags; + } + + function isClassType(type: any): type is ClassType { + return isTypeBase(type) && type.details && isClassDetail(type.details); + } + + function isClassDetail(type: any): boolean { + return ( + type.name !== undefined && type.fullName !== undefined && type.moduleName !== undefined && type.baseClasses + ); + } + + function isFunctionType(type: any): type is FunctionType { + return isTypeBase(type) && type.details && isFunctionDetail(type.details); + } + + function isFunctionDetail(type: any): boolean { + return ( + type.name !== undefined && type.fullName !== undefined && type.moduleName !== undefined && type.parameters + ); + } + + function isTypeVarType(type: any): type is TypeVarType { + return isTypeBase(type) && type.details && isTypeVarDetails(type.details); + } + + function isTypeVarDetails(type: any): type is TypeVarDetails { + return type.name !== undefined && type.constraints && type.variance !== undefined; + } + + function isParamSpecEntry(type: any): type is ParamSpecEntry { + return type.category && type.type; + } +} + +function getVarianceString(type: Variance) { + switch (type) { + case Variance.Invariant: + return 'Invariant'; + case Variance.Covariant: + return 'Covariant'; + case Variance.Contravariant: + return 'Contravariant'; + default: + return `Unknown Value!! (${type})`; + } +} + +function getFlagEnumString(enumMap: [E, string][], enumValue: E): string { + const str: string[] = []; + enumMap.forEach((e) => { + if (enumValue & e[0]) { + str.push(e[1]); + } + }); + if (str.length === 0) { + if (enumValue === 0) { + return 'None'; + } + return ''; + } + + return str.join(','); +} + +const FunctionTypeFlagsToString: [FunctionTypeFlags, string][] = [ + [FunctionTypeFlags.AbstractMethod, 'AbstractMethod'], + [FunctionTypeFlags.Async, 'Async'], + [FunctionTypeFlags.ClassMethod, 'ClassMethod'], + [FunctionTypeFlags.ConstructorMethod, 'ConstructorMethod'], + [FunctionTypeFlags.DisableDefaultChecks, 'DisableDefaultChecks'], + [FunctionTypeFlags.Final, 'Final'], + [FunctionTypeFlags.Generator, 'Generator'], + [FunctionTypeFlags.Overloaded, 'Overloaded'], + [FunctionTypeFlags.ParamSpecValue, 'ParamSpecValue'], + [FunctionTypeFlags.PartiallyEvaluated, 'PartiallyEvaluated'], + [FunctionTypeFlags.PyTypedDefinition, 'PyTypedDefinition'], + [FunctionTypeFlags.SkipArgsKwargsCompatibilityCheck, 'SkipArgsKwargsCompatibilityCheck'], + [FunctionTypeFlags.SkipConstructorCheck, 'SkipConstructorCheck'], + [FunctionTypeFlags.StaticMethod, 'StaticMethod'], + [FunctionTypeFlags.StubDefinition, 'StubDefinition'], + [FunctionTypeFlags.SynthesizedMethod, 'SynthesizedMethod'], + [FunctionTypeFlags.UnannotatedParams, 'UnannotatedParams'], + [FunctionTypeFlags.WrapReturnTypeInAwait, 'WrapReturnTypeInAwait'], +]; + +function getFunctionTypeFlagsString(flags: FunctionTypeFlags) { + return getFlagEnumString(FunctionTypeFlagsToString, flags); +} + +const ClassTypeFlagsToString: [ClassTypeFlags, string][] = [ + [ClassTypeFlags.BuiltInClass, 'BuiltInClass'], + [ClassTypeFlags.CanOmitDictValues, 'CanOmitDictValues'], + [ClassTypeFlags.ClassProperty, 'ClassProperty'], + [ClassTypeFlags.DataClass, 'DataClass'], + [ClassTypeFlags.DataClassKeywordOnlyParams, 'DataClassKeywordOnlyParams'], + [ClassTypeFlags.DefinedInStub, 'DefinedInStub'], + [ClassTypeFlags.EnumClass, 'EnumClass'], + [ClassTypeFlags.Final, 'Final'], + [ClassTypeFlags.FrozenDataClass, 'FrozenDataClass'], + [ClassTypeFlags.GenerateDataClassSlots, 'GenerateDataClassSlots'], + [ClassTypeFlags.HasCustomClassGetItem, 'HasCustomClassGetItem'], + [ClassTypeFlags.PartiallyEvaluated, 'PartiallyEvaluated'], + [ClassTypeFlags.PropertyClass, 'PropertyClass'], + [ClassTypeFlags.ProtocolClass, 'ProtocolClass'], + [ClassTypeFlags.PseudoGenericClass, 'PseudoGenericClass'], + [ClassTypeFlags.ReadOnlyInstanceVariables, 'ReadOnlyInstanceVariables'], + [ClassTypeFlags.RuntimeCheckable, 'RuntimeCheckable'], + [ClassTypeFlags.SkipSynthesizedDataClassEq, 'SkipSynthesizedDataClassEq'], + [ClassTypeFlags.SkipSynthesizedDataClassInit, 'SkipSynthesizedDataClassInit'], + [ClassTypeFlags.SpecialBuiltIn, 'SpecialBuiltIn'], + [ClassTypeFlags.SupportsAbstractMethods, 'SupportsAbstractMethods'], + [ClassTypeFlags.SynthesizeDataClassUnsafeHash, 'SynthesizeDataClassUnsafeHash'], + [ClassTypeFlags.SynthesizedDataClassOrder, 'SynthesizedDataClassOrder'], + [ClassTypeFlags.TupleClass, 'TupleClass'], + [ClassTypeFlags.TypedDictClass, 'TypedDictClass'], + [ClassTypeFlags.TypingExtensionClass, 'TypingExtensionClass'], +]; + +function getClassTypeFlagsString(flags: ClassTypeFlags) { + return getFlagEnumString(ClassTypeFlagsToString, flags); +} + +function getTypeFlagsString(flags: TypeFlags) { + const str = []; + + if (flags & TypeFlags.Instantiable) { + str.push('Instantiable'); + } + + if (flags & TypeFlags.Instance) { + str.push('Instance'); + } + + if (str.length === 0) return 'None'; + + return str.join(','); +} + +function getTypeCategoryString(typeCategory: TypeCategory, type: any) { + switch (typeCategory) { + case TypeCategory.Unbound: + return 'Unbound'; + case TypeCategory.Unknown: + return 'Unknown'; + case TypeCategory.Any: + return 'Any'; + case TypeCategory.None: + return 'None'; + case TypeCategory.Never: + return 'Never'; + case TypeCategory.Function: + return 'Function'; + case TypeCategory.OverloadedFunction: + return 'OverloadedFunction'; + case TypeCategory.Class: + if (TypeBase.isInstantiable(type)) { + return 'Class'; + } else { + return 'Object'; + } + case TypeCategory.Module: + return 'Module'; + case TypeCategory.Union: + return 'Union'; + case TypeCategory.TypeVar: + return 'TypeVar'; + default: + return `Unknown Value!! (${typeCategory})`; + } +} + +class TreeDumper extends ParseTreeWalker { + private _indentation = ''; + private _output = ''; + + constructor(private _file: string, private _lines: TextRangeCollection) { + super(); + } + + get output(): string { + return this._output; + } + + override walk(node: ParseNode): void { + const childrenToWalk = this.visitNode(node); + if (childrenToWalk.length > 0) { + this._indentation += ' '; + this.walkMultiple(childrenToWalk); + this._indentation = this._indentation.substr(0, this._indentation.length - 2); + } + } + + private _log(value: string) { + this._output += `${this._indentation}${value}\r\n`; + } + + private _getPrefix(node: ParseNode) { + const pos = convertOffsetToPosition(node.start, this._lines); + // VS code's output window expects 1 based values, print the line/char with 1 based. + return `[${node.id}] '${this._file}:${pos.line + 1}:${pos.character + 1}' => ${printParseNodeType( + node.nodeType + )} ${getTextSpanString(node, this._lines)} =>`; + } + + reset() { + this._indentation = ''; + this._output = ''; + } + + override visitArgument(node: ArgumentNode) { + this._log(`${this._getPrefix(node)} ${getArgumentCategoryString(node.argumentCategory)}`); + return true; + } + + override visitAssert(node: AssertNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitAssignment(node: AssignmentNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitAssignmentExpression(node: AssignmentExpressionNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitAugmentedAssignment(node: AugmentedAssignmentNode) { + this._log(`${this._getPrefix(node)} ${getOperatorTypeString(node.operator)}`); + return true; + } + + override visitAwait(node: AwaitNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitBinaryOperation(node: BinaryOperationNode) { + this._log( + `${this._getPrefix(node)} ${getTokenString( + this._file, + node.operatorToken, + this._lines + )} ${getOperatorTypeString(node.operator)}} parenthesized:(${node.parenthesized})` + ); + return true; + } + + override visitBreak(node: BreakNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitCall(node: CallNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitClass(node: ClassNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitTernary(node: TernaryNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitContinue(node: ContinueNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitConstant(node: ConstantNode) { + this._log(`${this._getPrefix(node)} ${getKeywordTypeString(node.constType)}`); + return true; + } + + override visitDecorator(node: DecoratorNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitDel(node: DelNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitDictionary(node: DictionaryNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitDictionaryKeyEntry(node: DictionaryKeyEntryNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitDictionaryExpandEntry(node: DictionaryExpandEntryNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitError(node: ErrorNode) { + this._log(`${this._getPrefix(node)} ${getErrorExpressionCategoryString(node.category)}`); + return true; + } + + override visitEllipsis(node: EllipsisNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitIf(node: IfNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitImport(node: ImportNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitImportAs(node: ImportAsNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitImportFrom(node: ImportFromNode) { + this._log( + `${this._getPrefix(node)} wildcard import:(${node.isWildcardImport}) paren:(${ + node.usesParens + }) wildcard token:(${ + node.wildcardToken ? getTokenString(this._file, node.wildcardToken, this._lines) : 'N/A' + }) missing import keyword:(${node.missingImportKeyword})` + ); + return true; + } + + override visitImportFromAs(node: ImportFromAsNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitIndex(node: IndexNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitExcept(node: ExceptNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitFor(node: ForNode) { + this._log(`${this._getPrefix(node)} async:(${node.isAsync})`); + return true; + } + + override visitFormatString(node: FormatStringNode) { + this._log( + `${this._getPrefix(node)} ${getTokenString(this._file, node.token, this._lines)} ${ + node.value + } unescape errors:(${node.hasUnescapeErrors})` + ); + return true; + } + + override visitFunction(node: FunctionNode) { + this._log(`${this._getPrefix(node)} async:(${node.isAsync})`); + return true; + } + + override visitFunctionAnnotation(node: FunctionAnnotationNode) { + this._log(`${this._getPrefix(node)} ellipsis:(${node.isParamListEllipsis})`); + return true; + } + + override visitGlobal(node: GlobalNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitLambda(node: LambdaNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitList(node: ListNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitListComprehension(node: ListComprehensionNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitListComprehensionFor(node: ListComprehensionForNode) { + this._log(`${this._getPrefix(node)} async:(${node.isAsync})`); + return true; + } + + override visitListComprehensionIf(node: ListComprehensionIfNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitMemberAccess(node: MemberAccessNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitModule(node: ModuleNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitModuleName(node: ModuleNameNode) { + this._log(`${this._getPrefix(node)} leading dots:(${node.leadingDots}) trailing dot:(${node.hasTrailingDot})`); + return true; + } + + override visitName(node: NameNode) { + this._log(`${this._getPrefix(node)} ${getTokenString(this._file, node.token, this._lines)} ${node.value}`); + return true; + } + + override visitNonlocal(node: NonlocalNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitNumber(node: NumberNode) { + this._log(`${this._getPrefix(node)} ${node.value} int:(${node.isInteger}) imaginary:(${node.isImaginary})`); + return true; + } + + override visitParameter(node: ParameterNode) { + this._log(`${this._getPrefix(node)} ${getParameterCategoryString(node.category)}`); + return true; + } + + override visitPass(node: PassNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitRaise(node: RaiseNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitReturn(node: ReturnNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitSet(node: SetNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitSlice(node: SliceNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitStatementList(node: StatementListNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitString(node: StringNode) { + this._log( + `${this._getPrefix(node)} ${getTokenString(this._file, node.token, this._lines)} ${ + node.value + } unescape errors:(${node.hasUnescapeErrors})` + ); + return true; + } + + override visitStringList(node: StringListNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitSuite(node: SuiteNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitTuple(node: TupleNode) { + this._log(`${this._getPrefix(node)} paren:(${node.enclosedInParens})`); + return true; + } + + override visitTry(node: TryNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitTypeAnnotation(node: TypeAnnotationNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitUnaryOperation(node: UnaryOperationNode) { + this._log( + `${this._getPrefix(node)} ${getTokenString( + this._file, + node.operatorToken, + this._lines + )} ${getOperatorTypeString(node.operator)}` + ); + return true; + } + + override visitUnpack(node: UnpackNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitWhile(node: WhileNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitWith(node: WithNode) { + this._log(`${this._getPrefix(node)} async:(${node.isAsync})`); + return true; + } + + override visitWithItem(node: WithItemNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitYield(node: YieldNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } + + override visitYieldFrom(node: YieldFromNode) { + this._log(`${this._getPrefix(node)}`); + return true; + } +} + +function getParameterCategoryString(type: ParameterCategory) { + switch (type) { + case ParameterCategory.Simple: + return 'Simple'; + case ParameterCategory.VarArgList: + return 'VarArgList'; + case ParameterCategory.VarArgDictionary: + return 'VarArgDictionary'; + } +} + +function getArgumentCategoryString(type: ArgumentCategory) { + switch (type) { + case ArgumentCategory.Simple: + return 'Simple'; + case ArgumentCategory.UnpackedList: + return 'UnpackedList'; + case ArgumentCategory.UnpackedDictionary: + return 'UnpackedDictionary'; + default: + return `Unknown Value!! (${type})`; + } +} + +function getErrorExpressionCategoryString(type: ErrorExpressionCategory) { + switch (type) { + case ErrorExpressionCategory.MissingIn: + return 'MissingIn'; + case ErrorExpressionCategory.MissingElse: + return 'MissingElse'; + case ErrorExpressionCategory.MissingExpression: + return 'MissingExpression'; + case ErrorExpressionCategory.MissingIndexOrSlice: + return 'MissingIndexOrSlice'; + case ErrorExpressionCategory.MissingDecoratorCallName: + return 'MissingDecoratorCallName'; + case ErrorExpressionCategory.MissingCallCloseParen: + return 'MissingCallCloseParen'; + case ErrorExpressionCategory.MissingIndexCloseBracket: + return 'MissingIndexCloseBracket'; + case ErrorExpressionCategory.MissingMemberAccessName: + return 'MissingMemberAccessName'; + case ErrorExpressionCategory.MissingTupleCloseParen: + return 'MissingTupleCloseParen'; + case ErrorExpressionCategory.MissingListCloseBracket: + return 'MissingListCloseBracket'; + case ErrorExpressionCategory.MissingFunctionParameterList: + return 'MissingFunctionParameterList'; + default: + return `Unknown Value!! (${type})`; + } +} + +function getTokenString(file: string, token: Token, lines: TextRangeCollection) { + const pos = convertOffsetToPosition(token.start, lines); + let str = `'${file}:${pos.line + 1}:${pos.character + 1}' (`; + str += getTokenTypeString(token.type); + str += getNewLineInfo(token); + str += getOperatorInfo(token); + str += getKeywordInfo(token); + str += getStringTokenFlags(token); + str += `, ${getTextSpanString(token, lines)}`; + str += ') '; + str += JSON.stringify(token); + + return str; + + function getNewLineInfo(t: any) { + return t.newLineType ? `, ${getNewLineTypeString(t.newLineType)}` : ''; + } + + function getOperatorInfo(t: any) { + return t.operatorType ? `, ${getOperatorTypeString(t.operatorType)}` : ''; + } + + function getKeywordInfo(t: any) { + return t.keywordType ? `, ${getKeywordTypeString(t.keywordType)}` : ''; + } + + function getStringTokenFlags(t: any) { + return t.flags ? `, [${getStringTokenFlagsString(t.flags)}]` : ''; + } +} + +function getTextSpanString(span: TextRange, lines: TextRangeCollection) { + const range = convertOffsetsToRange(span.start, TextRange.getEnd(span), lines); + return `(${range.start.line},${range.start.character})-(${range.end.line},${range.end.character})`; +} + +function getTokenTypeString(type: TokenType) { + switch (type) { + case TokenType.Invalid: + return 'Invalid'; + case TokenType.EndOfStream: + return 'EndOfStream'; + case TokenType.NewLine: + return 'NewLine'; + case TokenType.Indent: + return 'Indent'; + case TokenType.Dedent: + return 'Dedent'; + case TokenType.String: + return 'String'; + case TokenType.Number: + return 'Number'; + case TokenType.Identifier: + return 'Identifier'; + case TokenType.Keyword: + return 'Keyword'; + case TokenType.Operator: + return 'Operator'; + case TokenType.Colon: + return 'Colon'; + case TokenType.Semicolon: + return 'Semicolon'; + case TokenType.Comma: + return 'Comma'; + case TokenType.OpenParenthesis: + return 'OpenParenthesis'; + case TokenType.CloseParenthesis: + return 'CloseParenthesis'; + case TokenType.OpenBracket: + return 'OpenBracket'; + case TokenType.CloseBracket: + return 'CloseBracket'; + case TokenType.OpenCurlyBrace: + return 'OpenCurlyBrace'; + case TokenType.CloseCurlyBrace: + return 'CloseCurlyBrace'; + case TokenType.Ellipsis: + return 'Ellipsis'; + case TokenType.Dot: + return 'Dot'; + case TokenType.Arrow: + return 'Arrow'; + case TokenType.Backtick: + return 'Backtick'; + default: + return `Unknown Value!! (${type})`; + } +} + +function getNewLineTypeString(type: NewLineType) { + switch (type) { + case NewLineType.CarriageReturn: + return 'CarriageReturn'; + case NewLineType.LineFeed: + return 'LineFeed'; + case NewLineType.CarriageReturnLineFeed: + return 'CarriageReturnLineFeed'; + case NewLineType.Implied: + return 'Implied'; + default: + return `Unknown Value!! (${type})`; + } +} + +function getOperatorTypeString(type: OperatorType) { + switch (type) { + case OperatorType.Add: + return 'Add'; + case OperatorType.AddEqual: + return 'AddEqual'; + case OperatorType.Assign: + return 'Assign'; + case OperatorType.BitwiseAnd: + return 'BitwiseAnd'; + case OperatorType.BitwiseAndEqual: + return 'BitwiseAndEqual'; + case OperatorType.BitwiseInvert: + return 'BitwiseInvert'; + case OperatorType.BitwiseOr: + return 'BitwiseOr'; + case OperatorType.BitwiseOrEqual: + return 'BitwiseOrEqual'; + case OperatorType.BitwiseXor: + return 'BitwiseXor'; + case OperatorType.BitwiseXorEqual: + return 'BitwiseXorEqual'; + case OperatorType.Divide: + return 'Divide'; + case OperatorType.DivideEqual: + return 'DivideEqual'; + case OperatorType.Equals: + return 'Equals'; + case OperatorType.FloorDivide: + return 'FloorDivide'; + case OperatorType.FloorDivideEqual: + return 'FloorDivideEqual'; + case OperatorType.GreaterThan: + return 'GreaterThan'; + case OperatorType.GreaterThanOrEqual: + return 'GreaterThanOrEqual'; + case OperatorType.LeftShift: + return 'LeftShift'; + case OperatorType.LeftShiftEqual: + return 'LeftShiftEqual'; + case OperatorType.LessOrGreaterThan: + return 'LessOrGreaterThan'; + case OperatorType.LessThan: + return 'LessThan'; + case OperatorType.LessThanOrEqual: + return 'LessThanOrEqual'; + case OperatorType.MatrixMultiply: + return 'MatrixMultiply'; + case OperatorType.MatrixMultiplyEqual: + return 'MatrixMultiplyEqual'; + case OperatorType.Mod: + return 'Mod'; + case OperatorType.ModEqual: + return 'ModEqual'; + case OperatorType.Multiply: + return 'Multiply'; + case OperatorType.MultiplyEqual: + return 'MultiplyEqual'; + case OperatorType.NotEquals: + return 'NotEquals'; + case OperatorType.Power: + return 'Power'; + case OperatorType.PowerEqual: + return 'PowerEqual'; + case OperatorType.RightShift: + return 'RightShift'; + case OperatorType.RightShiftEqual: + return 'RightShiftEqual'; + case OperatorType.Subtract: + return 'Subtract'; + case OperatorType.SubtractEqual: + return 'SubtractEqual'; + case OperatorType.Walrus: + return 'Walrus'; + case OperatorType.And: + return 'And'; + case OperatorType.Or: + return 'Or'; + case OperatorType.Not: + return 'Not'; + case OperatorType.Is: + return 'Is'; + case OperatorType.IsNot: + return 'IsNot'; + case OperatorType.In: + return 'In'; + case OperatorType.NotIn: + return 'NotIn'; + default: + return `Unknown Value!! (${type})`; + } +} + +function getKeywordTypeString(type: KeywordType) { + switch (type) { + case KeywordType.And: + return 'And'; + case KeywordType.As: + return 'As'; + case KeywordType.Assert: + return 'Assert'; + case KeywordType.Async: + return 'Async'; + case KeywordType.Await: + return 'Await'; + case KeywordType.Break: + return 'Break'; + case KeywordType.Class: + return 'Class'; + case KeywordType.Continue: + return 'Continue'; + case KeywordType.Debug: + return 'Debug'; + case KeywordType.Def: + return 'Def'; + case KeywordType.Del: + return 'Del'; + case KeywordType.Elif: + return 'Elif'; + case KeywordType.Else: + return 'Else'; + case KeywordType.Except: + return 'Except'; + case KeywordType.False: + return 'False'; + case KeywordType.Finally: + return 'Finally'; + case KeywordType.For: + return 'For'; + case KeywordType.From: + return 'From'; + case KeywordType.Global: + return 'Global'; + case KeywordType.If: + return 'If'; + case KeywordType.Import: + return 'Import'; + case KeywordType.In: + return 'In'; + case KeywordType.Is: + return 'Is'; + case KeywordType.Lambda: + return 'Lambda'; + case KeywordType.None: + return 'None'; + case KeywordType.Nonlocal: + return 'Nonlocal'; + case KeywordType.Not: + return 'Not'; + case KeywordType.Or: + return 'Or'; + case KeywordType.Pass: + return 'Pass'; + case KeywordType.Raise: + return 'Raise'; + case KeywordType.Return: + return 'Return'; + case KeywordType.True: + return 'True'; + case KeywordType.Try: + return 'Try'; + case KeywordType.While: + return 'While'; + case KeywordType.With: + return 'With'; + case KeywordType.Yield: + return 'Yield'; + default: + return `Unknown Value!! (${type})`; + } +} + +const StringTokenFlagsStrings: [StringTokenFlags, string][] = [ + [StringTokenFlags.Bytes, 'Bytes'], + [StringTokenFlags.DoubleQuote, 'DoubleQuote'], + [StringTokenFlags.ExceedsMaxSize, 'ExceedsMaxSize'], + [StringTokenFlags.Format, 'Format'], + [StringTokenFlags.Raw, 'Raw'], + [StringTokenFlags.SingleQuote, 'SingleQuote'], + [StringTokenFlags.Triplicate, 'Triplicate'], + [StringTokenFlags.Unicode, 'Unicode'], + [StringTokenFlags.Unterminated, 'Unterminated'], +]; + +function getStringTokenFlagsString(flags: StringTokenFlags) { + return getFlagEnumString(StringTokenFlagsStrings, flags); +} diff --git a/packages/vscode-pyright/package.json b/packages/vscode-pyright/package.json index 325636bbc..379fbdbde 100644 --- a/packages/vscode-pyright/package.json +++ b/packages/vscode-pyright/package.json @@ -51,7 +51,38 @@ "command": "pyright.restartserver", "title": "Restart Server", "category": "Pyright" + }, + { + "command": "pyright.dumpTokens", + "title": "Dump token streams ...", + "category": "Pyright", + "enablement": "editorLangId == python && pyright.development" + }, + { + "command": "pyright.dumpNodes", + "title": "Dump parse tree ...", + "category": "Pyright", + "enablement": "editorLangId == python && pyright.development" + }, + { + "command": "pyright.dumpTypes", + "title": "Dump type info ...", + "category": "Pyright", + "enablement": "editorLangId == python && pyright.development" + }, + { + "command": "pyright.dumpCachedTypes", + "title": "Dump cached type info ...", + "category": "Pyright", + "enablement": "editorLangId == python && pyright.development" + }, + { + "command": "pyright.dumpCodeFlowGraph", + "title": "Dump code flow graph for node ...", + "category": "Pyright", + "enablement": "editorLangId == python && pyright.development" } + ], "menus": { "editor/context": [ @@ -61,7 +92,30 @@ "group": "Pyright", "when": "editorLangId == python" } + ], + "commandPalette": [ + { + "command": "pyright.dumpTokens", + "when": "editorLangId == python && pyright.development" + }, + { + "command": "pyright.dumpNodes", + "when": "editorLangId == python && pyright.development" + }, + { + "command": "pyright.dumpTypes", + "when": "editorLangId == python && pyright.development" + }, + { + "command": "pyright.dumpCachedTypes", + "when": "editorLangId == python && pyright.development" + }, + { + "command": "pyright.dumpCodeFlowGraph", + "when": "editorLangId == python && pyright.development" + } ] + }, "configuration": { "type": "object", diff --git a/packages/vscode-pyright/src/extension.ts b/packages/vscode-pyright/src/extension.ts index 4aae97d50..d10422cfa 100644 --- a/packages/vscode-pyright/src/extension.ts +++ b/packages/vscode-pyright/src/extension.ts @@ -13,6 +13,7 @@ import * as path from 'path'; import { commands, ExtensionContext, + ExtensionMode, extensions, OutputChannel, Position, @@ -210,6 +211,81 @@ export async function activate(context: ExtensionContext) { ); }); + // Register the debug only commands when running under the debugger. + if (context.extensionMode === ExtensionMode.Development) { + // Create a 'when' context for development. + commands.executeCommand('setContext', 'pyright.development', true); + + // Register the commands that only work when in development mode. + context.subscriptions.push( + commands.registerCommand(Commands.dumpTokens, () => { + const fileName = window.activeTextEditor?.document.fileName; + if (fileName) { + client.sendRequest('workspace/executeCommand', { + command: Commands.dumpFileDebugInfo, + arguments: [fileName, 'tokens'], + }); + } + }) + ); + + context.subscriptions.push( + commands.registerCommand(Commands.dumpNodes, () => { + const fileName = window.activeTextEditor?.document.fileName; + if (fileName) { + client.sendRequest('workspace/executeCommand', { + command: Commands.dumpFileDebugInfo, + arguments: [fileName, 'nodes'], + }); + } + }) + ); + + context.subscriptions.push( + commands.registerCommand(Commands.dumpTypes, () => { + const fileName = window.activeTextEditor?.document.fileName; + if (fileName) { + const start = window.activeTextEditor!.selection.start; + const end = window.activeTextEditor!.selection.end; + const startOffset = window.activeTextEditor!.document.offsetAt(start); + const endOffset = window.activeTextEditor!.document.offsetAt(end); + client.sendRequest('workspace/executeCommand', { + command: Commands.dumpFileDebugInfo, + arguments: [fileName, 'types', startOffset, endOffset], + }); + } + }) + ); + context.subscriptions.push( + commands.registerCommand(Commands.dumpCachedTypes, () => { + const fileName = window.activeTextEditor?.document.fileName; + if (fileName) { + const start = window.activeTextEditor!.selection.start; + const end = window.activeTextEditor!.selection.end; + const startOffset = window.activeTextEditor!.document.offsetAt(start); + const endOffset = window.activeTextEditor!.document.offsetAt(end); + client.sendRequest('workspace/executeCommand', { + command: Commands.dumpFileDebugInfo, + arguments: [fileName, 'cachedtypes', startOffset, endOffset], + }); + } + }) + ); + context.subscriptions.push( + commands.registerCommand(Commands.dumpCodeFlowGraph, () => { + const fileName = window.activeTextEditor?.document.fileName; + if (fileName) { + const start = window.activeTextEditor!.selection.start; + const startOffset = window.activeTextEditor!.document.offsetAt(start); + client.sendRequest('workspace/executeCommand', { + command: Commands.dumpFileDebugInfo, + arguments: [fileName, 'codeflowgraph', startOffset], + }); + } + }) + ); + } + await client.start(); }