Skip to content

[Swagger Linter Migration] DescriptionMustNotBeNodeName + DescriptiveDescriptionRequired -> documentation-required - #5663

Open
Yuchao Yan (msyyc) wants to merge 13 commits into
mainfrom
promote-lintdiff-description-must-not-be-node-name
Open

Yuchao Yan (msyyc) wants to merge 13 commits into
mainfrom
promote-lintdiff-description-must-not-be-node-name

Conversation

@msyyc

@msyyc Yuchao Yan (msyyc) commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

Summary

Consolidate DescriptionMustNotBeNodeName and DescriptiveDescriptionRequired into the existing @azure-tools/typespec-azure-core/documentation-required rule. This PR absorbs #5674, including its implementation and regression coverage, rather than adding separate no-uninformative-doc or no-empty-doc public rules.

Implemented in merge commit e4e52dac5, preserving the histories of both promotion branches.

This follows the combined-migration presentation used in #5294 and #5423. Further review of both documentation-quality checks should continue here. Closing #5674 as superseded does not imply CI or review approval; its branch and review history are retained, and existing review threads are not resolved by this consolidation.

Combined behavior and legacy-rule mapping

Legacy Swagger linter Native coverage
SummaryOrDescription Existing missing-documentation requirements for types, operations, and members, with their existing scopes and exemptions retained.
DescriptionMustNotBeNodeName Reject explicit descriptions that repeat effective names or the placeholder description, ignoring surrounding whitespace, periods, and case. Operations are checked against both source names and effective HTTP verbs.
DescriptiveDescriptionRequired Reject explicitly empty or whitespace-only documentation, including compiler-exposed documentation on declarations exempt from missing-doc requirements.
  • HTTP path/query/header properties use wire names; ordinary properties use JSON encoded names; multipart properties use authored part names with source-name fallback.
  • Enum members and named union variants use member names; unnamed string variants use their values. HTTP status-code properties are exempt from name comparisons, not explicit blank-documentation checks.
  • Blank checks also cover namespaces, interfaces, scalars, inline properties, union variants, and explicitly documented declarations exempt from missing-doc requirements. Uninstantiated template documentation unavailable from the compiler remains outside coverage.
  • Absent documentation is not newly required on exempt declarations. Required targets receive one blank diagnostic, not duplicate findings from the combined validators.
  • The existing data-plane and resource-manager rulesets already enable documentation-required. This intentionally strengthens documentation quality for existing users; it is not a disabled-by-default opt-in migration.

The native contract uses compiler documentation metadata, not emitted Swagger summary/description occurrences. The legacy truthy selector's empty-string discrepancy is intentionally not reproduced, and exact emitted diagnostic parity is not claimed.

Consolidation and provenance

Check Immutable reviewed lintdiff source Separate promotion incorporated here
DescriptionMustNotBeNodeName #5662 at 3753a319 #5663 at 105eb1be8, including the sample CI fix.
DescriptiveDescriptionRequired #5672 at 72ab2239 #5674 at 8a3c53e2c.

The source lintdiff implementations are unchanged. Both validators share the same authored-documentation traversal and retain the missing-doc exemptions. The combined tests now expect name-only and placeholder descriptions to produce uninformative, not empty, findings; an unfiltered regression verifies the combined rule reports blank and uninformative documentation together.

The user-facing rule documentation has one combined legacy-coverage table, and Azure Core has one consolidated fix changelog entry. Widget Manager colors and the ARM library provisioning state have meaningful descriptions, with their OpenAPI snapshots regenerated.

Validation

  • Reproduced the merged test conflict before correcting the expectations.
  • Combined documentation suites: 85 passed.
  • Full affected-package run: 62 files passed; 576 tests passed and 2 existing skips, covering Azure Core, Azure rulesets, sample snapshots, and playground samples.
  • Full monorepo build, including the website and engineering type-check: passed.
  • Full repository lint and formatting: passed.
  • Chronus status and diff hygiene: passed.

These are local results, not a claim that fresh GitHub CI or review has completed. The separate REST API Specs integration check has adoption failures from strengthening an already-enabled rule; no downstream specification changes or diagnostic suppressions are included here.

Historical #5663 promotion notes before consolidation (superseded where noted above)

Current implementation after review

Uninformative-documentation checks are consolidated into existing documentation-required. Operations are compared against both their source name and HTTP verb; emitted-name, multipart and member behavior is retained. Existing missing-doc exemptions remain unchanged. The separate opt-in rule is removed: this intentionally strengthens documentation quality. Compatibility with the matching #5674 consolidation was verified.

