Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
15 changes: 6 additions & 9 deletions packages/typespec-lintdiff/src/rules/xms-examples-required.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
import { getExamples } from "@azure-tools/typespec-autorest";
Comment thread
msyyc marked this conversation as resolved.
import { createRule } from "@typespec/compiler";
import { getHttpOperation } from "@typespec/http";
import { getExtensions } from "@typespec/openapi";

const supportedVerbs = new Set([
"delete",
"get",
"head",
"options",
"patch",
"post",
"put",
]);
const supportedVerbs = new Set(["delete", "get", "head", "options", "patch", "post", "put"]);

export const xmsExamplesRequiredRule = createRule({
name: "xms-examples-required",
Expand All @@ -32,6 +25,10 @@ export const xmsExamplesRequiredRule = createRule({
return;
}

if (getExamples(context.program, operation)?.length) {
return;
}

context.reportDiagnostic({
target: operation,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"violation": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Test: autorest-example
// Compliance: @Autorest.example emits x-ms-examples for the operation.

import "@typespec/http";
import "@typespec/rest";
import "@typespec/versioning";
import "@azure-tools/typespec-autorest";

using TypeSpec.Http;
using TypeSpec.Rest;
using TypeSpec.Versioning;

@service(#{ title: "Test Service" })
@versioned(Versions)
namespace TestService;

enum Versions {
v2024_01_01: "2024-01-01",
}

@doc("A widget")
model Widget {
@doc("Widget name")
name: string;
}

@route("/widgets/{name}")
@doc("Get a widget")
@Autorest.example("./examples/getWidget.json", "getWidget")
@get
op getWidget(
@path
@doc("Widget name")
@maxLength(64)
@pattern("^[A-Za-z0-9-]+$")
name: string,
): Widget;
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"swagger": "2.0",
"info": {
"title": "Test Service",
"version": "2024-01-01",
"x-typespec-generated": [
{
"emitter": "@azure-tools/typespec-autorest"
}
]
},
"schemes": [
"https"
],
"produces": [
"application/json"
],
"consumes": [
"application/json"
],
"tags": [],
"paths": {
"/widgets/{name}": {
"get": {
"operationId": "GetWidget",
"description": "Get a widget",
"parameters": [
{
"name": "name",
"in": "path",
"description": "Widget name",
"required": true,
"type": "string",
"maxLength": 64,
"pattern": "^[A-Za-z0-9-]+$"
}
],
"responses": {
"200": {
"description": "The request has succeeded.",
"schema": {
"$ref": "#/definitions/Widget"
}
}
},
"x-ms-examples": {
"getWidget": {
"$ref": "./examples/getWidget.json"
}
}
}
}
},
"definitions": {
"Widget": {
"type": "object",
"description": "A widget",
"properties": {
"name": {
"type": "string",
"description": "Widget name"
}
},
"required": [
"name"
]
}
},
"parameters": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,15 @@ by default, which triggers this rule.
- The upstream unit tests only cover the missing-vs-present branches, so the
local suite includes an explicit empty-object control to match the shipped
validator behavior faithfully.
- Canonical TypeSpec authoring can emit this extension with
`@extension("x-ms-examples", ...)` from `TypeSpec.OpenAPI`, and there is no
existing local or template lint that requires it.
- TypeSpec can emit the extension from `@extension("x-ms-examples", ...)` or
`@Autorest.example(...)`. The latter is important because the autorest emitter
writes `x-ms-examples` from that decorator as `$ref` entries.
- Canonical TypeSpec authoring normally relies on adjacent example files with
`operationId` and `title`, which the autorest emitter loads from `examples-dir`.
A synchronous linter rule cannot enumerate those external files, so this rule
only mirrors authorable in-program evidence of emitted examples.
- There is no existing local or template lint that requires every operation to
have examples.
- The prior baseline gap only carried project-default noise
(`auth-required`, `operation-missing-api-version`, and
`arm-resource-operation`), with no `#suppress` directives or prerequisite
Expand All @@ -46,11 +52,14 @@ mirrors the shipped validator behavior for authorable TypeSpec:
- missing `x-ms-examples` on an applicable operation => violation
- populated `x-ms-examples` => compliant
- empty `x-ms-examples` object => compliant under current upstream behavior
- `@Autorest.example(...)` entries => compliant because they emit
`x-ms-examples` `$ref` values

## Test Cases

| ID | Violation | Description |
| -------------------------- | --------- | --------------------------------------------------- |
| `missing-xms-examples` | true | Operations lack x-ms-examples |
| `with-xms-examples` | false | Operation carries a populated x-ms-examples object |
| `empty-xms-examples` | false | Empty x-ms-examples still passes in shipped upstream |
| ID | Violation | Description |
| ---------------------- | --------- | ---------------------------------------------------- |
| `missing-xms-examples` | true | Operations lack x-ms-examples |
| `with-xms-examples` | false | Operation carries a populated x-ms-examples object |
| `empty-xms-examples` | false | Empty x-ms-examples still passes in shipped upstream |
| `autorest-example` | false | Operation uses @Autorest.example to emit examples |