From 14f76d0707e8ae475c14b680bb5356a926cefe8c Mon Sep 17 00:00:00 2001 From: RodgeFu Date: Fri, 30 May 2025 19:38:35 +0800 Subject: [PATCH 1/9] support full def in hover --- packages/compiler/src/server/serverlib.ts | 42 +++++++-- packages/compiler/src/server/type-details.ts | 9 ++ .../compiler/src/server/type-signature.ts | 92 ++++++++++++++++--- 3 files changed, 122 insertions(+), 21 deletions(-) diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index 1305af6b8b3..8330b0f8b34 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -48,6 +48,7 @@ import { WorkspaceEdit, WorkspaceFoldersChangeEvent, } from "vscode-languageserver/node.js"; +import { getSymNode } from "../core/binder.js"; import { CharCode } from "../core/charcode.js"; import { resolveCodeFix } from "../core/code-fixes.js"; import { compilerAssert, getSourceLocation } from "../core/diagnostics.js"; @@ -714,13 +715,38 @@ export function createServer(host: ServerHost): Server { const sym = id?.kind === SyntaxKind.Identifier ? program.checker.resolveRelatedSymbols(id) : undefined; - const markdown: MarkupContent = { - kind: MarkupKind.Markdown, - value: sym && sym.length > 0 ? getSymbolDetails(program, sym[0]) : "", - }; - return { - contents: markdown, - }; + if (!sym || sym.length === 0) { + return { contents: { kind: MarkupKind.Markdown, value: "" } }; + } else { + const type = sym[0].type ?? program.checker.getTypeOrValueForNode(getSymNode(sym[0])); + const modelHasExtendOrIs: boolean = + type !== null && + "kind" in type && + type.kind === "Model" && + (type.baseModel !== undefined || + type.sourceModel !== undefined || + type.sourceModels.length > 0); + const interfaceHasExtendOrIs: boolean = + type !== null && + "kind" in type && + type.kind === "Interface" && + type.sourceInterfaces.length > 0; + const includeFullDefinition = modelHasExtendOrIs || interfaceHasExtendOrIs; + const markdown: MarkupContent = { + kind: MarkupKind.Markdown, + value: + sym && sym.length > 0 + ? getSymbolDetails(program, sym[0], { + includeSignature: true, + includeParameterTags: true, + includeFullDefinition, + }) + : "", + }; + return { + contents: markdown, + }; + } } async function getSignatureHelp(params: SignatureHelpParams): Promise { @@ -796,6 +822,7 @@ export function createServer(host: ServerHost): Server { const doc = getSymbolDetails(program, sym[0], { includeSignature: false, includeParameterTags: false, + includeFullDefinition: false, }); if (doc) { help.signatures[0].documentation = { kind: MarkupKind.Markdown, value: doc }; @@ -874,6 +901,7 @@ export function createServer(host: ServerHost): Server { const doc = getSymbolDetails(program, sym[0], { includeSignature: false, includeParameterTags: false, + includeFullDefinition: false, }); if (doc) { help.signatures[0].documentation = { kind: MarkupKind.Markdown, value: doc }; diff --git a/packages/compiler/src/server/type-details.ts b/packages/compiler/src/server/type-details.ts index 1fc59528ecc..5ef89519900 100644 --- a/packages/compiler/src/server/type-details.ts +++ b/packages/compiler/src/server/type-details.ts @@ -17,6 +17,7 @@ export function getSymbolDetails( options = { includeSignature: true, includeParameterTags: true, + includeFullDefinition: false, }, ): string { const lines = []; @@ -43,6 +44,14 @@ export function getSymbolDetails( } } } + if (options.includeFullDefinition) { + lines.push(`\n\n*Full Definition:*\n`); + lines.push( + getSymbolSignature(program, symbol, { + includeBody: true, + }), + ); + } return lines.join("\n\n"); } diff --git a/packages/compiler/src/server/type-signature.ts b/packages/compiler/src/server/type-signature.ts index 352f0a7ce70..49be04ae581 100644 --- a/packages/compiler/src/server/type-signature.ts +++ b/packages/compiler/src/server/type-signature.ts @@ -9,6 +9,8 @@ import { Decorator, EnumMember, FunctionParameter, + Interface, + Model, ModelProperty, Operation, StringTemplate, @@ -18,19 +20,37 @@ import { UnionVariant, Value, } from "../core/types.js"; +import { walkPropertiesInherited } from "../index.js"; + +interface GetSymbolSignatureOptions { + /** + * Whether to include the body in the signature. Only support Model and Interface type now + */ + includeBody: boolean; +} /** @internal */ -export function getSymbolSignature(program: Program, sym: Sym): string { +export function getSymbolSignature( + program: Program, + sym: Sym, + options: GetSymbolSignatureOptions = { + includeBody: false, + }, +): string { const decl = getSymNode(sym); switch (decl?.kind) { case SyntaxKind.AliasStatement: return fence(`alias ${getAliasSignature(decl)}`); } const entity = sym.type ?? program.checker.getTypeOrValueForNode(decl); - return getEntitySignature(sym, entity); + return getEntitySignature(sym, entity, options); } -function getEntitySignature(sym: Sym, entity: Type | Value | null): string { +function getEntitySignature( + sym: Sym, + entity: Type | Value | null, + options: GetSymbolSignatureOptions, +): string { if (entity === null) { return "(error)"; } @@ -38,20 +58,22 @@ function getEntitySignature(sym: Sym, entity: Type | Value | null): string { return fence(`const ${sym.name}: ${getTypeName(entity.type)}`); } - return getTypeSignature(entity); + return getTypeSignature(entity, options); } -function getTypeSignature(type: Type): string { +function getTypeSignature(type: Type, options: GetSymbolSignatureOptions): string { switch (type.kind) { case "Scalar": case "Enum": case "Union": - case "Interface": - case "Model": case "Namespace": return fence(`${type.kind.toLowerCase()} ${getPrintableTypeName(type)}`); + case "Interface": + return fence(getInterfaceSignature(type, options.includeBody)); + case "Model": + return fence(getModelSignature(type, options.includeBody)); case "ScalarConstructor": - return fence(`init ${getTypeSignature(type.scalar)}.${type.name}`); + return fence(`init ${getTypeSignature(type.scalar, options)}.${type.name}`); case "Decorator": return fence(getDecoratorSignature(type)); case "Operation": @@ -80,7 +102,7 @@ function getTypeSignature(type: Type): string { case "UnionVariant": return `(union variant)\n${fence(getUnionVariantSignature(type))}`; case "Tuple": - return `(tuple)\n[${fence(type.values.map(getTypeSignature).join(", "))}]`; + return `(tuple)\n[${fence(type.values.map((v) => getTypeSignature(v, options)).join(", "))}]`; default: const _assertNever: never = type; compilerAssert(false, "Unexpected type kind"); @@ -94,9 +116,51 @@ function getDecoratorSignature(type: Decorator) { return `dec ${ns}${name}(${parameters.join(", ")})`; } -function getOperationSignature(type: Operation) { - const parameters = [...type.parameters.properties.values()].map(getModelPropertySignature); - return `op ${getTypeName(type)}(${parameters.join(", ")}): ${getPrintableTypeName(type.returnType)}`; +function getOperationSignature(type: Operation, includeQualifier: boolean = true) { + const parameters = [...type.parameters.properties.values()].map((p) => + getModelPropertySignature(p, false /* includeQualifier */), + ); + if (includeQualifier) { + return `op ${getTypeName(type)}(${parameters.join(", ")}): ${getPrintableTypeName(type.returnType)}`; + } else { + let opName = printIdentifier(type.name, "allow-reserved"); + if (type.node && type.node.templateParameters.length > 0) { + // template + const params = type.node.templateParameters.map((t) => + printIdentifier(t.id.sv, "allow-reserved"), + ); + opName += `<${params.join(", ")}>`; + } + return `op ${opName}(${parameters.join(", ")}): ${getPrintableTypeName(type.returnType)}`; + } +} + +function getInterfaceSignature(type: Interface, includeBody: boolean) { + if (includeBody) { + const INDENT = " "; + const opDescs = Array.from(type.operations).map( + ([name, op]) => INDENT + getOperationSignature(op), + ); + return `${type.kind.toLowerCase()} ${getPrintableTypeName(type)} {\n${opDescs.join("\n")}\n}`; + } else { + return `${type.kind.toLowerCase()} ${getPrintableTypeName(type)}`; + } +} + +/** + * All properties from 'extends' and 'is' will be included if includeBody is true. + */ +function getModelSignature(type: Model, includeBody: boolean) { + if (includeBody) { + const propDescs = []; + const INDENT = " "; + for (const prop of walkPropertiesInherited(type)) { + propDescs.push(INDENT + getModelPropertySignature(prop, false /*includeQualifier*/)); + } + return `${type.kind.toLowerCase()} ${getPrintableTypeName(type)}{\n${propDescs.map((d) => `${d};`).join("\n")}\n}`; + } else { + return `${type.kind.toLowerCase()} ${getPrintableTypeName(type)}`; + } } function getFunctionParameterSignature(parameter: FunctionParameter) { @@ -117,8 +181,8 @@ function getStringTemplateSignature(stringTemplate: StringTemplate) { ); } -function getModelPropertySignature(property: ModelProperty) { - const ns = getQualifier(property.model); +function getModelPropertySignature(property: ModelProperty, includeQualifier: boolean = true) { + const ns = includeQualifier ? getQualifier(property.model) : ""; return `${ns}${printIdentifier(property.name, "allow-reserved")}: ${getPrintableTypeName(property.type)}`; } From 2df8f9902611a1ef2c8919313a0f232ba0c9822f Mon Sep 17 00:00:00 2001 From: RodgeFu Date: Sun, 1 Jun 2025 16:23:05 +0800 Subject: [PATCH 2/9] add test and some refactor --- .../src/core/helpers/type-name-utils.ts | 15 +++- packages/compiler/src/server/serverlib.ts | 36 ++++---- packages/compiler/src/server/type-details.ts | 20 ++++- .../compiler/src/server/type-signature.ts | 18 +--- .../compiler/test/server/get-hover.test.ts | 85 +++++++++++++++++++ 5 files changed, 135 insertions(+), 39 deletions(-) diff --git a/packages/compiler/src/core/helpers/type-name-utils.ts b/packages/compiler/src/core/helpers/type-name-utils.ts index af9e970fd85..2581e60bbe0 100644 --- a/packages/compiler/src/core/helpers/type-name-utils.ts +++ b/packages/compiler/src/core/helpers/type-name-utils.ts @@ -20,6 +20,9 @@ import { printIdentifier } from "./syntax-utils.js"; export interface TypeNameOptions { namespaceFilter?: (ns: Namespace) => boolean; printable?: boolean; + // Whether to include the interface prefix for operations defined in interfaces. + // Default is true + includeInterfacePrefix?: boolean; } export function getTypeName(type: Type, options?: TypeNameOptions): string { @@ -234,10 +237,14 @@ function getOperationName(op: Operation, options: TypeNameOptions | undefined) { const params = op.node.templateParameters.map((t) => getIdentifierName(t.id.sv, options)); opName += `<${params.join(", ")}>`; } - const prefix = op.interface - ? getInterfaceName(op.interface, options) + "." - : getNamespacePrefix(op.namespace, options); - return `${prefix}${opName}`; + if (op.interface) { + return options?.includeInterfacePrefix === false + ? opName + : `${getInterfaceName(op.interface, options)}.${opName}`; + } else { + const prefix = getNamespacePrefix(op.namespace, options); + return `${prefix}${opName}`; + } } function getIdentifierName(name: string, options: TypeNameOptions | undefined) { diff --git a/packages/compiler/src/server/serverlib.ts b/packages/compiler/src/server/serverlib.ts index 8330b0f8b34..68b9b7952cf 100644 --- a/packages/compiler/src/server/serverlib.ts +++ b/packages/compiler/src/server/serverlib.ts @@ -718,20 +718,24 @@ export function createServer(host: ServerHost): Server { if (!sym || sym.length === 0) { return { contents: { kind: MarkupKind.Markdown, value: "" } }; } else { - const type = sym[0].type ?? program.checker.getTypeOrValueForNode(getSymNode(sym[0])); - const modelHasExtendOrIs: boolean = - type !== null && - "kind" in type && - type.kind === "Model" && - (type.baseModel !== undefined || - type.sourceModel !== undefined || - type.sourceModels.length > 0); - const interfaceHasExtendOrIs: boolean = - type !== null && - "kind" in type && - type.kind === "Interface" && - type.sourceInterfaces.length > 0; - const includeFullDefinition = modelHasExtendOrIs || interfaceHasExtendOrIs; + // Only show full definition if the symbol is a model or interface that has extends or is clauses. + // Avoid showing full definition in other cases which can be long and not useful + let includeExpandedDefinition = false; + const sn = getSymNode(sym[0]); + if (sn.kind !== SyntaxKind.AliasStatement) { + const type = sym[0].type ?? program.checker.getTypeOrValueForNode(sn); + if (type && "kind" in type) { + const modelHasExtendOrIs: boolean = + type.kind === "Model" && + (type.baseModel !== undefined || + type.sourceModel !== undefined || + type.sourceModels.length > 0); + const interfaceHasExtend: boolean = + type.kind === "Interface" && type.sourceInterfaces.length > 0; + includeExpandedDefinition = modelHasExtendOrIs || interfaceHasExtend; + } + } + const markdown: MarkupContent = { kind: MarkupKind.Markdown, value: @@ -739,7 +743,7 @@ export function createServer(host: ServerHost): Server { ? getSymbolDetails(program, sym[0], { includeSignature: true, includeParameterTags: true, - includeFullDefinition, + includeExpandedDefinition, }) : "", }; @@ -822,7 +826,6 @@ export function createServer(host: ServerHost): Server { const doc = getSymbolDetails(program, sym[0], { includeSignature: false, includeParameterTags: false, - includeFullDefinition: false, }); if (doc) { help.signatures[0].documentation = { kind: MarkupKind.Markdown, value: doc }; @@ -901,7 +904,6 @@ export function createServer(host: ServerHost): Server { const doc = getSymbolDetails(program, sym[0], { includeSignature: false, includeParameterTags: false, - includeFullDefinition: false, }); if (doc) { help.signatures[0].documentation = { kind: MarkupKind.Markdown, value: doc }; diff --git a/packages/compiler/src/server/type-details.ts b/packages/compiler/src/server/type-details.ts index 5ef89519900..87ed80aa02b 100644 --- a/packages/compiler/src/server/type-details.ts +++ b/packages/compiler/src/server/type-details.ts @@ -6,6 +6,17 @@ import { isType } from "../core/type-utils.js"; import { DocContent, Node, Sym, SyntaxKind, TemplateDeclarationNode, Type } from "../core/types.js"; import { getSymbolSignature } from "./type-signature.js"; +interface GetSymbolDetailsOptions { + includeSignature: boolean; + includeParameterTags: boolean; + /** + * Whether to include the final expended definition of the symbol + * For Model and Interface, it's body with expended members will be included. Otherwise, it will be the same as signature. (Support for other type may be added in the future as needed) + * This is useful for models and interfaces with complex 'extends' and 'is' relationship when user wants to know the final expended definition. + */ + includeExpandedDefinition?: boolean; +} + /** * Get the detailed documentation for a symbol. * @param program The program @@ -14,10 +25,10 @@ import { getSymbolSignature } from "./type-signature.js"; export function getSymbolDetails( program: Program, symbol: Sym, - options = { + options: GetSymbolDetailsOptions = { includeSignature: true, includeParameterTags: true, - includeFullDefinition: false, + includeExpandedDefinition: false, }, ): string { const lines = []; @@ -44,14 +55,15 @@ export function getSymbolDetails( } } } - if (options.includeFullDefinition) { - lines.push(`\n\n*Full Definition:*\n`); + if (options.includeExpandedDefinition) { + lines.push(`*Full Definition:*`); lines.push( getSymbolSignature(program, symbol, { includeBody: true, }), ); } + return lines.join("\n\n"); } diff --git a/packages/compiler/src/server/type-signature.ts b/packages/compiler/src/server/type-signature.ts index 49be04ae581..7eccb131f02 100644 --- a/packages/compiler/src/server/type-signature.ts +++ b/packages/compiler/src/server/type-signature.ts @@ -120,26 +120,16 @@ function getOperationSignature(type: Operation, includeQualifier: boolean = true const parameters = [...type.parameters.properties.values()].map((p) => getModelPropertySignature(p, false /* includeQualifier */), ); - if (includeQualifier) { - return `op ${getTypeName(type)}(${parameters.join(", ")}): ${getPrintableTypeName(type.returnType)}`; - } else { - let opName = printIdentifier(type.name, "allow-reserved"); - if (type.node && type.node.templateParameters.length > 0) { - // template - const params = type.node.templateParameters.map((t) => - printIdentifier(t.id.sv, "allow-reserved"), - ); - opName += `<${params.join(", ")}>`; - } - return `op ${opName}(${parameters.join(", ")}): ${getPrintableTypeName(type.returnType)}`; - } + return `op ${getTypeName(type, { + includeInterfacePrefix: includeQualifier, + })}(${parameters.join(", ")}): ${getPrintableTypeName(type.returnType)}`; } function getInterfaceSignature(type: Interface, includeBody: boolean) { if (includeBody) { const INDENT = " "; const opDescs = Array.from(type.operations).map( - ([name, op]) => INDENT + getOperationSignature(op), + ([name, op]) => INDENT + getOperationSignature(op, false /* includeQualifier */) + ";", ); return `${type.kind.toLowerCase()} ${getPrintableTypeName(type)} {\n${opDescs.join("\n")}\n}`; } else { diff --git a/packages/compiler/test/server/get-hover.test.ts b/packages/compiler/test/server/get-hover.test.ts index 62dd8c95ab4..36980f0f8b8 100644 --- a/packages/compiler/test/server/get-hover.test.ts +++ b/packages/compiler/test/server/get-hover.test.ts @@ -394,6 +394,57 @@ describe("compiler: server: on hover", () => { }, }); }); + + it("model with extends and is (full definition expected)", async () => { + const hover = await getHoverAtCursor( + ` + @service(#{title: "RT"}) + namespace TestNs; + + model Do┆g is Animal { + barkVolume: int32; + } + + model Animal extends AnimalBase

{ + name: string; + age: int16; + tTag: T; + } + + model AnimalBase

{ + id: string; + properties: P; + } + + + model DogProperties { + breed: string; + color: string; + } + `, + ); + deepStrictEqual(hover, { + contents: { + kind: MarkupKind.Markdown, + value: `\`\`\`typespec +model TestNs.Dog +\`\`\` + +*Full Definition:* + +\`\`\`typespec +model TestNs.Dog{ + name: string; + age: int16; + tTag: string; + barkVolume: int32; + id: string; + properties: TestNs.DogProperties; +} +\`\`\``, + }, + }); + }); }); describe("interface", () => { @@ -449,6 +500,40 @@ describe("compiler: server: on hover", () => { }, }); }); + + it("interface with extends", async () => { + const hover = await getHoverAtCursor( + ` + @service(#{title: "RT"}) + namespace TestNs; + + interface IActions{ + fly(): void; + } + + interface Bi┆rd extends IActions { + eat(): void; + } + `, + ); + deepStrictEqual(hover, { + contents: { + kind: MarkupKind.Markdown, + value: `\`\`\`typespec +interface TestNs.Bird +\`\`\` + +*Full Definition:* + +\`\`\`typespec +interface TestNs.Bird { + op fly(): void; + op eat(): void; +} +\`\`\``, + }, + }); + }); }); describe("operation", () => { From b33b97ef33b53dde6528d59d9cd7c7be9a99b435 Mon Sep 17 00:00:00 2001 From: RodgeFu Date: Tue, 3 Jun 2025 14:54:43 +0800 Subject: [PATCH 3/9] add changelog --- .chronus/changes/full-def-hover-2025-5-3-14-54-27.md | 7 +++++++ packages/compiler/src/server/type-signature.ts | 10 +++++----- 2 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 .chronus/changes/full-def-hover-2025-5-3-14-54-27.md diff --git a/.chronus/changes/full-def-hover-2025-5-3-14-54-27.md b/.chronus/changes/full-def-hover-2025-5-3-14-54-27.md new file mode 100644 index 00000000000..8eb2f9bebaf --- /dev/null +++ b/.chronus/changes/full-def-hover-2025-5-3-14-54-27.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@typespec/compiler" +--- + +Show the full definition of model and interface when it has 'extends' and 'is' relationship in the hover text \ No newline at end of file diff --git a/packages/compiler/src/server/type-signature.ts b/packages/compiler/src/server/type-signature.ts index 7eccb131f02..f3cd261c501 100644 --- a/packages/compiler/src/server/type-signature.ts +++ b/packages/compiler/src/server/type-signature.ts @@ -128,10 +128,10 @@ function getOperationSignature(type: Operation, includeQualifier: boolean = true function getInterfaceSignature(type: Interface, includeBody: boolean) { if (includeBody) { const INDENT = " "; - const opDescs = Array.from(type.operations).map( + const opDesc = Array.from(type.operations).map( ([name, op]) => INDENT + getOperationSignature(op, false /* includeQualifier */) + ";", ); - return `${type.kind.toLowerCase()} ${getPrintableTypeName(type)} {\n${opDescs.join("\n")}\n}`; + return `${type.kind.toLowerCase()} ${getPrintableTypeName(type)} {\n${opDesc.join("\n")}\n}`; } else { return `${type.kind.toLowerCase()} ${getPrintableTypeName(type)}`; } @@ -142,12 +142,12 @@ function getInterfaceSignature(type: Interface, includeBody: boolean) { */ function getModelSignature(type: Model, includeBody: boolean) { if (includeBody) { - const propDescs = []; + const propDesc = []; const INDENT = " "; for (const prop of walkPropertiesInherited(type)) { - propDescs.push(INDENT + getModelPropertySignature(prop, false /*includeQualifier*/)); + propDesc.push(INDENT + getModelPropertySignature(prop, false /*includeQualifier*/)); } - return `${type.kind.toLowerCase()} ${getPrintableTypeName(type)}{\n${propDescs.map((d) => `${d};`).join("\n")}\n}`; + return `${type.kind.toLowerCase()} ${getPrintableTypeName(type)}{\n${propDesc.map((d) => `${d};`).join("\n")}\n}`; } else { return `${type.kind.toLowerCase()} ${getPrintableTypeName(type)}`; } From 60396d464ac695fff2f27a2a4aa051aef9e5e5f8 Mon Sep 17 00:00:00 2001 From: Rodge Fu Date: Wed, 4 Jun 2025 11:17:20 +0800 Subject: [PATCH 4/9] Update packages/compiler/test/server/get-hover.test.ts Co-authored-by: Timothee Guerin --- packages/compiler/test/server/get-hover.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compiler/test/server/get-hover.test.ts b/packages/compiler/test/server/get-hover.test.ts index 36980f0f8b8..b4fb9aef5ea 100644 --- a/packages/compiler/test/server/get-hover.test.ts +++ b/packages/compiler/test/server/get-hover.test.ts @@ -433,7 +433,7 @@ model TestNs.Dog *Full Definition:* \`\`\`typespec -model TestNs.Dog{ +model TestNs.Dog { name: string; age: int16; tTag: string; From 8fa76aab7b531c62d44dc66bbd2047783372d05d Mon Sep 17 00:00:00 2001 From: Rodge Fu Date: Wed, 4 Jun 2025 11:25:45 +0800 Subject: [PATCH 5/9] Update packages/compiler/test/server/get-hover.test.ts Co-authored-by: Timothee Guerin --- packages/compiler/test/server/get-hover.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/compiler/test/server/get-hover.test.ts b/packages/compiler/test/server/get-hover.test.ts index b4fb9aef5ea..0481fa6792e 100644 --- a/packages/compiler/test/server/get-hover.test.ts +++ b/packages/compiler/test/server/get-hover.test.ts @@ -504,7 +504,6 @@ model TestNs.Dog { it("interface with extends", async () => { const hover = await getHoverAtCursor( ` - @service(#{title: "RT"}) namespace TestNs; interface IActions{ From 15b9d341cf19e90a8b8624c5c255d31e2805764d Mon Sep 17 00:00:00 2001 From: Rodge Fu Date: Wed, 4 Jun 2025 11:25:56 +0800 Subject: [PATCH 6/9] Update packages/compiler/test/server/get-hover.test.ts Co-authored-by: Timothee Guerin --- packages/compiler/test/server/get-hover.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/compiler/test/server/get-hover.test.ts b/packages/compiler/test/server/get-hover.test.ts index 0481fa6792e..95d52286223 100644 --- a/packages/compiler/test/server/get-hover.test.ts +++ b/packages/compiler/test/server/get-hover.test.ts @@ -398,7 +398,6 @@ describe("compiler: server: on hover", () => { it("model with extends and is (full definition expected)", async () => { const hover = await getHoverAtCursor( ` - @service(#{title: "RT"}) namespace TestNs; model Do┆g is Animal { From 522d2228f684fcae6541c7196658997b1bba5647 Mon Sep 17 00:00:00 2001 From: RodgeFu Date: Wed, 4 Jun 2025 17:24:33 +0800 Subject: [PATCH 7/9] revert the update in type-name-utils --- .../compiler/src/core/helpers/type-name-utils.ts | 15 ++++----------- packages/compiler/src/server/type-signature.ts | 14 +++++++++++--- packages/compiler/test/server/get-hover.test.ts | 2 +- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/compiler/src/core/helpers/type-name-utils.ts b/packages/compiler/src/core/helpers/type-name-utils.ts index 2581e60bbe0..af9e970fd85 100644 --- a/packages/compiler/src/core/helpers/type-name-utils.ts +++ b/packages/compiler/src/core/helpers/type-name-utils.ts @@ -20,9 +20,6 @@ import { printIdentifier } from "./syntax-utils.js"; export interface TypeNameOptions { namespaceFilter?: (ns: Namespace) => boolean; printable?: boolean; - // Whether to include the interface prefix for operations defined in interfaces. - // Default is true - includeInterfacePrefix?: boolean; } export function getTypeName(type: Type, options?: TypeNameOptions): string { @@ -237,14 +234,10 @@ function getOperationName(op: Operation, options: TypeNameOptions | undefined) { const params = op.node.templateParameters.map((t) => getIdentifierName(t.id.sv, options)); opName += `<${params.join(", ")}>`; } - if (op.interface) { - return options?.includeInterfacePrefix === false - ? opName - : `${getInterfaceName(op.interface, options)}.${opName}`; - } else { - const prefix = getNamespacePrefix(op.namespace, options); - return `${prefix}${opName}`; - } + const prefix = op.interface + ? getInterfaceName(op.interface, options) + "." + : getNamespacePrefix(op.namespace, options); + return `${prefix}${opName}`; } function getIdentifierName(name: string, options: TypeNameOptions | undefined) { diff --git a/packages/compiler/src/server/type-signature.ts b/packages/compiler/src/server/type-signature.ts index f3cd261c501..a082408144c 100644 --- a/packages/compiler/src/server/type-signature.ts +++ b/packages/compiler/src/server/type-signature.ts @@ -120,9 +120,17 @@ function getOperationSignature(type: Operation, includeQualifier: boolean = true const parameters = [...type.parameters.properties.values()].map((p) => getModelPropertySignature(p, false /* includeQualifier */), ); - return `op ${getTypeName(type, { - includeInterfacePrefix: includeQualifier, - })}(${parameters.join(", ")}): ${getPrintableTypeName(type.returnType)}`; + if (includeQualifier) { + return `op ${getTypeName(type)}(${parameters.join(", ")}): ${getPrintableTypeName(type.returnType)}`; + } else { + let opName = type.name; + if (type.node && type.node.templateParameters.length > 0) { + // template + const params = type.node.templateParameters.map((t) => printIdentifier(t.id.sv)); + opName += `<${params.join(", ")}>`; + } + return `op ${opName}(${parameters.join(", ")}): ${getPrintableTypeName(type.returnType)}`; + } } function getInterfaceSignature(type: Interface, includeBody: boolean) { diff --git a/packages/compiler/test/server/get-hover.test.ts b/packages/compiler/test/server/get-hover.test.ts index 95d52286223..a2fea2025f9 100644 --- a/packages/compiler/test/server/get-hover.test.ts +++ b/packages/compiler/test/server/get-hover.test.ts @@ -432,7 +432,7 @@ model TestNs.Dog *Full Definition:* \`\`\`typespec -model TestNs.Dog { +model TestNs.Dog{ name: string; age: int16; tTag: string; From 2da12f07e1eb8c3785af89dac4b9a7f536123926 Mon Sep 17 00:00:00 2001 From: RodgeFu Date: Thu, 5 Jun 2025 12:00:42 +0800 Subject: [PATCH 8/9] Revert "revert the update in type-name-utils" This reverts commit 522d2228f684fcae6541c7196658997b1bba5647. --- .../compiler/src/core/helpers/type-name-utils.ts | 15 +++++++++++---- packages/compiler/src/server/type-signature.ts | 14 +++----------- packages/compiler/test/server/get-hover.test.ts | 2 +- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/packages/compiler/src/core/helpers/type-name-utils.ts b/packages/compiler/src/core/helpers/type-name-utils.ts index af9e970fd85..2581e60bbe0 100644 --- a/packages/compiler/src/core/helpers/type-name-utils.ts +++ b/packages/compiler/src/core/helpers/type-name-utils.ts @@ -20,6 +20,9 @@ import { printIdentifier } from "./syntax-utils.js"; export interface TypeNameOptions { namespaceFilter?: (ns: Namespace) => boolean; printable?: boolean; + // Whether to include the interface prefix for operations defined in interfaces. + // Default is true + includeInterfacePrefix?: boolean; } export function getTypeName(type: Type, options?: TypeNameOptions): string { @@ -234,10 +237,14 @@ function getOperationName(op: Operation, options: TypeNameOptions | undefined) { const params = op.node.templateParameters.map((t) => getIdentifierName(t.id.sv, options)); opName += `<${params.join(", ")}>`; } - const prefix = op.interface - ? getInterfaceName(op.interface, options) + "." - : getNamespacePrefix(op.namespace, options); - return `${prefix}${opName}`; + if (op.interface) { + return options?.includeInterfacePrefix === false + ? opName + : `${getInterfaceName(op.interface, options)}.${opName}`; + } else { + const prefix = getNamespacePrefix(op.namespace, options); + return `${prefix}${opName}`; + } } function getIdentifierName(name: string, options: TypeNameOptions | undefined) { diff --git a/packages/compiler/src/server/type-signature.ts b/packages/compiler/src/server/type-signature.ts index a082408144c..f3cd261c501 100644 --- a/packages/compiler/src/server/type-signature.ts +++ b/packages/compiler/src/server/type-signature.ts @@ -120,17 +120,9 @@ function getOperationSignature(type: Operation, includeQualifier: boolean = true const parameters = [...type.parameters.properties.values()].map((p) => getModelPropertySignature(p, false /* includeQualifier */), ); - if (includeQualifier) { - return `op ${getTypeName(type)}(${parameters.join(", ")}): ${getPrintableTypeName(type.returnType)}`; - } else { - let opName = type.name; - if (type.node && type.node.templateParameters.length > 0) { - // template - const params = type.node.templateParameters.map((t) => printIdentifier(t.id.sv)); - opName += `<${params.join(", ")}>`; - } - return `op ${opName}(${parameters.join(", ")}): ${getPrintableTypeName(type.returnType)}`; - } + return `op ${getTypeName(type, { + includeInterfacePrefix: includeQualifier, + })}(${parameters.join(", ")}): ${getPrintableTypeName(type.returnType)}`; } function getInterfaceSignature(type: Interface, includeBody: boolean) { diff --git a/packages/compiler/test/server/get-hover.test.ts b/packages/compiler/test/server/get-hover.test.ts index a2fea2025f9..95d52286223 100644 --- a/packages/compiler/test/server/get-hover.test.ts +++ b/packages/compiler/test/server/get-hover.test.ts @@ -432,7 +432,7 @@ model TestNs.Dog *Full Definition:* \`\`\`typespec -model TestNs.Dog{ +model TestNs.Dog { name: string; age: int16; tTag: string; From ec48d01b400a28057e1ad66a85fc1917f037e8d6 Mon Sep 17 00:00:00 2001 From: RodgeFu Date: Thu, 5 Jun 2025 12:22:42 +0800 Subject: [PATCH 9/9] update to add option nameOnly --- .../src/core/helpers/type-name-utils.ts | 19 ++++++++++--------- .../compiler/src/server/type-signature.ts | 2 +- .../compiler/test/server/get-hover.test.ts | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/compiler/src/core/helpers/type-name-utils.ts b/packages/compiler/src/core/helpers/type-name-utils.ts index 2581e60bbe0..8347eb789f1 100644 --- a/packages/compiler/src/core/helpers/type-name-utils.ts +++ b/packages/compiler/src/core/helpers/type-name-utils.ts @@ -20,9 +20,7 @@ import { printIdentifier } from "./syntax-utils.js"; export interface TypeNameOptions { namespaceFilter?: (ns: Namespace) => boolean; printable?: boolean; - // Whether to include the interface prefix for operations defined in interfaces. - // Default is true - includeInterfacePrefix?: boolean; + nameOnly?: boolean; } export function getTypeName(type: Type, options?: TypeNameOptions): string { @@ -138,7 +136,7 @@ export function getNamespaceFullName(type: Namespace, options?: TypeNameOptions) } function getNamespacePrefix(type: Namespace | undefined, options?: TypeNameOptions) { - if (type === undefined || isStdNamespace(type)) { + if (type === undefined || isStdNamespace(type) || options?.nameOnly === true) { return ""; } const namespaceFullName = getNamespaceFullName(type, options); @@ -215,6 +213,9 @@ function isInTypeSpecNamespace(type: Type & { namespace?: Namespace }): boolean } function getModelPropertyName(prop: ModelProperty, options: TypeNameOptions | undefined) { + if (options?.nameOnly === true) { + return prop.name; + } const modelName = prop.model ? getModelName(prop.model, options) : undefined; return `${modelName ?? "(anonymous model)"}.${prop.name}`; @@ -237,12 +238,12 @@ function getOperationName(op: Operation, options: TypeNameOptions | undefined) { const params = op.node.templateParameters.map((t) => getIdentifierName(t.id.sv, options)); opName += `<${params.join(", ")}>`; } - if (op.interface) { - return options?.includeInterfacePrefix === false - ? opName - : `${getInterfaceName(op.interface, options)}.${opName}`; + if (options?.nameOnly === true) { + return opName; } else { - const prefix = getNamespacePrefix(op.namespace, options); + const prefix = op.interface + ? getInterfaceName(op.interface, options) + "." + : getNamespacePrefix(op.namespace, options); return `${prefix}${opName}`; } } diff --git a/packages/compiler/src/server/type-signature.ts b/packages/compiler/src/server/type-signature.ts index f3cd261c501..f9d181c6686 100644 --- a/packages/compiler/src/server/type-signature.ts +++ b/packages/compiler/src/server/type-signature.ts @@ -121,7 +121,7 @@ function getOperationSignature(type: Operation, includeQualifier: boolean = true getModelPropertySignature(p, false /* includeQualifier */), ); return `op ${getTypeName(type, { - includeInterfacePrefix: includeQualifier, + nameOnly: !includeQualifier, })}(${parameters.join(", ")}): ${getPrintableTypeName(type.returnType)}`; } diff --git a/packages/compiler/test/server/get-hover.test.ts b/packages/compiler/test/server/get-hover.test.ts index 95d52286223..a2fea2025f9 100644 --- a/packages/compiler/test/server/get-hover.test.ts +++ b/packages/compiler/test/server/get-hover.test.ts @@ -432,7 +432,7 @@ model TestNs.Dog *Full Definition:* \`\`\`typespec -model TestNs.Dog { +model TestNs.Dog{ name: string; age: int16; tTag: string;