Implemented in 5e8dc5927. Review threads are left unresolved.

Historical promotion notes and earlier validation (superseded where noted above)

Summary

Promote Swagger DescriptionMustNotBeNodeName to @azure-tools/typespec-azure-core/no-uninformative-doc.

The rule warns when explicit documentation repeats the name it describes or uses the placeholder description. It is registered in Azure Core and explicitly disabled by default in both the data-plane and resource-manager rulesets, so existing specs do not acquire new warnings automatically.

This PR includes the native rule, regression tests, documentation, generated linter references, ruleset registration, and separate Chronus entries for the Core feature and internal ruleset registration.

Original Swagger linter

The legacy rule recursively examines objects containing string descriptions. It trims whitespace, removes periods, and compares case-insensitively against an explicit name or the object's path key; it also rejects the placeholder description. Its executable registration is ARM.

Source TypeSpec lintdiff rule

Item Value
Development PR #5662
Local rule description-must-not-be-node-name
Immutable reviewed source 3753a319f32f3926005536029f9ff32b94f3c905
Source implementation description-must-not-be-node-name.ts
Migration evidence migration.md

The promoted create(context) implementation and normalization function are identical to the reviewed source. Promotion changes the public rule/export name, documentation metadata, package registration, native testing setup, and rulesets. The source was not modified during promotion.

Destination analysis

Azure Core owns this shared documentation-quality concern: the implementation uses compiler and HTTP APIs, without ARM resource metadata or SDK-name overrides. Core's existing documentation-required rule checks missing documentation, not repetitive or placeholder text. ARM would unnecessarily restrict the native rule's audience; TCGC is not appropriate because this is not an SDK-naming policy.

How the promoted TypeSpec linter works

  • Reads explicit @doc text and doc comments through compiler getDoc; normalizes surrounding whitespace, periods, and case.
  • Checks named models, scalars, enums, unions, enum members, properties, union variants, and operations.
  • Compares path/query/header properties against HTTP wire names. Ordinary non-part properties use JSON encoded names, falling back to source names.
  • Compares multipart properties against authored HttpPart names, including repeated-part arrays. Unnamed parts use the source property name; JSON encoding does not rename multipart parts.
  • Compares operations against their effective HTTP verb, including inferred GET and POST. HTTP resolution diagnostics remain owned by HTTP validation.
  • Compares named union variants against their names and unnamed string variants against their literal values. Other unnamed variants are checked only for placeholder text.
  • Ignores missing or normalized-empty documentation and status-code properties. Key properties are not exempt. Diagnostics target authored semantic declarations.

For example, @doc("Ready") Ready: "available" warns, while @doc("available") Ready: "available" does not.

The production rule does not emit or read Swagger, predict generated schema names, project API versions, or add emitter, OpenAPI, ARM, or TCGC dependencies.

Fixture-to-native test mapping

All 15 source fixtures are represented in the 40 native test cases. Source fixtures are pinned under DescriptionMustNotBeNodeName; native tests exercise the compiler/HTTP predicates directly rather than copying snapshots or ARM template scaffolding.

Source fixture(s) Native coverage
description-matches-name Property-name equality and placeholder text
descriptive-docs Meaningful documentation
literal-description Placeholder normalization
model-description-matches-model-name Model-name equality; whitespace, periods, and case
operation-description-matches-http-verb Explicit HTTP verbs
operation-description-matches-typespec-name-only Source-operation-name compliance
parameter-description-matches-emitted-name, parameter-description-matches-source-name-only HTTP wire names and source-name-only controls
property-description-matches-property-name Ordinary property-name equality
member-and-key-documentation Enum/union member names versus literal values; key properties
inferred-http-verbs Effective inferred GET/POST and explicit-verb precedence
json-encoded-property, json-encoded-source-name-only JSON names and source-name-only compliance
multipart-authored-names, multipart-source-name-only Direct/custom/array/repeated parts; authored names and compliant controls

Additional native coverage includes scalar/enum/union names, doc comments, query/header metadata, unnamed variants, missing/empty docs, status-code exemption, exact diagnostic locations, multipart JSON-name isolation, and both published documentation examples.

Migration evidence

The source validation passed 47 native tests, 15 strict fixtures, and five affected fixture groups. The final full corpus run attempted 468 projects, successfully compiled 462, and retained six documented compilation exclusions. The final target-rule diagnostic multiset was unchanged at 8,499; generated corpus outputs were restored and are not included in this PR.

