-
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.
ARM: add
arm-resource-name-pattern
rule to allow disabling LintDiff…
… `ResourceNamePattern` rule (#359) Closes Azure/typespec-azure-pr#3903 **REST API Specs** (6 violations) Azure/azure-rest-api-specs#28085 **OpenAPI Validator PR** Azure/azure-openapi-validator#669 --------- Co-authored-by: Mark Cowlishaw <[email protected]>
- Loading branch information
1 parent
909ed30
commit 8d9d99a
Showing
8 changed files
with
255 additions
and
19 deletions.
There are no files selected for viewing
8 changes: 8 additions & 0 deletions
8
.chronus/changes/arm-resourceNamePatternRule-2024-2-4-21-52-56.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: feature | ||
packages: | ||
- "@azure-tools/typespec-azure-resource-manager" | ||
--- | ||
|
||
ARM: add `arm-resource-name-pattern` rule to allow disabling LintDiff `ResourceNamePattern` rule |
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
32 changes: 32 additions & 0 deletions
32
docs/libraries/azure-resource-manager/rules/resource-name-pattern.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,32 @@ | ||
--- | ||
title: resource-name-pattern | ||
--- | ||
|
||
```text title=- Full name- | ||
@azure-tools/typespec-azure-resource-manager/resource-name-pattern | ||
``` | ||
|
||
Resource names must specify a pattern string using `@pattern`, providing a regular expression that the name must match. | ||
|
||
#### ❌ Incorrect | ||
|
||
```tsp | ||
model Employee is ProxyResource<{}> { | ||
@key("employeeName") | ||
@path | ||
@segment("employees") | ||
name: string; | ||
} | ||
``` | ||
|
||
#### ✅ Correct | ||
|
||
```tsp | ||
model Employee is ProxyResource<{}> { | ||
@pattern("^[a-zA-Z0-9-]{3,24}$") | ||
@key("employeeName") | ||
@path | ||
@segment("employees") | ||
name: 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
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
74 changes: 74 additions & 0 deletions
74
packages/typespec-azure-resource-manager/src/rules/arm-resource-name-pattern.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,74 @@ | ||
import { | ||
DiagnosticTarget, | ||
Program, | ||
SourceLocation, | ||
createRule, | ||
defineCodeFix, | ||
getPattern, | ||
getSourceLocation, | ||
} from "@typespec/compiler"; | ||
|
||
import { getArmResources } from "../resource.js"; | ||
|
||
// TODO: Replace this with a reusable implementation from the compiler package when implemented. | ||
// Issue: https://github.com/microsoft/typespec/issues/3044 | ||
function createPatternCodeFix(diagnosticTarget: DiagnosticTarget) { | ||
return defineCodeFix({ | ||
id: "add-pattern-decorator", | ||
label: "Add `@pattern` decorator to the resource name property with the default ARM pattern.", | ||
fix: (context) => { | ||
const location = getSourceLocation(diagnosticTarget); | ||
const { lineStart, indent } = findLineStartAndIndent(location); | ||
const updatedLocation = { ...location, pos: lineStart }; | ||
return context.prependText(updatedLocation, `${indent}@pattern(/^[a-zA-Z0-9-]{3,24}$/)\n`); | ||
}, | ||
}); | ||
} | ||
|
||
function findLineStartAndIndent(location: SourceLocation): { lineStart: number; indent: string } { | ||
const text = location.file.text; | ||
let pos = location.pos; | ||
let indent = 0; | ||
while (pos > 0 && text[pos - 1] !== "\n") { | ||
if ([" ", "\t", "\n"].includes(text[pos - 1])) { | ||
indent++; | ||
} else { | ||
indent = 0; | ||
} | ||
pos--; | ||
} | ||
return { lineStart: pos, indent: location.file.text.slice(pos, pos + indent) }; | ||
} | ||
|
||
/** | ||
* Verify that a delete operation only | ||
*/ | ||
export const armResourceNamePatternRule = createRule({ | ||
name: "arm-resource-name-pattern", | ||
severity: "warning", | ||
url: "https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/resource-name-pattern", | ||
description: "The resource name parameter should be defined with a 'pattern' restriction.", | ||
messages: { | ||
default: `The resource name parameter should be defined with a 'pattern' restriction. Decorate the "name" property in the resource definition using the @pattern decorator, with a regular expression indicating the allowed characters in the resource name.`, | ||
}, | ||
create(context) { | ||
return { | ||
root: (program: Program) => { | ||
const resources = getArmResources(program); | ||
for (const resource of resources) { | ||
// find the name property | ||
const nameProperty = resource.typespecType.properties.get("name"); | ||
if (nameProperty !== undefined) { | ||
const pattern = getPattern(program, nameProperty); | ||
if (pattern === undefined) { | ||
context.reportDiagnostic({ | ||
target: nameProperty, | ||
codefixes: [createPatternCodeFix(nameProperty)], | ||
}); | ||
} | ||
} | ||
} | ||
}, | ||
}; | ||
}, | ||
}); |
116 changes: 116 additions & 0 deletions
116
packages/typespec-azure-resource-manager/test/rules/arm-resource-name-pattern.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,116 @@ | ||
import { | ||
BasicTestRunner, | ||
LinterRuleTester, | ||
createLinterRuleTester, | ||
} from "@typespec/compiler/testing"; | ||
import { beforeEach, it } from "vitest"; | ||
import { armResourceNamePatternRule } from "../../src/rules/arm-resource-name-pattern.js"; | ||
import { createAzureResourceManagerTestRunner } from "../test-host.js"; | ||
|
||
let runner: BasicTestRunner; | ||
let tester: LinterRuleTester; | ||
|
||
beforeEach(async () => { | ||
runner = await createAzureResourceManagerTestRunner(); | ||
tester = createLinterRuleTester( | ||
runner, | ||
armResourceNamePatternRule, | ||
"@azure-tools/typespec-azure-resource-manager" | ||
); | ||
}); | ||
|
||
it("Emits a warning for an ARM resource that doesn't specify `@pattern` on the name", async () => { | ||
await tester | ||
.expect( | ||
` | ||
@armProviderNamespace | ||
@useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1) | ||
namespace Microsoft.Contoso; | ||
model Employee is ProxyResource<{}> { | ||
@key("employeeName") | ||
@path | ||
@segment("employees") | ||
name: string; | ||
} | ||
@parentResource(Employee) | ||
model EmployeeRole is ProxyResource<{}> { | ||
@key("roleName") | ||
@segment("roles") | ||
@path | ||
@visibility("read") | ||
name: string; | ||
} | ||
` | ||
) | ||
.toEmitDiagnostics([ | ||
{ | ||
code: "@azure-tools/typespec-azure-resource-manager/arm-resource-name-pattern", | ||
}, | ||
{ | ||
code: "@azure-tools/typespec-azure-resource-manager/arm-resource-name-pattern", | ||
}, | ||
]); | ||
}); | ||
|
||
it("Allows codefix when ARM resource name is missing pattern.", async () => { | ||
await tester | ||
.expect( | ||
` | ||
@armProviderNamespace | ||
@useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1) | ||
namespace Microsoft.Contoso; | ||
model Employee is ProxyResource<{}> { | ||
@key("employeeName") | ||
@path | ||
@segment("employees") | ||
name: string; | ||
} | ||
` | ||
) | ||
.applyCodeFix("add-pattern-decorator").toEqual(` | ||
@armProviderNamespace | ||
@useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1) | ||
namespace Microsoft.Contoso; | ||
model Employee is ProxyResource<{}> { | ||
@pattern(/^[a-zA-Z0-9-]{3,24}$/) | ||
@key("employeeName") | ||
@path | ||
@segment("employees") | ||
name: string; | ||
} | ||
`); | ||
}); | ||
|
||
it("Does not emit a warning for an ARM resource that specifies `@pattern` on the name", async () => { | ||
await tester | ||
.expect( | ||
` | ||
@armProviderNamespace | ||
@useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1) | ||
namespace Microsoft.Contoso; | ||
model Employee is ProxyResource<{}> { | ||
@doc("Name of employee") | ||
@pattern("^[a-zA-Z0-9-]{3,24}$") | ||
@key("employeeName") | ||
@path | ||
@segment("employees") | ||
name: string; | ||
} | ||
@parentResource(Employee) | ||
model EmployeeRole is ProxyResource<{}> { | ||
@key("roleName") | ||
@segment("roles") | ||
@pattern("^[a-zA-Z0-9-]{3,24}$") | ||
@path | ||
@visibility("read") | ||
name: string; | ||
}` | ||
) | ||
.toBeValid(); | ||
}); |