Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .chronus/changes/no-uninformative-doc-2026-10-10.md
Comment thread
msyyc marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@azure-tools/typespec-azure-core"
---

Strengthen `documentation-required` to reject explicitly empty or whitespace-only documentation and descriptions that repeat a declaration's effective name or the placeholder `description`. Explicit-documentation checks also cover namespaces, interfaces, scalars, inline properties, union variants, and declarations exempt from missing-doc requirements when their documentation is exposed by the compiler. Operation descriptions are compared with both the source operation name and effective HTTP verb. Missing-documentation requirements and exemptions are unchanged. The existing data-plane and resource-manager rulesets already enable this rule.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@azure-tools/typespec-samples"
---

Update Widget Manager color and ARM library provisioning-state descriptions to satisfy documentation-required, and regenerate sample snapshots.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this redundant internal changeset. This PR already has a non-internal entry for @azure-tools/typespec-azure-core: .chronus/changes/no-uninformative-doc-2026-10-10.md (fix).

Review-driven cleanup, test changes, helper reuse, formatting, and syncing with main do not need extra internal entries just to satisfy Chronus. Keep the final shipped behavior in the existing feature/fix entry instead of accumulating unpublished review-history noise. Preserve separate registration entries for packages that have no non-internal entry.

Same feedback as PR 5502.

Copilot-generated comment.

packages:
- "@azure-tools/typespec-azure-core"
---

