Skip to content

Commit

Permalink
[TypeSpecValidation] Enforce ".Management" suffix (Azure#29663)
Browse files Browse the repository at this point in the history
- Fixes Azure#29654
  • Loading branch information
mikeharder authored Jul 16, 2024
1 parent f32a626 commit 0be23b4
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 10 deletions.
41 changes: 31 additions & 10 deletions eng/tools/typespec-validation/src/rules/folder-structure.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import path from "path";
import { parse as yamlParse } from "yaml";
import { Rule } from "../rule.js";
import { RuleResult } from "../rule-result.js";
import { TsvHost } from "../tsv-host.js";
Expand All @@ -10,8 +11,8 @@ export class FolderStructureRule implements Rule {
let success = true;
let stdOutput = "";
let errorOutput = "";
let gitRoot = host.normalizePath(await host.gitOperation(folder).revparse("--show-toplevel"));
let relativePath = path.relative(gitRoot, folder).split(path.sep).join("/");
const gitRoot = host.normalizePath(await host.gitOperation(folder).revparse("--show-toplevel"));
const relativePath = path.relative(gitRoot, folder).split(path.sep).join("/");

stdOutput += `folder: ${folder}\n`;
if (!(await host.checkFileExists(folder))) {
Expand All @@ -32,13 +33,13 @@ export class FolderStructureRule implements Rule {
});

// Verify top level folder is lower case and remove empty entries when splitting by slash
let folderStruct = relativePath.split("/").filter(Boolean);
const folderStruct = relativePath.split("/").filter(Boolean);
if (folderStruct[1].match(/[A-Z]/g)) {
success = false;
errorOutput += `Invalid folder name. Folders under specification/ must be lower case.\n`;
}

let packageFolder = folderStruct[folderStruct.length - 1];
const packageFolder = folderStruct[folderStruct.length - 1];

// Verify package folder is at most 3 levels deep
if (folderStruct.length > 4) {
Expand All @@ -61,8 +62,9 @@ export class FolderStructureRule implements Rule {
}

// Verify tspconfig, main.tsp, examples/
let mainExists = await host.checkFileExists(path.join(folder, "main.tsp"));
let clientExists = await host.checkFileExists(path.join(folder, "client.tsp"));
const mainExists = await host.checkFileExists(path.join(folder, "main.tsp"));
const clientExists = await host.checkFileExists(path.join(folder, "client.tsp"));
const tspConfigExists = await host.checkFileExists(path.join(folder, "tspconfig.yaml"));

if (!mainExists && !clientExists) {
errorOutput += `Invalid folder structure: Spec folder must contain main.tsp or client.tsp.`;
Expand All @@ -74,14 +76,33 @@ export class FolderStructureRule implements Rule {
success = false;
}

if (
!packageFolder.includes("Shared") &&
!(await host.checkFileExists(path.join(folder, "tspconfig.yaml")))
) {
if (!packageFolder.includes("Shared") && !tspConfigExists) {
errorOutput += `Invalid folder structure: Spec folder must contain tspconfig.yaml.`;
success = false;
}

if (tspConfigExists) {
const configText = await host.readTspConfig(folder);
const config = yamlParse(configText);
const rpFolder =
config?.options?.["@azure-tools/typespec-autorest"]?.["azure-resource-provider-folder"];
stdOutput += `azure-resource-provider-folder: ${JSON.stringify(rpFolder)}\n`;

if (
rpFolder?.trim()?.endsWith("resource-manager") &&
!packageFolder.endsWith(".Management")
) {
errorOutput += `Invalid folder structure: TypeSpec for resource-manager specs must be in a folder ending with '.Management'`;
success = false;
} else if (
!rpFolder?.trim()?.endsWith("resource-manager") &&
packageFolder.endsWith(".Management")
) {
errorOutput += `Invalid folder structure: TypeSpec for data-plane specs or shared code must be in a folder NOT ending with '.Management'`;
success = false;
}
}

return {
success: success,
stdOutput: stdOutput,
Expand Down
90 changes: 90 additions & 0 deletions eng/tools/typespec-validation/test/folder-structure.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,94 @@ describe("folder-structure", function () {
assert(result.errorOutput);
assert(result.errorOutput.includes("must contain"));
});

it("should succeed with resource-manager/Management", async function() {
let host = new TsvTestHost();
host.globby = async () => {
return ["/foo/Foo.Management/tspconfig.yaml"];
};
host.normalizePath = () => {
return "/gitroot";
};
host.readTspConfig = async (_folder: string) => `
options:
"@azure-tools/typespec-autorest":
azure-resource-provider-folder: "resource-manager"
`;

const result = await new FolderStructureRule().execute(
host,
"/gitroot/specification/foo/Foo.Management",
);

assert(result.success);
});

it("should succeed with data-plane/NoManagement", async function() {
let host = new TsvTestHost();
host.globby = async () => {
return ["/foo/Foo/tspconfig.yaml"];
};
host.normalizePath = () => {
return "/gitroot";
};
host.readTspConfig = async (_folder: string) => `
options:
"@azure-tools/typespec-autorest":
azure-resource-provider-folder: "data-plane"
`;

const result = await new FolderStructureRule().execute(
host,
"/gitroot/specification/foo/Foo",
);

assert(result.success);
});

it("should fail with resource-manager/NoManagement", async function() {
let host = new TsvTestHost();
host.globby = async () => {
return ["/foo/Foo/tspconfig.yaml"];
};
host.normalizePath = () => {
return "/gitroot";
};
host.readTspConfig = async (_folder: string) => `
options:
"@azure-tools/typespec-autorest":
azure-resource-provider-folder: "resource-manager"
`;

const result = await new FolderStructureRule().execute(
host,
"/gitroot/specification/foo/Foo",
);

assert(result.errorOutput);
assert(result.errorOutput.includes(".Management"));
});

it("should fail with data-plane/Management", async function() {
let host = new TsvTestHost();
host.globby = async () => {
return ["/foo/Foo.Management/tspconfig.yaml"];
};
host.normalizePath = () => {
return "/gitroot";
};
host.readTspConfig = async (_folder: string) => `
options:
"@azure-tools/typespec-autorest":
azure-resource-provider-folder: "data-plane"
`;

const result = await new FolderStructureRule().execute(
host,
"/gitroot/specification/foo/Foo.Management",
);

assert(result.errorOutput);
assert(result.errorOutput.includes(".Management"));
});
});

0 comments on commit 0be23b4

Please sign in to comment.