Skip to content
Draft
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
2 changes: 2 additions & 0 deletions packages/typespec-azure-resource-manager/src/linter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { retryAfterRule } from "./rules/retry-after.js";
import { secretProprule } from "./rules/secret-prop.js";
import { unsupportedTypeRule } from "./rules/unsupported-type.js";
import { versionProgressionRule } from "./rules/version-progression.js";
import { modelNameRequestSuffixRule } from "./rules/arm-model-name-request-suffix.js";

const rules = [
armNoRecordRule,
Expand Down Expand Up @@ -73,6 +74,7 @@ const rules = [
unsupportedTypeRule,
secretProprule,
noEmptyModel,
modelNameRequestSuffixRule,
];

export const $linter = defineLinter({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { createRule, defineCodeFix, getSourceLocation, Model, paramMessage } from "@typespec/compiler";

function createReplaceRequestWithContentCodeFix(target: Model, name: string) {
const newName = name.replace(/Request$/, "Content");
return defineCodeFix({
id: "replace-request-with-content",
label: `Add @clientName("${newName}", "csharp")`,
fix: (fixContext) => {
const location = getSourceLocation(target);
const text = location.file.text;
let lineStart = location.pos;
while (lineStart > 0 && text[lineStart - 1] !== "\n") {
lineStart--;
}
let indentEnd = lineStart;
while (indentEnd < text.length && (text[indentEnd] === " " || text[indentEnd] === "\t")) {
indentEnd++;
}
const indent = text.slice(lineStart, indentEnd);
const updatedLocation = { ...location, pos: lineStart };
return fixContext.prependText(
updatedLocation,
`${indent}@clientName("${newName}", "csharp")\n`,
);
},
});
}

export const modelNameRequestSuffixRule = createRule({
name: "arm-model-name-request-suffix",
description: "Model names should not end with 'Request'. Use 'Content' suffix instead.",
severity: "warning",
url: "https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/arm-model-name-request-suffix",
messages: {
default: paramMessage`Model name '${"name"}' ends with 'Request'. Consider renaming it to '${"suggestion"}' or use @clientName("${"suggestion"}", "csharp") to rename it for C#.`,
},
create(context) {
// NOTE: Cannot use getLibraryName/createTCGCContext here because
// typespec-azure-resource-manager does not depend on typespec-client-generator-core.
// This means we can only check the raw TypeSpec model name, NOT the C#-scoped
// name from @clientName overrides. If a model has @clientName("FooContent", "csharp")
// but its TypeSpec name is still FooRequest, this rule will STILL flag it as a
// false positive.
return {
model: (model: Model) => {
if (typeof model.name !== "string") return;
const name = model.name;
if (!name.endsWith("Request")) return;
const suggestion = name.replace(/Request$/, "Content");
context.reportDiagnostic({
format: {
name,
suggestion,
},
target: model,
codefixes: [createReplaceRequestWithContentCodeFix(model, name)],
});
},
};
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export default {
"@azure-tools/typespec-azure-resource-manager/retry-after": false, // Disable https://github.com/Azure/typespec-azure/issues/3351
"@azure-tools/typespec-azure-resource-manager/secret-prop": true,
"@azure-tools/typespec-azure-resource-manager/unsupported-type": true,
"@azure-tools/typespec-azure-resource-manager/arm-model-name-request-suffix": true,

// TCGC rules
"@azure-tools/typespec-client-generator-core/require-client-suffix": true,
Expand Down
Loading