- name
- create-linter-rule
- description
- Create a new TypeSpec linter rule, lint diagnostic, or design guideline checker with a TDD approach, including implementation, tests, documentation, ruleset registration, and changeset. Use this skill when asked to create, add, implement, or write a new linter rule, lint warning, validation rule, or design guideline enforcement for TypeSpec Azure libraries.
- allowed-tools
- shell
# Create a TypeSpec Linter Rule
Follow these steps in order to create a complete, high-quality linter rule.
## Step 1: IDENTIFY
Determine the rule metadata before writing any code:
- **Package**: `typespec-azure-core` (data-plane rules), `typespec-azure-resource-manager` (ARM rules), or `typespec-client-generator-core` (client SDK generation rules)
- **Rule name**: Must follow these naming conventions:
- Use kebab-case (lowercase letters, numbers, hyphens)
- DO NOT include the package name in the rule ID (the package is already part of the fully-qualified diagnostic code)
- Use `no-<thing>` when the rule bans a construct or usage (e.g., `no-nullable`, `no-enum`, `no-format`)
- Use `use-<preferred-thing>` when the rule points users to a standard/preferred TypeSpec pattern (e.g., `use-standard-operations`, `use-extensible-enum`)
- Keep names short and concise — prefer `no-enum` over `no-enum-type-usage`
- **Severity**: All linter rules are warnings; no severity choice is needed
- **Target ruleset(s)**: `data-plane`, `resource-manager`, or both
- **Description**: One-line explanation of what the rule enforces
Choose the package by scope:
- Decorators from `typespec-client-generator-core`, or rules only about client SDK generation → `typespec-client-generator-core`
- Rules specific to ARM APIs → `typespec-azure-resource-manager`
- Rules that apply to both data-plane and ARM, or only data-plane → `typespec-azure-core`
## Step 2: SCAFFOLD
Generate all required files using the repo's scaffolding tool:
```bash
pnpm create:linter-rule < rule-name > --package < azure-core | azure-resource-manager | client-generator-core > --description "<description>"
```
This creates:
- `packages/<pkg>/src/rules/<rule-name>.ts` — rule skeleton
- `packages/<pkg>/test/rules/<rule-name>.test.ts` — test skeleton
- `packages/<pkg>/src/rules/<rule-name>.md` — docs skeleton (referenced from the rule via `docs: fileRef.fromPackageRoot(...)`)
- Updates `packages/<pkg>/src/linter.ts` — registers the rule
## Step 3: WRITE FAILING TESTS FIRST (TDD)
Edit `packages/<pkg>/test/rules/<rule-name>.test.ts`:
1. Write tests for **valid code** that should produce no diagnostics (`.toBeValid()`)
2. Write tests for **invalid code** that should produce specific diagnostics (`.toEmitDiagnostics()`)
3. Create **equivalence classes** for the input and write tests covering at least one instance of each class:
- Group inputs by how the rule handles them (e.g., for a rule targeting `ModelProperty`):
- Simply defined properties
- Properties defined using `spread` or `is`
- Properties inherited from a base class
- Add boundary conditions specific to the rule logic (e.g., for a name-prefix rule):
- Properties with the forbidden prefix
- Properties with the prefix text in the middle or end of the name
- Properties with names shorter than the prefix
4. Always include at least one test verifying that **library types in `Azure.Core` and `Azure.ResourceManager` are not subject to the rule**
Test API reference:
```typescript
// No diagnostics expected
await tester.expect(`model Foo {}`).toBeValid();
// Specific diagnostic expected
await tester.expect(`model foo {}`).toEmitDiagnostics([
{
code: "@azure-tools/typespec-<pkg>/<rule-name>",
severity: "warning",
message: "Expected message text",
},
]);
// Test code fix (if rule provides one)
await tester
.expect(`enum Color { red }`)
.applyCodeFix("fix-id")
.toEqual(`union Color { string, red: "red" }`);
```
Verify tests fail before implementing:
```bash
pnpm --filter "@azure-tools/typespec-<pkg>..." build
pnpm --filter "@azure-tools/typespec-<pkg>..." test
```
## Step 4: IMPLEMENT THE RULE
Edit `packages/<pkg>/src/rules/<rule-name>.ts`:
- Implement visitor logic in the `create(context)` function
- Return a `SemanticNodeListener` with the appropriate hooks:
- `model` — for model-level checks
- `modelProperty` — for property-level checks
- `operation` — for operation-level checks
- `enum` — for enum checks
- `namespace` — for namespace-level checks
- `interface` — for interface checks
- `union` / `unionVariant` — for union checks
- Use `context.reportDiagnostic({ target })` to report violations
- Use `paramMessage` from `@typespec/compiler` for interpolated messages
- Add `codefixes` array to `reportDiagnostic()` if a fix is possible
## Step 5: VERIFY TESTS PASS
```bash
pnpm --filter "@azure-tools/typespec-<pkg>..." build
pnpm --filter "@azure-tools/typespec-<pkg>..." test
```
All tests should pass. If not, fix the implementation until they do.
## Step 6: REGISTER IN RULESETS
Add the rule to the appropriate ruleset(s):
- **Data-plane rules**: Edit `packages/typespec-azure-rulesets/src/rulesets/data-plane.ts`
- **ARM rules**: Edit `packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts`
TCGC rules generally go in **both** rulesets. Rules in `typespec-azure-core` that apply to both ARM and data-plane specs also go in **both** rulesets. Only rules that are truly ARM-specific go exclusively in `resource-manager.ts`; otherwise, add the rule to both `resource-manager.ts` and `data-plane.ts` as appropriate.
Add an entry: `"@azure-tools/typespec-<pkg>/<rule-name>": true,`
Every rule MUST be explicitly listed (enabled or disabled). The `validate-rules-defined.test.ts` test will fail otherwise.
Verify:
```bash
pnpm --filter "@azure-tools/typespec-azure-rulesets..." build
pnpm --filter "@azure-tools/typespec-azure-rulesets..." test
```
## Step 7: WRITE DOCUMENTATION AND REGENERATE DOCS
Edit `packages/<pkg>/src/rules/<rule-name>.md`:
- Replace all placeholder text
- Write a clear description of what the rule checks and why
- Provide realistic ❌ Incorrect and ✅ Correct examples using actual TypeSpec patterns
- Add a required **`## Impact`** section: a bulleted `**Area:**` line (e.g. `API`, `SDK`, `Emitters`) followed by a short paragraph describing what breaks or degrades when the rule is violated
- Add a required **`## Suppression`** section: state whether suppression is acceptable and why, and point to the correct fix. Suppressions use the `#suppress "<rule-id>" "<justification-string>"` **directive** placed on the line above the target — NOT a `// suppress` comment
- Add a **`## LintDiff Equivalent`** section when an equivalent LintDiff (azure-openapi-validator) rule exists, deep-linking to the specific rule id in the [OpenAPI authoring automated guidelines](https://github.com/Azure/azure-rest-api-specs/blob/main/documentation/openapi-authoring-automated-guidelines.md). Omit only when no equivalent exists
- This file holds only the extended documentation body — the page title, rule id, and
short description are generated from the rule definition. `tspd` renders the page at
`website/src/content/docs/docs/libraries/<pkg>/rules/<rule-name>.md`
- For ARM rules, also update the alphabetized rule table in
`website/src/content/docs/docs/howtos/ARM/arm-rules.md` with the documentation URL,
LintDiff equivalent, and impact. This table is authored manually and is not updated
by `regen-docs`.
Then regenerate the library's reference docs (updates the rule listing):
```bash
pnpm --filter "@azure-tools/typespec-<pkg>..." build
pnpm --filter "@azure-tools/typespec-<pkg>" regen-docs
```
## Step 8: CREATE CHANGESET
```bash
pnpm change add
```
When prompted:
- Select change kind: `feature` (new rule) or `fix` (bugfix to existing rule)
- Select affected package: `@azure-tools/typespec-<pkg>`
- Write a concise description: "Add `<rule-name>` linter rule that <what it does>"
## Step 9: FINAL VALIDATION
Run the general pre-PR validation to ensure everything is ready:
```bash
pnpm validate:pr
```
This checks: branch is up to date, build passes, tests pass, lint passes, format is clean, spelling is clean, regen-docs is clean, changeset exists, and diff only contains expected files.
If any check fails, fix the issue and re-run. Use `pnpm validate:pr --fix` to auto-fix formatting and lint issues.
## Step 10: EXTERNAL INTEGRATION CHECK
New linter rules MUST NOT break existing Azure service specs. After pushing your PR:
1. Apply the `int:azure-specs` label to the PR to trigger the External Integration check: `gh pr edit --add-label "int:azure-specs"`
2. If the agent cannot apply the label automatically, tell the user to apply the `int:azure-specs` label manually in the GitHub UI
3. After applying the label, the External Integration workflow will start. Monitor it via `gh run list --workflow=external-integration.yml`
4. This workflow packages your changes and runs TypeSpec validation against all specs in `Azure/azure-rest-api-specs`
5. Wait for the check to pass before requesting review
If the check fails, your rule produces diagnostics on existing specs. To resolve:
1. **Apply an API-neutral fix to the spec** (preferred): If the fix doesn't change API behavior, submit a PR to `Azure/azure-rest-api-specs` on the **main** branch.
2. **Suppress the rule**: If the spec cannot be fixed without changing API behavior, add a `#suppress "<rule-id>" "<justification-string>"` directive on the line above the target (NOT a `// suppress` comment). Suppressions always go to the **main** branch.
3. **Fix on typespec-next**: If the fix requires unreleased TypeSpec APIs or behavior, submit to the **typespec-next** branch (uses nightly builds).
4. **Link your spec fix PR**: Always link the spec fix PR in your linter rule PR description.
The External Integration workflow:
- Builds and packs all typespec-azure packages from your PR
- Checks out `Azure/azure-rest-api-specs` (main branch)
- Patches in your packaged changes
- Runs `tsp-integration azure-specs --stage validate`
- Checks for unexpected git changes
## Important Notes
- **Import extensions**: Always use `.js` extensions in imports (e.g., `from "./rules/my-rule.js"`)
- **Rule URL**: Must match `https://azure.github.io/typespec-azure/docs/libraries/<pkg>/rules/<rule-name>`
- **Test coverage**: Aim for ≥ 95% branch coverage of the rule file
- **Existing patterns**: Look at similar existing rules in `packages/<pkg>/src/rules/` for reference
- **ARM rules can import from azure-core**: `import { ... } from "@azure-tools/typespec-azure-core"`
عرض على GitHub