The pinned migration note records project overlap, selected-version attribution, one-sided findings, outliers, and limitations. Swagger counts emitted occurrences; TypeSpec diagnostics describe authored semantic declarations and instances. Repeated roots/versions, inline copies, qualified schema names, compatibility or unused types, version selection, and constant route parameters explain population differences.

The native documentation-quality contract is covered. Corpus overlap and counts are not proof of arbitrary executable Swagger parity.

Validation and review

Required promotion checks passed:

  • 40 focused native tests, including incorrect/correct documentation examples.
  • Full Azure Core suite: 474 passed, 2 existing skips across 58 test files.
  • 6 rulesets tests, including disabled-by-default registration.
  • Dependency-closure/package builds, package lint, scoped formatting, and diff hygiene.
  • Documentation regeneration and formatting of authored/generated guidance.
  • Chronus status for the Core feature and internal ruleset-registration entries.

The two existing skips are the unrelated @example and @useRef groups in no-openapi.test.ts. Documentation regeneration retains existing missing-diagnostic-documentation warnings outside this rule. No new tests are skipped. A website build/full-repository wrapper was not part of the targeted promotion checks.

Fresh independent local reviews were clean. The final source review completed after 2 Balanced Copilot rounds at 3753a319; the final promotion review completed after 1 Balanced round at 2c5214be. Independent refetches confirmed zero review-specific comments and zero unresolved Copilot threads on both final heads. The inferred-verb, JSON-name, and multipart findings are implemented and resolved.

Promotion sync policy

Source semantics remain immutable during promotion. Any future confirmed source-semantic defect must be repaired and reviewed in lintdiff first, then promoted from that exact reviewed commit; it must not be fixed only in the official copy.

heavy

Historical #5674 promotion notes before consolidation (superseded where noted above)

Current implementation after review

Empty/whitespace explicit documentation is checked by existing documentation-required, preserving its missing-doc exemptions. The separate no-empty-doc rule/registration is removed and semantic coverage is transferred. This intentionally strengthens documentation quality. Compatibility with the matching #5663 consolidation was verified.

Implemented in 20683492f. Review threads are left unresolved.

Historical promotion notes and earlier validation (superseded where noted above)

Original Swagger linter

The original rule:

  • Selects description fields in unresolved OpenAPI 2 documents.
  • Ignores null and non-string inputs in its function.
  • Reports selected strings whose trim() result is empty, at their selected JSON paths.
  • Accepts nonblank text without judging prose quality.

How the Swagger linter works

Spectral recursively selects objects using $..[?(@object() && @.description)].description, then applies the blank-string check. This truthy selector skips missing, empty-string, null, and numeric descriptions before the function runs; whitespace strings are selected and rejected. That executable discrepancy is not copied into the native rule. Emitted occurrences and semantic TypeSpec declarations are different diagnostic populations, so exact Swagger diagnostic parity is intentionally partial.

Source TypeSpec lintdiff rule

  • Validator ID: DescriptiveDescriptionRequired.
  • Local rule: descriptive-description-required.
  • Canonical validator slug: descriptive-description-required.
  • Source repository: Azure/typespec-azure; source draft #5672.
  • Source branch: lintdiff-descriptive-description-required.
  • Immutable, cleanly reviewed and pushed source commit: 72ab22394704fce1a1555c879ff7335da8debf42.
  • Source worktree: C:\dev\worktrees\lintdiff-descriptive-description-required, clean at the pinned source head.
  • Original lintdiff source rule.

The source rule was assumed done for this promotion invocation. It was not modified during promotion. The development review completed two rounds with no new comments at the pinned head. The earlier development publication commit was 2423e379680a19091a1b86ffd5c240daa3d4089e; the reviewed source includes subsequent test/evidence fixes without changing production rule behavior.

Destination analysis

The agent selected @azure-tools/typespec-azure-core: the rule uses compiler documentation metadata only, applies to both ARM and data-plane authoring, and needs neither ARM resource semantics nor SDK-name resolution. ARM would unnecessarily narrow a shared authoring requirement; TCGC is not appropriate because this is not a generated SDK API policy. No ARM or TCGC dependency is introduced.

Core's existing documentation-required addresses missing documentation and tests documentation truthiness. It does not reject nonempty whitespace strings and has a different declaration scope. This rule complements that policy rather than duplicating it.

How the promoted TypeSpec linter works

