-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add no-string-discriminator linter rule (#693)
fix [#97](#97) add linter rule to recommend the discriminator property to be explicitly defined as an extensible union
- Loading branch information
1 parent
3af118a
commit 9b7a6a2
Showing
7 changed files
with
187 additions
and
0 deletions.
There are no files selected for viewing
8 changes: 8 additions & 0 deletions
8
.chronus/changes/linter-no-string-discriminator-2024-3-18-15-54-43.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
--- | ||
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking | ||
changeKind: fix | ||
packages: | ||
- "@azure-tools/typespec-azure-core" | ||
--- | ||
|
||
Add new `no-string-discriminator` linter rule suggesting using an explicit extensible union as the discriminator kind. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
docs/libraries/azure-core/rules/no-string-discriminator.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
--- | ||
title: "no-string-discriminator" | ||
--- | ||
|
||
```text title="Full name" | ||
@azure-tools/typespec-azure-core/no-string-discriminator | ||
``` | ||
|
||
Azure services favor using an extensible union to define the discriminator property. This allow the service to add new discriminated models without being breaking. | ||
|
||
#### ❌ Incorrect | ||
|
||
- Missing explicit property | ||
|
||
```tsp | ||
@discriminator("kind") | ||
model Pet { | ||
name: string; | ||
} | ||
``` | ||
|
||
- Property is a string | ||
|
||
```tsp | ||
@discriminator("kind") | ||
model Pet { | ||
kind: string; | ||
name: string; | ||
} | ||
``` | ||
|
||
- Property is a closed enum | ||
|
||
```tsp | ||
@discriminator("kind") | ||
model Pet { | ||
kind: PetKind; | ||
name: string; | ||
} | ||
enum PetKind { | ||
cat, | ||
dog, | ||
} | ||
``` | ||
|
||
#### ✅ Correct | ||
|
||
- Property is a closed enum | ||
|
||
```tsp | ||
@discriminator("kind") | ||
model Pet { | ||
kind: PetKind; | ||
name: string; | ||
} | ||
union PetKind { | ||
cat: "cat", | ||
dog: "dog", | ||
string, | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
packages/typespec-azure-core/src/rules/no-string-discriminator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { Model, createRule, getDiscriminator, paramMessage } from "@typespec/compiler"; | ||
|
||
export const noStringDiscriminatorRule = createRule({ | ||
name: "no-string-discriminator", | ||
description: | ||
"Azure services discriminated models should define the discriminated property as an extensible union.", | ||
severity: "warning", | ||
url: "https://azure.github.io/typespec-azure/docs/libraries/azure-core/rules/no-string-discriminator", | ||
messages: { | ||
default: `Use an extensible union instead of a plain string (ex: \`union PetKind { cat: "cat", dog: "dog", string };\`)`, | ||
noProp: paramMessage`Discriminated model should define an the discriminator property ${"propName"} with an extensible union as type.`, | ||
}, | ||
create(context) { | ||
return { | ||
model: (model: Model) => { | ||
const discriminator = getDiscriminator(context.program, model); | ||
if (discriminator === undefined) { | ||
return; | ||
} | ||
|
||
const prop = model.properties.get(discriminator.propertyName); | ||
if (prop === undefined) { | ||
context.reportDiagnostic({ | ||
format: { propName: discriminator.propertyName }, | ||
target: model, | ||
}); | ||
} else { | ||
if (prop.type.kind !== "Union") { | ||
context.reportDiagnostic({ | ||
format: { propName: discriminator.propertyName }, | ||
target: prop, | ||
}); | ||
} | ||
} | ||
}, | ||
}; | ||
}, | ||
}); |
75 changes: 75 additions & 0 deletions
75
packages/typespec-azure-core/test/rules/no-string-discriminator.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { | ||
BasicTestRunner, | ||
LinterRuleTester, | ||
createLinterRuleTester, | ||
} from "@typespec/compiler/testing"; | ||
import { beforeEach, it } from "vitest"; | ||
import { noStringDiscriminatorRule } from "../../src/rules/no-string-discriminator.js"; | ||
import { createAzureCoreTestRunner } from "../test-host.js"; | ||
|
||
let runner: BasicTestRunner; | ||
let tester: LinterRuleTester; | ||
|
||
beforeEach(async () => { | ||
runner = await createAzureCoreTestRunner({ omitServiceNamespace: true }); | ||
tester = createLinterRuleTester( | ||
runner, | ||
noStringDiscriminatorRule, | ||
"@azure-tools/typespec-azure-core" | ||
); | ||
}); | ||
|
||
it("emits a warning @discriminator is used without the explicit property", async () => { | ||
await tester | ||
.expect( | ||
` | ||
@discriminator("kind") | ||
model Pet { | ||
} | ||
` | ||
) | ||
.toEmitDiagnostics([ | ||
{ | ||
code: "@azure-tools/typespec-azure-core/no-string-discriminator", | ||
message: | ||
'Use an extensible union instead of a plain string (ex: `union PetKind { cat: "cat", dog: "dog", string };`)', | ||
}, | ||
]); | ||
}); | ||
|
||
it("emits a warning @discriminator points to a property that is not a union", async () => { | ||
await tester | ||
.expect( | ||
` | ||
@discriminator("kind") | ||
model Pet { | ||
kind: string; | ||
} | ||
` | ||
) | ||
.toEmitDiagnostics([ | ||
{ | ||
code: "@azure-tools/typespec-azure-core/no-string-discriminator", | ||
message: | ||
'Use an extensible union instead of a plain string (ex: `union PetKind { cat: "cat", dog: "dog", string };`)', | ||
}, | ||
]); | ||
}); | ||
|
||
it("doesn't warn when using an extensible union as the type", async () => { | ||
await tester | ||
.expect( | ||
` | ||
@discriminator("kind") | ||
model Pet { kind: PetKind; } | ||
model Cat extends Pet { kind: "cat" } | ||
union PetKind { | ||
dog: "dog", | ||
cat: "cat", | ||
} | ||
` | ||
) | ||
.toBeValid(); | ||
}); |