Skip to content

Commit

Permalink
Add no-generic-numeric rule to disable LintDiff `IntegerTypeMustHav…
Browse files Browse the repository at this point in the history
…eFormat` (#619)

Closes #669.

**REST API Specs**
Azure/azure-rest-api-specs#28731
  • Loading branch information
tjprescott committed Apr 18, 2024
1 parent bd2d2a9 commit a5d144e
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .chronus/changes/core-IntegerTypesRule-2024-3-10-16-52-31.md
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 `no-generic-numeric` rule to disable LintDiff `IntegerTypeMustHaveFormat`
1 change: 1 addition & 0 deletions docs/libraries/azure-core/reference/linter.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Available ruleSets:
| `@azure-tools/typespec-azure-core/no-explicit-routes-resource-ops` | The @route decorator should not be used on standard resource operation signatures. |
| `@azure-tools/typespec-azure-core/no-fixed-enum-discriminator` | Discriminator shouldn't be a fixed enum. |
| [`@azure-tools/typespec-azure-core/non-breaking-versioning`](/libraries/azure-core/rules/non-breaking-versioning.md) | Check that only backward compatible versioning change are done to a service. |
| [`@azure-tools/typespec-azure-core/no-generic-numeric`](/libraries/azure-core/rules/no-generic-numeric.md) | Don't use generic types. Use more specific types instead. |
| `@azure-tools/typespec-azure-core/no-nullable` | Use `?` for optional properties. |
| `@azure-tools/typespec-azure-core/no-offsetdatetime` | Prefer using `utcDateTime` when representing a datetime unless an offset is necessary. |
| `@azure-tools/typespec-azure-core/no-response-body` | Ensure that the body is set correctly for the response type. |
Expand Down
47 changes: 47 additions & 0 deletions docs/libraries/azure-core/rules/no-generic-numeric.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
title: "no-generic-numeric"
---

```text title="Full name"
@azure-tools/typespec-azure-core/no-generic-numeric
```

Azure services should use numeric types that specify the bit-width instead of generic types.

#### ❌ Incorrect

```tsp
model Widget {
id: integer;
cost: float;
}
```

#### ✅ Correct

```tsp
model Widget {
id: safeint;
cost: float32;
}
```

This includes extending generic numeric types.

#### ❌ Incorrect

```tsp
model GenericInteger extends integer;
model Widget {
id: GenericInteger;
}
```

#### ✅ Correct

```tsp
model Widget {
id: safeint;
}
```
1 change: 1 addition & 0 deletions packages/typespec-azure-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Available ruleSets:
| `@azure-tools/typespec-azure-core/no-explicit-routes-resource-ops` | The @route decorator should not be used on standard resource operation signatures. |
| `@azure-tools/typespec-azure-core/no-fixed-enum-discriminator` | Discriminator shouldn't be a fixed enum. |
| [`@azure-tools/typespec-azure-core/non-breaking-versioning`](https://azure.github.io/typespec-azure/docs/libraries/azure-core/rules/non-breaking-versioning) | Check that only backward compatible versioning change are done to a service. |
| [`@azure-tools/typespec-azure-core/no-generic-numeric`](https://azure.github.io/typespec-azure/docs/libraries/azure-core/rules/no-generic-numeric) | Don't use generic types. Use more specific types instead. |
| `@azure-tools/typespec-azure-core/no-nullable` | Use `?` for optional properties. |
| `@azure-tools/typespec-azure-core/no-offsetdatetime` | Prefer using `utcDateTime` when representing a datetime unless an offset is necessary. |
| `@azure-tools/typespec-azure-core/no-response-body` | Ensure that the body is set correctly for the response type. |
Expand Down
2 changes: 2 additions & 0 deletions packages/typespec-azure-core/src/linter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { noEnumRule } from "./rules/no-enum.js";
import { noErrorStatusCodesRule } from "./rules/no-error-status-codes.js";
import { noExplicitRoutesResourceOps } from "./rules/no-explicit-routes-resource-ops.js";
import { noFixedEnumDiscriminatorRule } from "./rules/no-fixed-enum-discriminator.js";
import { noGenericNumericRule } from "./rules/no-generic-numeric.js";
import { noNullableRule } from "./rules/no-nullable.js";
import { noOffsetDateTimeRule } from "./rules/no-offsetdatetime.js";
import { operationIdRule } from "./rules/no-operation-id.js";
Expand Down Expand Up @@ -51,6 +52,7 @@ const rules = [
noExplicitRoutesResourceOps,
noFixedEnumDiscriminatorRule,
nonBreakingVersioningRule,
noGenericNumericRule,
noNullableRule,
noOffsetDateTimeRule,
noResponseBodyRule,
Expand Down
64 changes: 64 additions & 0 deletions packages/typespec-azure-core/src/rules/no-generic-numeric.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Model, Scalar, createRule, paramMessage } from "@typespec/compiler";

const disallowList = new Set(["integer", "numeric", "float", "decimal"]);
const alternatives = new Map([
["integer", "int32"],
["numeric", "int32"],
["float", "float32"],
["decimal", "float32"],
]);

export const noGenericNumericRule = createRule({
name: "no-generic-numeric",
description: "Don't use generic types. Use more specific types instead.",
severity: "warning",
url: "https://azure.github.io/typespec-azure/docs/libraries/azure-core/rules/no-generic-numeric",
messages: {
default: paramMessage`Don't use generic type '${"name"}'. Use a more specific type that specifies the bit size, such as '${"alternative"}' instead.`,
extend: paramMessage`Don't extend generic type '${"name"}'. Use a more specific type that specifies the bit size, such as '${"alternative"}' instead.`,
},
create(context) {
return {
model: (model: Model) => {
for (const [_, prop] of model.properties) {
if (prop.type.kind === "Scalar") {
if (disallowList.has(prop.type.name)) {
context.reportDiagnostic({
target: prop,
format: {
name: prop.type.name,
alternative: alternatives.get(prop.type.name)!,
},
});
}
}
}
},
scalar: (scalar: Scalar) => {
// if the scalar is the base scalar, then we don't need to check it as it will surface
// in usage (for example: as a model property)
if (disallowList.has(scalar.name)) {
return;
}
let baseScalar: Scalar | undefined = undefined;
while (scalar.baseScalar !== undefined) {
baseScalar = scalar.baseScalar;
break;
}
if (baseScalar === undefined) {
return;
}
if (disallowList.has(baseScalar.name)) {
context.reportDiagnostic({
target: scalar,
messageId: "extend",
format: {
name: baseScalar.name,
alternative: alternatives.get(baseScalar.name)!,
},
});
}
},
};
},
});
97 changes: 97 additions & 0 deletions packages/typespec-azure-core/test/rules/no-generic-numeric.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {
BasicTestRunner,
LinterRuleTester,
createLinterRuleTester,
} from "@typespec/compiler/testing";
import { beforeEach, it } from "vitest";
import { noGenericNumericRule } from "../../src/rules/no-generic-numeric.js";
import { createAzureCoreTestRunner } from "../test-host.js";

let runner: BasicTestRunner;
let tester: LinterRuleTester;

beforeEach(async () => {
runner = await createAzureCoreTestRunner({ omitServiceNamespace: true });
tester = createLinterRuleTester(runner, noGenericNumericRule, "@azure-tools/typespec-azure-core");
});

it("emits a warning for generic numeric types", async () => {
await tester
.expect(
`
namespace Azure.Widget;
model Widget {
prop1: integer;
prop2: numeric;
prop3: float;
prop4: decimal;
}
`
)
.toEmitDiagnostics([
{
code: "@azure-tools/typespec-azure-core/no-generic-numeric",
message:
"Don't use generic type 'integer'. Use a more specific type that specifies the bit size, such as 'int32' instead.",
},
{
code: "@azure-tools/typespec-azure-core/no-generic-numeric",
message:
"Don't use generic type 'numeric'. Use a more specific type that specifies the bit size, such as 'int32' instead.",
},
{
code: "@azure-tools/typespec-azure-core/no-generic-numeric",
message:
"Don't use generic type 'float'. Use a more specific type that specifies the bit size, such as 'float32' instead.",
},
{
code: "@azure-tools/typespec-azure-core/no-generic-numeric",
message:
"Don't use generic type 'decimal'. Use a more specific type that specifies the bit size, such as 'float32' instead.",
},
]);
});

it("emits a warning when extending generic numeric types", async () => {
await tester
.expect(
`
namespace Azure.Widget;
scalar GenericInteger extends integer;
scalar GenericNumeric extends numeric;
scalar GenericFloat extends float;
scalar GenericDecimal extends decimal;
model Widget {
prop1: GenericInteger;
prop2: GenericNumeric;
prop3: GenericFloat;
prop4: GenericDecimal;
}
`
)
.toEmitDiagnostics([
{
code: "@azure-tools/typespec-azure-core/no-generic-numeric",
message:
"Don't extend generic type 'integer'. Use a more specific type that specifies the bit size, such as 'int32' instead.",
},
{
code: "@azure-tools/typespec-azure-core/no-generic-numeric",
message:
"Don't extend generic type 'numeric'. Use a more specific type that specifies the bit size, such as 'int32' instead.",
},
{
code: "@azure-tools/typespec-azure-core/no-generic-numeric",
message:
"Don't extend generic type 'float'. Use a more specific type that specifies the bit size, such as 'float32' instead.",
},
{
code: "@azure-tools/typespec-azure-core/no-generic-numeric",
message:
"Don't extend generic type 'decimal'. Use a more specific type that specifies the bit size, such as 'float32' instead.",
},
]);
});

0 comments on commit a5d144e

Please sign in to comment.