The official rule is @azure-tools/typespec-azure-core/no-empty-doc.

It reads getDoc(program, target) on namespaces, interfaces, operations, models, model properties, scalars, enums, enum members, unions, and union variants. Undefined documentation is compliant; defined documentation with an empty trimmed value produces a warning targeted at the offending declaration. Any non-whitespace character is sufficient.

The enum callback explicitly iterates enum.members because the compiler does not dispatch enum-member listener callbacks. Tests preserve exact property and enum-member diagnostic locations. Standard linter traversal owns library filtering; there is no custom HTTP reachability, provider, version/projection, emitted-reference, or OpenAPI-extension filter.

The native rule's create(context) implementation matches the immutable source after line-ending normalization. Promotion adapts only the official name/export, native documentation metadata, and documentation URL. The source's unprojected native authoring population remains unchanged.

The rule is explicitly disabled (false) in both data-plane and resource-manager rulesets. The Core feature change and disabled-only rulesets internal change have separate Chronus entries.

Fixture-to-native test mapping

Snapshots and validator output are evidence only; they were not copied into the official package. Reduced compiler-native snippets exercise the same rule-owned branches.

Original lintdiff fixture Native vitest case Coverage note
empty-description it("rejects a whitespace-only property description") Reduced property snippet; one blank-description diagnostic.
whitespace-param-description it("rejects a whitespace-only parameter description") Direct operation parameter; one diagnostic.
non-empty-description it("accepts nonblank descriptions on models, properties, and operations") Compliant supplied documentation, without emitted snapshots or ambient suppressions.
enum-member-description it("targets offending enum members rather than their containing enum") Whitespace-member branch and precise member targeting.
N/A (review regression) it("targets offending properties rather than their containing model") Two diagnostics with exact property file/position/end.
N/A (review regression) it("targets offending enum members rather than their containing enum") Source-review member-location regression retained.
N/A (native documentation examples) it("validates the documented incorrect and correct examples") Reads the actual authored Markdown examples; incorrect warns and correct is valid with this rule enabled.

The 24 native cases also cover registered declaration kinds, absent and nonblank documentation, empty and Unicode-whitespace content, documentation comments, and diagnostic targeting. Fresh asynchronous tester initialization follows the Core test lifecycle.

Migration evidence

See the checked-in migration.md for focused comparisons, representative and full real-service corpus evidence, one-sided project explanations, compile exclusions, and remaining uncertainty. Those counts describe the immutable source's comparison population, not a fresh official-library corpus run or universal executable equivalence.

Validation

All required targeted checks are complete:

  • Core prerequisite closure and affected package builds.

  • Focused native rule tests: 24 passed, including the actual authored documentation examples.

  • Core package lint.

  • Core documentation regeneration, inspection, and explicit formatting/checking of the README, authored rule, generated linter reference, and ignored generated rule page.

  • Maintained-file formatting check and git diff --check.

  • LF Chronus entries and pnpm chronus status.

  • Rulesets dependency closure/build, package lint, and five tests passed.

  • Full Core native suite: 58 files passed, 458 tests passed, two original skips, 460 total, using the approved package-scoped profile:

    mise exec -- pnpm --filter @azure-tools/typespec-azure-core exec vitest run --pool=threads --maxWorkers=1
    
  • Independent persistent local precommit review of the exact ten-file scope: no significant issues.

Validation limitations

The original full Core run had two per-test 5,000ms timeouts (override-api-version and casing-style); the authorized single-worker diagnostic retained one casing-style timeout. Both failed receipts remain preserved. A coordinator-authorized bounded comparison ran the exact remaining casing test once against the baseline registration and once against the restored draft using threads/single-worker; both passed. The subsequent single authorized full suite passed with the identical 460-test population and the same two original skips.

The original timeout cause is unproven. This is not a claimed environmental diagnosis. Vitest 5, isolate: false, the configured 5,000ms test and 10,000ms hook limits, assertions, dependencies, and skips were unchanged. The passing profile does not grant further retries. Baseline isolation restored the exact draft registration and all 404 archived compiled outputs.

Repository-wide validate:pr and the website build were not run; the required targeted plan above is complete, and CI's dedicated Website job remains authoritative for the Astro build. The generated rule page was inspected/formatted but remains ignored and is not force-added.

Promotion sync policy

