Skip to content

File tree

11 files changed

+237
-21
lines changed

11 files changed

+237
-21
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
3+
changeKind: feature
4+
packages:
5+
- "@typespec/compiler"
6+
---
7+
8+
Add support for `@prop` doc comment tag to describe model properties

grammars/typespec.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@
228228
},
229229
"doc-comment-param": {
230230
"name": "comment.block.tsp",
231-
"match": "(?x)((@)(?:param|template))\\s+(\\b[_$[:alpha:]][_$[:alnum:]]*\\b|`(?:[^`\\\\]|\\\\.)*`)\\b",
231+
"match": "(?x)((@)(?:param|template|prop))\\s+(\\b[_$[:alpha:]][_$[:alnum:]]*\\b|`(?:[^`\\\\]|\\\\.)*`)\\b",
232232
"captures": {
233233
"1": {
234234
"name": "keyword.tag.tspdoc"

packages/compiler/src/core/checker.ts

+42-16
Original file line numberDiff line numberDiff line change
@@ -2501,8 +2501,9 @@ export function createChecker(program: Program): Checker {
25012501

25022502
if (parameterModelSym?.members) {
25032503
const members = getOrCreateAugmentedSymbolTable(parameterModelSym.members);
2504+
const paramDocs = extractParamDocs(node);
25042505
for (const [name, memberSym] of members) {
2505-
const doc = extractParamDoc(node, name);
2506+
const doc = paramDocs[name];
25062507
if (doc) {
25072508
docFromCommentForSym.set(memberSym, doc);
25082509
}
@@ -3844,6 +3845,18 @@ export function createChecker(program: Program): Checker {
38443845
derivedModels: [],
38453846
});
38463847
linkType(links, type, mapper);
3848+
3849+
if (node.symbol.members) {
3850+
const members = getOrCreateAugmentedSymbolTable(node.symbol.members);
3851+
const propDocs = extractPropDocs(node);
3852+
for (const [name, memberSym] of members) {
3853+
const doc = propDocs[name];
3854+
if (doc) {
3855+
docFromCommentForSym.set(memberSym, doc);
3856+
}
3857+
}
3858+
}
3859+
38473860
const isBase = checkModelIs(node, node.is, mapper);
38483861

38493862
if (isBase) {
@@ -3859,7 +3872,8 @@ export function createChecker(program: Program): Checker {
38593872

38603873
if (isBase) {
38613874
for (const prop of isBase.properties.values()) {
3862-
const newProp = cloneType(prop, {
3875+
const memberSym = getMemberSymbol(node.symbol, prop.name)!;
3876+
const newProp = cloneTypeForSymbol(memberSym, prop, {
38633877
sourceProperty: prop,
38643878
model: type,
38653879
});
@@ -5125,7 +5139,8 @@ export function createChecker(program: Program): Checker {
51255139
prop: ModelPropertyNode,
51265140
mapper: TypeMapper | undefined
51275141
): ModelProperty {
5128-
const symId = getSymbolId(getSymbolForMember(prop)!);
5142+
const sym = getSymbolForMember(prop)!;
5143+
const symId = getSymbolId(sym);
51295144
const links = getSymbolLinksForMember(prop);
51305145

51315146
if (links && links.declaredType && mapper === undefined) {
@@ -5172,14 +5187,9 @@ export function createChecker(program: Program): Checker {
51725187
linkMapper(type, mapper);
51735188

51745189
if (!parentTemplate || shouldCreateTypeForTemplate(parentTemplate, mapper)) {
5175-
if (
5176-
prop.parent?.parent?.kind === SyntaxKind.OperationSignatureDeclaration &&
5177-
prop.parent.parent.parent?.kind === SyntaxKind.OperationStatement
5178-
) {
5179-
const doc = extractParamDoc(prop.parent.parent.parent, type.name);
5180-
if (doc) {
5181-
type.decorators.unshift(createDocFromCommentDecorator("self", doc));
5182-
}
5190+
const docComment = docFromCommentForSym.get(sym);
5191+
if (docComment) {
5192+
type.decorators.unshift(createDocFromCommentDecorator("self", docComment));
51835193
}
51845194
finishType(type);
51855195
}
@@ -8442,18 +8452,34 @@ function extractReturnsDocs(type: Type): {
84428452
return result;
84438453
}
84448454

8445-
function extractParamDoc(node: OperationStatementNode, paramName: string): string | undefined {
8455+
function extractParamDocs(node: OperationStatementNode): Record<string, string> {
84468456
if (node.docs === undefined) {
8447-
return undefined;
8457+
return {};
84488458
}
8459+
const paramDocs: Record<string, string> = {};
84498460
for (const doc of node.docs) {
84508461
for (const tag of doc.tags) {
8451-
if (tag.kind === SyntaxKind.DocParamTag && tag.paramName.sv === paramName) {
8452-
return getDocContent(tag.content);
8462+
if (tag.kind === SyntaxKind.DocParamTag) {
8463+
paramDocs[tag.paramName.sv] = getDocContent(tag.content);
84538464
}
84548465
}
84558466
}
8456-
return undefined;
8467+
return paramDocs;
8468+
}
8469+
8470+
function extractPropDocs(node: ModelStatementNode): Record<string, string> {
8471+
if (node.docs === undefined) {
8472+
return {};
8473+
}
8474+
const propDocs: Record<string, string> = {};
8475+
for (const doc of node.docs) {
8476+
for (const tag of doc.tags) {
8477+
if (tag.kind === SyntaxKind.DocPropTag) {
8478+
propDocs[tag.propName.sv] = getDocContent(tag.content);
8479+
}
8480+
}
8481+
}
8482+
return propDocs;
84578483
}
84588484

84598485
function getDocContent(content: readonly DocContent[]) {

packages/compiler/src/core/messages.ts

+1
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ const diagnostics = {
252252
default: "Invalid identifier.",
253253
tag: "Invalid tag name. Use backticks around code if this was not meant to be a tag.",
254254
param: "Invalid parameter name.",
255+
prop: "Invalid property name.",
255256
templateParam: "Invalid template parameter name.",
256257
},
257258
},

packages/compiler/src/core/parser.ts

+30-3
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ import {
3434
DocErrorsTagNode,
3535
DocNode,
3636
DocParamTagNode,
37+
DocPropTagNode,
3738
DocReturnsTagNode,
3839
DocTag,
3940
DocTemplateTagNode,
41+
DocTextNode,
4042
DocUnknownTagNode,
4143
EmptyStatementNode,
4244
EnumMemberNode,
@@ -2891,6 +2893,8 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa
28912893
return parseDocParamLikeTag(pos, tagName, SyntaxKind.DocParamTag, "param");
28922894
case "template":
28932895
return parseDocParamLikeTag(pos, tagName, SyntaxKind.DocTemplateTag, "templateParam");
2896+
case "prop":
2897+
return parseDocPropTag(pos, tagName);
28942898
case "return":
28952899
case "returns":
28962900
return parseDocSimpleTag(pos, tagName, SyntaxKind.DocReturnsTag);
@@ -2911,9 +2915,7 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa
29112915
kind: ParamLikeTag["kind"],
29122916
messageId: keyof CompilerDiagnostics["doc-invalid-identifier"]
29132917
): ParamLikeTag {
2914-
const name = parseDocIdentifier(messageId);
2915-
parseOptionalHyphenDocParamLikeTag();
2916-
const content = parseDocContent();
2918+
const { name, content } = parseDocParamLikeTagInternal(messageId);
29172919

29182920
return {
29192921
kind,
@@ -2924,6 +2926,27 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa
29242926
};
29252927
}
29262928

2929+
function parseDocPropTag(pos: number, tagName: IdentifierNode): DocPropTagNode {
2930+
const { name, content } = parseDocParamLikeTagInternal("prop");
2931+
2932+
return {
2933+
kind: SyntaxKind.DocPropTag,
2934+
tagName,
2935+
propName: name,
2936+
content,
2937+
...finishNode(pos),
2938+
};
2939+
}
2940+
2941+
function parseDocParamLikeTagInternal(
2942+
messageId: keyof CompilerDiagnostics["doc-invalid-identifier"]
2943+
): { name: IdentifierNode; content: DocTextNode[] } {
2944+
const name = parseDocIdentifier(messageId);
2945+
parseOptionalHyphenDocParamLikeTag();
2946+
const content = parseDocContent();
2947+
return { name, content };
2948+
}
2949+
29272950
/**
29282951
* Handles the optional hyphen in param-like documentation comment tags.
29292952
*
@@ -3669,6 +3692,10 @@ export function visitChildren<T>(node: Node, cb: NodeCallback<T>): T | undefined
36693692
return (
36703693
visitNode(cb, node.tagName) || visitNode(cb, node.paramName) || visitEach(cb, node.content)
36713694
);
3695+
case SyntaxKind.DocPropTag:
3696+
return (
3697+
visitNode(cb, node.tagName) || visitNode(cb, node.propName) || visitEach(cb, node.content)
3698+
);
36723699
case SyntaxKind.DocReturnsTag:
36733700
case SyntaxKind.DocErrorsTag:
36743701
case SyntaxKind.DocUnknownTag:

packages/compiler/src/core/types.ts

+7
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,7 @@ export enum SyntaxKind {
949949
Doc,
950950
DocText,
951951
DocParamTag,
952+
DocPropTag,
952953
DocReturnsTag,
953954
DocErrorsTag,
954955
DocTemplateTag,
@@ -1903,6 +1904,7 @@ export type DocTag =
19031904
| DocReturnsTagNode
19041905
| DocErrorsTagNode
19051906
| DocParamTagNode
1907+
| DocPropTagNode
19061908
| DocTemplateTagNode
19071909
| DocUnknownTagNode;
19081910
export type DocContent = DocTextNode;
@@ -1925,6 +1927,11 @@ export interface DocParamTagNode extends DocTagBaseNode {
19251927
readonly paramName: IdentifierNode;
19261928
}
19271929

1930+
export interface DocPropTagNode extends DocTagBaseNode {
1931+
readonly kind: SyntaxKind.DocPropTag;
1932+
readonly propName: IdentifierNode;
1933+
}
1934+
19281935
export interface DocTemplateTagNode extends DocTagBaseNode {
19291936
readonly kind: SyntaxKind.DocTemplateTag;
19301937
readonly paramName: IdentifierNode;

packages/compiler/src/formatter/print/printer.ts

+1
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ export function printNode(
362362
return printDoc(path as AstPath<DocNode>, options, print);
363363
case SyntaxKind.DocText:
364364
case SyntaxKind.DocParamTag:
365+
case SyntaxKind.DocPropTag:
365366
case SyntaxKind.DocTemplateTag:
366367
case SyntaxKind.DocReturnsTag:
367368
case SyntaxKind.DocErrorsTag:

packages/compiler/src/server/classify.ts

+4
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,10 @@ export function getSemanticTokens(ast: TypeSpecScriptNode): SemanticToken[] {
292292
classifyDocTag(node.tagName, SemanticTokenKind.DocCommentTag);
293293
classifyOverride(node.paramName, SemanticTokenKind.Variable);
294294
break;
295+
case SyntaxKind.DocPropTag:
296+
classifyDocTag(node.tagName, SemanticTokenKind.DocCommentTag);
297+
classifyOverride(node.propName, SemanticTokenKind.Variable);
298+
break;
295299
case SyntaxKind.DocReturnsTag:
296300
classifyDocTag(node.tagName, SemanticTokenKind.DocCommentTag);
297301
break;

packages/compiler/src/server/tmlanguage.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -191,13 +191,14 @@ const blockComment: BeginEndRule = {
191191
const docCommentParam: MatchRule = {
192192
key: "doc-comment-param",
193193
scope: "comment.block.tsp",
194-
match: `(?x)((@)(?:param|template))\\s+(${identifier})\\b`,
194+
match: `(?x)((@)(?:param|template|prop))\\s+(${identifier})\\b`,
195195
captures: {
196196
"1": { scope: "keyword.tag.tspdoc" },
197197
"2": { scope: "keyword.tag.tspdoc" },
198198
"3": { scope: "variable.name.tsp" },
199199
},
200200
};
201+
201202
const docCommentReturn: MatchRule = {
202203
key: "doc-comment-return-tag",
203204
scope: "comment.block.tsp",
@@ -207,6 +208,7 @@ const docCommentReturn: MatchRule = {
207208
"2": { scope: "keyword.tag.tspdoc" },
208209
},
209210
};
211+
210212
const docCommentUnknownTag: MatchRule = {
211213
key: "doc-comment-unknown-tag",
212214
scope: "comment.block.tsp",

0 commit comments

Comments
 (0)