Avoid resolving HTTP operations when documentation is absent, normalized-empty, or already matches the source name; simplify fixed documentation validator wiring.
10 changes: 5 additions & 5 deletions packages/samples/specs/data-plane/widget-manager/main.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,19 @@ enum Versions {
union WidgetColor {
string,

@doc("Black")
@doc("The widget is black.")
Black: "Black",

@doc("White")
@doc("The widget is white.")
White: "White",

@doc("Red")
@doc("The widget is red.")
Red: "Red",

@doc("Green")
@doc("The widget is green.")
Green: "Green",

@doc("Blue")
@doc("The widget is blue.")
Blue: "Blue",
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ union SystemEnvelopeProvisioningState {
string,
ResourceProvisioningState,

@doc("Working")
@doc("The resource provisioning operation is in progress.")
Working: "Working",
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2249,27 +2249,27 @@
{
"name": "Black",
"value": "Black",
"description": "Black"
"description": "The widget is black."
},
{
"name": "White",
"value": "White",
"description": "White"
"description": "The widget is white."
},
{
"name": "Red",
"value": "Red",
"description": "Red"
"description": "The widget is red."
},
{
"name": "Green",
"value": "Green",
"description": "Green"
"description": "The widget is green."
},
{
"name": "Blue",
"value": "Blue",
"description": "Blue"
"description": "The widget is blue."
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1982,7 +1982,7 @@
{
"name": "Working",
"value": "Working",
"description": "Working"
"description": "The resource provisioning operation is in progress."
}
]
},
Expand Down
2 changes: 1 addition & 1 deletion packages/typespec-azure-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Available ruleSets:
| [`@azure-tools/typespec-azure-core/no-rest-library-interfaces`](https://azure.github.io/typespec-azure/docs/libraries/azure-core/rules/no-rest-library-interfaces) | Resource interfaces from the TypeSpec.Rest.Resource library are incompatible with Azure.Core. |
| [`@azure-tools/typespec-azure-core/no-unknown`](https://azure.github.io/typespec-azure/docs/libraries/azure-core/rules/no-unknown) | Azure services must not have properties of type `unknown`. |
| [`@azure-tools/typespec-azure-core/bad-record-type`](https://azure.github.io/typespec-azure/docs/libraries/azure-core/rules/bad-record-type) | Identify bad record definitions. |
| [`@azure-tools/typespec-azure-core/documentation-required`](https://azure.github.io/typespec-azure/docs/libraries/azure-core/rules/documentation-required) | Require documentation over enums, models, and operations. |
| [`@azure-tools/typespec-azure-core/documentation-required`](https://azure.github.io/typespec-azure/docs/libraries/azure-core/rules/documentation-required) | Require meaningful documentation over enums, models, and operations. |
| [`@azure-tools/typespec-azure-core/key-visibility-required`](https://azure.github.io/typespec-azure/docs/libraries/azure-core/rules/key-visibility-required) | Key properties need to have a Lifecycle visibility setting. |
| [`@azure-tools/typespec-azure-core/response-schema-problem`](https://azure.github.io/typespec-azure/docs/libraries/azure-core/rules/response-schema-problem) | Warn about operations having multiple non-error response schemas. |
| [`@azure-tools/typespec-azure-core/rpc-operation-request-body`](https://azure.github.io/typespec-azure/docs/libraries/azure-core/rules/rpc-operation-request-body) | Warning for RPC body problems. |
Expand Down
22 changes: 22 additions & 0 deletions packages/typespec-azure-core/src/rules/documentation-empty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { getDoc, type LinterRuleContext } from "@typespec/compiler";
import { type DocumentationTarget, type documentationMessages } from "./documentation.js";

export function createEmptyDocumentationValidator(
context: LinterRuleContext<typeof documentationMessages>,
): (target: DocumentationTarget) => void {
return (target) => {
const doc = getDoc(context.program, target);
if (doc === undefined) {
return;
}

if (doc.trim().length > 0) {
return;
}

context.reportDiagnostic({
messageId: "empty",
target,
});
};
}
133 changes: 133 additions & 0 deletions packages/typespec-azure-core/src/rules/documentation-names.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import {
getDoc,
ignoreDiagnostics,
isArrayModelType,
resolveEncodedName,
type LinterRuleContext,
} from "@typespec/compiler";
import {
getHeaderFieldName,
getHttpOperation,
getHttpPart,
getPathParamName,
getQueryParamName,
isStatusCode,
} from "@typespec/http";
import { type DocumentationTarget, type documentationMessages } from "./documentation.js";

export function createDocumentationNameValidator(
context: LinterRuleContext<typeof documentationMessages>,
): (target: DocumentationTarget) => void {
const checkTarget = (
target: Parameters<typeof getDoc>[1],
nodeName: string | undefined = "name" in target && typeof target.name === "string"
? target.name
: undefined,
) => {
if (
!nodeName &&
(target.kind === "Model" ||
target.kind === "Scalar" ||
target.kind === "Enum" ||
target.kind === "EnumMember" ||
target.kind === "Union")
) {
return;
}
const doc = getDoc(context.program, target);
if (doc === undefined) {
return;
}

const normalizedDescription = normalize(doc);
if (normalizedDescription.length === 0) {
return;
}

const normalizedNodeName = nodeName ? normalize(nodeName) : undefined;
if (
normalizedDescription !== "description" &&
(normalizedNodeName === undefined || normalizedNodeName !== normalizedDescription)
) {
return;
}

context.reportDiagnostic({
messageId: "uninformative",
target,
format: {
name: nodeName ?? "description",
description: doc,
},
});
};

return (target) => {
switch (target.kind) {
case "Model":
case "Scalar":
case "Enum":
case "EnumMember":
case "Union":
checkTarget(target);
return;
case "UnionVariant":
checkTarget(
target,
typeof target.name === "string"
? target.name
: target.type.kind === "String"
? target.type.value
: undefined,
);
return;
case "Operation": {
const doc = getDoc(context.program, target);
const normalizedDescription = doc === undefined ? undefined : normalize(doc);
if (!normalizedDescription) {
return;
}
if (normalizedDescription === normalize(target.name)) {
checkTarget(target);
return;
}
// HTTP validation owns resolution diagnostics; this rule only checks documentation.
const httpOperation = ignoreDiagnostics(getHttpOperation(context.program, target));
Comment thread
msyyc marked this conversation as resolved.
checkTarget(target, httpOperation.verb);
return;
}
case "ModelProperty": {
if (isStatusCode(context.program, target)) {
return;
}

const emittedName =
getPathParamName(context.program, target) ??
getQueryParamName(context.program, target) ??
getHeaderFieldName(context.program, target);

if (emittedName !== undefined) {
checkTarget(target, emittedName);
return;
}

const partType =
target.type.kind === "Model" && isArrayModelType(target.type)
? target.type.indexer.value
: target.type;
const httpPart = getHttpPart(context.program, partType);
checkTarget(
target,
httpPart
? (httpPart.options.name ?? target.name)
: resolveEncodedName(context.program, target, "application/json"),
);
return;
}
}
};
}

function normalize(value: string): string {
return value.trim().replace(/\./g, "").toLowerCase();
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,51 @@
Enums, models, operations, and their members/properties should have documentation. Use doc comments (`/** */`) to provide descriptions.
Enums, models, operations, and their members/properties should have meaningful, nonblank documentation. Use doc comments (`/** */`) to provide descriptions.

This strengthens the existing `documentation-required` rule, already enabled in the data-plane and resource-manager rulesets; no separate opt-in rule is needed. Empty and whitespace-only strings do not satisfy existing documentation requirements. Missing-documentation scopes and exemptions are unchanged.

## Impact

- **Area:** API, SDK

Missing documentation produces poor SDK reference docs and API documentation.

## Descriptive documentation

Explicit descriptions on models, scalars, enums and members, unions and variants, operations, and properties must not repeat their effective name or the placeholder `description`. Comparisons trim whitespace, ignore periods, and ignore case.

- Operations are compared with **both their source operation name and their effective HTTP verb**, whether explicit or inferred.
- Ordinary properties use their JSON encoded name. Header, path, and query parameters use their HTTP name.
- Multipart properties use the authored part name, falling back to the property name (including array parts).
- Enum members use the member name, not its serialized value. Named union variants use their name; unnamed string variants use their literal value. Other unnamed variants are checked only for placeholder text.
- HTTP status-code properties are exempt from name comparisons.

These explicit-description checks also apply to scalars and inline properties, and to explicitly documented declarations otherwise exempt from missing-doc requirements. They do not require new documentation on those declarations. Existing rule users may now see quality warnings without changing their configuration.

```tsp
// Incorrect: repeating the source name is not a description, even with @get.
@get @doc("read") op read(): string;
```

```tsp
// Correct.
@get @doc("Reads the current widget.") op read(): string;
```

## LintDiff Equivalent

This rule corresponds to the LintDiff rule [SummaryOrDescription](https://github.com/Azure/azure-openapi-validator/blob/main/docs/operation-summary-or-description.md).

### Consolidated documentation coverage

The checks formerly proposed as `no-uninformative-doc` and `no-empty-doc` are part of the existing `documentation-required` rule, not separate opt-in rules.

| Migrated lintdiff rule | Coverage in this existing TypeSpec rule |
| ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [SummaryOrDescription](https://github.com/Azure/azure-openapi-validator/blob/main/docs/operation-summary-or-description.md) | Existing documentation requirements for types, operations, and members, with the existing missing-documentation scopes and exemptions retained. |
| [DescriptionMustNotBeNodeName](https://github.com/Azure/azure-openapi-validator/blob/main/docs/description-must-not-be-node-name.md) | Rejects explicit descriptions that repeat effective declaration/member names or placeholder text, using the JSON/HTTP/multipart/member naming rules above. Operations are checked against both their source name and HTTP verb. |
| [DescriptiveDescriptionRequired](https://github.com/Azure/azure-openapi-validator/blob/main/docs/descriptive-description-required.md) | Rejects empty or whitespace-only explicit documentation, including on declarations exempt from missing-doc requirements when the compiler exposes their documentation. |

The operation-source-name comparison intentionally strengthens the migrated HTTP-verb comparison. Explicit-description checks do not newly require documentation on declarations otherwise exempt from missing-doc requirements. These mappings concern compiler-exposed documentation metadata, not acceptance of emitted Swagger summary/description fields.

#### ❌ Incorrect

Model without documentation:
Expand Down Expand Up @@ -58,9 +94,31 @@ enum PetKind {
```

:::note
Version enums and discriminator enums/unions are exempt from this rule as they are considered self-documenting.
Version enums and discriminator enums/unions are exempt from missing-documentation requirements as they are considered self-documenting. Explicit descriptions on these declarations are still checked for blank or uninformative content.
:::

## Explicit documentation

Every explicit `@doc` string must contain non-whitespace content. In addition to the existing required targets, this includes namespaces, interfaces, scalars, inline model properties, all union variants, and explicitly documented declarations exempt from missing-doc requirements. Enum members are checked individually.

Checks use documentation metadata exposed by the compiler. Uninstantiated template declarations whose documentation is not exposed remain exempt.

Documentation is not newly required on these additional targets: absent documentation remains allowed where it was previously allowed. However, an explicit blank description now warns for existing rule users without a configuration change. Whitespace-only strings previously passed the rule's truthiness check.

```tsp
// Incorrect.
@doc(" \t")
scalar Label extends string;
```

```tsp
// Correct.
@doc("A display label shown to users.")
scalar Label extends string;
```

This intentionally strengthens the previous truthiness check, which accepted whitespace-only strings. It uses compiler-exposed documentation metadata rather than inspecting emitted Swagger fields; uninstantiated template documentation unavailable from the compiler remains outside its coverage.

## Suppression

Do not suppress. Add a `/** */` doc comment or an `@doc` decorator.
Loading
Loading