The lintdiff source remains immutable during promotion. A confirmed source-semantic defect discovered in promotion review blocks this PR and returns evidence to the owning queue; only that coordinator may authorize a bounded return-to-development repair cycle. This PR would then be refreshed incrementally only after clean source review at a new exact pinned commit, with native regressions and provenance updated. Promotion itself never edits the source rule or silently changes its semantics.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@microsoft-github-policy-service microsoft-github-policy-service Bot added int:azure-specs Run integration tests against azure-rest-api-specs lib:azure-core Issues for @azure-tools/typespec-azure-core library meta:website TypeSpec.io updates linter Issues related to linter rules labels Oct 10, 2026
@msyyc
Yuchao Yan (msyyc) requested a balanced review from Copilot October 10, 2026 05:40
@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

All changed packages have been documented.

  • ✅ @azure-tools/typespec-azure-core
Show changes

@azure-tools/typespec-azure-core - fix ✏️

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.

@azure-tools/typespec-samples - internal ✏️

Update Widget Manager color and ARM library provisioning-state descriptions to satisfy documentation-required, and regenerate sample snapshots.

@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

📦 Package size report

2 packages changed size, +2.81 KB (+0.0%) packed overall.

Package Packed (base → head) Δ Packed Unpacked (base → head) Δ Unpacked
@azure-tools/typespec-azure-core 136.90 KB → 138.67 KB +1.78 KB (+1.3%) 🔴 737.25 KB → 743.43 KB +6.18 KB (+0.8%) 🔴
@azure-tools/typespec-python 42.21 KB → 42.54 KB +336 B (+0.8%) 164.90 KB → 165.94 KB +1.04 KB (+0.6%) 🔴
12 package(s) with no notable change
Package Packed (base → head) Δ Packed Unpacked (base → head) Δ Unpacked
@azure-tools/typespec-client-generator-core 243.47 KB → 243.95 KB +486 B (+0.2%) 1.30 MB → 1.30 MB +2.33 KB (+0.2%)
@azure-tools/typespec-ts 530.53 KB → 530.77 KB +251 B (+0.0%) 2.67 MB → 2.67 MB +1.63 KB (+0.1%)
@azure-tools/typespec-azure-examples 62.48 KB → 62.45 KB -28 B (-0.0%) 308.18 KB → 308.13 KB -47 B (-0.0%)
@azure-tools/azure-http-specs 143.51 KB → 143.52 KB +11 B (+0.0%) 1.20 MB → 1.20 MB +50 B (+0.0%)
@azure-tools/typespec-azure-portal-core 36.26 KB → 36.26 KB +3 B (+0.0%) 242.62 KB → 242.62 KB —
@azure-tools/typespec-autorest 87.87 KB → 87.87 KB — 424.15 KB → 424.15 KB —
@azure-tools/typespec-autorest-canonical 7.42 KB → 7.42 KB — 26.00 KB → 26.00 KB —
@azure-tools/typespec-azure-resource-manager 184.13 KB → 184.13 KB — 1.11 MB → 1.11 MB —
@azure-tools/typespec-azure-rulesets 5.50 KB → 5.50 KB — 33.69 KB → 33.69 KB —
@azure-tools/typespec-go 263.76 KB → 263.76 KB — 1.35 MB → 1.35 MB —
@azure-tools/typespec-java 13.51 MB → 13.51 MB — 15.04 MB → 15.04 MB —
@azure-tools/typespec-metadata 15.91 KB → 15.91 KB — 62.26 KB → 62.26 KB —

Packed = gzipped .tgz published to npm. Unpacked = total extracted size. 🆕 added, 🗑️ removed. Packages from the core/ submodule are not included.
🔴 grew · 🟢 shrank — only changes of at least 512 B and 0.5% are marked.

@pkg-pr-new

pkg-pr-new Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@azure-tools/typespec-azure-core@5663
npm i https://pkg.pr.new/@azure-tools/typespec-azure-rulesets@5663

commit: e4e52da

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Inferred HTTP verbs and encoded property names currently escape checks performed by the legacy validator.

2 open findings
What changed in this PR

Adds an opt-in Azure Core lint rule migrating DescriptionMustNotBeNodeName.

Changes:

  • Implements and tests uninformative-documentation detection.
  • Registers the rule disabled by default in both Azure rulesets.
  • Adds documentation and Chronus entries.
