Skip to content

Report linter diagnostics on library template members the user gave a type to - #11862

Draft
Timothee Guerin (timotheeguerin) wants to merge 4 commits into
microsoft:mainfrom
timotheeguerin:linter-template-instantiation
Draft

Report linter diagnostics on library template members the user gave a type to#11862
Timothee Guerin (timotheeguerin) wants to merge 4 commits into
microsoft:mainfrom
timotheeguerin:linter-template-instantiation

Conversation

@timotheeguerin

@timotheeguerin Timothee Guerin (timotheeguerin) commented Sep 4, 2026

Copy link
Copy Markdown
Member

Fixes #11861

A linter rule can never report on a type that only exists because the user instantiated a library template. Given a library that declares:

model ResourceNameParameter<..., Type extends string = string> {
  name: Type;
}

and a spec that writes ...ResourceNameParameter<Employee, Type = Azure.Core.uuid>, a rule such as no-uuid sees the name property and reports on it — and the linter silently throws the diagnostic away, because name's source location resolves to the library declaration. This was found while adding a no-uuid rule in Azure/typespec-azure#5336, where it hid 9 offending resource-name declarations across 6 projects (35 Swagger findings).

The coarse "is it in the user's project?" filter exists for a good reason — a rule must not blame a user for library-internal code they cannot change. But the type a user themselves passed is something they control.

So the filter now asks a sharper question: was this member declared as a template parameter the user supplied an argument for? If so, report it — on the argument, in the user's own file:

main.tsp:6:44 - warning no-uuid: UUID usage is not recommended.
> 6 |   ...ResourceNameParameter<Employee, Type = Azure.Core.uuid>;
    |                                             ^^^^^^^^^^^^^^^

Everything else a library template declares stays filtered.

The same PR also makes #suppress at an instantiation site apply to diagnostics raised inside the template — previously a diagnostic located in a library was unsuppressable from the spec that triggered it.

Getting the scope right

Every broader design was tried and rejected on evidence from the Azure ARM libraries and azure-rest-api-specs:

Design ARM sample failures
Report if any frame of the instantiation trace is user code 30 / 40 — including diagnostics on compiler/lib/intrinsics.tsp, four levels of library templates deep
Report if the direct instantiation is user code 8 — library-authored problems the user can't fix, plus a duplicate warning on every op is ArmXxx<T>
Report the user's argument, wherever it appears in the member's type 1
Report only where the member was declared as the parameter itself 0

That last narrowing matters. Azure.Core.Page<T> declares value: T[], so missing-x-ms-identifiers fired on every Page<X> in azure-rest-api-specs — but the array is the library's own declaration, and @identifiers belongs on it, not on anything the user wrote. A member declared as body: Request is the opposite case: its type is what the user passed. Measured on specification/web/.../AppService:

  no-unknown              client.tsp:625  | ArmResponse<unknown>                  kept
  missing-x-ms-identifiers client.tsp:2030 | ArmResponse<NetworkTrace[]>          kept  (the user's own array)
  missing-x-ms-identifiers models.tsp:4027 is Azure.Core.Page<InboundEnvironment> dropped
  missing-x-ms-identifiers models.tsp:13230 is Azure.Core.Page<PerfMonResponse>   dropped

Whole instantiated models and operations are excluded too — op delete is ArmResourceDeleteWithoutOkAsync<Employee> produces two operation types, so reporting on both duplicates every ARM operation diagnostic. And arguments left to their default are excluded, since the user never wrote them.

Known follow-up

documentation-required still reports at the template argument when the real cause is an augment such as @@doc(Replica.properties, "") elsewhere in the spec. The finding is correct — the spec really is blanking the doc — but the location is indirect. Preferring the user's augment node was tried and does not work generically: the linter has no way to know which decorator a rule cares about, so it lands on an unrelated @@clientName or @@flattenProperty just as often. The right fix is in the rule, which does know: report on the @doc application when there is one.

@microsoft-github-policy-service microsoft-github-policy-service Bot added compiler:core Issues for @typespec/compiler meta:website TypeSpec.io updates labels Sep 4, 2026
@pkg-pr-new

pkg-pr-new Bot commented Sep 4, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/compiler@11862

commit: 9c40289

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

All changed packages have been documented.

  • @typespec/compiler
Show changes

@typespec/compiler - fix ✏️

Report linter diagnostics on library template members the user gave a type to,> ,> A rule reporting on a member declared inside a library template — for example the value property of Wrapper<T> when the project writes Wrapper<uuid> — was silently dropped, because the member's source location resolves to the template declaration in the library. That member only has the type it has because of the argument the user passed, so it is now reported, on the argument in the user's own file.,> ,> Only a member declared as the parameter, such as value: T, counts. A member the parameter merely appears inside, such as value: T[], is still left alone: the array is the library's own declaration, so a diagnostic about it is the library's to fix no matter which item type the user passed.

@typespec/compiler - fix ✏️

Suppress a diagnostic reported inside a template where the template was instantiated,> ,> tsp,> model Widget {,> #suppress "some-rule" "Not applicable here",> page: Page<WidgetItem>;,> },> ,> ,> Previously the #suppress directive was only looked up on the target and its parents, so a diagnostic coming from a template declaration could not be suppressed from the code that instantiated it.

@azure-sdk-automation

azure-sdk-automation Bot commented Sep 4, 2026

Copy link
Copy Markdown

You can try these changes here

🛝 Playground 🌐 Website 🛝 VSCode Extension

@timotheeguerin Timothee Guerin (timotheeguerin) added the int:azure-specs Run integration tests against azure-rest-api-specs label Sep 4, 2026
Narrow the fix so a library-declared member is reported only when its declared
type depends on a template parameter the user passed an argument for, and
retarget the diagnostic to that argument node in the user's project.
@timotheeguerin Timothee Guerin (timotheeguerin) changed the title Report linter diagnostics on library templates instantiated by the user project Report linter diagnostics on library template members whose type the user supplied Sep 4, 2026
A member the parameter merely appears inside, such as `value: T[]` in
`Page<T>`, is the library's own declaration: a diagnostic about it is the
library's to fix no matter which item type the user passed. Reported against
azure-rest-api-specs this removes the `missing-x-ms-identifiers` findings on
`Page<X>` while keeping `no-unknown` on `ArmResponse<unknown>` and
`missing-x-ms-identifiers` where the user passed the array itself.
@timotheeguerin Timothee Guerin (timotheeguerin) changed the title Report linter diagnostics on library template members whose type the user supplied Report linter diagnostics on library template members the user gave a type to Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

compiler:core Issues for @typespec/compiler int:azure-specs Run integration tests against azure-rest-api-specs meta:website TypeSpec.io updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Linter silently drops diagnostics on library templates instantiated by the user project

1 participant