Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add no-generic-numeric rule to disable LintDiff IntegerTypeMustHaveFormat #619

Merged
merged 10 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
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"],
tjprescott marked this conversation as resolved.
Show resolved Hide resolved
["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)!,
},
});
}
},
};
},
});
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.",
},
]);
});
Loading