File Description
website/​.../​reference/​linter.md Lists the rule.
packages/​typespec-azure-rulesets/​test/​validate-rules-defined.test.ts Verifies opt-in status.
packages/​typespec-azure-rulesets/​src/​rulesets/​resource-manager.ts Registers disabled rule.
packages/​typespec-azure-rulesets/​src/​rulesets/​data-plane.ts Registers disabled rule.
packages/​typespec-azure-core/​test/​rules/​no-uninformative-doc.test.ts Adds rule tests.
packages/​typespec-azure-core/​src/​rules/​no-uninformative-doc.ts Implements the rule.
packages/​typespec-azure-core/​src/​rules/​no-uninformative-doc.md Documents behavior.
packages/​typespec-azure-core/​src/​linter.ts Exports the rule.
packages/​typespec-azure-core/​README.md Lists the rule.
.chronus/​changes/​register-no-uninformative-doc-2026-10-10.md Describes ruleset registration.
.chronus/​changes/​no-uninformative-doc-2026-10-10.md Describes the Core feature.

🧠 Review effort: Balanced


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/typespec-azure-core/src/rules/no-uninformative-doc.ts Outdated
Comment thread packages/typespec-azure-core/src/rules/no-uninformative-doc.ts Outdated
@azure-sdk-automation

Copy link
Copy Markdown
Contributor

You can try these changes here

🛝 Playground 🌐 Website

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Renamed multipart parameters bypass the intended uninformative-documentation check.

1 open finding
2 resolved since last review

🧠 Review effort: Balanced

Comment thread packages/typespec-azure-core/src/rules/no-uninformative-doc.ts Outdated
Resolve authored and default HTTP part names independently of JSON naming, preserve repeated-part metadata and port reviewed native regressions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The implementation, registration, documentation, release metadata, and regression coverage are complete and consistent.

0 open findings

1 resolved since last review

🧠 Review effort: Balanced

Yuchao Yan (msyyc) added a commit that referenced this pull request Oct 12, 2026
## Original Swagger linter

