diff --git a/packages/vipeye-internal/src/analyzer/typeEvaluator.ts b/packages/vipeye-internal/src/analyzer/typeEvaluator.ts index 3097d40c..97aa70a7 100644 --- a/packages/vipeye-internal/src/analyzer/typeEvaluator.ts +++ b/packages/vipeye-internal/src/analyzer/typeEvaluator.ts @@ -6186,12 +6186,58 @@ export function createTypeEvaluator( ); } + // Viper 扩展:记录源码中书写的限定路径(如 `ast.AST`),供类型展示保留完整 + // module 路径(见 typePrinter 的 ViperSourceQualifiedNames)。 + typeResult.type = addViperDisplayName(node, typeResult.type); + // Cache the type information in the member name node. writeTypeCache(node.d.member, typeResult, flags); return typeResult; } + // Viper 扩展:把成员访问链在源码中书写的完整路径(如 `ast.AST`、`memhub.MemBuddy`) + // 记到类型上,仅供展示使用(ClassDetailsPriv.displayName,不参与任何类型判定)。 + // 只处理纯点号路径(Name/MemberAccess 组成,不含调用/下标),且结果必须是类类型; + // 其它情况原样返回。 + function addViperDisplayName(node: MemberAccessNode, type: Type): Type { + if (!isClassInstance(type) && !isInstantiableClass(type)) { + return type; + } + + if (!IsViperDialect(AnalyzerNodeInfo.getFileInfo(node).executionEnvironment.dialect)) { + return type; + } + + const path = getDottedNamePath(node); + if (!path) { + return type; + } + + return ClassType.cloneForDisplayName(type, path); + } + + // 把 `a.b.c` 形式的访问链还原为源码路径文本;链上出现非 Name/MemberAccess + // 节点(调用、下标等)时返回 undefined。 + function getDottedNamePath(node: ExpressionNode): string | undefined { + const nameParts: string[] = []; + let curNode: ExpressionNode = node; + + while (true) { + if (curNode.nodeType === ParseNodeType.MemberAccess) { + nameParts.push(curNode.d.member.d.value); + curNode = curNode.d.leftExpr; + } else if (curNode.nodeType === ParseNodeType.Name) { + nameParts.push(curNode.d.value); + break; + } else { + return undefined; + } + } + + return nameParts.reverse().join('.'); + } + function getTypeOfMemberAccessWithBaseType( node: MemberAccessNode, baseTypeResult: TypeResult, @@ -30865,8 +30911,15 @@ export function createTypeEvaluator( return TypePrinter.printObjectTypeForClass(type, evaluatorOptions.printTypeFlags, getEffectiveReturnType); } - function printFunctionParts(type: FunctionType, extraFlags?: TypePrinter.PrintTypeFlags): [string[], string] { - const flags = extraFlags ? evaluatorOptions.printTypeFlags | extraFlags : evaluatorOptions.printTypeFlags; + function printFunctionParts( + type: FunctionType, + extraFlags?: TypePrinter.PrintTypeFlags, + options?: PrintTypeOptions + ): [string[], string] { + let flags = extraFlags ? evaluatorOptions.printTypeFlags | extraFlags : evaluatorOptions.printTypeFlags; + if (options?.sourceQualifiedNames) { + flags |= TypePrinter.PrintTypeFlags.ViperSourceQualifiedNames; + } return TypePrinter.printFunctionParts(type, flags, getEffectiveReturnType); } @@ -30918,6 +30971,9 @@ export function createTypeEvaluator( if (options?.useFullyQualifiedNames) { flags |= TypePrinter.PrintTypeFlags.UseFullyQualifiedNames; } + if (options?.sourceQualifiedNames) { + flags |= TypePrinter.PrintTypeFlags.ViperSourceQualifiedNames; + } if (options?.disablePep604) { flags &= ~TypePrinter.PrintTypeFlags.PEP604; } diff --git a/packages/vipeye-internal/src/analyzer/typeEvaluatorTypes.ts b/packages/vipeye-internal/src/analyzer/typeEvaluatorTypes.ts index 55b2a234..34990475 100644 --- a/packages/vipeye-internal/src/analyzer/typeEvaluatorTypes.ts +++ b/packages/vipeye-internal/src/analyzer/typeEvaluatorTypes.ts @@ -527,6 +527,11 @@ export enum Reachability { export interface PrintTypeOptions { expandTypeAlias?: boolean; enforcePythonSyntax?: boolean; + // Viper 方言:类名保留源码中书写时的完整 module 路径(如 `ast.AST`)。 + // 与 useFullyQualifiedNames 不同:后者用类型声明所在的 fullName + // (如 `ast.base.AST`、容器 `_list.list`),这里用源码里实际书写的路径, + // 未写限定路径的裸名(`list`/`str`)保持短名。 + sourceQualifiedNames?: boolean; useFullyQualifiedNames?: boolean; useTypingUnpack?: boolean; printUnknownWithAny?: boolean; @@ -878,7 +883,11 @@ export interface TypeEvaluator { printType: (type: Type, options?: PrintTypeOptions) => string; printSrcDestTypes: (srcType: Type, destType: Type) => { sourceType: string; destType: string }; - printFunctionParts: (type: FunctionType, extraFlags?: PrintTypeFlags) => [string[], string]; + printFunctionParts: ( + type: FunctionType, + extraFlags?: PrintTypeFlags, + options?: PrintTypeOptions + ) => [string[], string]; getTypeCacheEntryCount: () => number; disposeEvaluator: () => void; diff --git a/packages/vipeye-internal/src/analyzer/typePrinter.ts b/packages/vipeye-internal/src/analyzer/typePrinter.ts index 7c5859af..b6860bdd 100644 --- a/packages/vipeye-internal/src/analyzer/typePrinter.ts +++ b/packages/vipeye-internal/src/analyzer/typePrinter.ts @@ -102,10 +102,36 @@ export const enum PrintTypeFlags { // Viper 方言:将 NoneType 显示为 CVoid(C 语义 void)。 ViperDialect = 1 << 14, + + // Viper 方言:类名保留源码中书写时的完整 module 路径(如 `ast.AST`、`memhub.MemBuddy`)。 + // Viper 源码注解习惯写完整 module 路径,而类型的 fullName 是「声明所在模块」 + // (如 `ast.base.AST`、容器类 `_list.list`),两者并不一致,故只能按源码书写形式展示: + // 写了限定路径的保留路径,写裸名的(`list`/`str`)仍显示短名。 + ViperSourceQualifiedNames = 1 << 15, } export type FunctionReturnTypeCallback = (type: FunctionType) => Type; +// 计算类的显示名(不含 aliasName 与 rawptr 等特殊覆盖)。 +// 该函数必须被 printObjectTypeForClassInternal 与 UniqueNameMap 同时使用, +// 否则「名字唯一性判定」与「实际打印名」不一致会导致名字被错误地降级/保留。 +function getPrintableClassName(type: ClassType, printTypeFlags: PrintTypeFlags): string { + // Viper 方言(展示模式):按源码书写的完整 module 路径显示, + // 未写限定路径的裸名(`list`/`str` 等)保持短名。 + if ( + (printTypeFlags & PrintTypeFlags.ViperSourceQualifiedNames) !== 0 && + (printTypeFlags & PrintTypeFlags.ViperDialect) !== 0 + ) { + return type.priv.displayName ?? type.shared.name; + } + + if ((printTypeFlags & PrintTypeFlags.UseFullyQualifiedNames) !== 0) { + return type.shared.fullName; + } + + return type.shared.name; +} + export function printType( type: Type, printTypeFlags: PrintTypeFlags, @@ -1010,8 +1036,7 @@ function printObjectTypeForClassInternal( ): string { let objName = type.priv.aliasName; if (!objName) { - objName = - (printTypeFlags & PrintTypeFlags.UseFullyQualifiedNames) !== 0 ? type.shared.fullName : type.shared.name; + objName = getPrintableClassName(type, printTypeFlags); } // Special-case NoneType to convert it to None. @@ -1523,10 +1548,7 @@ class UniqueNameMap { let className = type.priv.aliasName; if (!className) { - className = - (this._printTypeFlags & PrintTypeFlags.UseFullyQualifiedNames) !== 0 - ? type.shared.fullName - : type.shared.name; + className = getPrintableClassName(type, this._printTypeFlags); } this._addIfUnique(className, type); diff --git a/packages/vipeye-internal/src/analyzer/types.ts b/packages/vipeye-internal/src/analyzer/types.ts index 373dce43..a34a9736 100644 --- a/packages/vipeye-internal/src/analyzer/types.ts +++ b/packages/vipeye-internal/src/analyzer/types.ts @@ -810,6 +810,11 @@ export interface ClassDetailsPriv { // name. aliasName?: string | undefined; + // Viper 扩展:该类型在源码中被引用时书写的完整路径(如 `ast.AST`、`memhub.MemBuddy`)。 + // Viper 源码注解习惯写完整 module 路径,类型展示需要保留它而不是退化为短名。 + // 该字段**仅用于展示**(typePrinter 读取),不参与任何类型判定/兼容性比较。 + displayName?: string | undefined; + // Used for "narrowing" of typed dicts where some entries // that are not required have been confirmed to be present // through the use of a guard expression. @@ -985,6 +990,17 @@ export namespace ClassType { return newClassType; } + // Viper 扩展:把「源码中书写类型时使用的完整路径」记到类型上,仅供展示用。 + // 已经记录过同一路径时直接返回原类型,避免无意义的克隆。 + export function cloneForDisplayName(classType: ClassType, displayName: string): ClassType { + if (classType.priv.displayName === displayName) { + return classType; + } + const newClassType = TypeBase.cloneType(classType); + newClassType.priv.displayName = displayName; + return newClassType; + } + export function cloneForNarrowedTypedDictEntries( classType: ClassType, narrowedEntries?: Map diff --git a/packages/vipeye-internal/src/languageService/completionProvider.ts b/packages/vipeye-internal/src/languageService/completionProvider.ts index 12090d08..743b2afb 100644 --- a/packages/vipeye-internal/src/languageService/completionProvider.ts +++ b/packages/vipeye-internal/src/languageService/completionProvider.ts @@ -1146,6 +1146,11 @@ export class CompletionProvider { } if (detail?.typeDetail) { + // 围栏语言保持 `python`:宿主渲染时按 languageService.getLanguageIdByLanguageName() + // 解析围栏语言并取该语言的 TM tokenization。本扩展的 `viper` 语法虽能被取到, + // 但只有注释/字符串/控制流关键字/数字等通用 scope 能命中主题规则 + // (`def`/`class` 的 storage.type.*.viper 与按 Python 精调的主题规则不匹配, + // 裸类型名语法里没有规则),着色不完整;`python`(MagicPython)齐全,故用它。 markdownString += '```python\n' + detail.typeDetail + '\n```\n'; } diff --git a/packages/vipeye-internal/src/languageService/completionProviderUtils.ts b/packages/vipeye-internal/src/languageService/completionProviderUtils.ts index 87e0b5ce..43328ec6 100644 --- a/packages/vipeye-internal/src/languageService/completionProviderUtils.ts +++ b/packages/vipeye-internal/src/languageService/completionProviderUtils.ts @@ -160,6 +160,11 @@ export function getCompletionItemDocumentation( declaration: Declaration | undefined ): MarkupContent | undefined { if (markupKind === MarkupKind.Markdown) { + // 围栏语言保持 `python`:宿主渲染时按 languageService.getLanguageIdByLanguageName() + // 解析围栏语言并取该语言的 TM tokenization。本扩展的 `viper` 语法虽能被取到, + // 但只有注释/字符串/控制流关键字/数字等通用 scope 能命中主题规则 + // (`def`/`class` 的 storage.type.*.viper 与按 Python 精调的主题规则不匹配, + // 裸类型名语法里没有规则),着色不完整;`python`(MagicPython)齐全,故用它。 let markdownString = '```python\n' + typeDetail + '\n```\n'; if (documentation) { diff --git a/packages/vipeye-internal/src/languageService/hoverProvider.ts b/packages/vipeye-internal/src/languageService/hoverProvider.ts index ca76aeea..c760cbcb 100644 --- a/packages/vipeye-internal/src/languageService/hoverProvider.ts +++ b/packages/vipeye-internal/src/languageService/hoverProvider.ts @@ -69,6 +69,7 @@ import { } from './tooltipUtils'; export interface HoverTextPart { + // 该部分按代码块渲染(markdown 下加代码围栏,见 convertHoverResults)。 python?: boolean; text: string; } @@ -87,6 +88,16 @@ export function convertHoverResults(hoverResults: HoverResults | null, format: M .map((part) => { if (part.python) { if (format === MarkupKind.Markdown) { + // 围栏语言保持 `python`:hover 代码块由宿主 markdown 渲染器着色,它先用 + // languageService.getLanguageIdByLanguageName() 把围栏语言名解析成语言 id, + // 再取该语言的 TM tokenization,取不到才整块退化为纯文本。 + // 本扩展贡献的 `viper` 语法在该路径下能取到 tokenization,但只有通用 scope + // (comment/string/keyword.control.flow/constant.numeric)能命中主题规则; + // `def`/`class` 用的 storage.type.*.viper 匹配不到主题里按 Python 精调的 + // 规则,裸类型名(int/str)语法里也没有规则 —— 实测 ```viper 只部分着色, + // 而 ```python 走 MagicPython,关键字/类型/装饰器齐全,故用它。 + // Viper 语法本就是 CPython 派生,Python 语法对关键字/装饰器/字符串/数字的 + // 着色是正确的。 return '```python\n' + part.text + '\n```\n'; } else if (format === MarkupKind.PlainText) { return part.text + '\n\n'; @@ -217,7 +228,8 @@ export function getVariableTypeText( type: Type, typeNode: ExpressionNode, functionSignatureDisplay: SignatureDisplayType, - sourceMapper?: SourceMapper + sourceMapper?: SourceMapper, + printOptions?: PrintTypeOptions ) { let label = 'variable'; if (declaration) { @@ -235,6 +247,7 @@ export function getVariableTypeText( } else { // Handle type aliases specially. const typeText = evaluator.printType(convertToInstance(getTypeForToolTip(evaluator, typeNode)), { + ...printOptions, expandTypeAlias: true, }); @@ -254,6 +267,7 @@ export function getVariableTypeText( (declaration.typeAnnotationNode as any).d?.member?.d?.value === 'CTypedef' ) { const typeText = evaluator.printType(convertToInstance(getTypeForToolTip(evaluator, typeNode)), { + ...printOptions, expandTypeAlias: false, }); return `(type) ${name} = ` + typeText; @@ -272,11 +286,13 @@ export function getVariableTypeText( /* isProperty */ false, functionSignatureDisplay, typeNode, - sourceMapper + sourceMapper, + printOptions ); } - const typeText = typeVarName ?? name + ': ' + evaluator.printType(getTypeForToolTip(evaluator, typeNode)); + const typeText = + typeVarName ?? name + ': ' + evaluator.printType(getTypeForToolTip(evaluator, typeNode), printOptions); // Viper 扩展:变量类型是 typedef 别名时,附加 typedef 信息换行显示。 // 例如 `k: UINT16PTR` 悬浮显示: @@ -293,10 +309,10 @@ export function getVariableTypeText( const fileInfo = getFileInfo(typeNode); if (IsViperDialect(fileInfo.executionEnvironment.dialect)) { const aliasName = aliasInfo.shared.name; - const actualType = evaluator.printType( - convertToInstance(getTypeForToolTip(evaluator, typeNode)), - { expandTypeAlias: false } - ); + const actualType = evaluator.printType(convertToInstance(getTypeForToolTip(evaluator, typeNode)), { + ...printOptions, + expandTypeAlias: false, + }); return `(${label}) ` + typeText + `\n(typedef) ${aliasName} = ` + actualType; } } @@ -356,6 +372,16 @@ export class HoverProvider { return this._program.configOptions.functionSignatureDisplay; } + // Viper 方言:hover 的类型展示保留源码中书写时的完整 module 路径(如 `ast.AST`)。 + // 未写限定路径的裸名(`list`/`str`)仍显示短名,具体规则见 typePrinter。 + private _getPrintTypeOptions(options?: PrintTypeOptions): PrintTypeOptions { + return { ...options, sourceQualifiedNames: true }; + } + + private _printType(type: Type, options?: PrintTypeOptions): string { + return this._evaluator.printType(type, this._getPrintTypeOptions(options)); + } + private _getHoverResult(): HoverResults | null { throwIfCancellationRequested(this._token); @@ -422,7 +448,10 @@ export class HoverProvider { node.d.value, this._evaluator, isProperty, - this._functionSignatureDisplay + this._functionSignatureDisplay, + /* typeNode */ undefined, + /* sourceMapper */ undefined, + this._getPrintTypeOptions() ); } @@ -515,7 +544,8 @@ export class HoverProvider { type, typeNode, this._functionSignatureDisplay, - this._sourceMapper + this._sourceMapper, + this._getPrintTypeOptions() ); this._addResultsPart(parts, typeText, /* python */ true); @@ -535,7 +565,7 @@ export class HoverProvider { if (aliasInfo && aliasInfo.shared.name !== node.d.value) { const fileInfo = getFileInfo(node); if (IsViperDialect(fileInfo.executionEnvironment.dialect)) { - const actualType = this._evaluator.printType( + const actualType = this._printType( convertToInstance(getTypeForToolTip(this._evaluator, node)), { expandTypeAlias: false } ); @@ -605,7 +635,8 @@ export class HoverProvider { isProperty, this._functionSignatureDisplay, /* typeNode */ undefined, - this._sourceMapper + this._sourceMapper, + this._getPrintTypeOptions() ); this._addResultsPart(parts, signatureString, /* python */ true); @@ -626,7 +657,7 @@ export class HoverProvider { case DeclarationType.TypeAlias: { const type = convertToInstance(this._getType(node)); - const typeText = this._evaluator.printType(type, { expandTypeAlias: true }); + const typeText = this._printType(type, { expandTypeAlias: true }); this._addResultsPart(parts, `(type) ${node.d.value} = ${typeText}`, /* python */ true); this._addDocumentationPart(parts, node, resolvedDecl); break; @@ -699,7 +730,7 @@ export class HoverProvider { // 返回类型 const returnType = this._getViperFunctionReturnType(funcNode); if (returnType) { - lines.push(`${bold('返回类型')}: ${code(this._evaluator.printType(convertToInstance(returnType)))}`); + lines.push(`${bold('返回类型')}: ${code(this._printType(convertToInstance(returnType)))}`); } if (lines.length === 0) { @@ -727,7 +758,7 @@ export class HoverProvider { paramType = this._evaluator.getTypeOfExpression(param.d.name).type; } if (paramType) { - text += ': ' + this._evaluator.printType(convertToInstance(paramType)); + text += ': ' + this._printType(convertToInstance(paramType)); } if (param.d.defaultValue) { @@ -767,7 +798,8 @@ export class HoverProvider { type, node, this._functionSignatureDisplay, - this._sourceMapper + this._sourceMapper, + this._getPrintTypeOptions() ); } @@ -785,7 +817,7 @@ export class HoverProvider { const entry = subtype.shared.typedDictEntries?.knownItems.get(node.d.value); if (entry) { // e.g. (key) name: str - const text = '(key) ' + node.d.value + ': ' + this._evaluator.printType(entry.valueType); + const text = '(key) ' + node.d.value + ': ' + this._printType(entry.valueType); const declarations = ClassType.getSymbolTable(subtype).get(node.d.value)?.getDeclarations(); let declarationWithDoc: Declaration | undefined; @@ -833,7 +865,13 @@ export class HoverProvider { if (result.methodType && isFunctionOrOverloaded(result.methodType)) { this._addResultsPart( parts, - getConstructorTooltip(node.d.value, result.methodType, this._evaluator, this._functionSignatureDisplay), + getConstructorTooltip( + node.d.value, + result.methodType, + this._evaluator, + this._functionSignatureDisplay, + this._getPrintTypeOptions() + ), /* python */ true ); @@ -855,7 +893,7 @@ export class HoverProvider { private _getTypeText(node: ExpressionNode, options?: PrintTypeOptions): string { const type = this._getType(node); - return ': ' + this._evaluator.printType(type, options); + return ': ' + this._printType(type, options); } private _addDocumentationPart(parts: HoverTextPart[], node: NameNode, resolvedDecl: Declaration | undefined) { diff --git a/packages/vipeye-internal/src/languageService/tooltipUtils.ts b/packages/vipeye-internal/src/languageService/tooltipUtils.ts index f29f0420..c66444a5 100644 --- a/packages/vipeye-internal/src/languageService/tooltipUtils.ts +++ b/packages/vipeye-internal/src/languageService/tooltipUtils.ts @@ -24,7 +24,7 @@ import { getPropertyDocStringInherited, getVariableDocString, } from '../analyzer/typeDocStringUtils'; -import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes'; +import { PrintTypeOptions, TypeEvaluator } from '../analyzer/typeEvaluatorTypes'; import { MemberAccessFlags, lookUpClassMember } from '../analyzer/typeUtils'; import { ClassType, @@ -67,7 +67,8 @@ export function getToolTipForType( isProperty: boolean, functionSignatureDisplay: SignatureDisplayType, typeNode?: ExpressionNode, - sourceMapper?: SourceMapper + sourceMapper?: SourceMapper, + printOptions?: PrintTypeOptions ): string { // Support __call__ method for class instances to show the signature of the method if (type.category === TypeCategory.Class && isClassInstance(type) && typeNode) { @@ -91,7 +92,8 @@ export function getToolTipForType( evaluator, functionSignatureDisplay, /* columnThreshold */ 70, - sourceMapper + sourceMapper, + printOptions )}`; } else if (isFunction(type)) { signatureString = `${getFunctionTooltip( @@ -101,11 +103,12 @@ export function getToolTipForType( evaluator, isProperty, functionSignatureDisplay, - sourceMapper + sourceMapper, + printOptions )}`; } else { signatureString = label.length > 0 ? `(${label}) ` : ''; - signatureString += `${name}: ${evaluator.printType(type)}`; + signatureString += `${name}: ${evaluator.printType(type, printOptions)}`; } return signatureString; @@ -117,7 +120,8 @@ export function getOverloadedTooltip( evaluator: TypeEvaluator, functionSignatureDisplay: SignatureDisplayType, columnThreshold = 70, - sourceMapper?: SourceMapper + sourceMapper?: SourceMapper, + printOptions?: PrintTypeOptions ) { let content = ''; const overloads = OverloadedType.getOverloads(type).map((o) => @@ -128,7 +132,8 @@ export function getOverloadedTooltip( evaluator, /* isProperty */ false, functionSignatureDisplay, - sourceMapper + sourceMapper, + printOptions ) ); @@ -157,12 +162,13 @@ export function getFunctionTooltip( evaluator: TypeEvaluator, isProperty = false, functionSignatureDisplay: SignatureDisplayType, - sourceMapper?: SourceMapper + sourceMapper?: SourceMapper, + printOptions?: PrintTypeOptions ) { const labelFormatted = label.length === 0 ? '' : `(${label}) `; const indentStr = functionSignatureDisplay === SignatureDisplayType.formatted ? '\n' + ' '.repeat(functionParamIndentOffset) : ''; - let funcParts = evaluator.printFunctionParts(type); + let funcParts = evaluator.printFunctionParts(type, /* extraFlags */ undefined, printOptions); if (sourceMapper) { funcParts = replaceStubEllipsisDefaultValues(type, funcParts, sourceMapper); } @@ -285,14 +291,15 @@ export function getConstructorTooltip( constructorName: string, type: Type, evaluator: TypeEvaluator, - functionSignatureDisplay: SignatureDisplayType + functionSignatureDisplay: SignatureDisplayType, + printOptions?: PrintTypeOptions ) { const classText = `class `; let signature = ''; if (isOverloaded(type)) { const overloads = OverloadedType.getOverloads(type).map((overload) => - getConstructorTooltip(constructorName, overload, evaluator, functionSignatureDisplay) + getConstructorTooltip(constructorName, overload, evaluator, functionSignatureDisplay, printOptions) ); overloads.forEach((overload, index) => { signature += overload + ': ...' + '\n\n'; @@ -302,7 +309,7 @@ export function getConstructorTooltip( functionSignatureDisplay === SignatureDisplayType.formatted ? '\n' + ' '.repeat(functionParamIndentOffset) : ' '; - const funcParts = evaluator.printFunctionParts(type); + const funcParts = evaluator.printFunctionParts(type, /* extraFlags */ undefined, printOptions); const paramSignature = formatSignature(funcParts, indentStr, functionSignatureDisplay); signature += `${classText}${constructorName}${paramSignature}`; } diff --git a/packages/vipeye-internal/src/tests/viperFunctionHover.test.ts b/packages/vipeye-internal/src/tests/viperFunctionHover.test.ts index 990aa75c..152e5d81 100644 --- a/packages/vipeye-internal/src/tests/viperFunctionHover.test.ts +++ b/packages/vipeye-internal/src/tests/viperFunctionHover.test.ts @@ -87,7 +87,12 @@ def use(): console.log('=== hover 内容 ==='); console.log(contents); - // 签名部分(原有 python 代码块) + // 签名部分:代码围栏语言为 `python`。hover 代码块由宿主 markdown 渲染器用 TM 语法着色, + // `viper` 围栏虽能取到本扩展贡献的语法,但只有注释/字符串/控制流关键字/数字等通用 scope + // 能命中主题规则(`def`/`class` 的 storage.type.*.viper 与主题里按 Python 精调的规则不匹配, + // 裸类型名语法里没有规则),只部分着色;`python`(MagicPython)着色齐全,故用它。 + assert(contents.includes('```python'), `should use python code fence: ${contents}`); + assert(!contents.includes('```viper'), `should not use viper code fence: ${contents}`); assert(contents.includes('(function)'), `should include signature: ${contents}`); // 调试信息标题 assert(contents.includes('函数调试信息'), `should include debug heading: ${contents}`); @@ -100,13 +105,13 @@ def use(): // 泛型参数 assert(contents.includes('泛型参数'), `should include 泛型参数: ${contents}`); assert(contents.includes('[T]'), `should include [T]: ${contents}`); - // 参数 + // 参数(类型按源码书写的完整 module 路径展示:注解写的是 `t.CInt`) assert(contents.includes('参数'), `should include 参数: ${contents}`); - assert(contents.includes('a: CInt'), `should include param a: ${contents}`); - assert(contents.includes('b: CInt = 10'), `should include param b with default: ${contents}`); + assert(contents.includes('a: t.CInt'), `should include param a: ${contents}`); + assert(contents.includes('b: t.CInt = 10'), `should include param b with default: ${contents}`); // 返回类型 assert(contents.includes('返回类型'), `should include 返回类型: ${contents}`); - assert(contents.includes('CInt'), `should include return type: ${contents}`); + assert(contents.includes('t.CInt'), `should include return type: ${contents}`); program.dispose(); serviceProvider.dispose(); diff --git a/packages/vipeye-internal/src/tests/viperUnaryPointer.test.ts b/packages/vipeye-internal/src/tests/viperUnaryPointer.test.ts index a72528ce..ede48f09 100644 --- a/packages/vipeye-internal/src/tests/viperUnaryPointer.test.ts +++ b/packages/vipeye-internal/src/tests/viperUnaryPointer.test.ts @@ -29,6 +29,7 @@ import { UriEx } from '../common/uri/uriUtils'; import { FullAccessHost } from '../common/fullAccessHost'; import { convertOffsetToPosition } from '../common/positionUtils'; import { FunctionNode, ParameterNode, TypeAnnotationNode, UnaryOperationNode } from '../parser/parseNodes'; +import { Type } from '../analyzer/types'; import { OperatorType } from '../parser/tokenizerTypes'; (global as any).__rootDirectory = path.resolve(); @@ -337,11 +338,19 @@ class Holder: const ev = evaluator; const anns: { line: number; type: string }[] = []; + let directType: Type | undefined; + let chRType: Type | undefined; class AnnCollector extends ParseTreeWalker { override visitTypeAnnotation(node: TypeAnnotationNode) { + const line = convertOffsetToPosition(node.d.annotation.start, parseResults.parserOutput.lines).line + 1; const t = ev.getTypeOfAnnotation(node.d.annotation); + if (line === 5) { + chRType = t; + } else if (line === 6) { + directType = t; + } anns.push({ - line: convertOffsetToPosition(node.d.annotation.start, parseResults.parserOutput.lines).line + 1, + line, type: t ? program.printType(t) : '(undefined)', }); return true; @@ -363,6 +372,22 @@ class Holder: assert(direct, 'direct annotation not found (L6)'); assert(direct.type.includes('rawptr[AST]'), `direct should be rawptr[AST]: ${direct.type}`); + // LSP 展示形态(hover):sourceQualifiedNames → 按源码书写的完整 module 路径显示。 + // 源码写 `rawptr[ast.AST]`,故展示 `rawptr[ast.AST]`;源码写裸名 `list`,故仍是 `list`。 + assert(directType, 'direct type not captured (L6)'); + const directDisplay = program.printType(directType!, { sourceQualifiedNames: true }); + console.log(`=== LSP 展示形态 L6 direct: ${directDisplay}`); + assert.strictEqual(directDisplay, 'rawptr[ast.AST]', `direct 应保留源码 module 路径: ${directDisplay}`); + + assert(chRType, 'ch_r type not captured (L5)'); + const chRDisplay = program.printType(chRType!, { sourceQualifiedNames: true }); + console.log(`=== LSP 展示形态 L5 ch_r: ${chRDisplay}`); + assert.strictEqual( + chRDisplay, + 'rawptr[list[rawptr[ast.AST]]]', + `ch_r 应保留源码 module 路径且裸名 list 保持短名: ${chRDisplay}` + ); + // 不得出现「"[" 未关闭」「rawptr 未定义」等错误(category=0 = Error) const diagnostics = getDiagnostics(program, configOptions, uri); const errors = diagnostics.filter((d) => d.includes('category=0'));