Repository navigation
[Swagger Linter Migration] DescriptionMustNotBeNodeName + DescriptiveDescriptionRequired -> documentation-required #5663
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d7b4a89
9bdd488
2c5214b
0a9455b
603330a
b6834e1
2068349
5e8dc59
208e3e6
7cd29bd
8a3c53e
105eb1b
e4e52da
4153348
4868be2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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. | ||
| 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, | ||
| }); | ||
| }; | ||
| } |
| 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)); | ||
|
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(); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.