- linter code:
[DescriptionMustNotBeNodeName](https://github.com/Azure/azure-openapi-validator/blob/main/packages/rulesets/src/native/legacyRules/DescriptionMustNotBeNodeName.ts)
- linter doc:
[description-must-not-be-node-name.md](https://github.com/Azure/azure-openapi-validator/blob/main/docs/description-must-not-be-node-name.md)

Specific checks:

- [x] Select objects containing a `description` field recursively.
- [x] Ignore non-string descriptions.
- [x] Normalize description/name text by trimming whitespace, removing
periods, and lowercasing.
- [x] Reject a description matching an explicit string `name`.
- [x] Otherwise reject a description matching the containing node's
string path key.
- [x] Otherwise reject the literal placeholder `description`.
- [x] Report at the offending `description` field.

## How the Swagger linter works

The legacy ARM rule recursively selects description-bearing Swagger
objects and compares their text with an explicit name or the object's
path key. Consequently it can inspect schema/property names, HTTP verbs,
parameters, and `x-ms-enum.values` metadata. It emits at most one
finding per selected object using the ordered conditions above. Its
executable ARM registration is the audience source of truth despite the
documentation's broader applicability wording.

The validator counts emitted occurrences, not unique authored
declarations. SQL's repeated root files and AlertsManagement's inline
parameter copies therefore multiply findings. Generated or qualified
schema names, client compatibility types, unused source declarations,
and literal route parameters also make output nodes differ from editable
TypeSpec targets. Those differences are investigated rather than
recreated through emitter-dependent production logic.

## How the migrated TypeSpec linter works

Mapping: `DescriptionMustNotBeNodeName` →
`tsp-lintdiff-local-linter/description-must-not-be-node-name`.

The native rule checks explicit documentation obtained through compiler
`getDoc` on named models, scalars, enums, unions, enum members, union
variants, model properties, and operations. HTTP path/query/header names
take precedence over compiler-resolved ordinary JSON property names.
`getHttpOperation` supplies the effective operation verb, including
inferred GET without a body and POST with a body. Missing or
normalized-empty documentation remains outside this rule; status-code
properties remain exempt.

Earlier changes closed missing enum-member/union-variant checks and
removed the blanket key-property exemption. Enum members are iterated
from the enum listener, following official `documentation-required`
prior art because the installed compiler walker does not dispatch
enum-member events. Named union variants compare their name; unnamed
string variants compare their literal value, while other unnamed
variants are checked only for placeholder text.

Cycle1 repairs two additional native semantic omissions confirmed during
[promotion
review](#5663 (review)):

- `@route("/widgets") @doc("get") op read(): string;` now warns using
its inferred GET verb; a body-bearing operation documented only as
`post` likewise warns.
- `@encodedName("application/json", "wire-name") @doc("wire-name")
sourceName: string;` now warns using its JSON wire name. Documentation
matching only `sourceName` is compliant when JSON serialization renames
it.

These are supported compiler/HTTP semantics, not emitter overrides.
Explicit verbs, normalization, placeholder handling, HTTP parameter-name
precedence, and authored diagnostic targets are preserved.

Each violation targets its semantic declaration. The rule does not
deduplicate distinct semantic instances merely because they share a
source location. It does not project versions, invoke emission, predict
generated schema names, or depend on OpenAPI/emitter/SDK APIs. Valid
authored warnings from older versions remain native findings;
selected-version attribution is separate research evidence.

## Migration evidence and validation

See
[migration.md](https://github.com/Azure/typespec-azure/blob/lintdiff-description-must-not-be-node-name/packages/typespec-lintdiff/test/fixtures/DescriptionMustNotBeNodeName/migration.md)
for the native contract, supported-shape evidence, regenerated full
corpus, verified selected-version attribution reuse, all one-sided
projects, six compilation exclusions, concrete outlier explanations, and
preserved research limitations. The unchanged corpus diagnostic multiset
did not expose the two repaired shapes; focused native tests and emitted
comparison fixtures establish those fixes.

Required cycle1 validation passed: explicit maintained-file Prettier
write/check, package build, changed-TypeScript oxlint with denied
warnings, 41 native predicate tests, 13 strict rule fixtures, the
affected 14-case PATCH fixture group, representative and full corpus
runs, and diff hygiene. Independent persistent local precommit review
was clean on the complete branch diff and verified all supplied
content/receipt hashes, with nine additional native probes.

The documented native contract is covered; arbitrary executable Swagger
equivalence remains partial, not claimed from project overlap or raw
count equality. All 1,005 generated corpus changes were archived and
restored through the exact approved manifest plan. Corpus/coverage
outputs and temporary fixture junctions are excluded from this PR.


## Cycle2 multipart source repair

Confirmed promotion review thread `PRRT_kwDOKoCXvs6rCcjD`
(comment4237005053) exposed a missed authored multipart part name. The
native rule now uses an authored `getHttpPart(program,
type)?.options.name` when present and otherwise the source property
name, including repeated parts via compiler array metadata; JSON encoded
names remain limited to ordinary non-part properties. Path/query/header
precedence and status-code exemption remain intact. Renamed-part
violation, source-name-only compliance, default-name JSON-isolation,
meaningful-documentation, and supported scalar/array/repeated-part
regressions preserve earlier inferred-verb and ordinary JSON-name fixes.
Production uses public compiler/HTTP APIs only.

Validation:47 native tests,15 strict target-rule fixtures and all five
genuinely affected fixture groups pass. The full current468-project
corpus has462 successful projects and six preserved compiler exclusions.
Complete8,499 diagnostic multisets match prior archived results,
allowing conservative selected-version attribution reuse:7,501
findings/142projects. Generated corpus output was archived and
exactly1,005 planned paths restored; none is included in this PR.
Detailed native contract, parity limits and current corpus evidence
remain in the linked migration document above.

Historical Formdata required-gate failure is preserved; explicit user
authorization prospectively excluded its unchanged unmapped data-plane
group without retry or edit. A separately authorized single snapshot
correction only reordered seven identical warnings for preexisting
listener-phase drift; no unrelated semantics changed.

Outer persistent independent local review `f235a949` is clean, with
actual follow-up capability and additional
multiple-operation/shared/spread/repeated-part probes verified.
SourceRepairCycles2/3, development ordinary corrections3/3 and task-wide
reserve2/3 remain unchanged. Fresh GitHub review of this exact repair
head is required before promotion.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@msyyc
Yuchao Yan (msyyc) marked this pull request as ready for review October 12, 2026 04:43
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment on lines +67 to +74
const checkNamedTarget = (target: NamedTarget) => {
const { name } = target;
if (!name || name.length === 0) {
return;
}

checkTarget(target, name);
};

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.

Do we need this extra function or can it be combined with the one above?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The wrapper separates checking named declarations from checking other targets. checkNamedTarget skips unnamed declarations, while checkTarget also handles HTTP verbs, wire names, and unnamed union variants that still need placeholder checks. Keeping them separate makes those different behaviors explicit.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Combined the wrappers into one checker with a default declaration name. Kept the named-declaration empty-name exemption and the explicit HTTP/JSON/multipart/union-variant name overrides, so this refactor does not broaden documentation policy.

Implemented in 603330abc. Scoped formatting, type-aware lint, package build and the focused native tests passed. Review threads are intentionally left open.

Comment on lines +178 to +180
it("accepts operation documentation matching only its source name", async () => {
await tester.expect('@get @doc("read") op read(): string;').toBeValid();
});

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.

do we want this to be valid? Seems like this should violate the rule too

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is intentional for compatibility with the legacy Swagger rule. It compares an operation’s description against its HTTP verb (get here), not its TypeSpec operation name or operationId. The test verifies that matching only the source name does not introduce a warning beyond the behavior being migrated.

@msyyc Yuchao Yan (msyyc) Oct 14, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Implemented in 5e8dc5927. Documentation that merely repeats either the source operation name or the HTTP verb is now uninformative. These checks were folded into the existing documentation-required rule rather than retained as a separate opt-in rule, with operation-name regressions and the original emitted-name/member coverage transferred. This is an intentional quality-policy expansion, not just wrapper cleanup. It replaces my earlier scope-only response; the thread remains unresolved.

@catalinaperalta catalinaperalta left a comment

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.

Seems pretty complete, just a couple of questions

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.

I don't think this deserve to be its own rule should just upgrade the documentation-required to make sure that the name is not the node id

@msyyc Yuchao Yan (msyyc) Oct 14, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Implemented the requested consolidation in 5e8dc5927: documentation-quality checks now belong to the existing documentation-required rule. The separate no-uninformative-doc registration/docs were removed, while encoded JSON/HTTP names, multipart and enum/union-member semantics and all original regressions were retained. Missing-documentation exemptions remain unchanged. The matching #5674 consolidation was also checked in combination so the two PRs do not create conflicting documentation-rule implementations. These stronger checks are intentional and documented; this replaces my earlier pending-design response without resolving the thread.

Coverage-documentation follow-up: the existing rule documentation now explicitly maps each migrated lintdiff rule to its native check, complementary rules, applicability and intentional semantic differences. This records what consolidation preserves instead of treating registration or overlapping rule names as proof of duplication. Documentation only; behavior is unchanged.

Yuchao Yan (msyyc) and others added 4 commits October 14, 2026 14:08
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Upgrade the existing default-enabled documentation rule instead of introducing no-empty-doc. Preserve missing-doc scopes and exemptions, reject explicit blanks on additional declarations, and transfer the original regression fixtures.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Preserve missing-documentation exemptions while rejecting blank required docs and explicit descriptions matching effective names. Compare operation docs against both source names and HTTP verbs, and transfer the regression coverage to the existing default-enabled rule.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@msyyc Yuchao Yan (msyyc) changed the title [Swagger Linter Migration] DescriptionMustNotBeNodeName -> no-uninformative-doc [Swagger Linter Migration] DescriptionMustNotBeNodeName -> documentation-required Oct 14, 2026
Yuchao Yan (msyyc) and others added 4 commits October 14, 2026 15:44
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace name-only union variant descriptions with meaningful documentation and regenerate the affected OpenAPI snapshots.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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.

why do we have a separate file here and a whole dispatcher, this seems uncessarly complex

Merge PR #5674 into #5663, preserving both documentation validators and missing-documentation exemptions. Update combined diagnostic regressions, consolidate rule coverage documentation, and retain one Azure Core fix changelog entry.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@msyyc Yuchao Yan (msyyc) changed the title [Swagger Linter Migration] DescriptionMustNotBeNodeName -> documentation-required [Swagger Linter Migration] DescriptionMustNotBeNodeName + DescriptiveDescriptionRequired -> documentation-required Oct 14, 2026
`
model Widget {
@doc("") /*name*/name: string;
@doc(" ") /*value*/value: string;

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.

why do all those test have /* */

return;
case "Operation": {
// HTTP validation owns resolution diagnostics; this rule only checks documentation.
const httpOperation = ignoreDiagnostics(getHttpOperation(context.program, target));

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.

Can we read and normalize the documentation before resolving the HTTP operation? Return early when documentation is absent or normalizes to empty, and check the source-operation-name match first. When it already matches the source name, we can report that without resolving HTTP metadata; only resolve the effective verb when that comparison is still needed. This keeps the same behavior while avoiding unconditional HTTP resolution.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

int:azure-specs Run integration tests against azure-rest-api-specs lib:azure-core Issues for @azure-tools/typespec-azure-core library linter Issues related to linter rules meta:website TypeSpec.io updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants