From 6bb00d39a0409c4e9188223718536c7fca769194 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 17 Dec 2024 08:30:16 +0000 Subject: [PATCH 001/106] WIP: Implement TSV as ESLint Rules --- eng/tools/eslint-plugin-tsv/package.json | 24 ++++++++++++++ .../eslint-plugin-tsv/src/enforce-foo-bar.cjs | 32 +++++++++++++++++++ .../src/eslint-plugin-tsv.cjs | 5 +++ .../eslint-plugin-tsv/test/eslint.config.cjs | 15 +++++++++ eng/tools/eslint-plugin-tsv/test/test.yaml | 1 + eng/tools/eslint-plugin-tsv/tsconfig.json | 6 ++++ 6 files changed, 83 insertions(+) create mode 100644 eng/tools/eslint-plugin-tsv/package.json create mode 100644 eng/tools/eslint-plugin-tsv/src/enforce-foo-bar.cjs create mode 100644 eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.cjs create mode 100644 eng/tools/eslint-plugin-tsv/test/eslint.config.cjs create mode 100644 eng/tools/eslint-plugin-tsv/test/test.yaml create mode 100644 eng/tools/eslint-plugin-tsv/tsconfig.json diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json new file mode 100644 index 000000000000..b27f237b803f --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -0,0 +1,24 @@ +{ + "name": "@azure-tools/eslint-plugin-tsv", + "private": true, + "type": "module", + "main": "src/index.js", + "dependencies": { + "yaml-eslint-parser": "^1.2.3" + }, + "peerDependencies": { + "eslint": ">=9.0.0" + }, + "devDependencies": { + "@types/node": "^18.19.31", + "eslint": "^9.17.0", + "typescript": "~5.6.2" + }, + "scripts": { + "build": "tsc --build", + "test": "eslint --config test/eslint.config.cjs test/test.yaml" + }, + "engines": { + "node": ">= 18.0.0" + } +} diff --git a/eng/tools/eslint-plugin-tsv/src/enforce-foo-bar.cjs b/eng/tools/eslint-plugin-tsv/src/enforce-foo-bar.cjs new file mode 100644 index 000000000000..92ffd91d632c --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/enforce-foo-bar.cjs @@ -0,0 +1,32 @@ +// TODO: Convert to TS + +module.exports = { + meta: { + type: "problem", + docs: { + description: + "Enforce that a variable named `foo` can only be assigned a value of 'bar'.", + }, + fixable: "code", + schema: [], + }, + create(context) { + return { + YAMLPair(node) { + if (node.key.value == "foo" && node.value.value != "bar") { + context.report({ + node, + message: + 'Value other than "bar" assigned to `foo`. Unexpected value: {{ notBar }}.', + data: { + notBar: node.value.value, + }, + fix(fixer) { + return fixer.replaceText(node.value, 'bar'); + }, + }); + } + }, + }; + }, +}; diff --git a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.cjs b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.cjs new file mode 100644 index 000000000000..7652b9759d93 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.cjs @@ -0,0 +1,5 @@ +// TODO: Convert to TS + +const enforceFooBar = require("./enforce-foo-bar.cjs"); +const plugin = { rules: { "enforce-foo-bar": enforceFooBar } }; +module.exports = plugin; diff --git a/eng/tools/eslint-plugin-tsv/test/eslint.config.cjs b/eng/tools/eslint-plugin-tsv/test/eslint.config.cjs new file mode 100644 index 000000000000..19a942ee144c --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/test/eslint.config.cjs @@ -0,0 +1,15 @@ +const eslintPluginTsv = require("../src/eslint-plugin-tsv.cjs"); +const parser = require("yaml-eslint-parser"); + +module.exports = [ + { + plugins: { tsv: eslintPluginTsv }, + files: ["*.yaml", "**/*.yaml"], + languageOptions: { + parser, + }, + rules: { + "tsv/enforce-foo-bar": "error", + }, + }, +]; diff --git a/eng/tools/eslint-plugin-tsv/test/test.yaml b/eng/tools/eslint-plugin-tsv/test/test.yaml new file mode 100644 index 000000000000..c444f32c5010 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/test/test.yaml @@ -0,0 +1 @@ +foo: baz diff --git a/eng/tools/eslint-plugin-tsv/tsconfig.json b/eng/tools/eslint-plugin-tsv/tsconfig.json new file mode 100644 index 000000000000..ec6d6640928a --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + } +} From 2b9965f136145fe568bfb12cc2588bf5ec00e752 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 17 Dec 2024 08:56:38 +0000 Subject: [PATCH 002/106] Convert to TS (but still cjs) --- eng/tools/eslint-plugin-tsv/package.json | 2 +- .../src/{enforce-foo-bar.cjs => enforce-foo-bar.ts} | 5 +++-- .../src/{eslint-plugin-tsv.cjs => eslint-plugin-tsv.ts} | 4 ++-- eng/tools/eslint-plugin-tsv/test/eslint.config.cjs | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) rename eng/tools/eslint-plugin-tsv/src/{enforce-foo-bar.cjs => enforce-foo-bar.ts} (92%) rename eng/tools/eslint-plugin-tsv/src/{eslint-plugin-tsv.cjs => eslint-plugin-tsv.ts} (53%) diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index b27f237b803f..cde9b843ac8b 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -1,7 +1,7 @@ { "name": "@azure-tools/eslint-plugin-tsv", "private": true, - "type": "module", + "TODO-type": "module", "main": "src/index.js", "dependencies": { "yaml-eslint-parser": "^1.2.3" diff --git a/eng/tools/eslint-plugin-tsv/src/enforce-foo-bar.cjs b/eng/tools/eslint-plugin-tsv/src/enforce-foo-bar.ts similarity index 92% rename from eng/tools/eslint-plugin-tsv/src/enforce-foo-bar.cjs rename to eng/tools/eslint-plugin-tsv/src/enforce-foo-bar.ts index 92ffd91d632c..efaf05a01d21 100644 --- a/eng/tools/eslint-plugin-tsv/src/enforce-foo-bar.cjs +++ b/eng/tools/eslint-plugin-tsv/src/enforce-foo-bar.ts @@ -1,5 +1,3 @@ -// TODO: Convert to TS - module.exports = { meta: { type: "problem", @@ -10,8 +8,10 @@ module.exports = { fixable: "code", schema: [], }, + // @ts-ignore create(context) { return { + // @ts-ignore YAMLPair(node) { if (node.key.value == "foo" && node.value.value != "bar") { context.report({ @@ -21,6 +21,7 @@ module.exports = { data: { notBar: node.value.value, }, + // @ts-ignore fix(fixer) { return fixer.replaceText(node.value, 'bar'); }, diff --git a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.cjs b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts similarity index 53% rename from eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.cjs rename to eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts index 7652b9759d93..d05d6b0f2684 100644 --- a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.cjs +++ b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts @@ -1,5 +1,5 @@ -// TODO: Convert to TS +// @ts-ignore +import enforceFooBar from "./enforce-foo-bar"; -const enforceFooBar = require("./enforce-foo-bar.cjs"); const plugin = { rules: { "enforce-foo-bar": enforceFooBar } }; module.exports = plugin; diff --git a/eng/tools/eslint-plugin-tsv/test/eslint.config.cjs b/eng/tools/eslint-plugin-tsv/test/eslint.config.cjs index 19a942ee144c..bf779d80b834 100644 --- a/eng/tools/eslint-plugin-tsv/test/eslint.config.cjs +++ b/eng/tools/eslint-plugin-tsv/test/eslint.config.cjs @@ -1,4 +1,4 @@ -const eslintPluginTsv = require("../src/eslint-plugin-tsv.cjs"); +const eslintPluginTsv = require("../dist/src/eslint-plugin-tsv.js"); const parser = require("yaml-eslint-parser"); module.exports = [ From 1ae2452a552adc8ea25e9561ebc6938e4b3d33eb Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 17 Dec 2024 09:12:10 +0000 Subject: [PATCH 003/106] Convert to mjs --- eng/tools/eslint-plugin-tsv/package.json | 4 +- .../eslint-plugin-tsv/src/enforce-foo-bar.ts | 4 +- .../src/eslint-plugin-tsv.ts | 6 +- .../{eslint.config.cjs => eslint.config.ts} | 8 ++- eng/tools/package.json | 1 + eng/tools/tsconfig.json | 1 + package-lock.json | 70 ++++++++++++++++--- 7 files changed, 77 insertions(+), 17 deletions(-) rename eng/tools/eslint-plugin-tsv/test/{eslint.config.cjs => eslint.config.ts} (56%) diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index cde9b843ac8b..9b71d5104535 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -1,7 +1,7 @@ { "name": "@azure-tools/eslint-plugin-tsv", "private": true, - "TODO-type": "module", + "type": "module", "main": "src/index.js", "dependencies": { "yaml-eslint-parser": "^1.2.3" @@ -16,7 +16,7 @@ }, "scripts": { "build": "tsc --build", - "test": "eslint --config test/eslint.config.cjs test/test.yaml" + "test": "eslint --config dist/test/eslint.config.js test/test.yaml" }, "engines": { "node": ">= 18.0.0" diff --git a/eng/tools/eslint-plugin-tsv/src/enforce-foo-bar.ts b/eng/tools/eslint-plugin-tsv/src/enforce-foo-bar.ts index efaf05a01d21..81145fe52414 100644 --- a/eng/tools/eslint-plugin-tsv/src/enforce-foo-bar.ts +++ b/eng/tools/eslint-plugin-tsv/src/enforce-foo-bar.ts @@ -1,4 +1,4 @@ -module.exports = { +export const rule = { meta: { type: "problem", docs: { @@ -31,3 +31,5 @@ module.exports = { }; }, }; + +export default rule; diff --git a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts index d05d6b0f2684..9edfef2e76b7 100644 --- a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts +++ b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts @@ -1,5 +1,5 @@ // @ts-ignore -import enforceFooBar from "./enforce-foo-bar"; +import enforceFooBar from "./enforce-foo-bar.js"; -const plugin = { rules: { "enforce-foo-bar": enforceFooBar } }; -module.exports = plugin; +export const plugin = { rules: { "enforce-foo-bar": enforceFooBar } }; +export default plugin; diff --git a/eng/tools/eslint-plugin-tsv/test/eslint.config.cjs b/eng/tools/eslint-plugin-tsv/test/eslint.config.ts similarity index 56% rename from eng/tools/eslint-plugin-tsv/test/eslint.config.cjs rename to eng/tools/eslint-plugin-tsv/test/eslint.config.ts index bf779d80b834..88e5b14a5596 100644 --- a/eng/tools/eslint-plugin-tsv/test/eslint.config.cjs +++ b/eng/tools/eslint-plugin-tsv/test/eslint.config.ts @@ -1,7 +1,7 @@ -const eslintPluginTsv = require("../dist/src/eslint-plugin-tsv.js"); -const parser = require("yaml-eslint-parser"); +import eslintPluginTsv from "../src/eslint-plugin-tsv.js"; +import parser from "yaml-eslint-parser"; -module.exports = [ +export const config = [ { plugins: { tsv: eslintPluginTsv }, files: ["*.yaml", "**/*.yaml"], @@ -13,3 +13,5 @@ module.exports = [ }, }, ]; + +export default config; diff --git a/eng/tools/package.json b/eng/tools/package.json index 8dcd0c6a9d2d..298f6cf8c821 100644 --- a/eng/tools/package.json +++ b/eng/tools/package.json @@ -1,6 +1,7 @@ { "name": "azure-rest-api-specs-eng-tools", "devDependencies": { + "@azure-tools/eslint-plugin-tsv": "file:eslint-plugin-tsv", "@azure-tools/specs-model": "file:specs-model", "@azure-tools/suppressions": "file:suppressions", "@azure-tools/tsp-client-tests": "file:tsp-client-tests", diff --git a/eng/tools/tsconfig.json b/eng/tools/tsconfig.json index ffa89e56a6d8..008ddb8c064d 100644 --- a/eng/tools/tsconfig.json +++ b/eng/tools/tsconfig.json @@ -11,6 +11,7 @@ "composite": true, }, "references": [ + { "path": "./eslint-plugin-tsv" }, { "path": "./specs-model" }, { "path": "./suppressions" }, { "path": "./tsp-client-tests" }, diff --git a/package-lock.json b/package-lock.json index 4ff3c2f2b07d..f3328e3ac5ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "dev": true, "hasInstallScript": true, "devDependencies": { + "@azure-tools/eslint-plugin-tsv": "file:eslint-plugin-tsv", "@azure-tools/specs-model": "file:specs-model", "@azure-tools/suppressions": "file:suppressions", "@azure-tools/tsp-client-tests": "file:tsp-client-tests", @@ -46,6 +47,24 @@ "@azure-tools/typespec-validation": "file:typespec-validation" } }, + "eng/tools/eslint-plugin-tsv": { + "name": "@azure-tools/eslint-plugin-tsv", + "dev": true, + "dependencies": { + "yaml-eslint-parser": "^1.2.3" + }, + "devDependencies": { + "@types/node": "^18.19.31", + "eslint": "^9.17.0", + "typescript": "~5.6.2" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0" + } + }, "eng/tools/node_modules/@types/node": { "version": "18.19.68", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.68.tgz", @@ -818,6 +837,10 @@ "node": ">=12.0.0" } }, + "node_modules/@azure-tools/eslint-plugin-tsv": { + "resolved": "eng/tools/eslint-plugin-tsv", + "link": true + }, "node_modules/@azure-tools/openapi-tools-common": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@azure-tools/openapi-tools-common/-/openapi-tools-common-1.2.2.tgz", @@ -2234,9 +2257,9 @@ "license": "MIT" }, "node_modules/@eslint/js": { - "version": "9.16.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz", - "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", + "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", "dev": true, "license": "MIT", "engines": { @@ -4956,9 +4979,9 @@ } }, "node_modules/eslint": { - "version": "9.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz", - "integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", + "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", "dev": true, "license": "MIT", "dependencies": { @@ -4967,7 +4990,7 @@ "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.9.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.16.0", + "@eslint/js": "9.17.0", "@eslint/plugin-kit": "^0.2.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -4976,7 +4999,7 @@ "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.5", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", @@ -10023,6 +10046,37 @@ "node": ">= 14" } }, + "node_modules/yaml-eslint-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/yaml-eslint-parser/-/yaml-eslint-parser-1.2.3.tgz", + "integrity": "sha512-4wZWvE398hCP7O8n3nXKu/vdq1HcH01ixYlCREaJL5NUMwQ0g3MaGFUBNSlmBtKmhbtVG/Cm6lyYmSVTEVil8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.0.0", + "lodash": "^4.17.21", + "yaml": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, + "node_modules/yaml-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", From 57655ba61a5d8140fc4cf0ffc99d4faa5ad51de7 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 17 Dec 2024 09:30:34 +0000 Subject: [PATCH 004/106] Add contoso test --- eng/tools/eslint-plugin-tsv/package.json | 3 +- ...int.config.ts => contoso.eslint.config.ts} | 0 .../test/contoso.tspconfig.yaml | 39 +++++++++++++++++++ .../test/test.eslint.config.ts | 17 ++++++++ 4 files changed, 58 insertions(+), 1 deletion(-) rename eng/tools/eslint-plugin-tsv/test/{eslint.config.ts => contoso.eslint.config.ts} (100%) create mode 100644 eng/tools/eslint-plugin-tsv/test/contoso.tspconfig.yaml create mode 100644 eng/tools/eslint-plugin-tsv/test/test.eslint.config.ts diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index 9b71d5104535..edd3a608e216 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -16,7 +16,8 @@ }, "scripts": { "build": "tsc --build", - "test": "eslint --config dist/test/eslint.config.js test/test.yaml" + "test": "eslint --config dist/test/test.eslint.config.js test/test.yaml", + "test:contoso": "eslint --config dist/test/contoso.eslint.config.js test/contoso.tspconfig.yaml" }, "engines": { "node": ">= 18.0.0" diff --git a/eng/tools/eslint-plugin-tsv/test/eslint.config.ts b/eng/tools/eslint-plugin-tsv/test/contoso.eslint.config.ts similarity index 100% rename from eng/tools/eslint-plugin-tsv/test/eslint.config.ts rename to eng/tools/eslint-plugin-tsv/test/contoso.eslint.config.ts diff --git a/eng/tools/eslint-plugin-tsv/test/contoso.tspconfig.yaml b/eng/tools/eslint-plugin-tsv/test/contoso.tspconfig.yaml new file mode 100644 index 000000000000..2633b3a76f34 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/test/contoso.tspconfig.yaml @@ -0,0 +1,39 @@ +parameters: + "service-dir": + default: "sdk/contosowidgetmanager" + "dependencies": + "additionalDirectories": + - "specification/contosowidgetmanager/Contoso.WidgetManager.Shared/" + default: "" +emit: + - "@azure-tools/typespec-autorest" +linter: + extends: + - "@azure-tools/typespec-azure-rulesets/data-plane" +options: + "@azure-tools/typespec-autorest": + azure-resource-provider-folder: "data-plane" + emit-lro-options: "none" + emitter-output-dir: "{project-root}/.." + output-file: "{azure-resource-provider-folder}/{service-name}/{version-status}/{version}/widgets.json" + "@azure-tools/typespec-python": + package-dir: "azure-contoso-widgetmanager" + package-name: "{package-dir}" + generate-test: true + generate-sample: true + flavor: azure + "@azure-tools/typespec-csharp": + package-dir: "Azure.Template.Contoso" + clear-output-folder: true + model-namespace: false + namespace: "{package-dir}" + flavor: azure + "@azure-tools/typespec-ts": + package-dir: "contosowidgetmanager-rest" + packageDetails: + name: "@azure-rest/contoso-widgetmanager-rest" + flavor: azure + "@azure-tools/typespec-java": + package-dir: "azure-contoso-widgetmanager" + namespace: com.azure.contoso.widgetmanager + flavor: azure diff --git a/eng/tools/eslint-plugin-tsv/test/test.eslint.config.ts b/eng/tools/eslint-plugin-tsv/test/test.eslint.config.ts new file mode 100644 index 000000000000..88e5b14a5596 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/test/test.eslint.config.ts @@ -0,0 +1,17 @@ +import eslintPluginTsv from "../src/eslint-plugin-tsv.js"; +import parser from "yaml-eslint-parser"; + +export const config = [ + { + plugins: { tsv: eslintPluginTsv }, + files: ["*.yaml", "**/*.yaml"], + languageOptions: { + parser, + }, + rules: { + "tsv/enforce-foo-bar": "error", + }, + }, +]; + +export default config; From 06153631beb4e5c9bfed0280725d1017f2a41728 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 17 Dec 2024 09:53:51 +0000 Subject: [PATCH 005/106] Add rule top-level-folder-lowercase --- eng/tools/eslint-plugin-tsv/package.json | 4 +- .../src/eslint-plugin-tsv.ts | 8 +++- .../src/top-level-folder-lowercase.ts | 29 ++++++++++++++ .../test/contoso.eslint.config.ts | 17 -------- .../Contoso.WidgetManager/tspconfig.yaml} | 0 .../Contoso.WidgetManager/tspconfig.yaml | 39 +++++++++++++++++++ .../test/specification/eslint.config.ts | 18 +++++++++ 7 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 eng/tools/eslint-plugin-tsv/src/top-level-folder-lowercase.ts rename eng/tools/eslint-plugin-tsv/test/{contoso.tspconfig.yaml => specification/InvalidCase/Contoso.WidgetManager/tspconfig.yaml} (100%) create mode 100644 eng/tools/eslint-plugin-tsv/test/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml create mode 100644 eng/tools/eslint-plugin-tsv/test/specification/eslint.config.ts diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index edd3a608e216..ae0cd1e6240d 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -17,7 +17,9 @@ "scripts": { "build": "tsc --build", "test": "eslint --config dist/test/test.eslint.config.js test/test.yaml", - "test:contoso": "eslint --config dist/test/contoso.eslint.config.js test/contoso.tspconfig.yaml" + "test:contoso": "eslint --config dist/test/specification/eslint.config.js test/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml", + "test:top-level-folder-mixed-case": "eslint --config dist/test/specification/eslint.config.js test/specification/InvalidCase/Contoso.WidgetManager/tspconfig.yaml", + "test:all": "npm run test; npm run test:contoso; npm run test:top-level-folder-mixed-case" }, "engines": { "node": ">= 18.0.0" diff --git a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts index 9edfef2e76b7..ce4e4443a5c0 100644 --- a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts +++ b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts @@ -1,5 +1,11 @@ // @ts-ignore import enforceFooBar from "./enforce-foo-bar.js"; +import topLevelFolderLowercase from "./top-level-folder-lowercase.js"; -export const plugin = { rules: { "enforce-foo-bar": enforceFooBar } }; +export const plugin = { + rules: { + "enforce-foo-bar": enforceFooBar, + "top-level-folder-lowercase": topLevelFolderLowercase, + }, +}; export default plugin; diff --git a/eng/tools/eslint-plugin-tsv/src/top-level-folder-lowercase.ts b/eng/tools/eslint-plugin-tsv/src/top-level-folder-lowercase.ts new file mode 100644 index 000000000000..887d91e029e2 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/top-level-folder-lowercase.ts @@ -0,0 +1,29 @@ +export const rule = { + meta: { + type: "problem", + docs: { + description: "Enforce that top level folder under 'specification' is lower case.", + }, + schema: [], + }, + // @ts-ignore + create(context) { + const filename = context.getFilename() as string; + const regex = /specification\/[^/]*[A-Z]+[^/]*\//; + + return { + // @ts-ignore + Program(node) { + // Check if the filename ends with '.test.js' + if (filename.match(regex)) { + context.report({ + node, + message: "invalidPath", + }); + } + }, + }; + }, +}; + +export default rule; diff --git a/eng/tools/eslint-plugin-tsv/test/contoso.eslint.config.ts b/eng/tools/eslint-plugin-tsv/test/contoso.eslint.config.ts index 88e5b14a5596..e69de29bb2d1 100644 --- a/eng/tools/eslint-plugin-tsv/test/contoso.eslint.config.ts +++ b/eng/tools/eslint-plugin-tsv/test/contoso.eslint.config.ts @@ -1,17 +0,0 @@ -import eslintPluginTsv from "../src/eslint-plugin-tsv.js"; -import parser from "yaml-eslint-parser"; - -export const config = [ - { - plugins: { tsv: eslintPluginTsv }, - files: ["*.yaml", "**/*.yaml"], - languageOptions: { - parser, - }, - rules: { - "tsv/enforce-foo-bar": "error", - }, - }, -]; - -export default config; diff --git a/eng/tools/eslint-plugin-tsv/test/contoso.tspconfig.yaml b/eng/tools/eslint-plugin-tsv/test/specification/InvalidCase/Contoso.WidgetManager/tspconfig.yaml similarity index 100% rename from eng/tools/eslint-plugin-tsv/test/contoso.tspconfig.yaml rename to eng/tools/eslint-plugin-tsv/test/specification/InvalidCase/Contoso.WidgetManager/tspconfig.yaml diff --git a/eng/tools/eslint-plugin-tsv/test/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml b/eng/tools/eslint-plugin-tsv/test/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml new file mode 100644 index 000000000000..2633b3a76f34 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/test/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml @@ -0,0 +1,39 @@ +parameters: + "service-dir": + default: "sdk/contosowidgetmanager" + "dependencies": + "additionalDirectories": + - "specification/contosowidgetmanager/Contoso.WidgetManager.Shared/" + default: "" +emit: + - "@azure-tools/typespec-autorest" +linter: + extends: + - "@azure-tools/typespec-azure-rulesets/data-plane" +options: + "@azure-tools/typespec-autorest": + azure-resource-provider-folder: "data-plane" + emit-lro-options: "none" + emitter-output-dir: "{project-root}/.." + output-file: "{azure-resource-provider-folder}/{service-name}/{version-status}/{version}/widgets.json" + "@azure-tools/typespec-python": + package-dir: "azure-contoso-widgetmanager" + package-name: "{package-dir}" + generate-test: true + generate-sample: true + flavor: azure + "@azure-tools/typespec-csharp": + package-dir: "Azure.Template.Contoso" + clear-output-folder: true + model-namespace: false + namespace: "{package-dir}" + flavor: azure + "@azure-tools/typespec-ts": + package-dir: "contosowidgetmanager-rest" + packageDetails: + name: "@azure-rest/contoso-widgetmanager-rest" + flavor: azure + "@azure-tools/typespec-java": + package-dir: "azure-contoso-widgetmanager" + namespace: com.azure.contoso.widgetmanager + flavor: azure diff --git a/eng/tools/eslint-plugin-tsv/test/specification/eslint.config.ts b/eng/tools/eslint-plugin-tsv/test/specification/eslint.config.ts new file mode 100644 index 000000000000..c885798b20d1 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/test/specification/eslint.config.ts @@ -0,0 +1,18 @@ +import eslintPluginTsv from "../../src/eslint-plugin-tsv.js"; +import parser from "yaml-eslint-parser"; + +export const config = [ + { + plugins: { tsv: eslintPluginTsv }, + files: ["*.yaml", "**/*.yaml"], + languageOptions: { + parser, + }, + rules: { + "tsv/enforce-foo-bar": "error", + "tsv/top-level-folder-lowercase": "error", + }, + }, +]; + +export default config; From 6e44905cc7a5defe8bbad676e21282f47d443047 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 17 Dec 2024 09:56:01 +0000 Subject: [PATCH 006/106] build before test --- eng/tools/eslint-plugin-tsv/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index ae0cd1e6240d..93ce29a093e1 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -19,7 +19,7 @@ "test": "eslint --config dist/test/test.eslint.config.js test/test.yaml", "test:contoso": "eslint --config dist/test/specification/eslint.config.js test/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml", "test:top-level-folder-mixed-case": "eslint --config dist/test/specification/eslint.config.js test/specification/InvalidCase/Contoso.WidgetManager/tspconfig.yaml", - "test:all": "npm run test; npm run test:contoso; npm run test:top-level-folder-mixed-case" + "test:all": "npm run build; npm run test; npm run test:contoso; npm run test:top-level-folder-mixed-case" }, "engines": { "node": ">= 18.0.0" From ae8a8a5a0b18a93dc798d94854b743f88402321b Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 17 Dec 2024 10:55:03 +0000 Subject: [PATCH 007/106] Rename rule to align with eslint conventions --- .../src/eslint-plugin-tsv.ts | 4 +-- .../src/no-uppercase-under-specification.ts | 34 +++++++++++++++++++ .../src/top-level-folder-lowercase.ts | 29 ---------------- .../test/contoso.eslint.config.ts | 0 .../test/specification/eslint.config.ts | 2 +- 5 files changed, 37 insertions(+), 32 deletions(-) create mode 100644 eng/tools/eslint-plugin-tsv/src/no-uppercase-under-specification.ts delete mode 100644 eng/tools/eslint-plugin-tsv/src/top-level-folder-lowercase.ts delete mode 100644 eng/tools/eslint-plugin-tsv/test/contoso.eslint.config.ts diff --git a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts index ce4e4443a5c0..450a4b54628b 100644 --- a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts +++ b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts @@ -1,11 +1,11 @@ // @ts-ignore import enforceFooBar from "./enforce-foo-bar.js"; -import topLevelFolderLowercase from "./top-level-folder-lowercase.js"; +import noUppercaseUnderSpecification from "./no-uppercase-under-specification.js"; export const plugin = { rules: { "enforce-foo-bar": enforceFooBar, - "top-level-folder-lowercase": topLevelFolderLowercase, + "no-uppercase-under-specification": noUppercaseUnderSpecification, }, }; export default plugin; diff --git a/eng/tools/eslint-plugin-tsv/src/no-uppercase-under-specification.ts b/eng/tools/eslint-plugin-tsv/src/no-uppercase-under-specification.ts new file mode 100644 index 000000000000..52469942ecee --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/no-uppercase-under-specification.ts @@ -0,0 +1,34 @@ +export const rule = { + meta: { + type: "problem", + docs: { + description: "Disallow uppercase letters in first folder under '/specification/'", + }, + schema: [], + messages: { + upper: "'{{path}}' contains uppercase letters in the first folder under '/specification/'", + }, + }, + // @ts-ignore + create(context) { + const filename = context.getFilename() as string; + + const regex = /\/specification\/[^/A-Z]+\//; + const uppercaseLettersInFirstFolder = !filename.match(regex); + + return { + // @ts-ignore + Program(node) { + if (uppercaseLettersInFirstFolder) { + context.report({ + node, + messageId: "upper", + data: { path: filename }, + }); + } + }, + }; + }, +}; + +export default rule; diff --git a/eng/tools/eslint-plugin-tsv/src/top-level-folder-lowercase.ts b/eng/tools/eslint-plugin-tsv/src/top-level-folder-lowercase.ts deleted file mode 100644 index 887d91e029e2..000000000000 --- a/eng/tools/eslint-plugin-tsv/src/top-level-folder-lowercase.ts +++ /dev/null @@ -1,29 +0,0 @@ -export const rule = { - meta: { - type: "problem", - docs: { - description: "Enforce that top level folder under 'specification' is lower case.", - }, - schema: [], - }, - // @ts-ignore - create(context) { - const filename = context.getFilename() as string; - const regex = /specification\/[^/]*[A-Z]+[^/]*\//; - - return { - // @ts-ignore - Program(node) { - // Check if the filename ends with '.test.js' - if (filename.match(regex)) { - context.report({ - node, - message: "invalidPath", - }); - } - }, - }; - }, -}; - -export default rule; diff --git a/eng/tools/eslint-plugin-tsv/test/contoso.eslint.config.ts b/eng/tools/eslint-plugin-tsv/test/contoso.eslint.config.ts deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/eng/tools/eslint-plugin-tsv/test/specification/eslint.config.ts b/eng/tools/eslint-plugin-tsv/test/specification/eslint.config.ts index c885798b20d1..a4ffcf819285 100644 --- a/eng/tools/eslint-plugin-tsv/test/specification/eslint.config.ts +++ b/eng/tools/eslint-plugin-tsv/test/specification/eslint.config.ts @@ -10,7 +10,7 @@ export const config = [ }, rules: { "tsv/enforce-foo-bar": "error", - "tsv/top-level-folder-lowercase": "error", + "tsv/no-uppercase-under-specification": "error", }, }, ]; From 79256c4d182ad73330f488eac3b4838b5aa15915 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 17 Dec 2024 11:15:44 +0000 Subject: [PATCH 008/106] Rename rule --- .../src/eslint-plugin-tsv.ts | 4 +-- .../src/kebab-case-first-path-segment.ts | 33 ++++++++++++++++++ .../src/no-uppercase-under-specification.ts | 34 ------------------- .../test/specification/eslint.config.ts | 2 +- 4 files changed, 36 insertions(+), 37 deletions(-) create mode 100644 eng/tools/eslint-plugin-tsv/src/kebab-case-first-path-segment.ts delete mode 100644 eng/tools/eslint-plugin-tsv/src/no-uppercase-under-specification.ts diff --git a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts index 450a4b54628b..eeeed694819e 100644 --- a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts +++ b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts @@ -1,11 +1,11 @@ // @ts-ignore import enforceFooBar from "./enforce-foo-bar.js"; -import noUppercaseUnderSpecification from "./no-uppercase-under-specification.js"; +import kebabCaseFirstPathSegment from "./kebab-case-first-path-segment.js"; export const plugin = { rules: { "enforce-foo-bar": enforceFooBar, - "no-uppercase-under-specification": noUppercaseUnderSpecification, + "kebab-case-first-path-segment": kebabCaseFirstPathSegment, }, }; export default plugin; diff --git a/eng/tools/eslint-plugin-tsv/src/kebab-case-first-path-segment.ts b/eng/tools/eslint-plugin-tsv/src/kebab-case-first-path-segment.ts new file mode 100644 index 000000000000..7f5fb04b631e --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/kebab-case-first-path-segment.ts @@ -0,0 +1,33 @@ +export const rule = { + meta: { + type: "problem", + docs: { + description: "Requires first path segment after 'specification' to use kebab-case", + }, + schema: [], + messages: { + kebab: "First path segment after 'specification' does not use kebab-case", + }, + }, + // @ts-ignore + create(context) { + const filename = context.getFilename() as string; + + const regex = /\/specification\/[a-z0-9]+(-[a-z0-9]+)*\//; + const kebabCaseFirstFolder = filename.match(regex); + + return { + // @ts-ignore + Program(node) { + if (!kebabCaseFirstFolder) { + context.report({ + node, + messageId: "kebab", + }); + } + }, + }; + }, +}; + +export default rule; diff --git a/eng/tools/eslint-plugin-tsv/src/no-uppercase-under-specification.ts b/eng/tools/eslint-plugin-tsv/src/no-uppercase-under-specification.ts deleted file mode 100644 index 52469942ecee..000000000000 --- a/eng/tools/eslint-plugin-tsv/src/no-uppercase-under-specification.ts +++ /dev/null @@ -1,34 +0,0 @@ -export const rule = { - meta: { - type: "problem", - docs: { - description: "Disallow uppercase letters in first folder under '/specification/'", - }, - schema: [], - messages: { - upper: "'{{path}}' contains uppercase letters in the first folder under '/specification/'", - }, - }, - // @ts-ignore - create(context) { - const filename = context.getFilename() as string; - - const regex = /\/specification\/[^/A-Z]+\//; - const uppercaseLettersInFirstFolder = !filename.match(regex); - - return { - // @ts-ignore - Program(node) { - if (uppercaseLettersInFirstFolder) { - context.report({ - node, - messageId: "upper", - data: { path: filename }, - }); - } - }, - }; - }, -}; - -export default rule; diff --git a/eng/tools/eslint-plugin-tsv/test/specification/eslint.config.ts b/eng/tools/eslint-plugin-tsv/test/specification/eslint.config.ts index a4ffcf819285..544fb6ea7ffd 100644 --- a/eng/tools/eslint-plugin-tsv/test/specification/eslint.config.ts +++ b/eng/tools/eslint-plugin-tsv/test/specification/eslint.config.ts @@ -10,7 +10,7 @@ export const config = [ }, rules: { "tsv/enforce-foo-bar": "error", - "tsv/no-uppercase-under-specification": "error", + "tsv/kebab-case-first-path-segment": "error", }, }, ]; From 85db15861501d53ecef7d8cf92f2bdb4a5eebde0 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 17 Dec 2024 11:19:31 +0000 Subject: [PATCH 009/106] Improve test --- eng/tools/eslint-plugin-tsv/package.json | 4 ++-- .../Not.KebabCase}/tspconfig.yaml | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename eng/tools/eslint-plugin-tsv/test/specification/{InvalidCase/Contoso.WidgetManager => Not-Kebab-Case/Not.KebabCase}/tspconfig.yaml (100%) diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index 93ce29a093e1..b675154c60bf 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -18,8 +18,8 @@ "build": "tsc --build", "test": "eslint --config dist/test/test.eslint.config.js test/test.yaml", "test:contoso": "eslint --config dist/test/specification/eslint.config.js test/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml", - "test:top-level-folder-mixed-case": "eslint --config dist/test/specification/eslint.config.js test/specification/InvalidCase/Contoso.WidgetManager/tspconfig.yaml", - "test:all": "npm run build; npm run test; npm run test:contoso; npm run test:top-level-folder-mixed-case" + "test:not-kebab-case": "eslint --config dist/test/specification/eslint.config.js test/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml", + "test:all": "npm run build; npm run test; npm run test:contoso; npm run test:not-kebab-case" }, "engines": { "node": ">= 18.0.0" diff --git a/eng/tools/eslint-plugin-tsv/test/specification/InvalidCase/Contoso.WidgetManager/tspconfig.yaml b/eng/tools/eslint-plugin-tsv/test/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml similarity index 100% rename from eng/tools/eslint-plugin-tsv/test/specification/InvalidCase/Contoso.WidgetManager/tspconfig.yaml rename to eng/tools/eslint-plugin-tsv/test/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml From 497387870cddee37aba0047004bb08c2f89d776d Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 17 Dec 2024 21:30:24 +0000 Subject: [PATCH 010/106] Remove sample rule --- eng/tools/eslint-plugin-tsv/package.json | 3 +- .../eslint-plugin-tsv/src/enforce-foo-bar.ts | 35 ------------------- .../src/eslint-plugin-tsv.ts | 3 -- .../test/specification/eslint.config.ts | 1 - .../test/test.eslint.config.ts | 17 --------- eng/tools/eslint-plugin-tsv/test/test.yaml | 1 - 6 files changed, 1 insertion(+), 59 deletions(-) delete mode 100644 eng/tools/eslint-plugin-tsv/src/enforce-foo-bar.ts delete mode 100644 eng/tools/eslint-plugin-tsv/test/test.eslint.config.ts delete mode 100644 eng/tools/eslint-plugin-tsv/test/test.yaml diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index b675154c60bf..61d6e6e955f9 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -16,10 +16,9 @@ }, "scripts": { "build": "tsc --build", - "test": "eslint --config dist/test/test.eslint.config.js test/test.yaml", "test:contoso": "eslint --config dist/test/specification/eslint.config.js test/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml", "test:not-kebab-case": "eslint --config dist/test/specification/eslint.config.js test/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml", - "test:all": "npm run build; npm run test; npm run test:contoso; npm run test:not-kebab-case" + "test:all": "npm run build; npm run test:contoso; npm run test:not-kebab-case" }, "engines": { "node": ">= 18.0.0" diff --git a/eng/tools/eslint-plugin-tsv/src/enforce-foo-bar.ts b/eng/tools/eslint-plugin-tsv/src/enforce-foo-bar.ts deleted file mode 100644 index 81145fe52414..000000000000 --- a/eng/tools/eslint-plugin-tsv/src/enforce-foo-bar.ts +++ /dev/null @@ -1,35 +0,0 @@ -export const rule = { - meta: { - type: "problem", - docs: { - description: - "Enforce that a variable named `foo` can only be assigned a value of 'bar'.", - }, - fixable: "code", - schema: [], - }, - // @ts-ignore - create(context) { - return { - // @ts-ignore - YAMLPair(node) { - if (node.key.value == "foo" && node.value.value != "bar") { - context.report({ - node, - message: - 'Value other than "bar" assigned to `foo`. Unexpected value: {{ notBar }}.', - data: { - notBar: node.value.value, - }, - // @ts-ignore - fix(fixer) { - return fixer.replaceText(node.value, 'bar'); - }, - }); - } - }, - }; - }, -}; - -export default rule; diff --git a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts index eeeed694819e..c60b21834ec8 100644 --- a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts +++ b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts @@ -1,10 +1,7 @@ -// @ts-ignore -import enforceFooBar from "./enforce-foo-bar.js"; import kebabCaseFirstPathSegment from "./kebab-case-first-path-segment.js"; export const plugin = { rules: { - "enforce-foo-bar": enforceFooBar, "kebab-case-first-path-segment": kebabCaseFirstPathSegment, }, }; diff --git a/eng/tools/eslint-plugin-tsv/test/specification/eslint.config.ts b/eng/tools/eslint-plugin-tsv/test/specification/eslint.config.ts index 544fb6ea7ffd..ca4f81f688f3 100644 --- a/eng/tools/eslint-plugin-tsv/test/specification/eslint.config.ts +++ b/eng/tools/eslint-plugin-tsv/test/specification/eslint.config.ts @@ -9,7 +9,6 @@ export const config = [ parser, }, rules: { - "tsv/enforce-foo-bar": "error", "tsv/kebab-case-first-path-segment": "error", }, }, diff --git a/eng/tools/eslint-plugin-tsv/test/test.eslint.config.ts b/eng/tools/eslint-plugin-tsv/test/test.eslint.config.ts deleted file mode 100644 index 88e5b14a5596..000000000000 --- a/eng/tools/eslint-plugin-tsv/test/test.eslint.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import eslintPluginTsv from "../src/eslint-plugin-tsv.js"; -import parser from "yaml-eslint-parser"; - -export const config = [ - { - plugins: { tsv: eslintPluginTsv }, - files: ["*.yaml", "**/*.yaml"], - languageOptions: { - parser, - }, - rules: { - "tsv/enforce-foo-bar": "error", - }, - }, -]; - -export default config; diff --git a/eng/tools/eslint-plugin-tsv/test/test.yaml b/eng/tools/eslint-plugin-tsv/test/test.yaml deleted file mode 100644 index c444f32c5010..000000000000 --- a/eng/tools/eslint-plugin-tsv/test/test.yaml +++ /dev/null @@ -1 +0,0 @@ -foo: baz From 89006ec56791f7439bb2c819db86a1f8711b2559 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 17 Dec 2024 21:37:22 +0000 Subject: [PATCH 011/106] Move e2e tests to separate folder --- eng/tools/eslint-plugin-tsv/package.json | 6 +++--- .../Not-Kebab-Case/Not.KebabCase/tspconfig.yaml | 0 .../Contoso.WidgetManager/tspconfig.yaml | 0 .../test/{ => e2e}/specification/eslint.config.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename eng/tools/eslint-plugin-tsv/test/{ => e2e}/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml (100%) rename eng/tools/eslint-plugin-tsv/test/{ => e2e}/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml (100%) rename eng/tools/eslint-plugin-tsv/test/{ => e2e}/specification/eslint.config.ts (81%) diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index 61d6e6e955f9..03ad2eec9f9f 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -16,9 +16,9 @@ }, "scripts": { "build": "tsc --build", - "test:contoso": "eslint --config dist/test/specification/eslint.config.js test/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml", - "test:not-kebab-case": "eslint --config dist/test/specification/eslint.config.js test/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml", - "test:all": "npm run build; npm run test:contoso; npm run test:not-kebab-case" + "test:e2e": "npm run build && npm run test:e2e:contoso && npm run test:e2e:not-kebab-case", + "test:e2e:contoso": "eslint --config dist/test/specification/eslint.config.js test/e2e/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml", + "test:e2e:not-kebab-case": "eslint --config dist/test/specification/eslint.config.js test/e2e/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml" }, "engines": { "node": ">= 18.0.0" diff --git a/eng/tools/eslint-plugin-tsv/test/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml b/eng/tools/eslint-plugin-tsv/test/e2e/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml similarity index 100% rename from eng/tools/eslint-plugin-tsv/test/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml rename to eng/tools/eslint-plugin-tsv/test/e2e/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml diff --git a/eng/tools/eslint-plugin-tsv/test/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml b/eng/tools/eslint-plugin-tsv/test/e2e/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml similarity index 100% rename from eng/tools/eslint-plugin-tsv/test/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml rename to eng/tools/eslint-plugin-tsv/test/e2e/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml diff --git a/eng/tools/eslint-plugin-tsv/test/specification/eslint.config.ts b/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts similarity index 81% rename from eng/tools/eslint-plugin-tsv/test/specification/eslint.config.ts rename to eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts index ca4f81f688f3..2c011ea0eb4c 100644 --- a/eng/tools/eslint-plugin-tsv/test/specification/eslint.config.ts +++ b/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts @@ -1,4 +1,4 @@ -import eslintPluginTsv from "../../src/eslint-plugin-tsv.js"; +import eslintPluginTsv from "../../../src/eslint-plugin-tsv.js"; import parser from "yaml-eslint-parser"; export const config = [ From 0fe7173f02749cfddce9688d4d65ca5db60c47fb Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 17 Dec 2024 21:50:36 +0000 Subject: [PATCH 012/106] Add comment --- .../eslint-plugin-tsv/src/kebab-case-first-path-segment.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/eng/tools/eslint-plugin-tsv/src/kebab-case-first-path-segment.ts b/eng/tools/eslint-plugin-tsv/src/kebab-case-first-path-segment.ts index 7f5fb04b631e..1abc563e8a42 100644 --- a/eng/tools/eslint-plugin-tsv/src/kebab-case-first-path-segment.ts +++ b/eng/tools/eslint-plugin-tsv/src/kebab-case-first-path-segment.ts @@ -1,3 +1,5 @@ +// TODO: Add types + export const rule = { meta: { type: "problem", From da8c9aa3d09aa8eeb97355ec30530c717cbb9e3c Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 17 Dec 2024 21:51:05 +0000 Subject: [PATCH 013/106] Add manual unit test --- eng/tools/eslint-plugin-tsv/package.json | 1 + .../test/kebab-case-first-path-segment.test.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 eng/tools/eslint-plugin-tsv/test/kebab-case-first-path-segment.test.ts diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index 03ad2eec9f9f..c308e3982f56 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -16,6 +16,7 @@ }, "scripts": { "build": "tsc --build", + "test":"node dist/test/kebab-case-first-path-segment.test.js", "test:e2e": "npm run build && npm run test:e2e:contoso && npm run test:e2e:not-kebab-case", "test:e2e:contoso": "eslint --config dist/test/specification/eslint.config.js test/e2e/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml", "test:e2e:not-kebab-case": "eslint --config dist/test/specification/eslint.config.js test/e2e/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml" diff --git a/eng/tools/eslint-plugin-tsv/test/kebab-case-first-path-segment.test.ts b/eng/tools/eslint-plugin-tsv/test/kebab-case-first-path-segment.test.ts new file mode 100644 index 000000000000..a73f59f69e01 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/test/kebab-case-first-path-segment.test.ts @@ -0,0 +1,13 @@ +import { Rule, RuleTester } from "eslint"; +import kebabCaseFirstPathSegment from "../src/kebab-case-first-path-segment.js"; + +const ruleTester = new RuleTester(); + +ruleTester.run("kebab-case-first-path-segment", kebabCaseFirstPathSegment as Rule.RuleModule, { + valid: [{ code: "", filename: "/specification/contoso/Contoso.WidgetManager/tspconfig.yaml" }], + invalid: [ + { code: "", filename: "/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml", errors: 1 }, + ], +}); + +console.log("All tests passed!"); From abf58cc4c3fb7b56eb5aa1a1cc45465395e49597 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 17 Dec 2024 22:02:18 +0000 Subject: [PATCH 014/106] Convert test to vitest --- eng/tools/eslint-plugin-tsv/package.json | 6 ++-- .../src/eslint-plugin-tsv.ts | 3 +- .../src/kebab-case-first-path-segment.ts | 1 + .../kebab-case-first-path-segment.test.ts | 28 +++++++++++++------ 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index c308e3982f56..efa52f11f64c 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -12,11 +12,13 @@ "devDependencies": { "@types/node": "^18.19.31", "eslint": "^9.17.0", - "typescript": "~5.6.2" + "typescript": "~5.6.2", + "vitest": "^2.0.4" }, "scripts": { "build": "tsc --build", - "test":"node dist/test/kebab-case-first-path-segment.test.js", + "test": "vitest", + "test:ci": "vitest run --reporter=verbose", "test:e2e": "npm run build && npm run test:e2e:contoso && npm run test:e2e:not-kebab-case", "test:e2e:contoso": "eslint --config dist/test/specification/eslint.config.js test/e2e/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml", "test:e2e:not-kebab-case": "eslint --config dist/test/specification/eslint.config.js test/e2e/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml" diff --git a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts index c60b21834ec8..04b159227007 100644 --- a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts +++ b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts @@ -2,7 +2,8 @@ import kebabCaseFirstPathSegment from "./kebab-case-first-path-segment.js"; export const plugin = { rules: { - "kebab-case-first-path-segment": kebabCaseFirstPathSegment, + [kebabCaseFirstPathSegment.meta.name]: kebabCaseFirstPathSegment, }, }; + export default plugin; diff --git a/eng/tools/eslint-plugin-tsv/src/kebab-case-first-path-segment.ts b/eng/tools/eslint-plugin-tsv/src/kebab-case-first-path-segment.ts index 1abc563e8a42..c7cb4b2eb773 100644 --- a/eng/tools/eslint-plugin-tsv/src/kebab-case-first-path-segment.ts +++ b/eng/tools/eslint-plugin-tsv/src/kebab-case-first-path-segment.ts @@ -2,6 +2,7 @@ export const rule = { meta: { + name: "kebab-case-first-path-segment", type: "problem", docs: { description: "Requires first path segment after 'specification' to use kebab-case", diff --git a/eng/tools/eslint-plugin-tsv/test/kebab-case-first-path-segment.test.ts b/eng/tools/eslint-plugin-tsv/test/kebab-case-first-path-segment.test.ts index a73f59f69e01..d2899fc54b86 100644 --- a/eng/tools/eslint-plugin-tsv/test/kebab-case-first-path-segment.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/kebab-case-first-path-segment.test.ts @@ -1,13 +1,25 @@ import { Rule, RuleTester } from "eslint"; +import { test } from "vitest"; + import kebabCaseFirstPathSegment from "../src/kebab-case-first-path-segment.js"; -const ruleTester = new RuleTester(); +test(kebabCaseFirstPathSegment.meta.name, async ({ expect }) => { + const ruleTester = new RuleTester(); -ruleTester.run("kebab-case-first-path-segment", kebabCaseFirstPathSegment as Rule.RuleModule, { - valid: [{ code: "", filename: "/specification/contoso/Contoso.WidgetManager/tspconfig.yaml" }], - invalid: [ - { code: "", filename: "/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml", errors: 1 }, - ], + ruleTester.run( + kebabCaseFirstPathSegment.meta.name, + kebabCaseFirstPathSegment as Rule.RuleModule, + { + valid: [ + { code: "", filename: "/specification/contoso/Contoso.WidgetManager/tspconfig.yaml" }, + ], + invalid: [ + { + code: "", + filename: "/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml", + errors: 1, + }, + ], + }, + ); }); - -console.log("All tests passed!"); From 043d24e299d2adfa134cb3045e124f5b73099a7a Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 17 Dec 2024 22:10:02 +0000 Subject: [PATCH 015/106] Remove unnecessary async --- .../test/kebab-case-first-path-segment.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/tools/eslint-plugin-tsv/test/kebab-case-first-path-segment.test.ts b/eng/tools/eslint-plugin-tsv/test/kebab-case-first-path-segment.test.ts index d2899fc54b86..a8717f856c7e 100644 --- a/eng/tools/eslint-plugin-tsv/test/kebab-case-first-path-segment.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/kebab-case-first-path-segment.test.ts @@ -3,7 +3,7 @@ import { test } from "vitest"; import kebabCaseFirstPathSegment from "../src/kebab-case-first-path-segment.js"; -test(kebabCaseFirstPathSegment.meta.name, async ({ expect }) => { +test(kebabCaseFirstPathSegment.meta.name, () => { const ruleTester = new RuleTester(); ruleTester.run( From 6184c64a5618d7f228602d4ac9105d0f6e915de3 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Wed, 18 Dec 2024 01:18:58 +0000 Subject: [PATCH 016/106] Rename rule and improve error message --- eng/tools/eslint-plugin-tsv/package.json | 8 +- .../src/eslint-plugin-tsv.ts | 4 +- .../src/kebab-case-first-path-segment.ts | 36 ------ .../eslint-plugin-tsv/src/kebab-case-org.ts | 47 +++++++ .../test/e2e/specification/eslint.config.ts | 2 +- .../kebab-case-first-path-segment.test.ts | 25 ---- .../test/kebab-case-org.test.ts | 19 +++ package-lock.json | 117 +++++++++++++++++- 8 files changed, 190 insertions(+), 68 deletions(-) delete mode 100644 eng/tools/eslint-plugin-tsv/src/kebab-case-first-path-segment.ts create mode 100644 eng/tools/eslint-plugin-tsv/src/kebab-case-org.ts delete mode 100644 eng/tools/eslint-plugin-tsv/test/kebab-case-first-path-segment.test.ts create mode 100644 eng/tools/eslint-plugin-tsv/test/kebab-case-org.test.ts diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index efa52f11f64c..e2ecf79e6ab1 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -12,16 +12,18 @@ "devDependencies": { "@types/node": "^18.19.31", "eslint": "^9.17.0", + "rimraf": "^6.0.1", "typescript": "~5.6.2", "vitest": "^2.0.4" }, "scripts": { "build": "tsc --build", + "clean": "rimraf ./dist ./temp", "test": "vitest", "test:ci": "vitest run --reporter=verbose", - "test:e2e": "npm run build && npm run test:e2e:contoso && npm run test:e2e:not-kebab-case", - "test:e2e:contoso": "eslint --config dist/test/specification/eslint.config.js test/e2e/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml", - "test:e2e:not-kebab-case": "eslint --config dist/test/specification/eslint.config.js test/e2e/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml" + "test:e2e": "npm run clean && npm run build && npm run test:e2e:contoso && npm run test:e2e:not-kebab-case", + "test:e2e:contoso": "eslint --config dist/test/e2e/specification/eslint.config.js test/e2e/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml", + "test:e2e:not-kebab-case": "eslint --config dist/test/e2e/specification/eslint.config.js test/e2e/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml" }, "engines": { "node": ">= 18.0.0" diff --git a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts index 04b159227007..a9b684a2ca3a 100644 --- a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts +++ b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts @@ -1,8 +1,8 @@ -import kebabCaseFirstPathSegment from "./kebab-case-first-path-segment.js"; +import kebabCaseOrg from "./kebab-case-org.js"; export const plugin = { rules: { - [kebabCaseFirstPathSegment.meta.name]: kebabCaseFirstPathSegment, + [kebabCaseOrg.meta.name]: kebabCaseOrg, }, }; diff --git a/eng/tools/eslint-plugin-tsv/src/kebab-case-first-path-segment.ts b/eng/tools/eslint-plugin-tsv/src/kebab-case-first-path-segment.ts deleted file mode 100644 index c7cb4b2eb773..000000000000 --- a/eng/tools/eslint-plugin-tsv/src/kebab-case-first-path-segment.ts +++ /dev/null @@ -1,36 +0,0 @@ -// TODO: Add types - -export const rule = { - meta: { - name: "kebab-case-first-path-segment", - type: "problem", - docs: { - description: "Requires first path segment after 'specification' to use kebab-case", - }, - schema: [], - messages: { - kebab: "First path segment after 'specification' does not use kebab-case", - }, - }, - // @ts-ignore - create(context) { - const filename = context.getFilename() as string; - - const regex = /\/specification\/[a-z0-9]+(-[a-z0-9]+)*\//; - const kebabCaseFirstFolder = filename.match(regex); - - return { - // @ts-ignore - Program(node) { - if (!kebabCaseFirstFolder) { - context.report({ - node, - messageId: "kebab", - }); - } - }, - }; - }, -}; - -export default rule; diff --git a/eng/tools/eslint-plugin-tsv/src/kebab-case-org.ts b/eng/tools/eslint-plugin-tsv/src/kebab-case-org.ts new file mode 100644 index 000000000000..9801c4f8e337 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/kebab-case-org.ts @@ -0,0 +1,47 @@ +// TODO: Add types + +import path from "path"; + +export const rule = { + meta: { + name: "kebab-case-org", + type: "problem", + docs: { + description: + "Requires kebab-case for'organization' name (first path segment after 'specification')", + }, + schema: [], + messages: { + kebab: + "Organization name (first path segment after 'specification') does not use kebab-case: '{{orgName}}'", + }, + }, + // @ts-ignore + create(context) { + const filename = context.getFilename() as string; + + const pathSegments = filename.split(path.sep); + + // TODO: Handle errors + // - No "specification" segment + // - No segemnt after "specification" + const orgName = pathSegments[pathSegments.indexOf("specification") + 1]; + const kebabCaseRegex = /^[a-z0-9]+(-[a-z0-9]+)*$/; + const orgNameKebabCase = orgName.match(kebabCaseRegex); + + return { + // @ts-ignore + Program(node) { + if (!orgNameKebabCase) { + context.report({ + node, + messageId: "kebab", + data: { orgName: orgName }, + }); + } + }, + }; + }, +}; + +export default rule; diff --git a/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts b/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts index 2c011ea0eb4c..449163503222 100644 --- a/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts +++ b/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts @@ -9,7 +9,7 @@ export const config = [ parser, }, rules: { - "tsv/kebab-case-first-path-segment": "error", + "tsv/kebab-case-org": "error", }, }, ]; diff --git a/eng/tools/eslint-plugin-tsv/test/kebab-case-first-path-segment.test.ts b/eng/tools/eslint-plugin-tsv/test/kebab-case-first-path-segment.test.ts deleted file mode 100644 index a8717f856c7e..000000000000 --- a/eng/tools/eslint-plugin-tsv/test/kebab-case-first-path-segment.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Rule, RuleTester } from "eslint"; -import { test } from "vitest"; - -import kebabCaseFirstPathSegment from "../src/kebab-case-first-path-segment.js"; - -test(kebabCaseFirstPathSegment.meta.name, () => { - const ruleTester = new RuleTester(); - - ruleTester.run( - kebabCaseFirstPathSegment.meta.name, - kebabCaseFirstPathSegment as Rule.RuleModule, - { - valid: [ - { code: "", filename: "/specification/contoso/Contoso.WidgetManager/tspconfig.yaml" }, - ], - invalid: [ - { - code: "", - filename: "/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml", - errors: 1, - }, - ], - }, - ); -}); diff --git a/eng/tools/eslint-plugin-tsv/test/kebab-case-org.test.ts b/eng/tools/eslint-plugin-tsv/test/kebab-case-org.test.ts new file mode 100644 index 000000000000..313dd0cdfa0b --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/test/kebab-case-org.test.ts @@ -0,0 +1,19 @@ +import { Rule, RuleTester } from "eslint"; +import { test } from "vitest"; + +import kebabCaseOrg from "../src/kebab-case-org.js"; + +test(kebabCaseOrg.meta.name, () => { + const ruleTester = new RuleTester(); + + ruleTester.run(kebabCaseOrg.meta.name, kebabCaseOrg as Rule.RuleModule, { + valid: [{ code: "", filename: "/specification/contoso/Contoso.WidgetManager/tspconfig.yaml" }], + invalid: [ + { + code: "", + filename: "/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml", + errors: 1, + }, + ], + }); +}); diff --git a/package-lock.json b/package-lock.json index f3328e3ac5ba..b625e0443439 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,7 +56,9 @@ "devDependencies": { "@types/node": "^18.19.31", "eslint": "^9.17.0", - "typescript": "~5.6.2" + "rimraf": "^6.0.1", + "typescript": "~5.6.2", + "vitest": "^2.0.4" }, "engines": { "node": ">= 18.0.0" @@ -8558,6 +8560,119 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/jackspeak": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", + "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/lru-cache": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", + "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rollup": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz", From b03b6665042019c706faab1a78ddba223e0291f9 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Wed, 18 Dec 2024 01:30:09 +0000 Subject: [PATCH 017/106] Test eslint disablement comment --- .../eslint-plugin-tsv/test/kebab-case-org.test.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/test/kebab-case-org.test.ts b/eng/tools/eslint-plugin-tsv/test/kebab-case-org.test.ts index 313dd0cdfa0b..ddc68881f589 100644 --- a/eng/tools/eslint-plugin-tsv/test/kebab-case-org.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/kebab-case-org.test.ts @@ -1,13 +1,24 @@ import { Rule, RuleTester } from "eslint"; +import parser from "yaml-eslint-parser"; import { test } from "vitest"; import kebabCaseOrg from "../src/kebab-case-org.js"; test(kebabCaseOrg.meta.name, () => { - const ruleTester = new RuleTester(); + const ruleTester = new RuleTester({ + languageOptions: { + parser: parser, + }, + }); ruleTester.run(kebabCaseOrg.meta.name, kebabCaseOrg as Rule.RuleModule, { - valid: [{ code: "", filename: "/specification/contoso/Contoso.WidgetManager/tspconfig.yaml" }], + valid: [ + { code: "", filename: "/specification/contoso/Contoso.WidgetManager/tspconfig.yaml" }, + { + code: "# eslint-disable", + filename: "/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml", + }, + ], invalid: [ { code: "", From 9c098940d9231cdb12f665769dd84a200b154f71 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Wed, 18 Dec 2024 02:04:09 +0000 Subject: [PATCH 018/106] Add tests for disabled rule --- eng/tools/eslint-plugin-tsv/package.json | 3 +- .../Not.KebabCase/tspconfig.yaml | 40 +++++++++++++++++++ .../test/kebab-case-org.test.ts | 6 +-- 3 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 eng/tools/eslint-plugin-tsv/test/e2e/specification/Not-Kebab-Case-Disabled/Not.KebabCase/tspconfig.yaml diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index e2ecf79e6ab1..d5c4240fbd69 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -21,8 +21,9 @@ "clean": "rimraf ./dist ./temp", "test": "vitest", "test:ci": "vitest run --reporter=verbose", - "test:e2e": "npm run clean && npm run build && npm run test:e2e:contoso && npm run test:e2e:not-kebab-case", + "test:e2e": "npm run clean && npm run build && npm run test:e2e:contoso && npm run test:e2e:not-kebab-case-disabled && npm run test:e2e:not-kebab-case", "test:e2e:contoso": "eslint --config dist/test/e2e/specification/eslint.config.js test/e2e/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml", + "test:e2e:not-kebab-case-disabled": "eslint --config dist/test/e2e/specification/eslint.config.js test/e2e/specification/Not-Kebab-Case-Disabled/Not.KebabCase/tspconfig.yaml", "test:e2e:not-kebab-case": "eslint --config dist/test/e2e/specification/eslint.config.js test/e2e/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml" }, "engines": { diff --git a/eng/tools/eslint-plugin-tsv/test/e2e/specification/Not-Kebab-Case-Disabled/Not.KebabCase/tspconfig.yaml b/eng/tools/eslint-plugin-tsv/test/e2e/specification/Not-Kebab-Case-Disabled/Not.KebabCase/tspconfig.yaml new file mode 100644 index 000000000000..87dd5cf24346 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/test/e2e/specification/Not-Kebab-Case-Disabled/Not.KebabCase/tspconfig.yaml @@ -0,0 +1,40 @@ +# eslint-disable tsv/kebab-case-org +parameters: + "service-dir": + default: "sdk/contosowidgetmanager" + "dependencies": + "additionalDirectories": + - "specification/contosowidgetmanager/Contoso.WidgetManager.Shared/" + default: "" +emit: + - "@azure-tools/typespec-autorest" +linter: + extends: + - "@azure-tools/typespec-azure-rulesets/data-plane" +options: + "@azure-tools/typespec-autorest": + azure-resource-provider-folder: "data-plane" + emit-lro-options: "none" + emitter-output-dir: "{project-root}/.." + output-file: "{azure-resource-provider-folder}/{service-name}/{version-status}/{version}/widgets.json" + "@azure-tools/typespec-python": + package-dir: "azure-contoso-widgetmanager" + package-name: "{package-dir}" + generate-test: true + generate-sample: true + flavor: azure + "@azure-tools/typespec-csharp": + package-dir: "Azure.Template.Contoso" + clear-output-folder: true + model-namespace: false + namespace: "{package-dir}" + flavor: azure + "@azure-tools/typespec-ts": + package-dir: "contosowidgetmanager-rest" + packageDetails: + name: "@azure-rest/contoso-widgetmanager-rest" + flavor: azure + "@azure-tools/typespec-java": + package-dir: "azure-contoso-widgetmanager" + namespace: com.azure.contoso.widgetmanager + flavor: azure diff --git a/eng/tools/eslint-plugin-tsv/test/kebab-case-org.test.ts b/eng/tools/eslint-plugin-tsv/test/kebab-case-org.test.ts index ddc68881f589..fa484d729581 100644 --- a/eng/tools/eslint-plugin-tsv/test/kebab-case-org.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/kebab-case-org.test.ts @@ -4,7 +4,7 @@ import { test } from "vitest"; import kebabCaseOrg from "../src/kebab-case-org.js"; -test(kebabCaseOrg.meta.name, () => { +test("tsv/" + kebabCaseOrg.meta.name, () => { const ruleTester = new RuleTester({ languageOptions: { parser: parser, @@ -15,7 +15,7 @@ test(kebabCaseOrg.meta.name, () => { valid: [ { code: "", filename: "/specification/contoso/Contoso.WidgetManager/tspconfig.yaml" }, { - code: "# eslint-disable", + code: `# eslint-disable rule-to-test/${kebabCaseOrg.meta.name}`, filename: "/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml", }, ], @@ -23,7 +23,7 @@ test(kebabCaseOrg.meta.name, () => { { code: "", filename: "/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml", - errors: 1, + errors: ["Organization name (first path segment after 'specification') does not use kebab-case: 'Not-Kebab-Case'"], }, ], }); From a0d0fef8a5f21d78ab665e266c1e73e57a263c0d Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Wed, 18 Dec 2024 02:26:54 +0000 Subject: [PATCH 019/106] Include recommended config --- .../src/eslint-plugin-tsv.ts | 23 +++++++++++++++++++ .../test/e2e/specification/eslint.config.ts | 12 +--------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts index a9b684a2ca3a..16390fce9131 100644 --- a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts +++ b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts @@ -1,9 +1,32 @@ import kebabCaseOrg from "./kebab-case-org.js"; +import parser from "yaml-eslint-parser"; +const pluginName = "tsv"; + +// TODO: Add types export const plugin = { + configs: { }, rules: { [kebabCaseOrg.meta.name]: kebabCaseOrg, }, }; +// assign configs here so we can reference `plugin` +Object.assign(plugin.configs, { + recommended: [ + { + plugins: { + [pluginName]: plugin, + }, + files: ["*.yaml", "**/*.yaml"], + rules: { + [`${pluginName}/${kebabCaseOrg.meta.name}`]: "error", + }, + languageOptions: { + parser: parser, + }, + }, + ], +}); + export default plugin; diff --git a/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts b/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts index 449163503222..66462fb0d5f1 100644 --- a/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts +++ b/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts @@ -1,17 +1,7 @@ import eslintPluginTsv from "../../../src/eslint-plugin-tsv.js"; -import parser from "yaml-eslint-parser"; export const config = [ - { - plugins: { tsv: eslintPluginTsv }, - files: ["*.yaml", "**/*.yaml"], - languageOptions: { - parser, - }, - rules: { - "tsv/kebab-case-org": "error", - }, - }, + ...(eslintPluginTsv.configs as any).recommended, ]; export default config; From 0de1b3ee6a0c31f6517056fb813ffa18f3a370ef Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Wed, 18 Dec 2024 02:30:23 +0000 Subject: [PATCH 020/106] Move rules to subfolder --- eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts | 2 +- eng/tools/eslint-plugin-tsv/src/{ => rules}/kebab-case-org.ts | 0 .../eslint-plugin-tsv/test/{ => rules}/kebab-case-org.test.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename eng/tools/eslint-plugin-tsv/src/{ => rules}/kebab-case-org.ts (100%) rename eng/tools/eslint-plugin-tsv/test/{ => rules}/kebab-case-org.test.ts (93%) diff --git a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts index 16390fce9131..1e8409a219b8 100644 --- a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts +++ b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts @@ -1,4 +1,4 @@ -import kebabCaseOrg from "./kebab-case-org.js"; +import kebabCaseOrg from "./rules/kebab-case-org.js"; import parser from "yaml-eslint-parser"; const pluginName = "tsv"; diff --git a/eng/tools/eslint-plugin-tsv/src/kebab-case-org.ts b/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts similarity index 100% rename from eng/tools/eslint-plugin-tsv/src/kebab-case-org.ts rename to eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts diff --git a/eng/tools/eslint-plugin-tsv/test/kebab-case-org.test.ts b/eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts similarity index 93% rename from eng/tools/eslint-plugin-tsv/test/kebab-case-org.test.ts rename to eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts index fa484d729581..fbfe7d3b415c 100644 --- a/eng/tools/eslint-plugin-tsv/test/kebab-case-org.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts @@ -2,7 +2,7 @@ import { Rule, RuleTester } from "eslint"; import parser from "yaml-eslint-parser"; import { test } from "vitest"; -import kebabCaseOrg from "../src/kebab-case-org.js"; +import kebabCaseOrg from "../../src/rules/kebab-case-org.js"; test("tsv/" + kebabCaseOrg.meta.name, () => { const ruleTester = new RuleTester({ From 2208b20858b43602035ecdc4c19d299d9324b200 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Wed, 18 Dec 2024 03:04:21 +0000 Subject: [PATCH 021/106] Cleanup ts-ignore --- eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts b/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts index 9801c4f8e337..fd5192997f93 100644 --- a/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts +++ b/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts @@ -1,5 +1,3 @@ -// TODO: Add types - import path from "path"; export const rule = { @@ -16,8 +14,7 @@ export const rule = { "Organization name (first path segment after 'specification') does not use kebab-case: '{{orgName}}'", }, }, - // @ts-ignore - create(context) { + create(context: any) { const filename = context.getFilename() as string; const pathSegments = filename.split(path.sep); @@ -30,8 +27,7 @@ export const rule = { const orgNameKebabCase = orgName.match(kebabCaseRegex); return { - // @ts-ignore - Program(node) { + Program(node: any) { if (!orgNameKebabCase) { context.report({ node, From 5b3571f3c6571a07dbfed009c8727b868a72e0b3 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Wed, 18 Dec 2024 03:05:40 +0000 Subject: [PATCH 022/106] Remove comment --- eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts index 1e8409a219b8..9a0600671c69 100644 --- a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts +++ b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts @@ -3,7 +3,6 @@ import parser from "yaml-eslint-parser"; const pluginName = "tsv"; -// TODO: Add types export const plugin = { configs: { }, rules: { From cc6d19c160687976fc6698a3e1e02a49f176361b Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Wed, 18 Dec 2024 22:15:21 +0000 Subject: [PATCH 023/106] WIP: Add Npm.prefix() helper method and tests --- eng/tools/eslint-plugin-tsv/package.json | 1 + eng/tools/eslint-plugin-tsv/src/utils/npm.ts | 9 +++++++++ .../eslint-plugin-tsv/test/utils/npm.test.ts | 16 ++++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 eng/tools/eslint-plugin-tsv/src/utils/npm.ts create mode 100644 eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index d5c4240fbd69..b5451de0a5aa 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -12,6 +12,7 @@ "devDependencies": { "@types/node": "^18.19.31", "eslint": "^9.17.0", + "memfs":"^4.15.0", "rimraf": "^6.0.1", "typescript": "~5.6.2", "vitest": "^2.0.4" diff --git a/eng/tools/eslint-plugin-tsv/src/utils/npm.ts b/eng/tools/eslint-plugin-tsv/src/utils/npm.ts new file mode 100644 index 000000000000..62c5b333e1ce --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/utils/npm.ts @@ -0,0 +1,9 @@ +import { dirname } from "path"; + +export class Npm { + // Simulates `npm prefix` by finding the nearest parent directory containing `package.json` or `node_modules`. + // If neither exist in any parent directories, returns the directory containing the path itself. + static prefix(path: string): string { + return dirname(path); + } +} diff --git a/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts b/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts new file mode 100644 index 000000000000..88610ef159d1 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts @@ -0,0 +1,16 @@ +// import { beforeEach, test, vi } from "vitest"; +import { describe, expect, it } from "vitest"; + +import { Npm } from "../../src/utils/npm.js"; + +// vi.mock('fs') +describe("prefix", () => { + describe("returns current directory if no match", () => { + it.each([ + ["/tmp/foo/tspconfig.yaml", "/tmp/foo"], + ["/tmp/foo", "/tmp/foo"], + ])("%s", async (path, expected) => { + expect(Npm.prefix(path)).toBe(expected); + }); + }); +}); From 659ed1e9bd31292b003408ec760b296eab96160a Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Wed, 18 Dec 2024 23:07:21 +0000 Subject: [PATCH 024/106] Use memfs to test fs code --- eng/tools/eslint-plugin-tsv/src/utils/npm.ts | 17 ++- .../eslint-plugin-tsv/test/utils/npm.test.ts | 23 +++- package-lock.json | 118 ++++++++++++++++++ 3 files changed, 150 insertions(+), 8 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/src/utils/npm.ts b/eng/tools/eslint-plugin-tsv/src/utils/npm.ts index 62c5b333e1ce..711a05f2ef70 100644 --- a/eng/tools/eslint-plugin-tsv/src/utils/npm.ts +++ b/eng/tools/eslint-plugin-tsv/src/utils/npm.ts @@ -1,9 +1,20 @@ -import { dirname } from "path"; +import * as path from "path"; +import * as fs from "fs/promises"; export class Npm { // Simulates `npm prefix` by finding the nearest parent directory containing `package.json` or `node_modules`. // If neither exist in any parent directories, returns the directory containing the path itself. - static prefix(path: string): string { - return dirname(path); + static async prefix(filePath: string): Promise { + const stats = await fs.stat(filePath); + + let currentDirectory: string; + if (stats.isDirectory()) { + currentDirectory = path.resolve(filePath); + } + else { + currentDirectory = path.dirname(filePath); + } + + return currentDirectory; } } diff --git a/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts b/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts index 88610ef159d1..b506100fe40c 100644 --- a/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts @@ -1,16 +1,29 @@ -// import { beforeEach, test, vi } from "vitest"; -import { describe, expect, it } from "vitest"; - +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { vol } from "memfs"; import { Npm } from "../../src/utils/npm.js"; -// vi.mock('fs') +vi.mock("fs/promises", async () => { + const memfs = await import("memfs"); + return { + ...memfs.fs.promises, + }; +}); + describe("prefix", () => { + beforeEach(() => { + vol.reset(); + }); + describe("returns current directory if no match", () => { it.each([ ["/tmp/foo/tspconfig.yaml", "/tmp/foo"], ["/tmp/foo", "/tmp/foo"], ])("%s", async (path, expected) => { - expect(Npm.prefix(path)).toBe(expected); + vol.fromJSON({ + "/tmp/foo/tspconfig.yaml": "1", + }); + + expect(await Npm.prefix(path)).toBe(expected); }); }); }); diff --git a/package-lock.json b/package-lock.json index b625e0443439..41cfa9dfc7a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,6 +56,7 @@ "devDependencies": { "@types/node": "^18.19.31", "eslint": "^9.17.0", + "memfs": "^4.15.0", "rimraf": "^6.0.1", "typescript": "~5.6.2", "vitest": "^2.0.4" @@ -2574,6 +2575,63 @@ "jsep": "^0.4.0||^1.0.0" } }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.1.tgz", + "integrity": "sha512-osjeBqMJ2lb/j/M8NCPjs1ylqWIcTRTycIhVB5pt6LgzgeRSb0YRZ7j9RfA8wIUrsr/medIuhVyonXRZWLyfdw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", + "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/@kwsites/file-exists": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", @@ -6183,6 +6241,16 @@ "dev": true, "license": "Unlicense" }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -7012,6 +7080,26 @@ "dev": true, "license": "MIT" }, + "node_modules/memfs": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.15.0.tgz", + "integrity": "sha512-q9MmZXd2rRWHS6GU3WEm3HyiXZyyoA1DqdOhEq0lxPBmKb5S7IAOwX0RgUCwJfqjelDCySa5h8ujOy24LqsWcw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -9348,6 +9436,19 @@ "dev": true, "license": "MIT" }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "dev": true, + "license": "Unlicense", + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -9429,6 +9530,23 @@ "dev": true, "license": "MIT" }, + "node_modules/tree-dump": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", + "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", From a097482925595b2eace597a94510b6adb1e36612 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Thu, 19 Dec 2024 00:28:07 +0000 Subject: [PATCH 025/106] Add failing tests --- .../eslint-plugin-tsv/test/utils/npm.test.ts | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts b/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts index b506100fe40c..feeb86707e61 100644 --- a/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts @@ -16,11 +16,37 @@ describe("prefix", () => { describe("returns current directory if no match", () => { it.each([ - ["/tmp/foo/tspconfig.yaml", "/tmp/foo"], - ["/tmp/foo", "/tmp/foo"], + ["/foo/bar/tspconfig.yaml", "/foo/bar"], + ["/foo/bar", "/foo/bar"], ])("%s", async (path, expected) => { vol.fromJSON({ - "/tmp/foo/tspconfig.yaml": "1", + "/foo/bar/tspconfig.yaml": "", + }); + + expect(await Npm.prefix(path)).toBe(expected); + }); + }); + + describe("returns first match", () => { + it.each([ + ["/pj", "/pj"], + ["/pj/none", "/pj"], + ["/pj/none/none/none", "/pj"], + ["/pj/nm", "/pj/nm"], + ["/pj/nm/none", "/pj/nm"], + ["/pj/pj", "/pj/pj"], + ["/pj/nm/pj", "/pj/nm/pj"], + ["/pj/pj/nm", "/pj/pj/nm"], + ])("%s", async (path, expected) => { + vol.fromJSON({ + "/pj/package.json": "", + "/pj/none": null, + "/pj/none/none/none": null, + "/pj/nm/node_modules": null, + "/pj/nm/none": null, + "/pj/pj/package.json": "", + "/pj/nm/pj/package.json": "", + "/pj/pj/nm/node_modules": null, }); expect(await Npm.prefix(path)).toBe(expected); From 1afea81b4e7b057b8d792d22b6d6c069130cb6ba Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Thu, 19 Dec 2024 00:40:52 +0000 Subject: [PATCH 026/106] Implement prefix() --- eng/tools/eslint-plugin-tsv/src/utils/npm.ts | 34 ++++++++++++++------ 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/src/utils/npm.ts b/eng/tools/eslint-plugin-tsv/src/utils/npm.ts index 711a05f2ef70..9af3dd86db81 100644 --- a/eng/tools/eslint-plugin-tsv/src/utils/npm.ts +++ b/eng/tools/eslint-plugin-tsv/src/utils/npm.ts @@ -1,20 +1,36 @@ -import * as path from "path"; -import * as fs from "fs/promises"; +import { dirname, join, resolve } from "path"; +import { stat, access } from "fs/promises"; export class Npm { // Simulates `npm prefix` by finding the nearest parent directory containing `package.json` or `node_modules`. // If neither exist in any parent directories, returns the directory containing the path itself. - static async prefix(filePath: string): Promise { - const stats = await fs.stat(filePath); + static async prefix(path: string): Promise { + const stats = await stat(path); - let currentDirectory: string; + let initialDir: string; if (stats.isDirectory()) { - currentDirectory = path.resolve(filePath); + initialDir = resolve(path); + } else { + initialDir = dirname(path); } - else { - currentDirectory = path.dirname(filePath); + + for ( + var currentDir = initialDir; + dirname(currentDir) != currentDir; + currentDir = dirname(currentDir) + ) { + try { + await access(join(currentDir, "package.json")); + return currentDir; + } catch {} + + try { + await access(join(currentDir, "node_modules")); + return currentDir; + } catch {} } - return currentDirectory; + // Neither found in an parent dir + return initialDir; } } From 67ddba439e37bf8175e6b6da7c9029c8dc56ee90 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Thu, 19 Dec 2024 00:43:26 +0000 Subject: [PATCH 027/106] Add more bulk test commands --- eng/tools/eslint-plugin-tsv/package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index b5451de0a5aa..476d39e84144 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -22,10 +22,12 @@ "clean": "rimraf ./dist ./temp", "test": "vitest", "test:ci": "vitest run --reporter=verbose", - "test:e2e": "npm run clean && npm run build && npm run test:e2e:contoso && npm run test:e2e:not-kebab-case-disabled && npm run test:e2e:not-kebab-case", + "test:e2e": "npm run clean && npm run build && npm run test:e2e:all", + "test:e2e:all": "npm run test:e2e:contoso && npm run test:e2e:not-kebab-case-disabled && npm run test:e2e:not-kebab-case", "test:e2e:contoso": "eslint --config dist/test/e2e/specification/eslint.config.js test/e2e/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml", "test:e2e:not-kebab-case-disabled": "eslint --config dist/test/e2e/specification/eslint.config.js test/e2e/specification/Not-Kebab-Case-Disabled/Not.KebabCase/tspconfig.yaml", - "test:e2e:not-kebab-case": "eslint --config dist/test/e2e/specification/eslint.config.js test/e2e/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml" + "test:e2e:not-kebab-case": "eslint --config dist/test/e2e/specification/eslint.config.js test/e2e/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml", + "test:all": "npm run clean && npm run build && npm run test:ci && npm run test:e2e:all" }, "engines": { "node": ">= 18.0.0" From 0c239f5d75cd2a0b34406897c0a71bc87cd7aad0 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Thu, 19 Dec 2024 01:07:16 +0000 Subject: [PATCH 028/106] Add test workflow --- .github/workflows/eslint-plugin-tsv-test.yaml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/eslint-plugin-tsv-test.yaml diff --git a/.github/workflows/eslint-plugin-tsv-test.yaml b/.github/workflows/eslint-plugin-tsv-test.yaml new file mode 100644 index 000000000000..f6af33b35953 --- /dev/null +++ b/.github/workflows/eslint-plugin-tsv-test.yaml @@ -0,0 +1,24 @@ +name: ESLint Plugin for TypeSpec Validation - Test + +on: + push: + branches: + - main + - typespec-next + pull_request: + paths: + - package-lock.json + - package.json + - tsconfig.json + - .github/workflows/_reusable-eng-tools-test.yaml + - .github/workflows/eslint-plugin-tsv-test.yaml + - eng/tools/package.json + - eng/tools/tsconfig.json + - eng/tools/eslint-plugin-tsv/** + workflow_dispatch: + +jobs: + typespec-validation: + uses: ./.github/workflows/_reusable-eng-tools-test.yaml + with: + package: eslint-plugin-tsv From 6903a12bbaadb2c18f63d388aa5ce3f586c860b1 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Thu, 19 Dec 2024 01:31:43 +0000 Subject: [PATCH 029/106] Downgrade rimraf to v5 for node18 compat --- eng/tools/eslint-plugin-tsv/package.json | 2 +- package-lock.json | 152 ++++++----------------- 2 files changed, 39 insertions(+), 115 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index 476d39e84144..731cc22a81f6 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -13,7 +13,7 @@ "@types/node": "^18.19.31", "eslint": "^9.17.0", "memfs":"^4.15.0", - "rimraf": "^6.0.1", + "rimraf": "^5.0.10", "typescript": "~5.6.2", "vitest": "^2.0.4" }, diff --git a/package-lock.json b/package-lock.json index 41cfa9dfc7a3..ab897d4ac67c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,7 @@ "@types/node": "^18.19.31", "eslint": "^9.17.0", "memfs": "^4.15.0", - "rimraf": "^6.0.1", + "rimraf": "^5.0.10", "typescript": "~5.6.2", "vitest": "^2.0.4" }, @@ -121,6 +121,27 @@ "balanced-match": "^1.0.0" } }, + "eng/tools/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "eng/tools/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -137,6 +158,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "eng/tools/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "eng/tools/node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -8648,119 +8685,6 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", - "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^11.0.0", - "package-json-from-dist": "^1.0.0" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", - "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/jackspeak": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", - "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/lru-cache": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", - "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rollup": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz", From 8978c866809076496c55c7bf83883f80342e49cd Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Thu, 19 Dec 2024 01:39:15 +0000 Subject: [PATCH 030/106] Add code coverage --- eng/tools/eslint-plugin-tsv/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index 731cc22a81f6..946631b2b9d5 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -11,6 +11,7 @@ }, "devDependencies": { "@types/node": "^18.19.31", + "@vitest/coverage-v8": "^2.0.4", "eslint": "^9.17.0", "memfs":"^4.15.0", "rimraf": "^5.0.10", @@ -21,7 +22,7 @@ "build": "tsc --build", "clean": "rimraf ./dist ./temp", "test": "vitest", - "test:ci": "vitest run --reporter=verbose", + "test:ci": "vitest run --coverage --reporter=verbose", "test:e2e": "npm run clean && npm run build && npm run test:e2e:all", "test:e2e:all": "npm run test:e2e:contoso && npm run test:e2e:not-kebab-case-disabled && npm run test:e2e:not-kebab-case", "test:e2e:contoso": "eslint --config dist/test/e2e/specification/eslint.config.js test/e2e/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml", From 83d874cc0c5568e78f57311b78b2aaa3f81f006c Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Thu, 19 Dec 2024 02:06:11 +0000 Subject: [PATCH 031/106] Normalize paths so tests pass on win32 --- eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts b/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts index feeb86707e61..fd8052491f80 100644 --- a/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts @@ -1,5 +1,6 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { vol } from "memfs"; +import { normalize } from "path"; import { Npm } from "../../src/utils/npm.js"; vi.mock("fs/promises", async () => { @@ -49,7 +50,7 @@ describe("prefix", () => { "/pj/pj/nm/node_modules": null, }); - expect(await Npm.prefix(path)).toBe(expected); + expect(await Npm.prefix(path)).toBe(normalize(expected)); }); }); }); From 786140b5adb0b736a764001ad2233f38ede02df4 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Thu, 19 Dec 2024 02:08:20 +0000 Subject: [PATCH 032/106] normalize expected value --- eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts b/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts index fd8052491f80..14499a509690 100644 --- a/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts @@ -24,7 +24,7 @@ describe("prefix", () => { "/foo/bar/tspconfig.yaml": "", }); - expect(await Npm.prefix(path)).toBe(expected); + expect(await Npm.prefix(path)).toBe(normalize(expected)); }); }); From 40c079688e52ad3532a65e6376e5ba9f06166cbf Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Thu, 19 Dec 2024 06:29:24 +0000 Subject: [PATCH 033/106] Rename job --- .github/workflows/eslint-plugin-tsv-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/eslint-plugin-tsv-test.yaml b/.github/workflows/eslint-plugin-tsv-test.yaml index f6af33b35953..fc85aa16bf8b 100644 --- a/.github/workflows/eslint-plugin-tsv-test.yaml +++ b/.github/workflows/eslint-plugin-tsv-test.yaml @@ -18,7 +18,7 @@ on: workflow_dispatch: jobs: - typespec-validation: + eslint-plugin-tsv: uses: ./.github/workflows/_reusable-eng-tools-test.yaml with: package: eslint-plugin-tsv From 908df37146267944b6a27ca9957cda941fabadb8 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Thu, 19 Dec 2024 06:29:37 +0000 Subject: [PATCH 034/106] Add comment --- eng/tools/eslint-plugin-tsv/src/utils/npm.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/eng/tools/eslint-plugin-tsv/src/utils/npm.ts b/eng/tools/eslint-plugin-tsv/src/utils/npm.ts index 9af3dd86db81..520657a2dd02 100644 --- a/eng/tools/eslint-plugin-tsv/src/utils/npm.ts +++ b/eng/tools/eslint-plugin-tsv/src/utils/npm.ts @@ -1,6 +1,8 @@ import { dirname, join, resolve } from "path"; import { stat, access } from "fs/promises"; +// TODO: Add @types/eslint + export class Npm { // Simulates `npm prefix` by finding the nearest parent directory containing `package.json` or `node_modules`. // If neither exist in any parent directories, returns the directory containing the path itself. From 5fe0a1f3f59e90cbbb3fb35f1c2ac4077a080af6 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Thu, 19 Dec 2024 06:45:18 +0000 Subject: [PATCH 035/106] Always return absolute path --- eng/tools/eslint-plugin-tsv/src/utils/npm.ts | 10 +++------- eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts | 6 +++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/src/utils/npm.ts b/eng/tools/eslint-plugin-tsv/src/utils/npm.ts index 520657a2dd02..b5f32e0e6aa8 100644 --- a/eng/tools/eslint-plugin-tsv/src/utils/npm.ts +++ b/eng/tools/eslint-plugin-tsv/src/utils/npm.ts @@ -6,15 +6,11 @@ import { stat, access } from "fs/promises"; export class Npm { // Simulates `npm prefix` by finding the nearest parent directory containing `package.json` or `node_modules`. // If neither exist in any parent directories, returns the directory containing the path itself. + // Always returns an absolute path. static async prefix(path: string): Promise { - const stats = await stat(path); + path = resolve(path); - let initialDir: string; - if (stats.isDirectory()) { - initialDir = resolve(path); - } else { - initialDir = dirname(path); - } + const initialDir = (await stat(path)).isDirectory() ? path : dirname(path); for ( var currentDir = initialDir; diff --git a/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts b/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts index 14499a509690..28e54924170a 100644 --- a/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { vol } from "memfs"; -import { normalize } from "path"; +import { resolve } from "path"; import { Npm } from "../../src/utils/npm.js"; vi.mock("fs/promises", async () => { @@ -24,7 +24,7 @@ describe("prefix", () => { "/foo/bar/tspconfig.yaml": "", }); - expect(await Npm.prefix(path)).toBe(normalize(expected)); + expect(await Npm.prefix(path)).toBe(resolve(expected)); }); }); @@ -50,7 +50,7 @@ describe("prefix", () => { "/pj/pj/nm/node_modules": null, }); - expect(await Npm.prefix(path)).toBe(normalize(expected)); + expect(await Npm.prefix(path)).toBe(resolve(expected)); }); }); }); From 30093111de4f982ad935c1a545091ed6f9a0087e Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Thu, 19 Dec 2024 09:19:42 +0000 Subject: [PATCH 036/106] Resolve path before splitting --- eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts b/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts index fd5192997f93..edbb866d3308 100644 --- a/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts +++ b/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts @@ -15,7 +15,7 @@ export const rule = { }, }, create(context: any) { - const filename = context.getFilename() as string; + const filename = path.resolve(context.filename as string); const pathSegments = filename.split(path.sep); From a92698f3f320d653064a4b580dd48efcac61de5d Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Thu, 19 Dec 2024 22:24:18 +0000 Subject: [PATCH 037/106] Add stub for e2e vite tests --- eng/tools/eslint-plugin-tsv/test/e2e/e2e.test.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 eng/tools/eslint-plugin-tsv/test/e2e/e2e.test.ts diff --git a/eng/tools/eslint-plugin-tsv/test/e2e/e2e.test.ts b/eng/tools/eslint-plugin-tsv/test/e2e/e2e.test.ts new file mode 100644 index 000000000000..381cc910950b --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/test/e2e/e2e.test.ts @@ -0,0 +1 @@ +// TODO: Convert e2e tests to vitest \ No newline at end of file From 3e93efae4ed186091921ad0c75b66954a03d49f2 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Thu, 19 Dec 2024 22:38:45 +0000 Subject: [PATCH 038/106] Remove comment --- eng/tools/eslint-plugin-tsv/src/utils/npm.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/src/utils/npm.ts b/eng/tools/eslint-plugin-tsv/src/utils/npm.ts index b5f32e0e6aa8..0d36838a1f8b 100644 --- a/eng/tools/eslint-plugin-tsv/src/utils/npm.ts +++ b/eng/tools/eslint-plugin-tsv/src/utils/npm.ts @@ -1,8 +1,6 @@ import { dirname, join, resolve } from "path"; import { stat, access } from "fs/promises"; -// TODO: Add @types/eslint - export class Npm { // Simulates `npm prefix` by finding the nearest parent directory containing `package.json` or `node_modules`. // If neither exist in any parent directories, returns the directory containing the path itself. From 41814f9292fafa2822f404d020173a68987680e3 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Thu, 19 Dec 2024 22:39:20 +0000 Subject: [PATCH 039/106] Add ESLint with names --- .../src/eslint-plugin-tsv.ts | 43 +++++++++---------- .../eslint-plugin-tsv/src/named-eslint.ts | 16 +++++++ .../src/rules/kebab-case-org.ts | 9 ++-- .../test/e2e/specification/eslint.config.ts | 2 +- .../test/rules/kebab-case-org.test.ts | 12 +++--- 5 files changed, 50 insertions(+), 32 deletions(-) create mode 100644 eng/tools/eslint-plugin-tsv/src/named-eslint.ts diff --git a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts index 9a0600671c69..d3f241a25b17 100644 --- a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts +++ b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts @@ -1,31 +1,30 @@ -import kebabCaseOrg from "./rules/kebab-case-org.js"; +import { Linter } from "eslint"; import parser from "yaml-eslint-parser"; +import { NamedESLint } from "./named-eslint.js"; +import kebabCaseOrg from "./rules/kebab-case-org.js"; -const pluginName = "tsv"; - -export const plugin = { - configs: { }, +const plugin: NamedESLint.Plugin = { + name: "tsv", rules: { - [kebabCaseOrg.meta.name]: kebabCaseOrg, + [kebabCaseOrg.name]: kebabCaseOrg, }, }; -// assign configs here so we can reference `plugin` -Object.assign(plugin.configs, { - recommended: [ - { - plugins: { - [pluginName]: plugin, - }, - files: ["*.yaml", "**/*.yaml"], - rules: { - [`${pluginName}/${kebabCaseOrg.meta.name}`]: "error", - }, - languageOptions: { - parser: parser, - }, +const configs: Record = { + recommended: { + plugins: { + [plugin.name]: plugin, + }, + files: ["*.yaml", "**/*.yaml"], + rules: { + [`${plugin.name}/${kebabCaseOrg.name}`]: "error", }, - ], -}); + languageOptions: { + parser: parser, + }, + }, +}; + +plugin.configs = configs; export default plugin; diff --git a/eng/tools/eslint-plugin-tsv/src/named-eslint.ts b/eng/tools/eslint-plugin-tsv/src/named-eslint.ts new file mode 100644 index 000000000000..3b51d37656f2 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/named-eslint.ts @@ -0,0 +1,16 @@ +import { ESLint, Rule } from "eslint"; + +// ESLint with names for convenience + +export namespace NamedRule { + export interface RuleModule extends Rule.RuleModule { + name: string; + } +} + +export namespace NamedESLint { + export interface Plugin extends ESLint.Plugin { + name: string; + rules?: Record; + } +} diff --git a/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts b/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts index edbb866d3308..e5165dfc6b85 100644 --- a/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts +++ b/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts @@ -1,8 +1,9 @@ import path from "path"; +import { NamedRule } from "../named-eslint.js"; -export const rule = { +export const rule: NamedRule.RuleModule = { + name: "kebab-case-org", meta: { - name: "kebab-case-org", type: "problem", docs: { description: @@ -14,7 +15,7 @@ export const rule = { "Organization name (first path segment after 'specification') does not use kebab-case: '{{orgName}}'", }, }, - create(context: any) { + create(context) { const filename = path.resolve(context.filename as string); const pathSegments = filename.split(path.sep); @@ -27,7 +28,7 @@ export const rule = { const orgNameKebabCase = orgName.match(kebabCaseRegex); return { - Program(node: any) { + Program(node) { if (!orgNameKebabCase) { context.report({ node, diff --git a/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts b/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts index 66462fb0d5f1..c1d84deb1966 100644 --- a/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts +++ b/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts @@ -1,7 +1,7 @@ import eslintPluginTsv from "../../../src/eslint-plugin-tsv.js"; export const config = [ - ...(eslintPluginTsv.configs as any).recommended, + ...eslintPluginTsv.configs.recommended, ]; export default config; diff --git a/eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts b/eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts index fbfe7d3b415c..bb43e0b61abf 100644 --- a/eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts @@ -1,21 +1,21 @@ import { Rule, RuleTester } from "eslint"; -import parser from "yaml-eslint-parser"; import { test } from "vitest"; +import parser from "yaml-eslint-parser"; import kebabCaseOrg from "../../src/rules/kebab-case-org.js"; -test("tsv/" + kebabCaseOrg.meta.name, () => { +test("tsv/" + kebabCaseOrg.name, () => { const ruleTester = new RuleTester({ languageOptions: { parser: parser, }, }); - ruleTester.run(kebabCaseOrg.meta.name, kebabCaseOrg as Rule.RuleModule, { + ruleTester.run(kebabCaseOrg.name, kebabCaseOrg as Rule.RuleModule, { valid: [ { code: "", filename: "/specification/contoso/Contoso.WidgetManager/tspconfig.yaml" }, { - code: `# eslint-disable rule-to-test/${kebabCaseOrg.meta.name}`, + code: `# eslint-disable rule-to-test/${kebabCaseOrg.name}`, filename: "/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml", }, ], @@ -23,7 +23,9 @@ test("tsv/" + kebabCaseOrg.meta.name, () => { { code: "", filename: "/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml", - errors: ["Organization name (first path segment after 'specification') does not use kebab-case: 'Not-Kebab-Case'"], + errors: [ + "Organization name (first path segment after 'specification') does not use kebab-case: 'Not-Kebab-Case'", + ], }, ], }); From 5907a2100a5fa694d7289ea12d8869b9cfa65553 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Thu, 19 Dec 2024 23:16:00 +0000 Subject: [PATCH 040/106] Add dummy test to prevent errors --- eng/tools/eslint-plugin-tsv/test/e2e/e2e.test.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/eng/tools/eslint-plugin-tsv/test/e2e/e2e.test.ts b/eng/tools/eslint-plugin-tsv/test/e2e/e2e.test.ts index 381cc910950b..3219a3a8d8f1 100644 --- a/eng/tools/eslint-plugin-tsv/test/e2e/e2e.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/e2e/e2e.test.ts @@ -1 +1,7 @@ -// TODO: Convert e2e tests to vitest \ No newline at end of file +import { describe, it } from "vitest"; + +// TODO: Convert e2e tests to vitest + +describe("e2e", () => { + it("passes", () => true); +}); From d0ae1deaef2a6da34a767956a73f983a8eddfaf7 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Thu, 19 Dec 2024 23:16:35 +0000 Subject: [PATCH 041/106] Stronger config typing --- .../src/eslint-plugin-tsv.ts | 26 ++++++++----------- .../eslint-plugin-tsv/src/named-eslint.ts | 3 ++- .../test/e2e/specification/eslint.config.ts | 6 +---- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts index d3f241a25b17..bdac409b2574 100644 --- a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts +++ b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts @@ -1,30 +1,26 @@ -import { Linter } from "eslint"; import parser from "yaml-eslint-parser"; import { NamedESLint } from "./named-eslint.js"; import kebabCaseOrg from "./rules/kebab-case-org.js"; const plugin: NamedESLint.Plugin = { + configs: { recommended: {} }, name: "tsv", rules: { [kebabCaseOrg.name]: kebabCaseOrg, }, }; -const configs: Record = { - recommended: { - plugins: { - [plugin.name]: plugin, - }, - files: ["*.yaml", "**/*.yaml"], - rules: { - [`${plugin.name}/${kebabCaseOrg.name}`]: "error", - }, - languageOptions: { - parser: parser, - }, +plugin.configs.recommended = { + plugins: { + [plugin.name]: plugin, + }, + files: ["*.yaml", "**/*.yaml"], + rules: { + [`${plugin.name}/${kebabCaseOrg.name}`]: "error", + }, + languageOptions: { + parser: parser, }, }; -plugin.configs = configs; - export default plugin; diff --git a/eng/tools/eslint-plugin-tsv/src/named-eslint.ts b/eng/tools/eslint-plugin-tsv/src/named-eslint.ts index 3b51d37656f2..a09d8d27f150 100644 --- a/eng/tools/eslint-plugin-tsv/src/named-eslint.ts +++ b/eng/tools/eslint-plugin-tsv/src/named-eslint.ts @@ -1,4 +1,4 @@ -import { ESLint, Rule } from "eslint"; +import { ESLint, Linter, Rule } from "eslint"; // ESLint with names for convenience @@ -10,6 +10,7 @@ export namespace NamedRule { export namespace NamedESLint { export interface Plugin extends ESLint.Plugin { + configs: { recommended: Linter.Config }; name: string; rules?: Record; } diff --git a/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts b/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts index c1d84deb1966..a62b6ebf16b2 100644 --- a/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts +++ b/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts @@ -1,7 +1,3 @@ import eslintPluginTsv from "../../../src/eslint-plugin-tsv.js"; -export const config = [ - ...eslintPluginTsv.configs.recommended, -]; - -export default config; +export default eslintPluginTsv.configs.recommended; From 49d09b681aa117b7d5e912479cb70ea333565719 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Thu, 19 Dec 2024 23:51:06 +0000 Subject: [PATCH 042/106] Move e2e test --- eng/tools/eslint-plugin-tsv/test/e2e.test.ts | 12 ++++++++++++ eng/tools/eslint-plugin-tsv/test/e2e/e2e.test.ts | 7 ------- 2 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 eng/tools/eslint-plugin-tsv/test/e2e.test.ts delete mode 100644 eng/tools/eslint-plugin-tsv/test/e2e/e2e.test.ts diff --git a/eng/tools/eslint-plugin-tsv/test/e2e.test.ts b/eng/tools/eslint-plugin-tsv/test/e2e.test.ts new file mode 100644 index 000000000000..ee6ab9a40fac --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/test/e2e.test.ts @@ -0,0 +1,12 @@ +import { ESLint } from "eslint"; +import { describe, expect, it } from "vitest"; + +// TODO: Convert e2e tests to vitest + +describe("e2e", () => { + it("/dev/null", async () => { + const eslint = new ESLint(); + const results = await eslint.lintFiles("/dev/null"); + expect(results).toHaveLength(0); + }); +}); diff --git a/eng/tools/eslint-plugin-tsv/test/e2e/e2e.test.ts b/eng/tools/eslint-plugin-tsv/test/e2e/e2e.test.ts deleted file mode 100644 index 3219a3a8d8f1..000000000000 --- a/eng/tools/eslint-plugin-tsv/test/e2e/e2e.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, it } from "vitest"; - -// TODO: Convert e2e tests to vitest - -describe("e2e", () => { - it("passes", () => true); -}); From 2563cd236049bf898121c3c65a7b001edb8a752c Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Fri, 20 Dec 2024 00:38:22 +0000 Subject: [PATCH 043/106] Run tests when contoso changes --- .github/workflows/eslint-plugin-tsv-test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/eslint-plugin-tsv-test.yaml b/.github/workflows/eslint-plugin-tsv-test.yaml index fc85aa16bf8b..9fcdc7b47f46 100644 --- a/.github/workflows/eslint-plugin-tsv-test.yaml +++ b/.github/workflows/eslint-plugin-tsv-test.yaml @@ -15,6 +15,7 @@ on: - eng/tools/package.json - eng/tools/tsconfig.json - eng/tools/eslint-plugin-tsv/** + - specification/contosowidgetmanager workflow_dispatch: jobs: From 6a555fb1fc8588dfc252f030393550a53e2c7e1c Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Fri, 20 Dec 2024 01:36:54 +0000 Subject: [PATCH 044/106] Add e2e test using real filesystem --- .../eslint-plugin-tsv/test/e2e-realfs.test.ts | 17 ++++++++ .../Contoso.WidgetManager/tspconfig.yaml | 39 ------------------- eng/tools/eslint-plugin-tsv/test/utils/e2e.ts | 10 +++++ 3 files changed, 27 insertions(+), 39 deletions(-) create mode 100644 eng/tools/eslint-plugin-tsv/test/e2e-realfs.test.ts delete mode 100644 eng/tools/eslint-plugin-tsv/test/e2e/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml create mode 100644 eng/tools/eslint-plugin-tsv/test/utils/e2e.ts diff --git a/eng/tools/eslint-plugin-tsv/test/e2e-realfs.test.ts b/eng/tools/eslint-plugin-tsv/test/e2e-realfs.test.ts new file mode 100644 index 000000000000..29fc83476f3a --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/test/e2e-realfs.test.ts @@ -0,0 +1,17 @@ +import { join, resolve } from "path"; +import { describe, expect, it } from "vitest"; +import { createESLint } from "./utils/e2e.js"; + +const specsFolder = resolve(__filename, "../../../../../specification"); + +describe("e2e-realfs", () => { + it("contosowidgetmanager/Contso.WidgetManager", async () => { + const eslint = createESLint(); + const filePath = join(specsFolder, "contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml"); + const results = await eslint.lintFiles(filePath); + + expect(results).toHaveLength(1); + expect(results[0].filePath).toBe(filePath); + expect(results[0].messages).toHaveLength(0); + }); +}); diff --git a/eng/tools/eslint-plugin-tsv/test/e2e/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml b/eng/tools/eslint-plugin-tsv/test/e2e/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml deleted file mode 100644 index 2633b3a76f34..000000000000 --- a/eng/tools/eslint-plugin-tsv/test/e2e/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml +++ /dev/null @@ -1,39 +0,0 @@ -parameters: - "service-dir": - default: "sdk/contosowidgetmanager" - "dependencies": - "additionalDirectories": - - "specification/contosowidgetmanager/Contoso.WidgetManager.Shared/" - default: "" -emit: - - "@azure-tools/typespec-autorest" -linter: - extends: - - "@azure-tools/typespec-azure-rulesets/data-plane" -options: - "@azure-tools/typespec-autorest": - azure-resource-provider-folder: "data-plane" - emit-lro-options: "none" - emitter-output-dir: "{project-root}/.." - output-file: "{azure-resource-provider-folder}/{service-name}/{version-status}/{version}/widgets.json" - "@azure-tools/typespec-python": - package-dir: "azure-contoso-widgetmanager" - package-name: "{package-dir}" - generate-test: true - generate-sample: true - flavor: azure - "@azure-tools/typespec-csharp": - package-dir: "Azure.Template.Contoso" - clear-output-folder: true - model-namespace: false - namespace: "{package-dir}" - flavor: azure - "@azure-tools/typespec-ts": - package-dir: "contosowidgetmanager-rest" - packageDetails: - name: "@azure-rest/contoso-widgetmanager-rest" - flavor: azure - "@azure-tools/typespec-java": - package-dir: "azure-contoso-widgetmanager" - namespace: com.azure.contoso.widgetmanager - flavor: azure diff --git a/eng/tools/eslint-plugin-tsv/test/utils/e2e.ts b/eng/tools/eslint-plugin-tsv/test/utils/e2e.ts new file mode 100644 index 000000000000..5a9ff9e14054 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/test/utils/e2e.ts @@ -0,0 +1,10 @@ +import { ESLint } from "eslint"; +import eslintPluginTsv from "../../src/eslint-plugin-tsv.js"; + +export function createESLint() { + return new ESLint({ + cwd: "/", + overrideConfig: eslintPluginTsv.configs.recommended, + overrideConfigFile: true, + }); +} From dd472f891381b27d952f04d992e549eafd02efc5 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Fri, 20 Dec 2024 01:37:31 +0000 Subject: [PATCH 045/106] Remove old test --- eng/tools/eslint-plugin-tsv/test/e2e.test.ts | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 eng/tools/eslint-plugin-tsv/test/e2e.test.ts diff --git a/eng/tools/eslint-plugin-tsv/test/e2e.test.ts b/eng/tools/eslint-plugin-tsv/test/e2e.test.ts deleted file mode 100644 index ee6ab9a40fac..000000000000 --- a/eng/tools/eslint-plugin-tsv/test/e2e.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ESLint } from "eslint"; -import { describe, expect, it } from "vitest"; - -// TODO: Convert e2e tests to vitest - -describe("e2e", () => { - it("/dev/null", async () => { - const eslint = new ESLint(); - const results = await eslint.lintFiles("/dev/null"); - expect(results).toHaveLength(0); - }); -}); From d7439da21cc0dede4f0fd9d6312da4a942cd2408 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Fri, 20 Dec 2024 02:18:33 +0000 Subject: [PATCH 046/106] Improve test name --- eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts b/eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts index bb43e0b61abf..dc9162122aea 100644 --- a/eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts @@ -4,7 +4,7 @@ import parser from "yaml-eslint-parser"; import kebabCaseOrg from "../../src/rules/kebab-case-org.js"; -test("tsv/" + kebabCaseOrg.name, () => { +test("RuleTester", () => { const ruleTester = new RuleTester({ languageOptions: { parser: parser, From 90fab866cd84cde5f42638d13c6ea646164bb154 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Fri, 20 Dec 2024 02:19:30 +0000 Subject: [PATCH 047/106] Fix e2e tests --- .../eslint-plugin-tsv/test/e2e-realfs.test.ts | 17 ------ eng/tools/eslint-plugin-tsv/test/e2e.test.ts | 52 +++++++++++++++++++ .../Not.KebabCase/tspconfig.yaml | 40 -------------- .../Not.KebabCase/tspconfig.yaml | 39 -------------- .../test/e2e/specification/eslint.config.ts | 3 -- eng/tools/eslint-plugin-tsv/test/utils/e2e.ts | 10 ---- 6 files changed, 52 insertions(+), 109 deletions(-) delete mode 100644 eng/tools/eslint-plugin-tsv/test/e2e-realfs.test.ts create mode 100644 eng/tools/eslint-plugin-tsv/test/e2e.test.ts delete mode 100644 eng/tools/eslint-plugin-tsv/test/e2e/specification/Not-Kebab-Case-Disabled/Not.KebabCase/tspconfig.yaml delete mode 100644 eng/tools/eslint-plugin-tsv/test/e2e/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml delete mode 100644 eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts delete mode 100644 eng/tools/eslint-plugin-tsv/test/utils/e2e.ts diff --git a/eng/tools/eslint-plugin-tsv/test/e2e-realfs.test.ts b/eng/tools/eslint-plugin-tsv/test/e2e-realfs.test.ts deleted file mode 100644 index 29fc83476f3a..000000000000 --- a/eng/tools/eslint-plugin-tsv/test/e2e-realfs.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { join, resolve } from "path"; -import { describe, expect, it } from "vitest"; -import { createESLint } from "./utils/e2e.js"; - -const specsFolder = resolve(__filename, "../../../../../specification"); - -describe("e2e-realfs", () => { - it("contosowidgetmanager/Contso.WidgetManager", async () => { - const eslint = createESLint(); - const filePath = join(specsFolder, "contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml"); - const results = await eslint.lintFiles(filePath); - - expect(results).toHaveLength(1); - expect(results[0].filePath).toBe(filePath); - expect(results[0].messages).toHaveLength(0); - }); -}); diff --git a/eng/tools/eslint-plugin-tsv/test/e2e.test.ts b/eng/tools/eslint-plugin-tsv/test/e2e.test.ts new file mode 100644 index 000000000000..33bd6daf518b --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/test/e2e.test.ts @@ -0,0 +1,52 @@ +import { ESLint } from "eslint"; +import { join, resolve } from "path"; +import { describe, expect, it } from "vitest"; +import eslintPluginTsv from "../src/eslint-plugin-tsv.js"; + +function createESLint() { + return new ESLint({ + cwd: "/", + overrideConfig: eslintPluginTsv.configs.recommended, + overrideConfigFile: true, + }); +} + +describe("lint-text", () => { + it("Not-Kebab-Case/Not.KebabCase", async () => { + const filePath = "/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml"; + const eslint = createESLint(); + + const results = await eslint.lintText("", { filePath: filePath }); + + expect(results).toHaveLength(1); + expect(results[0].filePath).toBe(filePath); + expect(results[0].messages[0].ruleId).toBe("tsv/kebab-case-org"); + }); + + it("Not-Kebab-Case-Disabled/Not.KebabCase", async () => { + const filePath = "/specification/Not-Kebab-Case-Disabled/Not.KebabCase/tspconfig.yaml"; + const eslint = createESLint(); + + const results = await eslint.lintText("# eslint-disable tsv/kebab-case-org", { + filePath: filePath, + }); + + expect(results).toHaveLength(1); + expect(results[0].filePath).toBe(filePath); + expect(results[0].messages).toHaveLength(0); + }); +}); + +describe("lint-files", () => { + const specsFolder = resolve(__filename, "../../../../../specification"); + + it("contosowidgetmanager/Contso.WidgetManager", async () => { + const eslint = createESLint(); + const filePath = join(specsFolder, "contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml"); + const results = await eslint.lintFiles(filePath); + + expect(results).toHaveLength(1); + expect(results[0].filePath).toBe(filePath); + expect(results[0].messages).toHaveLength(0); + }); +}); diff --git a/eng/tools/eslint-plugin-tsv/test/e2e/specification/Not-Kebab-Case-Disabled/Not.KebabCase/tspconfig.yaml b/eng/tools/eslint-plugin-tsv/test/e2e/specification/Not-Kebab-Case-Disabled/Not.KebabCase/tspconfig.yaml deleted file mode 100644 index 87dd5cf24346..000000000000 --- a/eng/tools/eslint-plugin-tsv/test/e2e/specification/Not-Kebab-Case-Disabled/Not.KebabCase/tspconfig.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# eslint-disable tsv/kebab-case-org -parameters: - "service-dir": - default: "sdk/contosowidgetmanager" - "dependencies": - "additionalDirectories": - - "specification/contosowidgetmanager/Contoso.WidgetManager.Shared/" - default: "" -emit: - - "@azure-tools/typespec-autorest" -linter: - extends: - - "@azure-tools/typespec-azure-rulesets/data-plane" -options: - "@azure-tools/typespec-autorest": - azure-resource-provider-folder: "data-plane" - emit-lro-options: "none" - emitter-output-dir: "{project-root}/.." - output-file: "{azure-resource-provider-folder}/{service-name}/{version-status}/{version}/widgets.json" - "@azure-tools/typespec-python": - package-dir: "azure-contoso-widgetmanager" - package-name: "{package-dir}" - generate-test: true - generate-sample: true - flavor: azure - "@azure-tools/typespec-csharp": - package-dir: "Azure.Template.Contoso" - clear-output-folder: true - model-namespace: false - namespace: "{package-dir}" - flavor: azure - "@azure-tools/typespec-ts": - package-dir: "contosowidgetmanager-rest" - packageDetails: - name: "@azure-rest/contoso-widgetmanager-rest" - flavor: azure - "@azure-tools/typespec-java": - package-dir: "azure-contoso-widgetmanager" - namespace: com.azure.contoso.widgetmanager - flavor: azure diff --git a/eng/tools/eslint-plugin-tsv/test/e2e/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml b/eng/tools/eslint-plugin-tsv/test/e2e/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml deleted file mode 100644 index 2633b3a76f34..000000000000 --- a/eng/tools/eslint-plugin-tsv/test/e2e/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml +++ /dev/null @@ -1,39 +0,0 @@ -parameters: - "service-dir": - default: "sdk/contosowidgetmanager" - "dependencies": - "additionalDirectories": - - "specification/contosowidgetmanager/Contoso.WidgetManager.Shared/" - default: "" -emit: - - "@azure-tools/typespec-autorest" -linter: - extends: - - "@azure-tools/typespec-azure-rulesets/data-plane" -options: - "@azure-tools/typespec-autorest": - azure-resource-provider-folder: "data-plane" - emit-lro-options: "none" - emitter-output-dir: "{project-root}/.." - output-file: "{azure-resource-provider-folder}/{service-name}/{version-status}/{version}/widgets.json" - "@azure-tools/typespec-python": - package-dir: "azure-contoso-widgetmanager" - package-name: "{package-dir}" - generate-test: true - generate-sample: true - flavor: azure - "@azure-tools/typespec-csharp": - package-dir: "Azure.Template.Contoso" - clear-output-folder: true - model-namespace: false - namespace: "{package-dir}" - flavor: azure - "@azure-tools/typespec-ts": - package-dir: "contosowidgetmanager-rest" - packageDetails: - name: "@azure-rest/contoso-widgetmanager-rest" - flavor: azure - "@azure-tools/typespec-java": - package-dir: "azure-contoso-widgetmanager" - namespace: com.azure.contoso.widgetmanager - flavor: azure diff --git a/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts b/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts deleted file mode 100644 index a62b6ebf16b2..000000000000 --- a/eng/tools/eslint-plugin-tsv/test/e2e/specification/eslint.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -import eslintPluginTsv from "../../../src/eslint-plugin-tsv.js"; - -export default eslintPluginTsv.configs.recommended; diff --git a/eng/tools/eslint-plugin-tsv/test/utils/e2e.ts b/eng/tools/eslint-plugin-tsv/test/utils/e2e.ts deleted file mode 100644 index 5a9ff9e14054..000000000000 --- a/eng/tools/eslint-plugin-tsv/test/utils/e2e.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ESLint } from "eslint"; -import eslintPluginTsv from "../../src/eslint-plugin-tsv.js"; - -export function createESLint() { - return new ESLint({ - cwd: "/", - overrideConfig: eslintPluginTsv.configs.recommended, - overrideConfigFile: true, - }); -} From 9f5ad9b5b106124520cec7d1ff8c1b29d041b210 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Fri, 20 Dec 2024 02:56:12 +0000 Subject: [PATCH 048/106] Exclude interface from coverage --- eng/tools/eslint-plugin-tsv/vitest.config.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 eng/tools/eslint-plugin-tsv/vitest.config.ts diff --git a/eng/tools/eslint-plugin-tsv/vitest.config.ts b/eng/tools/eslint-plugin-tsv/vitest.config.ts new file mode 100644 index 000000000000..21187f0fc527 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + coverage: { + include: ["src"], + exclude: ["src/named-eslint.ts"], + }, + }, +}); From fac51d74ffd5ecb96dc5f1242909b0731f1772ac Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Fri, 20 Dec 2024 03:02:01 +0000 Subject: [PATCH 049/106] Exclude interfaces from coverage --- eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts | 2 +- .../eslint-plugin-tsv/src/{ => interfaces}/named-eslint.ts | 0 eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts | 2 +- eng/tools/eslint-plugin-tsv/vitest.config.ts | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename eng/tools/eslint-plugin-tsv/src/{ => interfaces}/named-eslint.ts (100%) diff --git a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts index bdac409b2574..f620058f6048 100644 --- a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts +++ b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts @@ -1,5 +1,5 @@ import parser from "yaml-eslint-parser"; -import { NamedESLint } from "./named-eslint.js"; +import { NamedESLint } from "./interfaces/named-eslint.js"; import kebabCaseOrg from "./rules/kebab-case-org.js"; const plugin: NamedESLint.Plugin = { diff --git a/eng/tools/eslint-plugin-tsv/src/named-eslint.ts b/eng/tools/eslint-plugin-tsv/src/interfaces/named-eslint.ts similarity index 100% rename from eng/tools/eslint-plugin-tsv/src/named-eslint.ts rename to eng/tools/eslint-plugin-tsv/src/interfaces/named-eslint.ts diff --git a/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts b/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts index e5165dfc6b85..4b6e6468e0f1 100644 --- a/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts +++ b/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts @@ -1,5 +1,5 @@ import path from "path"; -import { NamedRule } from "../named-eslint.js"; +import { NamedRule } from "../interfaces/named-eslint.js"; export const rule: NamedRule.RuleModule = { name: "kebab-case-org", diff --git a/eng/tools/eslint-plugin-tsv/vitest.config.ts b/eng/tools/eslint-plugin-tsv/vitest.config.ts index 21187f0fc527..785acc9b7335 100644 --- a/eng/tools/eslint-plugin-tsv/vitest.config.ts +++ b/eng/tools/eslint-plugin-tsv/vitest.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ test: { coverage: { include: ["src"], - exclude: ["src/named-eslint.ts"], + exclude: ["src/interfaces"], }, }, }); From 313f9269a3df6f14a6252e3cba1df7fa4ea6e8d9 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Fri, 20 Dec 2024 03:05:16 +0000 Subject: [PATCH 050/106] Add Management e2e test --- eng/tools/eslint-plugin-tsv/test/e2e.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/eng/tools/eslint-plugin-tsv/test/e2e.test.ts b/eng/tools/eslint-plugin-tsv/test/e2e.test.ts index 33bd6daf518b..9d143eae745e 100644 --- a/eng/tools/eslint-plugin-tsv/test/e2e.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/e2e.test.ts @@ -49,4 +49,14 @@ describe("lint-files", () => { expect(results[0].filePath).toBe(filePath); expect(results[0].messages).toHaveLength(0); }); + + it("contosowidgetmanager/Contso.Management", async () => { + const eslint = createESLint(); + const filePath = join(specsFolder, "contosowidgetmanager/Contoso.Management/tspconfig.yaml"); + const results = await eslint.lintFiles(filePath); + + expect(results).toHaveLength(1); + expect(results[0].filePath).toBe(filePath); + expect(results[0].messages).toHaveLength(0); + }); }); From 5cb8558c2251b45cfedda95f9ee348a6566346d3 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Fri, 20 Dec 2024 03:18:13 +0000 Subject: [PATCH 051/106] improve error validation --- eng/tools/eslint-plugin-tsv/test/e2e.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/eng/tools/eslint-plugin-tsv/test/e2e.test.ts b/eng/tools/eslint-plugin-tsv/test/e2e.test.ts index 9d143eae745e..5d28184c575a 100644 --- a/eng/tools/eslint-plugin-tsv/test/e2e.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/e2e.test.ts @@ -21,6 +21,7 @@ describe("lint-text", () => { expect(results).toHaveLength(1); expect(results[0].filePath).toBe(filePath); expect(results[0].messages[0].ruleId).toBe("tsv/kebab-case-org"); + expect(results[0].messages[0].messageId).toBe("kebab"); }); it("Not-Kebab-Case-Disabled/Not.KebabCase", async () => { From 29c9f1fc71f0289743c9dfa36838e1722c13fb7f Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Fri, 20 Dec 2024 03:18:25 +0000 Subject: [PATCH 052/106] Handle invalid path errors --- .../src/rules/kebab-case-org.ts | 28 ++++++++++++------- .../test/rules/kebab-case-org.test.ts | 9 ++++-- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts b/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts index 4b6e6468e0f1..ca1dc5df2397 100644 --- a/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts +++ b/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts @@ -11,24 +11,32 @@ export const rule: NamedRule.RuleModule = { }, schema: [], messages: { + invalid: "Path does not match format '.*/specification/{orgName}/': ''{{filename}}'", kebab: "Organization name (first path segment after 'specification') does not use kebab-case: '{{orgName}}'", }, }, create(context) { - const filename = path.resolve(context.filename as string); + return { + Program(node) { + const filename = path.resolve(context.filename as string); + const pathSegments = filename.split(path.sep); + const specificationIndex = pathSegments.indexOf("specification"); + const pathValid = specificationIndex >= 0 && specificationIndex < pathSegments.length - 1; - const pathSegments = filename.split(path.sep); + if (!pathValid) { + context.report({ + node, + messageId: "invalid", + data: { filename: filename }, + }); + return; + } - // TODO: Handle errors - // - No "specification" segment - // - No segemnt after "specification" - const orgName = pathSegments[pathSegments.indexOf("specification") + 1]; - const kebabCaseRegex = /^[a-z0-9]+(-[a-z0-9]+)*$/; - const orgNameKebabCase = orgName.match(kebabCaseRegex); + const orgName = pathSegments[specificationIndex + 1]; + const kebabCaseRegex = /^[a-z0-9]+(-[a-z0-9]+)*$/; + const orgNameKebabCase = orgName.match(kebabCaseRegex); - return { - Program(node) { if (!orgNameKebabCase) { context.report({ node, diff --git a/eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts b/eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts index dc9162122aea..b7a0092f3282 100644 --- a/eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts @@ -23,9 +23,12 @@ test("RuleTester", () => { { code: "", filename: "/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml", - errors: [ - "Organization name (first path segment after 'specification') does not use kebab-case: 'Not-Kebab-Case'", - ], + errors: [{ messageId: "kebab" }], + }, + { + code: "", + filename: "tspconfig.yaml", + errors: [{ messageId: "invalid" }], }, ], }); From 81f2cc8e555136bf3756f23766101586c816c402 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Fri, 20 Dec 2024 07:38:08 +0000 Subject: [PATCH 053/106] Remove e2e scripts --- eng/tools/eslint-plugin-tsv/package.json | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index 946631b2b9d5..31d6aa9830e8 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -13,22 +13,17 @@ "@types/node": "^18.19.31", "@vitest/coverage-v8": "^2.0.4", "eslint": "^9.17.0", - "memfs":"^4.15.0", + "memfs": "^4.15.0", "rimraf": "^5.0.10", "typescript": "~5.6.2", "vitest": "^2.0.4" }, "scripts": { "build": "tsc --build", + "cbt": "npm run clean && npm run build && npm run test:ci", "clean": "rimraf ./dist ./temp", "test": "vitest", - "test:ci": "vitest run --coverage --reporter=verbose", - "test:e2e": "npm run clean && npm run build && npm run test:e2e:all", - "test:e2e:all": "npm run test:e2e:contoso && npm run test:e2e:not-kebab-case-disabled && npm run test:e2e:not-kebab-case", - "test:e2e:contoso": "eslint --config dist/test/e2e/specification/eslint.config.js test/e2e/specification/contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml", - "test:e2e:not-kebab-case-disabled": "eslint --config dist/test/e2e/specification/eslint.config.js test/e2e/specification/Not-Kebab-Case-Disabled/Not.KebabCase/tspconfig.yaml", - "test:e2e:not-kebab-case": "eslint --config dist/test/e2e/specification/eslint.config.js test/e2e/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml", - "test:all": "npm run clean && npm run build && npm run test:ci && npm run test:e2e:all" + "test:ci": "vitest run --coverage --reporter=verbose" }, "engines": { "node": ">= 18.0.0" From c7493c154ecd5a3868682a79d94e13719882c8da Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Sat, 21 Dec 2024 18:45:05 +0000 Subject: [PATCH 054/106] Remove problematic vitest config --- eng/tools/eslint-plugin-tsv/vitest.config.ts | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 eng/tools/eslint-plugin-tsv/vitest.config.ts diff --git a/eng/tools/eslint-plugin-tsv/vitest.config.ts b/eng/tools/eslint-plugin-tsv/vitest.config.ts deleted file mode 100644 index 785acc9b7335..000000000000 --- a/eng/tools/eslint-plugin-tsv/vitest.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - coverage: { - include: ["src"], - exclude: ["src/interfaces"], - }, - }, -}); From adbc241d21b48dba1c1ddf18a14ea3ab8a32f87a Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 24 Dec 2024 13:23:28 -0800 Subject: [PATCH 055/106] Revert "Remove problematic vitest config" This reverts commit c7493c154ecd5a3868682a79d94e13719882c8da. --- eng/tools/eslint-plugin-tsv/vitest.config.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 eng/tools/eslint-plugin-tsv/vitest.config.ts diff --git a/eng/tools/eslint-plugin-tsv/vitest.config.ts b/eng/tools/eslint-plugin-tsv/vitest.config.ts new file mode 100644 index 000000000000..785acc9b7335 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + coverage: { + include: ["src"], + exclude: ["src/interfaces"], + }, + }, +}); From 61fe81b8e4ce99eefa31e8bfd0ba91a24b2571a1 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 24 Dec 2024 13:39:20 -0800 Subject: [PATCH 056/106] Exclude vitest config from compilation - Prevents confusion caused if vite finds multiple config files (JS and TS) --- eng/tools/eslint-plugin-tsv/tsconfig.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/tsconfig.json b/eng/tools/eslint-plugin-tsv/tsconfig.json index ec6d6640928a..e97151ec5285 100644 --- a/eng/tools/eslint-plugin-tsv/tsconfig.json +++ b/eng/tools/eslint-plugin-tsv/tsconfig.json @@ -1,6 +1,11 @@ { "extends": "../tsconfig.json", "compilerOptions": { - "outDir": "./dist", - } + "outDir": "./dist" + }, + "exclude": [ + "node_modules", + "dist", + "vitest.config.ts" + ] } From 28a6aff2d3e231eaf05701396d74dc410d80a128 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Mon, 6 Jan 2025 22:30:22 +0000 Subject: [PATCH 057/106] [tsconfig.json] Replace excludes with includes --- eng/tools/eslint-plugin-tsv/tsconfig.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/tsconfig.json b/eng/tools/eslint-plugin-tsv/tsconfig.json index e97151ec5285..c16578a92bf1 100644 --- a/eng/tools/eslint-plugin-tsv/tsconfig.json +++ b/eng/tools/eslint-plugin-tsv/tsconfig.json @@ -3,9 +3,8 @@ "compilerOptions": { "outDir": "./dist" }, - "exclude": [ - "node_modules", - "dist", - "vitest.config.ts" + "include": [ + "src/**/*.ts", + "test/**/*.ts" ] } From 733db68762ce4908d723bc538e6afa0602eeb6f8 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 7 Jan 2025 03:34:45 +0000 Subject: [PATCH 058/106] Add comment --- eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts b/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts index ca1dc5df2397..fcc81c116138 100644 --- a/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts +++ b/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts @@ -1,6 +1,9 @@ import path from "path"; import { NamedRule } from "../interfaces/named-eslint.js"; +// Valid: /specification/kebab-case/Kebab.Case/tspconfig.yaml +// Invalid: /specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml + export const rule: NamedRule.RuleModule = { name: "kebab-case-org", meta: { From 3b7580edb7e0c3736eb0f6a872f7312def836dd0 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 7 Jan 2025 03:45:10 +0000 Subject: [PATCH 059/106] Add stub for new rule emit-autorest --- .../src/eslint-plugin-tsv.ts | 3 ++ .../src/rules/emit-autorest.ts | 47 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 eng/tools/eslint-plugin-tsv/src/rules/emit-autorest.ts diff --git a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts index f620058f6048..3e1867522c3f 100644 --- a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts +++ b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts @@ -1,5 +1,6 @@ import parser from "yaml-eslint-parser"; import { NamedESLint } from "./interfaces/named-eslint.js"; +import emitAutorest from "./rules/emit-autorest.js"; import kebabCaseOrg from "./rules/kebab-case-org.js"; const plugin: NamedESLint.Plugin = { @@ -7,6 +8,7 @@ const plugin: NamedESLint.Plugin = { name: "tsv", rules: { [kebabCaseOrg.name]: kebabCaseOrg, + [emitAutorest.name]: emitAutorest, }, }; @@ -17,6 +19,7 @@ plugin.configs.recommended = { files: ["*.yaml", "**/*.yaml"], rules: { [`${plugin.name}/${kebabCaseOrg.name}`]: "error", + [`${plugin.name}/${emitAutorest.name}`]: "error", }, languageOptions: { parser: parser, diff --git a/eng/tools/eslint-plugin-tsv/src/rules/emit-autorest.ts b/eng/tools/eslint-plugin-tsv/src/rules/emit-autorest.ts new file mode 100644 index 000000000000..216f3337bc90 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/rules/emit-autorest.ts @@ -0,0 +1,47 @@ +import { NamedRule } from "../interfaces/named-eslint.js"; + +export const rule: NamedRule.RuleModule = { + name: "emit-autorest", + meta: { + type: "problem", + docs: { + description: + "Requires emitter 'typespec-autorest' to be enabled by default, and requires emitted autorest to match content in repo", + }, + schema: [], + messages: { + disabled: "Path does not match format '.*/specification/{orgName}/': ''{{filename}}'", + autorestDiff: "Emitted autorest does not match content in repo", + }, + }, + create(context) { + return { + Program(node) { + // const filename = path.resolve(context.filename as string); + // const pathSegments = filename.split(path.sep); + // const specificationIndex = pathSegments.indexOf("specification"); + // const pathValid = specificationIndex >= 0 && specificationIndex < pathSegments.length - 1; + // if (!pathValid) { + // context.report({ + // node, + // messageId: "invalid", + // data: { filename: filename }, + // }); + // return; + // } + // const orgName = pathSegments[specificationIndex + 1]; + // const kebabCaseRegex = /^[a-z0-9]+(-[a-z0-9]+)*$/; + // const orgNameKebabCase = orgName.match(kebabCaseRegex); + // if (!orgNameKebabCase) { + // context.report({ + // node, + // messageId: "kebab", + // data: { orgName: orgName }, + // }); + // } + }, + }; + }, +}; + +export default rule; From f5d864fb86f854770e46a8e04fa0a06dab70b129 Mon Sep 17 00:00:00 2001 From: Mike Harder Date: Tue, 7 Jan 2025 22:09:38 +0000 Subject: [PATCH 060/106] Validate tspconfig.yaml, ensure default emitter --- eng/tools/eslint-plugin-tsv/package.json | 1 + .../src/config/config-schema.ts | 117 ++++++++++++++++++ .../eslint-plugin-tsv/src/config/types.ts | 112 +++++++++++++++++ .../src/rules/emit-autorest.ts | 62 ++++++---- eng/tools/eslint-plugin-tsv/src/yaml/types.ts | 24 ++++ eng/tools/eslint-plugin-tsv/test/e2e.test.ts | 9 +- .../test/rules/emit-autorest.test.ts | 32 +++++ 7 files changed, 329 insertions(+), 28 deletions(-) create mode 100644 eng/tools/eslint-plugin-tsv/src/config/config-schema.ts create mode 100644 eng/tools/eslint-plugin-tsv/src/config/types.ts create mode 100644 eng/tools/eslint-plugin-tsv/src/yaml/types.ts create mode 100644 eng/tools/eslint-plugin-tsv/test/rules/emit-autorest.test.ts diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index 31d6aa9830e8..b124f469f81f 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -4,6 +4,7 @@ "type": "module", "main": "src/index.js", "dependencies": { + "ajv": "^8.17.1", "yaml-eslint-parser": "^1.2.3" }, "peerDependencies": { diff --git a/eng/tools/eslint-plugin-tsv/src/config/config-schema.ts b/eng/tools/eslint-plugin-tsv/src/config/config-schema.ts new file mode 100644 index 000000000000..e92b473ebf20 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/config/config-schema.ts @@ -0,0 +1,117 @@ +// Copied from https://github.com/microsoft/typespec/blob/main/packages/compiler/src/config/config-schema.ts + +import type { JSONSchemaType } from "ajv"; +import { EmitterOptions, TypeSpecRawConfig } from "./types.js"; + +export const emitterOptionsSchema: JSONSchemaType = { + type: "object", + additionalProperties: true, + required: [], + properties: { + "emitter-output-dir": { type: "string", nullable: true } as any, + }, +}; + +export const TypeSpecConfigJsonSchema: JSONSchemaType = { + type: "object", + additionalProperties: false, + properties: { + extends: { + type: "string", + nullable: true, + }, + "environment-variables": { + type: "object", + nullable: true, + required: [], + additionalProperties: { + type: "object", + properties: { + default: { type: "string" }, + }, + required: ["default"], + }, + }, + parameters: { + type: "object", + nullable: true, + required: [], + additionalProperties: { + type: "object", + properties: { + default: { type: "string" }, + }, + required: ["default"], + }, + }, + + "output-dir": { + type: "string", + nullable: true, + }, + "warn-as-error": { + type: "boolean", + nullable: true, + }, + trace: { + oneOf: [ + { type: "string" }, + { + type: "array", + items: { type: "string" }, + }, + ], + } as any, // Issue with AJV optional property typing https://github.com/ajv-validator/ajv/issues/1664 + imports: { + type: "array", + nullable: true, + items: { type: "string" }, + }, + emit: { + type: "array", + nullable: true, + items: { type: "string" }, + }, + options: { + type: "object", + nullable: true, + required: [], + additionalProperties: emitterOptionsSchema, + }, + emitters: { + type: "object", + nullable: true, + deprecated: true, + required: [], + additionalProperties: { + oneOf: [{ type: "boolean" }, emitterOptionsSchema], + }, + }, + + linter: { + type: "object", + nullable: true, + required: [], + additionalProperties: false, + properties: { + extends: { + type: "array", + nullable: true, + items: { type: "string" }, + }, + enable: { + type: "object", + required: [], + nullable: true, + additionalProperties: { type: "boolean" }, + }, + disable: { + type: "object", + required: [], + nullable: true, + additionalProperties: { type: "string" }, + }, + }, + } as any, // ajv type system doesn't like the string templates + }, +}; diff --git a/eng/tools/eslint-plugin-tsv/src/config/types.ts b/eng/tools/eslint-plugin-tsv/src/config/types.ts new file mode 100644 index 000000000000..575e3b6df553 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/config/types.ts @@ -0,0 +1,112 @@ +// Copied from https://github.com/microsoft/typespec/blob/main/packages/compiler/src/config/types.ts + +import type { Diagnostic, RuleRef } from "@typespec/compiler"; +import type { YamlScript } from "../yaml/types.js"; + +/** + * Represent the normalized user configuration. + */ +export interface TypeSpecConfig { + /** + * Project root. + */ + projectRoot: string; + + /** Yaml file used in this configuration. */ + file?: YamlScript; + + /** + * Path to the config file used to create this configuration. + */ + filename?: string; + + /** + * Diagnostics reported while loading the configuration + */ + diagnostics: Diagnostic[]; + + /** + * Path to another TypeSpec config to extend. + */ + extends?: string; + + /** + * Environment variables configuration + */ + environmentVariables?: Record; + + /** + * Parameters that can be used + */ + parameters?: Record; + + /** + * Treat warning as error. + */ + warnAsError?: boolean; + + /** + * Output directory + */ + outputDir: string; + + /** + * Trace options. + */ + trace?: string[]; + + /** + * Additional imports. + */ + imports?: string[]; + + /** + * Name of emitters or path to emitters that should be used. + */ + emit?: string[]; + + /** + * Name of emitters or path to emitters that should be used. + */ + options?: Record; + + linter?: LinterConfig; +} + +/** + * Represent the configuration that can be provided in a config file. + */ +export interface TypeSpecRawConfig { + extends?: string; + "environment-variables"?: Record; + parameters?: Record; + + "warn-as-error"?: boolean; + "output-dir"?: string; + trace?: string | string[]; + imports?: string[]; + + emit?: string[]; + options?: Record; + emitters?: Record; + + linter?: LinterConfig; +} + +export interface ConfigEnvironmentVariable { + default: string; +} + +export interface ConfigParameter { + default: string; +} + +export type EmitterOptions = Record & { + "emitter-output-dir"?: string; +}; + +export interface LinterConfig { + extends?: RuleRef[]; + enable?: Record; + disable?: Record; +} diff --git a/eng/tools/eslint-plugin-tsv/src/rules/emit-autorest.ts b/eng/tools/eslint-plugin-tsv/src/rules/emit-autorest.ts index 216f3337bc90..b64953d1ccbf 100644 --- a/eng/tools/eslint-plugin-tsv/src/rules/emit-autorest.ts +++ b/eng/tools/eslint-plugin-tsv/src/rules/emit-autorest.ts @@ -1,3 +1,8 @@ +import { Ajv } from "ajv"; +import { Rule } from "eslint"; +import { AST, getStaticYAMLValue } from "yaml-eslint-parser"; +import { TypeSpecConfigJsonSchema } from "../config/config-schema.js"; +import { TypeSpecConfig } from "../config/types.js"; import { NamedRule } from "../interfaces/named-eslint.js"; export const rule: NamedRule.RuleModule = { @@ -10,35 +15,42 @@ export const rule: NamedRule.RuleModule = { }, schema: [], messages: { - disabled: "Path does not match format '.*/specification/{orgName}/': ''{{filename}}'", - autorestDiff: "Emitted autorest does not match content in repo", + invalid: "tspconfig.yaml is invalid per the schema: {{errors}}", + missing: + 'tspconfig.yaml must include the following emitter by default:\n\nemit:\n - "@azure-tools/typespec-autorest"', + // disabled: "Path does not match format '.*/specification/{orgName}/': ''{{filename}}'", + // autorestDiff: "Emitted autorest does not match content in repo", }, }, create(context) { return { - Program(node) { - // const filename = path.resolve(context.filename as string); - // const pathSegments = filename.split(path.sep); - // const specificationIndex = pathSegments.indexOf("specification"); - // const pathValid = specificationIndex >= 0 && specificationIndex < pathSegments.length - 1; - // if (!pathValid) { - // context.report({ - // node, - // messageId: "invalid", - // data: { filename: filename }, - // }); - // return; - // } - // const orgName = pathSegments[specificationIndex + 1]; - // const kebabCaseRegex = /^[a-z0-9]+(-[a-z0-9]+)*$/; - // const orgNameKebabCase = orgName.match(kebabCaseRegex); - // if (!orgNameKebabCase) { - // context.report({ - // node, - // messageId: "kebab", - // data: { orgName: orgName }, - // }); - // } + YAMLDocument(node: Rule.Node) { + const yamlDocument = node as unknown as AST.YAMLDocument; + + // If config yaml is empty, use empty object instead of "null" + const config = getStaticYAMLValue(yamlDocument) || {}; + + const ajv = new Ajv(); + const valid = ajv.validate(TypeSpecConfigJsonSchema, config); + + if (!valid) { + context.report({ + node, + messageId: "invalid", + data: { errors: ajv.errorsText(ajv.errors) }, + }); + return; + } + + const typedConfig = config as unknown as TypeSpecConfig; + if (!typedConfig.emit?.includes("@azure-tools/typespec-autorest")) { + // TODO: Move error message to "emit:" node + context.report({ + node, + messageId: "missing", + }); + return; + } }, }; }, diff --git a/eng/tools/eslint-plugin-tsv/src/yaml/types.ts b/eng/tools/eslint-plugin-tsv/src/yaml/types.ts new file mode 100644 index 000000000000..2c73c7c0da1e --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/yaml/types.ts @@ -0,0 +1,24 @@ +// Copied from https://github.com/microsoft/typespec/blob/main/packages/compiler/src/yaml/types.ts + +import { SourceFile } from "@typespec/compiler"; +import { Document } from "yaml"; + +export interface YamlScript { + readonly kind: "yaml-script"; + readonly file: SourceFile; + /** Value of the yaml script. */ + readonly value: unknown; + + /** @internal yaml library document. We do not expose this as the "yaml" library is not part of the contract. */ + readonly doc: Document.Parsed; +} + +/** + * Represent the location of a value in a yaml script. + */ +export interface YamlPathTarget { + kind: "path-target"; + script: YamlScript; + path: string[]; +} +export type YamlDiagnosticTargetType = "value" | "key"; diff --git a/eng/tools/eslint-plugin-tsv/test/e2e.test.ts b/eng/tools/eslint-plugin-tsv/test/e2e.test.ts index 5d28184c575a..7c8ed1376584 100644 --- a/eng/tools/eslint-plugin-tsv/test/e2e.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/e2e.test.ts @@ -28,9 +28,12 @@ describe("lint-text", () => { const filePath = "/specification/Not-Kebab-Case-Disabled/Not.KebabCase/tspconfig.yaml"; const eslint = createESLint(); - const results = await eslint.lintText("# eslint-disable tsv/kebab-case-org", { - filePath: filePath, - }); + const results = await eslint.lintText( + "# eslint-disable tsv/kebab-case-org, tsv/emit-autorest\n", + { + filePath: filePath, + }, + ); expect(results).toHaveLength(1); expect(results[0].filePath).toBe(filePath); diff --git a/eng/tools/eslint-plugin-tsv/test/rules/emit-autorest.test.ts b/eng/tools/eslint-plugin-tsv/test/rules/emit-autorest.test.ts new file mode 100644 index 000000000000..7f8938378634 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/test/rules/emit-autorest.test.ts @@ -0,0 +1,32 @@ +import { Rule, RuleTester } from "eslint"; +import { test } from "vitest"; +import parser from "yaml-eslint-parser"; + +import emitAutorest from "../../src/rules/emit-autorest.js"; + +test("RuleTester", () => { + const ruleTester = new RuleTester({ + languageOptions: { + parser: parser, + }, + }); + + ruleTester.run(emitAutorest.name, emitAutorest as Rule.RuleModule, { + valid: [ + { + code: 'emit:\n - "@azure-tools/typespec-autorest"', + }, + ], + invalid: [ + { + code: "", + errors: [{ messageId: "missing" }], + }, + { + code: "emit:\n - foo", + errors: [{ messageId: "missing" }], + }, + { code: "not: valid", errors: [{ messageId: "invalid" }] }, + ], + }); +}); From 6b5ebdbbd551b68cce47f0ac5c5b9b57efcbbf84 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Tue, 21 Jan 2025 09:25:19 +0800 Subject: [PATCH 061/106] add first test --- .../src/interfaces/rule-interfaces.ts | 19 +++++ ...-ts-mgmt-modular-generate-metadata-true.ts | 39 +++++++++ .../eslint-plugin-tsv/src/utils/constants.ts | 12 +++ eng/tools/eslint-plugin-tsv/src/utils/rule.ts | 32 +++++++ .../src/utils/tspconfig-validation-base.ts | 32 +++++++ .../tspconfig-options-validation.test.ts | 83 +++++++++++++++++++ 6 files changed, 217 insertions(+) create mode 100644 eng/tools/eslint-plugin-tsv/src/interfaces/rule-interfaces.ts create mode 100644 eng/tools/eslint-plugin-tsv/src/rules/tspconfig-ts-mgmt-modular-generate-metadata-true.ts create mode 100644 eng/tools/eslint-plugin-tsv/src/utils/constants.ts create mode 100644 eng/tools/eslint-plugin-tsv/src/utils/rule.ts create mode 100644 eng/tools/eslint-plugin-tsv/src/utils/tspconfig-validation-base.ts create mode 100644 eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts diff --git a/eng/tools/eslint-plugin-tsv/src/interfaces/rule-interfaces.ts b/eng/tools/eslint-plugin-tsv/src/interfaces/rule-interfaces.ts new file mode 100644 index 000000000000..c32c7cec72ba --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/interfaces/rule-interfaces.ts @@ -0,0 +1,19 @@ +import { Rule } from "eslint"; +import { TypeSpecConfig } from "../config/types.js"; + +export interface RuleDocuments { + description: string; + error: string; + action: string; + example: string; +} + +export interface RuleInfo { + name: string; + docs: RuleDocuments; + functions: { + messages: () => { [messageId: string]: string } | undefined; + condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => boolean; + validation: (tspconfig: TypeSpecConfig, context: Rule.RuleContext, node: Rule.Node) => void; + }; +} diff --git a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-ts-mgmt-modular-generate-metadata-true.ts b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-ts-mgmt-modular-generate-metadata-true.ts new file mode 100644 index 000000000000..ca03579a8e56 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-ts-mgmt-modular-generate-metadata-true.ts @@ -0,0 +1,39 @@ +import { RuleDocuments, RuleInfo } from "../interfaces/rule-interfaces.js"; +import { defaultMessageId, emitters } from "../utils/constants.js"; +import { isManagementForTsEmitter, makeRuleMessages } from "../utils/rule.js"; +import { createRule } from "../utils/tspconfig-validation-base.js"; + +const docs: RuleDocuments = { + description: + "Validate whether 'generateMetadata' is set to true in tspconfig.yaml when generating modular clients", + error: "'generateMetadata' is NOT set to true in tspconfig.yaml when generating modular clients", + action: `Set 'options.${emitters.ts}.generateMetadata' to true in tspconfig.yaml when generating modular clients`, + example: `... +options: + "@azure-tools/typespec-ts": + generateMetadata: true # <--- + generateSample: true + generateTest: true + experimentalExtensibleEnums: true + enableOperationGroup: true + hierarchyClient: false + package-dir: "pkg" + packageDetails: + name: "@azure/pkg" + flavor: azure +`, +}; +const ruleInfo: RuleInfo = { + name: "tspconfig-ts-mgmt-modular-generate-metadata-true", + docs, + functions: { + messages: () => makeRuleMessages(defaultMessageId, docs), + condition: (tspconfig, context) => isManagementForTsEmitter(tspconfig, context), + validation: (tspconfig, context, node) => { + const generateMetadata = tspconfig.options?.[emitters.ts].generateMetadata; + if (generateMetadata !== true) context.report({ node, messageId: defaultMessageId }); + }, + }, +}; + +export default createRule(ruleInfo); diff --git a/eng/tools/eslint-plugin-tsv/src/utils/constants.ts b/eng/tools/eslint-plugin-tsv/src/utils/constants.ts new file mode 100644 index 000000000000..2ca356ba4f48 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/utils/constants.ts @@ -0,0 +1,12 @@ +export const emitters = { + ts: "@azure-tools/typespec-ts", + java: "@azure-tools/typespec-java", + csharp: "@azure-tools/typespec-csharp", + python: "@azure-tools/typespec-python", + go: "@azure-tools/typespec-go", + autorest: "@azure-tools/typespec-autorest", +}; + +export const defaultMessageId = "problem"; + +export const defaultRuleType = "problem"; diff --git a/eng/tools/eslint-plugin-tsv/src/utils/rule.ts b/eng/tools/eslint-plugin-tsv/src/utils/rule.ts new file mode 100644 index 000000000000..d2768dd96b28 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/utils/rule.ts @@ -0,0 +1,32 @@ +import { Rule } from "eslint"; +import { TypeSpecConfig } from "../config/types.js"; +import { RuleDocuments } from "../interfaces/rule-interfaces.js"; +import { emitters } from "./constants.js"; + +export function makeRuleMessages(messageId: string, docs: RuleDocuments) { + return { + [messageId]: `${docs.error}.\n${docs.action}.\n${docs.example}`, + }; +} + +export function isManagementForTsEmitter(tspconfig: TypeSpecConfig, context: Rule.RuleContext) { + const flavor = tspconfig.options?.[emitters.ts]?.flavor as string; + const isModularLibrary = tspconfig.options?.[emitters.ts]?.isModularLibrary as + | boolean + | undefined; + const filename = context.filename; + return flavor === "azure" && filename.includes(".Management") && isModularLibrary == undefined; +} + +export function generateEmitterOptions( + emitter: string, + ...pairs: { key: string; value: string | boolean | {} }[] +) { + let content = `options: + "${emitter}":`; + for (const pair of pairs) { + content += ` + ${pair.key}: ${pair.value}`; + } + return content; +} diff --git a/eng/tools/eslint-plugin-tsv/src/utils/tspconfig-validation-base.ts b/eng/tools/eslint-plugin-tsv/src/utils/tspconfig-validation-base.ts new file mode 100644 index 000000000000..a36503084c85 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/utils/tspconfig-validation-base.ts @@ -0,0 +1,32 @@ +import { Rule } from "eslint"; +import { AST, getStaticYAMLValue } from "yaml-eslint-parser"; +import { TypeSpecConfig } from "../config/types.js"; +import { NamedRule } from "../interfaces/named-eslint.js"; +import { RuleInfo } from "../interfaces/rule-interfaces.js"; +import { defaultRuleType } from "./constants.js"; + +export function createRule(ruleContext: RuleInfo) { + const rule: NamedRule.RuleModule = { + name: ruleContext.name, + meta: { + type: defaultRuleType, + docs: { + description: ruleContext.docs.description, + }, + schema: [], + messages: ruleContext.functions.messages(), + }, + create(context) { + return { + YAMLDocument(node: Rule.Node) { + const yamlDocument = node as unknown as AST.YAMLDocument; + const config = getStaticYAMLValue(yamlDocument) || {}; + const typedConfig = config as unknown as TypeSpecConfig; + if (!ruleContext.functions.condition(typedConfig, context)) return; + ruleContext.functions.validation(typedConfig, context, node); + }, + }; + }, + }; + return rule; +} diff --git a/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts b/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts new file mode 100644 index 000000000000..e1f6adbdc79d --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts @@ -0,0 +1,83 @@ +import { Rule, RuleTester } from "eslint"; +import { describe, it } from "vitest"; +import parser from "yaml-eslint-parser"; +import { defaultMessageId, emitters } from "../../src/utils/constants.js"; +import { generateEmitterOptions } from "../../src/utils/rule.js"; + +interface Case { + description: string; + rulePath: string; + fileName?: string; + yamlContent: string; + shouldReportError: boolean; +} + +const managementTspconfigPath = "contosowidgetmanager/Contoso.Management/tspconfig.yaml"; +const tspconfigTsMgmtModularGenerateMetadataTrueRule = + "../../src/rules/tspconfig-ts-mgmt-modular-generate-metadata-true.js"; + +const managementGenerateMetadataTestCases: Case[] = [ + { + description: "valid: generateMetadata is true", + rulePath: tspconfigTsMgmtModularGenerateMetadataTrueRule, + fileName: managementTspconfigPath, + yamlContent: generateEmitterOptions( + emitters.ts, + { key: "generateMetadata", value: true }, + { key: "flavor", value: "azure" }, + ), + shouldReportError: false, + }, + { + description: "invalid: generateMetadata is false", + rulePath: tspconfigTsMgmtModularGenerateMetadataTrueRule, + fileName: managementTspconfigPath, + yamlContent: generateEmitterOptions( + emitters.ts, + { key: "generateMetadata", value: false }, + { key: "flavor", value: "azure" }, + ), + shouldReportError: true, + }, + { + description: "invalid: generateMetadata is undefined", + rulePath: tspconfigTsMgmtModularGenerateMetadataTrueRule, + fileName: managementTspconfigPath, + yamlContent: generateEmitterOptions(emitters.ts, { key: "flavor", value: "azure" }), + shouldReportError: true, + }, +]; + +describe("Tspconfig emitter options validation", () => { + it.each([...managementGenerateMetadataTestCases])("$description", async (c: Case) => { + const ruleTester = new RuleTester({ + languageOptions: { + parser: parser, + }, + }); + + const ruleModule = await import(c.rulePath); + const rule = ruleModule.default; + const tests = c.shouldReportError + ? { + valid: [], + invalid: [ + { + filename: c.fileName, + code: c.yamlContent, + errors: [{ messageId: defaultMessageId }], + }, + ], + } + : { + valid: [ + { + filename: c.fileName, + code: c.yamlContent, + }, + ], + invalid: [], + }; + ruleTester.run(rule.name, rule as Rule.RuleModule, tests); + }); +}); From 4c419809bf65d84a07b9f06b021da747a6bb2490 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Tue, 21 Jan 2025 13:58:36 +0800 Subject: [PATCH 062/106] simplify for adding new rules --- .../src/interfaces/rule-interfaces.ts | 4 +- ...-ts-mgmt-modular-generate-metadata-true.ts | 39 ------ .../src/rules/tspconfig-validation-rules.ts | 21 +++ eng/tools/eslint-plugin-tsv/src/utils/rule.ts | 77 ++++++++++- .../src/utils/tspconfig-validation-base.ts | 32 ----- .../tspconfig-options-validation.test.ts | 125 +++++++++++++----- 6 files changed, 187 insertions(+), 111 deletions(-) delete mode 100644 eng/tools/eslint-plugin-tsv/src/rules/tspconfig-ts-mgmt-modular-generate-metadata-true.ts create mode 100644 eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts delete mode 100644 eng/tools/eslint-plugin-tsv/src/utils/tspconfig-validation-base.ts diff --git a/eng/tools/eslint-plugin-tsv/src/interfaces/rule-interfaces.ts b/eng/tools/eslint-plugin-tsv/src/interfaces/rule-interfaces.ts index c32c7cec72ba..4ea789d7ba86 100644 --- a/eng/tools/eslint-plugin-tsv/src/interfaces/rule-interfaces.ts +++ b/eng/tools/eslint-plugin-tsv/src/interfaces/rule-interfaces.ts @@ -1,7 +1,7 @@ import { Rule } from "eslint"; import { TypeSpecConfig } from "../config/types.js"; -export interface RuleDocuments { +export interface RuleDocument { description: string; error: string; action: string; @@ -10,7 +10,7 @@ export interface RuleDocuments { export interface RuleInfo { name: string; - docs: RuleDocuments; + documentation: RuleDocument; functions: { messages: () => { [messageId: string]: string } | undefined; condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => boolean; diff --git a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-ts-mgmt-modular-generate-metadata-true.ts b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-ts-mgmt-modular-generate-metadata-true.ts deleted file mode 100644 index ca03579a8e56..000000000000 --- a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-ts-mgmt-modular-generate-metadata-true.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { RuleDocuments, RuleInfo } from "../interfaces/rule-interfaces.js"; -import { defaultMessageId, emitters } from "../utils/constants.js"; -import { isManagementForTsEmitter, makeRuleMessages } from "../utils/rule.js"; -import { createRule } from "../utils/tspconfig-validation-base.js"; - -const docs: RuleDocuments = { - description: - "Validate whether 'generateMetadata' is set to true in tspconfig.yaml when generating modular clients", - error: "'generateMetadata' is NOT set to true in tspconfig.yaml when generating modular clients", - action: `Set 'options.${emitters.ts}.generateMetadata' to true in tspconfig.yaml when generating modular clients`, - example: `... -options: - "@azure-tools/typespec-ts": - generateMetadata: true # <--- - generateSample: true - generateTest: true - experimentalExtensibleEnums: true - enableOperationGroup: true - hierarchyClient: false - package-dir: "pkg" - packageDetails: - name: "@azure/pkg" - flavor: azure -`, -}; -const ruleInfo: RuleInfo = { - name: "tspconfig-ts-mgmt-modular-generate-metadata-true", - docs, - functions: { - messages: () => makeRuleMessages(defaultMessageId, docs), - condition: (tspconfig, context) => isManagementForTsEmitter(tspconfig, context), - validation: (tspconfig, context, node) => { - const generateMetadata = tspconfig.options?.[emitters.ts].generateMetadata; - if (generateMetadata !== true) context.report({ node, messageId: defaultMessageId }); - }, - }, -}; - -export default createRule(ruleInfo); diff --git a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts new file mode 100644 index 000000000000..18d463d69fb4 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts @@ -0,0 +1,21 @@ +import { emitters } from "../utils/constants.js"; +import { createManagementClientRule } from "../utils/rule.js"; + +const args: [string, string, string, string | boolean][] = [ + ["tspconfig-ts-mgmt-modular-generate-metadata-true", emitters.ts, "generateMetadata", true], + ["tspconfig-ts-mgmt-modular-hierarchy-client-false", emitters.ts, "hierarchyClient", false], + [ + "tspconfig-ts-mgmt-modular-experimental-extensible-enums-true", + emitters.ts, + "experimentalExtensibleEnums", + true, + ], + [ + "tspconfig-ts-mgmt-modular-enable-operation-group-true", + emitters.ts, + "enableOperationGroup", + true, + ], +]; + +export default args.map((a) => createManagementClientRule(...a)); diff --git a/eng/tools/eslint-plugin-tsv/src/utils/rule.ts b/eng/tools/eslint-plugin-tsv/src/utils/rule.ts index d2768dd96b28..304f63fb7c21 100644 --- a/eng/tools/eslint-plugin-tsv/src/utils/rule.ts +++ b/eng/tools/eslint-plugin-tsv/src/utils/rule.ts @@ -1,9 +1,56 @@ import { Rule } from "eslint"; import { TypeSpecConfig } from "../config/types.js"; -import { RuleDocuments } from "../interfaces/rule-interfaces.js"; -import { emitters } from "./constants.js"; +import { RuleDocument, RuleInfo } from "../interfaces/rule-interfaces.js"; +import { defaultMessageId, defaultRuleType, emitters } from "./constants.js"; +import { NamedRule } from "../interfaces/named-eslint.js"; +import { AST, getStaticYAMLValue } from "yaml-eslint-parser"; -export function makeRuleMessages(messageId: string, docs: RuleDocuments) { +function createManagementRuleDocument( + emitterName: string, + optionName: string, + expectedOptionValue: string | boolean, +): RuleDocument { + const document: RuleDocument = { + description: `Validate whether '${optionName}' is set to true in tspconfig.yaml when generating modular clients`, + error: `'${optionName}' is NOT set to true in tspconfig.yaml when generating modular clients`, + action: `Set 'options.${emitters.ts}.${optionName}' to true in tspconfig.yaml when generating modular clients`, + example: `... + options: + "${emitterName}": + ${optionName}: ${expectedOptionValue} # <--- SET HERE + ... + `, + }; + return document; +} + +export function createRule(ruleContext: RuleInfo): NamedRule.RuleModule { + const rule: NamedRule.RuleModule = { + name: ruleContext.name, + meta: { + type: defaultRuleType, + docs: { + description: ruleContext.documentation.description, + }, + schema: [], + messages: ruleContext.functions.messages(), + }, + create(context) { + return { + YAMLDocument(node: Rule.Node) { + const yamlDocument = node as unknown as AST.YAMLDocument; + const config = getStaticYAMLValue(yamlDocument) || {}; + const typedConfig = config as unknown as TypeSpecConfig; + if (!ruleContext.functions.condition(typedConfig, context)) return; + ruleContext.functions.validation(typedConfig, context, node); + }, + }; + }, + }; + return rule; +} + +export function createRuleMessages(messageId: string, docs: RuleDocument) { return { [messageId]: `${docs.error}.\n${docs.action}.\n${docs.example}`, }; @@ -30,3 +77,27 @@ export function generateEmitterOptions( } return content; } + +export function createManagementClientRule( + ruleName: string, + emitterName: string, + optionName: string, + expectedOptionValue: string | boolean, +): NamedRule.RuleModule { + const documentation = createManagementRuleDocument(emitterName, optionName, expectedOptionValue); + + const ruleInfo: RuleInfo = { + name: ruleName, + documentation, + functions: { + messages: () => createRuleMessages(defaultMessageId, documentation), + condition: (tspconfig, context) => isManagementForTsEmitter(tspconfig, context), + validation: (tspconfig, context, node) => { + const option = tspconfig.options?.[emitters.ts][optionName]; + if (option !== expectedOptionValue) context.report({ node, messageId: defaultMessageId }); + }, + }, + }; + + return createRule(ruleInfo); +} diff --git a/eng/tools/eslint-plugin-tsv/src/utils/tspconfig-validation-base.ts b/eng/tools/eslint-plugin-tsv/src/utils/tspconfig-validation-base.ts deleted file mode 100644 index a36503084c85..000000000000 --- a/eng/tools/eslint-plugin-tsv/src/utils/tspconfig-validation-base.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Rule } from "eslint"; -import { AST, getStaticYAMLValue } from "yaml-eslint-parser"; -import { TypeSpecConfig } from "../config/types.js"; -import { NamedRule } from "../interfaces/named-eslint.js"; -import { RuleInfo } from "../interfaces/rule-interfaces.js"; -import { defaultRuleType } from "./constants.js"; - -export function createRule(ruleContext: RuleInfo) { - const rule: NamedRule.RuleModule = { - name: ruleContext.name, - meta: { - type: defaultRuleType, - docs: { - description: ruleContext.docs.description, - }, - schema: [], - messages: ruleContext.functions.messages(), - }, - create(context) { - return { - YAMLDocument(node: Rule.Node) { - const yamlDocument = node as unknown as AST.YAMLDocument; - const config = getStaticYAMLValue(yamlDocument) || {}; - const typedConfig = config as unknown as TypeSpecConfig; - if (!ruleContext.functions.condition(typedConfig, context)) return; - ruleContext.functions.validation(typedConfig, context, node); - }, - }; - }, - }; - return rule; -} diff --git a/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts b/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts index e1f6adbdc79d..bd283f5da2b3 100644 --- a/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts @@ -3,53 +3,108 @@ import { describe, it } from "vitest"; import parser from "yaml-eslint-parser"; import { defaultMessageId, emitters } from "../../src/utils/constants.js"; import { generateEmitterOptions } from "../../src/utils/rule.js"; +import { NamedRule } from "../../src/interfaces/named-eslint.js"; interface Case { description: string; rulePath: string; + ruleName: string; fileName?: string; yamlContent: string; shouldReportError: boolean; } const managementTspconfigPath = "contosowidgetmanager/Contoso.Management/tspconfig.yaml"; -const tspconfigTsMgmtModularGenerateMetadataTrueRule = - "../../src/rules/tspconfig-ts-mgmt-modular-generate-metadata-true.js"; +const rulePath = "../../src/rules/tspconfig-validation-rules.js"; -const managementGenerateMetadataTestCases: Case[] = [ - { - description: "valid: generateMetadata is true", - rulePath: tspconfigTsMgmtModularGenerateMetadataTrueRule, - fileName: managementTspconfigPath, - yamlContent: generateEmitterOptions( - emitters.ts, - { key: "generateMetadata", value: true }, - { key: "flavor", value: "azure" }, - ), - shouldReportError: false, - }, - { - description: "invalid: generateMetadata is false", - rulePath: tspconfigTsMgmtModularGenerateMetadataTrueRule, - fileName: managementTspconfigPath, - yamlContent: generateEmitterOptions( - emitters.ts, - { key: "generateMetadata", value: false }, - { key: "flavor", value: "azure" }, - ), - shouldReportError: true, - }, - { - description: "invalid: generateMetadata is undefined", - rulePath: tspconfigTsMgmtModularGenerateMetadataTrueRule, - fileName: managementTspconfigPath, - yamlContent: generateEmitterOptions(emitters.ts, { key: "flavor", value: "azure" }), - shouldReportError: true, - }, -]; +const managementGenerateMetadataTestCases = generateManagementClientBooleanTestCases( + emitters.ts, + rulePath, + "tspconfig-ts-mgmt-modular-generate-metadata-true", + managementTspconfigPath, + "generateMetadata", + true, +); + +const managementHierarchyClientTestCases = generateManagementClientBooleanTestCases( + emitters.ts, + rulePath, + "tspconfig-ts-mgmt-modular-hierarchy-client-false", + managementTspconfigPath, + "hierarchyClient", + false, +); + +const managementExperimentalExtensibleEnumsTestCases = generateManagementClientBooleanTestCases( + emitters.ts, + rulePath, + "tspconfig-ts-mgmt-modular-experimental-extensible-enums-true", + managementTspconfigPath, + "experimentalExtensibleEnums", + true, +); + +const managementEnableOperationGroupTestCases = generateManagementClientBooleanTestCases( + emitters.ts, + rulePath, + "tspconfig-ts-mgmt-modular-enable-operation-group-true", + managementTspconfigPath, + "enableOperationGroup", + true, +); + +function generateManagementClientBooleanTestCases( + emitterName: string, + rulePath: string, + ruleName: string, + fileName: string, + optionName: string, + expectedOptionValue: boolean, +): Case[] { + const managementGenerateMetadataTestCases: Case[] = [ + { + description: `valid: ${optionName} is ${expectedOptionValue}`, + rulePath, + ruleName, + fileName, + yamlContent: generateEmitterOptions( + emitterName, + { key: optionName, value: expectedOptionValue }, + { key: "flavor", value: "azure" }, + ), + shouldReportError: false, + }, + { + description: `invalid: ${optionName} is ${!expectedOptionValue}`, + rulePath, + ruleName, + fileName, + yamlContent: generateEmitterOptions( + emitterName, + { key: optionName, value: !expectedOptionValue }, + { key: "flavor", value: "azure" }, + ), + shouldReportError: true, + }, + { + description: `invalid: ${optionName} is undefined`, + rulePath, + ruleName, + fileName, + yamlContent: generateEmitterOptions(emitterName, { key: "flavor", value: "azure" }), + shouldReportError: true, + }, + ]; + return managementGenerateMetadataTestCases; +} describe("Tspconfig emitter options validation", () => { - it.each([...managementGenerateMetadataTestCases])("$description", async (c: Case) => { + it.each([ + ...managementGenerateMetadataTestCases, + ...managementHierarchyClientTestCases, + ...managementExperimentalExtensibleEnumsTestCases, + ...managementEnableOperationGroupTestCases, + ])("$ruleName - $description", async (c: Case) => { const ruleTester = new RuleTester({ languageOptions: { parser: parser, @@ -57,7 +112,7 @@ describe("Tspconfig emitter options validation", () => { }); const ruleModule = await import(c.rulePath); - const rule = ruleModule.default; + const rule = ruleModule.default.find((r: NamedRule.RuleModule) => r.name === c.ruleName); const tests = c.shouldReportError ? { valid: [], From b1ae1a3b40125d14c0150f218faa90abff0fe164 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Tue, 21 Jan 2025 15:18:02 +0800 Subject: [PATCH 063/106] all js rules added --- .../src/rules/tspconfig-validation-rules.ts | 28 +++++++- eng/tools/eslint-plugin-tsv/src/utils/rule.ts | 65 +++++++++++++------ .../tspconfig-options-validation.test.ts | 55 ++++++++++++---- 3 files changed, 112 insertions(+), 36 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts index 18d463d69fb4..d8214e5155a7 100644 --- a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts +++ b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts @@ -1,20 +1,42 @@ import { emitters } from "../utils/constants.js"; import { createManagementClientRule } from "../utils/rule.js"; -const args: [string, string, string, string | boolean][] = [ - ["tspconfig-ts-mgmt-modular-generate-metadata-true", emitters.ts, "generateMetadata", true], - ["tspconfig-ts-mgmt-modular-hierarchy-client-false", emitters.ts, "hierarchyClient", false], +const args: [string, string, string, string | boolean | RegExp, string | boolean][] = [ + ["tspconfig-ts-mgmt-modular-generate-metadata-true", emitters.ts, "generateMetadata", true, true], + [ + "tspconfig-ts-mgmt-modular-hierarchy-client-false", + emitters.ts, + "hierarchyClient", + false, + false, + ], [ "tspconfig-ts-mgmt-modular-experimental-extensible-enums-true", emitters.ts, "experimentalExtensibleEnums", true, + true, ], [ "tspconfig-ts-mgmt-modular-enable-operation-group-true", emitters.ts, "enableOperationGroup", true, + true, + ], + [ + "tspconfig-ts-mgmt-modular-package-dir-match-pattern", + emitters.ts, + "package-dir", + /^arm(?:-[a-z]+)+$/, + "arm-aaa-bbb", + ], + [ + "tspconfig-ts-mgmt-modular-package-name-match-pattern", + emitters.ts, + "packageDetails.name", + /^\@azure\/arm(?:-[a-z]+)+$/, + "@azure/arm-aaa-bbb", ], ]; diff --git a/eng/tools/eslint-plugin-tsv/src/utils/rule.ts b/eng/tools/eslint-plugin-tsv/src/utils/rule.ts index 304f63fb7c21..8de790b788a1 100644 --- a/eng/tools/eslint-plugin-tsv/src/utils/rule.ts +++ b/eng/tools/eslint-plugin-tsv/src/utils/rule.ts @@ -4,22 +4,20 @@ import { RuleDocument, RuleInfo } from "../interfaces/rule-interfaces.js"; import { defaultMessageId, defaultRuleType, emitters } from "./constants.js"; import { NamedRule } from "../interfaces/named-eslint.js"; import { AST, getStaticYAMLValue } from "yaml-eslint-parser"; +import { stringify } from "yaml"; function createManagementRuleDocument( emitterName: string, optionName: string, - expectedOptionValue: string | boolean, + expectedOptionValue: string | boolean | RegExp, + exampleValue: string | boolean, ): RuleDocument { const document: RuleDocument = { - description: `Validate whether '${optionName}' is set to true in tspconfig.yaml when generating modular clients`, - error: `'${optionName}' is NOT set to true in tspconfig.yaml when generating modular clients`, - action: `Set 'options.${emitters.ts}.${optionName}' to true in tspconfig.yaml when generating modular clients`, - example: `... - options: - "${emitterName}": - ${optionName}: ${expectedOptionValue} # <--- SET HERE - ... - `, + // TODO: improve description with natual language + description: `Validate whether 'options.${emitters.ts}.${optionName}' is set to '${expectedOptionValue}' in tspconfig.yaml when generating modular clients`, + error: `'options.${emitters.ts}.${optionName}' is NOT set to '${expectedOptionValue}' in tspconfig.yaml when generating modular clients`, + action: `Set 'options.${emitters.ts}.${optionName}' to '${expectedOptionValue}' in tspconfig.yaml when generating modular clients`, + example: createEmitterOptions(emitterName, { key: optionName, value: exampleValue }), }; return document; } @@ -65,16 +63,26 @@ export function isManagementForTsEmitter(tspconfig: TypeSpecConfig, context: Rul return flavor === "azure" && filename.includes(".Management") && isModularLibrary == undefined; } -export function generateEmitterOptions( +export function createEmitterOptions( emitter: string, ...pairs: { key: string; value: string | boolean | {} }[] ) { - let content = `options: - "${emitter}":`; + const obj = { options: { [emitter]: {} } }; for (const pair of pairs) { - content += ` - ${pair.key}: ${pair.value}`; + const segments = pair.key.split("."); + let cur: { [id: string]: any } = obj.options[emitter]; + for (const [i, segment] of segments.entries()) { + if (i === segments.length - 1) { + cur[segment] = pair.value; + break; + } + if (!(segment in cur)) { + cur[segment] = {}; + } + cur = cur[segment]; + } } + const content = stringify(obj); return content; } @@ -82,9 +90,15 @@ export function createManagementClientRule( ruleName: string, emitterName: string, optionName: string, - expectedOptionValue: string | boolean, + expectedOptionValue: string | boolean | RegExp, + exampleValue: string | boolean, ): NamedRule.RuleModule { - const documentation = createManagementRuleDocument(emitterName, optionName, expectedOptionValue); + const documentation = createManagementRuleDocument( + emitterName, + optionName, + expectedOptionValue, + exampleValue, + ); const ruleInfo: RuleInfo = { name: ruleName, @@ -93,8 +107,21 @@ export function createManagementClientRule( messages: () => createRuleMessages(defaultMessageId, documentation), condition: (tspconfig, context) => isManagementForTsEmitter(tspconfig, context), validation: (tspconfig, context, node) => { - const option = tspconfig.options?.[emitters.ts][optionName]; - if (option !== expectedOptionValue) context.report({ node, messageId: defaultMessageId }); + let option: any = tspconfig.options?.[emitters.ts]; + for (const segment of optionName.split(".")) { + if (segment in option) option = option[segment]; + } + switch (typeof expectedOptionValue) { + case "boolean": + case "string": + if (option !== expectedOptionValue) + context.report({ node, messageId: defaultMessageId }); + break; + case "object": + if (typeof option !== "string" || !expectedOptionValue.test(option)) + context.report({ node, messageId: defaultMessageId }); + break; + } }, }, }; diff --git a/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts b/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts index bd283f5da2b3..2b1c1f1ecd5c 100644 --- a/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts @@ -2,7 +2,7 @@ import { Rule, RuleTester } from "eslint"; import { describe, it } from "vitest"; import parser from "yaml-eslint-parser"; import { defaultMessageId, emitters } from "../../src/utils/constants.js"; -import { generateEmitterOptions } from "../../src/utils/rule.js"; +import { createEmitterOptions } from "../../src/utils/rule.js"; import { NamedRule } from "../../src/interfaces/named-eslint.js"; interface Case { @@ -17,71 +17,96 @@ interface Case { const managementTspconfigPath = "contosowidgetmanager/Contoso.Management/tspconfig.yaml"; const rulePath = "../../src/rules/tspconfig-validation-rules.js"; -const managementGenerateMetadataTestCases = generateManagementClientBooleanTestCases( +const managementGenerateMetadataTestCases = generateManagementClientTestCases( emitters.ts, rulePath, "tspconfig-ts-mgmt-modular-generate-metadata-true", managementTspconfigPath, "generateMetadata", true, + false, ); -const managementHierarchyClientTestCases = generateManagementClientBooleanTestCases( +const managementHierarchyClientTestCases = generateManagementClientTestCases( emitters.ts, rulePath, "tspconfig-ts-mgmt-modular-hierarchy-client-false", managementTspconfigPath, "hierarchyClient", false, + true, ); -const managementExperimentalExtensibleEnumsTestCases = generateManagementClientBooleanTestCases( +const managementExperimentalExtensibleEnumsTestCases = generateManagementClientTestCases( emitters.ts, rulePath, "tspconfig-ts-mgmt-modular-experimental-extensible-enums-true", managementTspconfigPath, "experimentalExtensibleEnums", true, + false, ); -const managementEnableOperationGroupTestCases = generateManagementClientBooleanTestCases( +const managementEnableOperationGroupTestCases = generateManagementClientTestCases( emitters.ts, rulePath, "tspconfig-ts-mgmt-modular-enable-operation-group-true", managementTspconfigPath, "enableOperationGroup", true, + false, +); + +const managementPackageDirTestCases = generateManagementClientTestCases( + emitters.ts, + rulePath, + "tspconfig-ts-mgmt-modular-package-dir-match-pattern", + managementTspconfigPath, + "package-dir", + "arm-aaa-bbb", + "aaa-bbb", +); + +const managementPackageNameTestCases = generateManagementClientTestCases( + emitters.ts, + rulePath, + "tspconfig-ts-mgmt-modular-package-name-match-pattern", + managementTspconfigPath, + "packageDetails.name", + "@azure/arm-aaa-bbb", + "@azure/aaa-bbb", ); -function generateManagementClientBooleanTestCases( +function generateManagementClientTestCases( emitterName: string, rulePath: string, ruleName: string, fileName: string, optionName: string, - expectedOptionValue: boolean, + validOptionValue: boolean | string, + invalidOptionValue: boolean | string, ): Case[] { const managementGenerateMetadataTestCases: Case[] = [ { - description: `valid: ${optionName} is ${expectedOptionValue}`, + description: `valid: ${optionName} is ${validOptionValue}`, rulePath, ruleName, fileName, - yamlContent: generateEmitterOptions( + yamlContent: createEmitterOptions( emitterName, - { key: optionName, value: expectedOptionValue }, + { key: optionName, value: validOptionValue }, { key: "flavor", value: "azure" }, ), shouldReportError: false, }, { - description: `invalid: ${optionName} is ${!expectedOptionValue}`, + description: `invalid: ${optionName} is ${invalidOptionValue}`, rulePath, ruleName, fileName, - yamlContent: generateEmitterOptions( + yamlContent: createEmitterOptions( emitterName, - { key: optionName, value: !expectedOptionValue }, + { key: optionName, value: invalidOptionValue }, { key: "flavor", value: "azure" }, ), shouldReportError: true, @@ -91,7 +116,7 @@ function generateManagementClientBooleanTestCases( rulePath, ruleName, fileName, - yamlContent: generateEmitterOptions(emitterName, { key: "flavor", value: "azure" }), + yamlContent: createEmitterOptions(emitterName, { key: "flavor", value: "azure" }), shouldReportError: true, }, ]; @@ -104,6 +129,8 @@ describe("Tspconfig emitter options validation", () => { ...managementHierarchyClientTestCases, ...managementExperimentalExtensibleEnumsTestCases, ...managementEnableOperationGroupTestCases, + ...managementPackageDirTestCases, + ...managementPackageNameTestCases, ])("$ruleName - $description", async (c: Case) => { const ruleTester = new RuleTester({ languageOptions: { From 40c65f02dcbfb151af7288941fb9126c2edd4427 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Tue, 21 Jan 2025 18:44:34 +0800 Subject: [PATCH 064/106] added go rules --- .../src/rules/tspconfig-validation-rules.ts | 49 ++-- .../src/utils/config-interpolation.ts | 221 ++++++++++++++++++ eng/tools/eslint-plugin-tsv/src/utils/rule.ts | 58 +++-- .../tspconfig-options-validation.test.ts | 114 ++++++++- 4 files changed, 401 insertions(+), 41 deletions(-) create mode 100644 eng/tools/eslint-plugin-tsv/src/utils/config-interpolation.ts diff --git a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts index d8214e5155a7..adf88d940aed 100644 --- a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts +++ b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts @@ -1,43 +1,64 @@ -import { emitters } from "../utils/constants.js"; import { createManagementClientRule } from "../utils/rule.js"; -const args: [string, string, string, string | boolean | RegExp, string | boolean][] = [ - ["tspconfig-ts-mgmt-modular-generate-metadata-true", emitters.ts, "generateMetadata", true, true], - [ - "tspconfig-ts-mgmt-modular-hierarchy-client-false", - emitters.ts, - "hierarchyClient", - false, - false, - ], +const args: [string, string, string | boolean | RegExp, string | boolean, string | undefined][] = [ + // ts + ["tspconfig-ts-mgmt-modular-generate-metadata-true", "generateMetadata", true, true, undefined], + ["tspconfig-ts-mgmt-modular-hierarchy-client-false", "hierarchyClient", false, false, undefined], [ "tspconfig-ts-mgmt-modular-experimental-extensible-enums-true", - emitters.ts, "experimentalExtensibleEnums", true, true, + undefined, ], [ "tspconfig-ts-mgmt-modular-enable-operation-group-true", - emitters.ts, "enableOperationGroup", true, true, + undefined, ], [ "tspconfig-ts-mgmt-modular-package-dir-match-pattern", - emitters.ts, "package-dir", /^arm(?:-[a-z]+)+$/, "arm-aaa-bbb", + "The package-dir should be a string that starts with 'arm' and is followed by one or more groups of a hyphen (-) and lowercase letters", ], [ "tspconfig-ts-mgmt-modular-package-name-match-pattern", - emitters.ts, "packageDetails.name", /^\@azure\/arm(?:-[a-z]+)+$/, "@azure/arm-aaa-bbb", + "The package name should be a string that starts with '@azure/arm' and is followed by one or more groups of a hyphen (-) and lowercase letters", + ], + // go + [ + "tspconfig-go-mgmt-service-dir-match-pattern", + "service-dir", + /^sdk\/resourcemanager\/[^\/]*$/, + "sdk/resourcemanager/aaa", + "The service-dir should be a string that start with 'sdk/resourcemanager/' followed by any characters except '/', and end there", + ], + [ + "tspconfig-go-mgmt-package-dir-match-pattern", + "package-dir", + /^arm[^\/]*$/, + "armaaa", + "The package-dir should be a string that start with 'arm' and do not contain a forward slash (/) after it", + ], + [ + "tspconfig-go-mgmt-module-equal-string", + "module", + "github.com/Azure/azure-sdk-for-go/{service-dir}/{package-dir}", + "github.com/Azure/azure-sdk-for-go/{service-dir}/{package-dir}", + undefined, ], + ["tspconfig-go-mgmt-fix-const-stuttering-true", "fix-const-stuttering", true, true, undefined], + ["tspconfig-go-mgmt-generate-examples-true", "generate-examples", true, true, undefined], + ["tspconfig-go-mgmt-generate-fakes-true", "generate-fakes", true, true, undefined], + ["tspconfig-go-mgmt-head-as-boolean-true", "head-as-boolean", true, true, undefined], + ["tspconfig-go-mgmt-inject-spans-true", "inject-spans", true, true, undefined], ]; export default args.map((a) => createManagementClientRule(...a)); diff --git a/eng/tools/eslint-plugin-tsv/src/utils/config-interpolation.ts b/eng/tools/eslint-plugin-tsv/src/utils/config-interpolation.ts new file mode 100644 index 000000000000..cd7113383d03 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/utils/config-interpolation.ts @@ -0,0 +1,221 @@ +import { + ConfigParameter, + EmitterOptions, + ConfigEnvironmentVariable, + TypeSpecConfig, +} from "../config/types.js"; + +// dummy +interface Diagnostic {} +type DiagnosticResult = [T, readonly Diagnostic[]]; +const NoTarget = 0; +function createDiagnosticCollector() { + return { + pipe: (x: DiagnosticResult): T => { + return x[0]; + }, + wrap: (x: T): [T, [Diagnostic]] => { + return [x, [{}]]; + }, + }; +} +function ignoreDiagnostics(result: DiagnosticResult): T { + return result[0]; +} +function createDiagnostic(diag: any): Diagnostic { + return {}; +} +// + +// Copied from https://github.com/microsoft/typespec/blob/main/packages/compiler/src/config/config-interpolation.ts +export interface ExpandConfigOptions { + readonly cwd: string; + readonly outputDir?: string; + readonly env?: Record; + readonly args?: Record; +} + +export function expandConfigVariables( + config: TypeSpecConfig, + expandOptions: ExpandConfigOptions, +): [TypeSpecConfig, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + const builtInVars = { + "project-root": config.projectRoot, + cwd: expandOptions.cwd, + }; + + const resolvedArgsParameters = diagnostics.pipe( + resolveArgs(config.parameters, expandOptions.args, builtInVars), + ); + const commonVars = { + ...builtInVars, + ...resolvedArgsParameters, + ...diagnostics.pipe(resolveArgs(config.options, {}, resolvedArgsParameters)), + env: diagnostics.pipe( + resolveArgs(config.environmentVariables, expandOptions.env, builtInVars, true), + ), + }; + const outputDir = diagnostics.pipe( + resolveValue(expandOptions.outputDir ?? config.outputDir, commonVars), + ); + + const result = { ...config, outputDir }; + if (config.options) { + const options: Record = {}; + for (const [name, emitterOptions] of Object.entries(config.options)) { + const emitterVars = { ...commonVars, "output-dir": outputDir, "emitter-name": name }; + options[name] = diagnostics.pipe(resolveValues(emitterOptions, emitterVars)); + } + result.options = options; + } + + return diagnostics.wrap(result); +} + +function resolveArgs( + declarations: + | Record + | undefined, + args: Record | undefined, + predefinedVariables: Record>, + allowUnspecified = false, +): [Record, readonly Diagnostic[]] { + function tryGetValue(value: any): string | undefined { + return typeof value === "string" ? value : undefined; + } + const unmatchedArgs = new Set(Object.keys(args ?? {})); + const result: Record = {}; + + function resolveNestedArgs( + parentName: string, + declarations: [string, ConfigParameter | ConfigEnvironmentVariable | EmitterOptions][], + ) { + for (const [declarationName, definition] of declarations) { + const name = parentName ? `${parentName}.${declarationName}` : declarationName; + if (hasNestedValues(definition)) { + resolveNestedArgs(name, Object.entries(definition ?? {})); + } + unmatchedArgs.delete(name); + result[name] = ignoreDiagnostics( + resolveValue( + args?.[name] ?? tryGetValue(definition.default) ?? tryGetValue(definition) ?? "", + predefinedVariables, + ), + ); + } + } + + if (declarations !== undefined) { + resolveNestedArgs("", Object.entries(declarations ?? {})); + } + + if (!allowUnspecified) { + const diagnostics: Diagnostic[] = [...unmatchedArgs].map((unmatchedArg) => { + return createDiagnostic({ + code: "config-invalid-argument", + format: { name: unmatchedArg }, + target: NoTarget, + }); + }); + return [result, diagnostics]; + } + return [result, []]; +} + +function hasNestedValues(value: any): boolean { + return ( + value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length > 0 + ); +} + +const VariableInterpolationRegex = /{([a-zA-Z-_.]+)}/g; + +function resolveValue( + value: string, + predefinedVariables: Record>, +): [string, readonly Diagnostic[]] { + const [result, diagnostics] = resolveValues({ value }, predefinedVariables); + return [result.value, diagnostics]; +} + +export function resolveValues>( + values: T, + predefinedVariables: Record> = {}, +): [T, readonly Diagnostic[]] { + const diagnostics: Diagnostic[] = []; + const resolvedValues: Record = {}; + const resolvingValues = new Set(); + + function resolveValue(keys: string[]): unknown { + resolvingValues.add(keys[0]); + let value: any = values; + value = keys.reduce((acc, key) => acc?.[key], value); + + if (typeof value !== "string") { + if (hasNestedValues(value)) { + value = value as Record; + const resultObject: Record = {}; + for (const [nestedKey] of Object.entries(value)) { + resolvingValues.add(nestedKey); + resultObject[nestedKey] = resolveValue(keys.concat(nestedKey)) as any; + } + return resultObject; + } + return value; + } + return value.replace(VariableInterpolationRegex, (match, expression) => { + return (resolveExpression(expression) as string) ?? `{${expression}}`; + }); + } + + function resolveExpression(expression: string): unknown | undefined { + if (expression in resolvedValues) { + return resolvedValues[expression]; + } + + if (resolvingValues.has(expression)) { + diagnostics.push( + createDiagnostic({ + code: "config-circular-variable", + target: NoTarget, + format: { name: expression }, + }), + ); + return undefined; + } + + if (expression in values) { + return resolveValue([expression]) as any; + } + + let resolved: any = predefinedVariables; + if (expression in resolved) { + return resolved[expression]; + } + + const segments = expression.split("."); + for (const segment of segments) { + resolved = resolved[segment]; + if (resolved === undefined) { + return undefined; + } + } + + if (typeof resolved === "string") { + return resolved; + } else { + return undefined; + } + } + + for (const key of Object.keys(values)) { + resolvingValues.clear(); + if (key in resolvedValues) { + continue; + } + resolvedValues[key] = resolveValue([key]) as any; + } + + return [resolvedValues as any, diagnostics]; +} diff --git a/eng/tools/eslint-plugin-tsv/src/utils/rule.ts b/eng/tools/eslint-plugin-tsv/src/utils/rule.ts index 8de790b788a1..c3d1cd5972af 100644 --- a/eng/tools/eslint-plugin-tsv/src/utils/rule.ts +++ b/eng/tools/eslint-plugin-tsv/src/utils/rule.ts @@ -5,16 +5,28 @@ import { defaultMessageId, defaultRuleType, emitters } from "./constants.js"; import { NamedRule } from "../interfaces/named-eslint.js"; import { AST, getStaticYAMLValue } from "yaml-eslint-parser"; import { stringify } from "yaml"; +import { resolveValues } from "./config-interpolation.js"; function createManagementRuleDocument( emitterName: string, optionName: string, expectedOptionValue: string | boolean | RegExp, exampleValue: string | boolean, + extraExplanation: string, ): RuleDocument { + let description: string = ""; + switch (typeof expectedOptionValue) { + case "string": + case "boolean": + `Validate whether 'options.${emitters.ts}.${optionName}' is set to '${expectedOptionValue}' in tspconfig.yaml when generating modular clients`; + break; + case "object": + `Validate whether 'options.${emitters.ts}.${optionName}' matches regex pattern '${expectedOptionValue}' in tspconfig.yaml when generating modular clients. ${extraExplanation}`; + break; + } + const document: RuleDocument = { - // TODO: improve description with natual language - description: `Validate whether 'options.${emitters.ts}.${optionName}' is set to '${expectedOptionValue}' in tspconfig.yaml when generating modular clients`, + description, error: `'options.${emitters.ts}.${optionName}' is NOT set to '${expectedOptionValue}' in tspconfig.yaml when generating modular clients`, action: `Set 'options.${emitters.ts}.${optionName}' to '${expectedOptionValue}' in tspconfig.yaml when generating modular clients`, example: createEmitterOptions(emitterName, { key: optionName, value: exampleValue }), @@ -37,10 +49,11 @@ export function createRule(ruleContext: RuleInfo): NamedRule.RuleModule { return { YAMLDocument(node: Rule.Node) { const yamlDocument = node as unknown as AST.YAMLDocument; - const config = getStaticYAMLValue(yamlDocument) || {}; - const typedConfig = config as unknown as TypeSpecConfig; - if (!ruleContext.functions.condition(typedConfig, context)) return; - ruleContext.functions.validation(typedConfig, context, node); + const rawConfig = getStaticYAMLValue(yamlDocument) || {}; + const config = rawConfig as unknown as TypeSpecConfig; + + if (!ruleContext.functions.condition(config, context)) return; + ruleContext.functions.validation(config, context, node); }, }; }, @@ -54,13 +67,14 @@ export function createRuleMessages(messageId: string, docs: RuleDocument) { }; } -export function isManagementForTsEmitter(tspconfig: TypeSpecConfig, context: Rule.RuleContext) { - const flavor = tspconfig.options?.[emitters.ts]?.flavor as string; - const isModularLibrary = tspconfig.options?.[emitters.ts]?.isModularLibrary as - | boolean - | undefined; +export function isManagementSDK( + tspconfig: TypeSpecConfig, + context: Rule.RuleContext, + emitterName: string, +) { + const flavor = tspconfig.options?.[emitterName]?.flavor as string; const filename = context.filename; - return flavor === "azure" && filename.includes(".Management") && isModularLibrary == undefined; + return flavor === "azure" && filename.includes(".Management"); } export function createEmitterOptions( @@ -88,16 +102,19 @@ export function createEmitterOptions( export function createManagementClientRule( ruleName: string, - emitterName: string, optionName: string, expectedOptionValue: string | boolean | RegExp, exampleValue: string | boolean, + extraExplanation: string = "", ): NamedRule.RuleModule { + const language = ruleName.split("-")[1]! as keyof typeof emitters; + const emitterName = emitters[language]; const documentation = createManagementRuleDocument( emitterName, optionName, expectedOptionValue, exampleValue, + extraExplanation, ); const ruleInfo: RuleInfo = { @@ -105,9 +122,20 @@ export function createManagementClientRule( documentation, functions: { messages: () => createRuleMessages(defaultMessageId, documentation), - condition: (tspconfig, context) => isManagementForTsEmitter(tspconfig, context), + condition: (tspconfig, context) => { + const isManagement = isManagementSDK(tspconfig, context, emitterName); + if (!isManagement) return false; + + if (emitterName === emitters.ts) { + const isModularLibrary = tspconfig.options?.[emitters.ts]?.isModularLibrary as + | boolean + | undefined; + return isModularLibrary === undefined || isModularLibrary === true; + } + return true; + }, validation: (tspconfig, context, node) => { - let option: any = tspconfig.options?.[emitters.ts]; + let option: any = tspconfig.options?.[emitterName]; for (const segment of optionName.split(".")) { if (segment in option) option = option[segment]; } diff --git a/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts b/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts index 2b1c1f1ecd5c..47506385e566 100644 --- a/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts @@ -17,7 +17,7 @@ interface Case { const managementTspconfigPath = "contosowidgetmanager/Contoso.Management/tspconfig.yaml"; const rulePath = "../../src/rules/tspconfig-validation-rules.js"; -const managementGenerateMetadataTestCases = generateManagementClientTestCases( +const tsManagementGenerateMetadataTestCases = generateManagementClientTestCases( emitters.ts, rulePath, "tspconfig-ts-mgmt-modular-generate-metadata-true", @@ -27,7 +27,7 @@ const managementGenerateMetadataTestCases = generateManagementClientTestCases( false, ); -const managementHierarchyClientTestCases = generateManagementClientTestCases( +const tsManagementHierarchyClientTestCases = generateManagementClientTestCases( emitters.ts, rulePath, "tspconfig-ts-mgmt-modular-hierarchy-client-false", @@ -37,7 +37,7 @@ const managementHierarchyClientTestCases = generateManagementClientTestCases( true, ); -const managementExperimentalExtensibleEnumsTestCases = generateManagementClientTestCases( +const tsManagementExperimentalExtensibleEnumsTestCases = generateManagementClientTestCases( emitters.ts, rulePath, "tspconfig-ts-mgmt-modular-experimental-extensible-enums-true", @@ -47,7 +47,7 @@ const managementExperimentalExtensibleEnumsTestCases = generateManagementClientT false, ); -const managementEnableOperationGroupTestCases = generateManagementClientTestCases( +const tsManagementEnableOperationGroupTestCases = generateManagementClientTestCases( emitters.ts, rulePath, "tspconfig-ts-mgmt-modular-enable-operation-group-true", @@ -57,7 +57,7 @@ const managementEnableOperationGroupTestCases = generateManagementClientTestCase false, ); -const managementPackageDirTestCases = generateManagementClientTestCases( +const tsManagementPackageDirTestCases = generateManagementClientTestCases( emitters.ts, rulePath, "tspconfig-ts-mgmt-modular-package-dir-match-pattern", @@ -67,7 +67,7 @@ const managementPackageDirTestCases = generateManagementClientTestCases( "aaa-bbb", ); -const managementPackageNameTestCases = generateManagementClientTestCases( +const tsManagementPackageNameTestCases = generateManagementClientTestCases( emitters.ts, rulePath, "tspconfig-ts-mgmt-modular-package-name-match-pattern", @@ -77,6 +77,86 @@ const managementPackageNameTestCases = generateManagementClientTestCases( "@azure/aaa-bbb", ); +const goManagementServiceDirTestCases = generateManagementClientTestCases( + emitters.go, + rulePath, + "tspconfig-go-mgmt-service-dir-match-pattern", + managementTspconfigPath, + "service-dir", + "sdk/resourcemanager/aaa", + "sdk/manager/aaa", +); + +const goManagementPackageDirTestCases = generateManagementClientTestCases( + emitters.go, + rulePath, + "tspconfig-go-mgmt-package-dir-match-pattern", + managementTspconfigPath, + "package-dir", + "armaaa", + "aaa", +); + +const goManagementModuleTestCases = generateManagementClientTestCases( + emitters.go, + rulePath, + "tspconfig-go-mgmt-module-equal-string", + managementTspconfigPath, + "module", + "github.com/Azure/azure-sdk-for-go/{service-dir}/{package-dir}", + "github.com/Azure/azure-sdk-for-java/{service-dir}/{package-dir}", +); + +const goManagementFixConstStutteringTestCases = generateManagementClientTestCases( + emitters.go, + rulePath, + "tspconfig-go-mgmt-fix-const-stuttering-true", + managementTspconfigPath, + "fix-const-stuttering", + true, + false, +); + +const goManagementGenerateExamplesTestCases = generateManagementClientTestCases( + emitters.go, + rulePath, + "tspconfig-go-mgmt-generate-examples-true", + managementTspconfigPath, + "generate-examples", + true, + false, +); + +const goManagementGenerateFakesTestCases = generateManagementClientTestCases( + emitters.go, + rulePath, + "tspconfig-go-mgmt-generate-fakes-true", + managementTspconfigPath, + "generate-fakes", + true, + false, +); + +const goManagementHeadAsBooleanTestCases = generateManagementClientTestCases( + emitters.go, + rulePath, + "tspconfig-go-mgmt-head-as-boolean-true", + managementTspconfigPath, + "head-as-boolean", + true, + false, +); + +const goManagementInjectSpansTestCases = generateManagementClientTestCases( + emitters.go, + rulePath, + "tspconfig-go-mgmt-inject-spans-true", + managementTspconfigPath, + "inject-spans", + true, + false, +); + function generateManagementClientTestCases( emitterName: string, rulePath: string, @@ -125,12 +205,22 @@ function generateManagementClientTestCases( describe("Tspconfig emitter options validation", () => { it.each([ - ...managementGenerateMetadataTestCases, - ...managementHierarchyClientTestCases, - ...managementExperimentalExtensibleEnumsTestCases, - ...managementEnableOperationGroupTestCases, - ...managementPackageDirTestCases, - ...managementPackageNameTestCases, + // ts + ...tsManagementGenerateMetadataTestCases, + ...tsManagementHierarchyClientTestCases, + ...tsManagementExperimentalExtensibleEnumsTestCases, + ...tsManagementEnableOperationGroupTestCases, + ...tsManagementPackageDirTestCases, + ...tsManagementPackageNameTestCases, + // go + ...goManagementServiceDirTestCases, + ...goManagementPackageDirTestCases, + ...goManagementModuleTestCases, + ...goManagementFixConstStutteringTestCases, + ...goManagementGenerateExamplesTestCases, + ...goManagementGenerateFakesTestCases, + ...goManagementHeadAsBooleanTestCases, + ...goManagementInjectSpansTestCases, ])("$ruleName - $description", async (c: Case) => { const ruleTester = new RuleTester({ languageOptions: { From 887282be8b2ab80977a8edc63ee46399e96f6867 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Wed, 22 Jan 2025 18:05:58 +0800 Subject: [PATCH 065/106] added all rules! : ) --- .../src/interfaces/rule-interfaces.ts | 15 + .../src/rules/tspconfig-validation-rules.ts | 307 ++++++++++++++---- .../src/utils/rule-creator.ts | 128 ++++++++ .../eslint-plugin-tsv/src/utils/rule-doc.ts | 114 +++++++ eng/tools/eslint-plugin-tsv/src/utils/rule.ts | 158 --------- .../tspconfig-options-validation.test.ts | 270 +++++++++++---- 6 files changed, 715 insertions(+), 277 deletions(-) create mode 100644 eng/tools/eslint-plugin-tsv/src/utils/rule-creator.ts create mode 100644 eng/tools/eslint-plugin-tsv/src/utils/rule-doc.ts delete mode 100644 eng/tools/eslint-plugin-tsv/src/utils/rule.ts diff --git a/eng/tools/eslint-plugin-tsv/src/interfaces/rule-interfaces.ts b/eng/tools/eslint-plugin-tsv/src/interfaces/rule-interfaces.ts index 4ea789d7ba86..d45d6ab20597 100644 --- a/eng/tools/eslint-plugin-tsv/src/interfaces/rule-interfaces.ts +++ b/eng/tools/eslint-plugin-tsv/src/interfaces/rule-interfaces.ts @@ -1,6 +1,11 @@ import { Rule } from "eslint"; import { TypeSpecConfig } from "../config/types.js"; +export enum KeyType { + EmitterOption, + Parameter, +} + export interface RuleDocument { description: string; error: string; @@ -17,3 +22,13 @@ export interface RuleInfo { validation: (tspconfig: TypeSpecConfig, context: Rule.RuleContext, node: Rule.Node) => void; }; } + +export interface CreateCodeGenSDKRuleArgs { + rule: string; + type: KeyType; + key: string; + expectedValue: string | boolean | RegExp; + exampleValue: string | boolean; + extraExplanation?: string; + condition?: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => boolean; +} \ No newline at end of file diff --git a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts index adf88d940aed..719e370ac560 100644 --- a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts +++ b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts @@ -1,64 +1,253 @@ -import { createManagementClientRule } from "../utils/rule.js"; +import { Rule } from "eslint"; +import { TypeSpecConfig } from "../config/types.js"; +import { + createCodeGenSDKRule, + isAzureSDK, + isManagementSDK, +} from "../utils/rule-creator.js"; +import { emitters } from "../utils/constants.js"; +import { CreateCodeGenSDKRuleArgs, KeyType } from "../interfaces/rule-interfaces.js"; -const args: [string, string, string | boolean | RegExp, string | boolean, string | undefined][] = [ +const tsIsManagementCondition = (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => { + const emitterName = emitters.ts; + const isModularLibrary = tspconfig.options?.[emitterName]?.isModularLibrary as + | boolean + | undefined; + return isManagementSDK(tspconfig, context, emitterName) && isModularLibrary !== false; +}; + +const args: CreateCodeGenSDKRuleArgs[] = [ + // common + { + rule: "tspconfig-common-az-service-dir-match-pattern", + key: "service-dir", + type: KeyType.Parameter, + expectedValue: /^sdk\/[^\/]*$/, + exampleValue: "sdk/aaa", + extraExplanation: + "The 'service-dir' should be a string that starts with 'sdk/', followed by zero or more characters that are not a '/', and ends there", + condition: (_: TypeSpecConfig, _1: Rule.RuleContext) => true, + }, // ts - ["tspconfig-ts-mgmt-modular-generate-metadata-true", "generateMetadata", true, true, undefined], - ["tspconfig-ts-mgmt-modular-hierarchy-client-false", "hierarchyClient", false, false, undefined], - [ - "tspconfig-ts-mgmt-modular-experimental-extensible-enums-true", - "experimentalExtensibleEnums", - true, - true, - undefined, - ], - [ - "tspconfig-ts-mgmt-modular-enable-operation-group-true", - "enableOperationGroup", - true, - true, - undefined, - ], - [ - "tspconfig-ts-mgmt-modular-package-dir-match-pattern", - "package-dir", - /^arm(?:-[a-z]+)+$/, - "arm-aaa-bbb", - "The package-dir should be a string that starts with 'arm' and is followed by one or more groups of a hyphen (-) and lowercase letters", - ], - [ - "tspconfig-ts-mgmt-modular-package-name-match-pattern", - "packageDetails.name", - /^\@azure\/arm(?:-[a-z]+)+$/, - "@azure/arm-aaa-bbb", - "The package name should be a string that starts with '@azure/arm' and is followed by one or more groups of a hyphen (-) and lowercase letters", - ], + { + rule: "tspconfig-ts-mgmt-modular-generate-metadata-true", + key: "generateMetadata", + type: KeyType.EmitterOption, + expectedValue: true, + exampleValue: true, + condition: tsIsManagementCondition, + }, + { + rule: "tspconfig-ts-mgmt-modular-hierarchy-client-false", + key: "hierarchyClient", + type: KeyType.EmitterOption, + expectedValue: false, + exampleValue: false, + condition: tsIsManagementCondition, + }, + { + rule: "tspconfig-ts-mgmt-modular-experimental-extensible-enums-true", + key: "experimentalExtensibleEnums", + type: KeyType.EmitterOption, + expectedValue: true, + exampleValue: true, + condition: tsIsManagementCondition, + }, + { + rule: "tspconfig-ts-mgmt-modular-enable-operation-group-true", + key: "enableOperationGroup", + type: KeyType.EmitterOption, + expectedValue: true, + exampleValue: true, + condition: tsIsManagementCondition, + }, + { + rule: "tspconfig-ts-mgmt-modular-package-dir-match-pattern", + key: "package-dir", + type: KeyType.EmitterOption, + expectedValue: /^arm(?:-[a-z]+)+$/, + exampleValue: "arm-aaa-bbb", + extraExplanation: + "The 'package-dir' should be a string that starts with 'arm' and is followed by one or more groups of a hyphen (-) and lowercase letters", + condition: tsIsManagementCondition, + }, + { + rule: "tspconfig-ts-mgmt-modular-package-name-match-pattern", + key: "packageDetails.name", + type: KeyType.EmitterOption, + expectedValue: /^\@azure\/arm(?:-[a-z]+)+$/, + exampleValue: "@azure/arm-aaa-bbb", + extraExplanation: + "The package name should be a string that starts with '@azure/arm' and is followed by one or more groups of a hyphen (-) and lowercase letters", + condition: tsIsManagementCondition, + }, // go - [ - "tspconfig-go-mgmt-service-dir-match-pattern", - "service-dir", - /^sdk\/resourcemanager\/[^\/]*$/, - "sdk/resourcemanager/aaa", - "The service-dir should be a string that start with 'sdk/resourcemanager/' followed by any characters except '/', and end there", - ], - [ - "tspconfig-go-mgmt-package-dir-match-pattern", - "package-dir", - /^arm[^\/]*$/, - "armaaa", - "The package-dir should be a string that start with 'arm' and do not contain a forward slash (/) after it", - ], - [ - "tspconfig-go-mgmt-module-equal-string", - "module", - "github.com/Azure/azure-sdk-for-go/{service-dir}/{package-dir}", - "github.com/Azure/azure-sdk-for-go/{service-dir}/{package-dir}", - undefined, - ], - ["tspconfig-go-mgmt-fix-const-stuttering-true", "fix-const-stuttering", true, true, undefined], - ["tspconfig-go-mgmt-generate-examples-true", "generate-examples", true, true, undefined], - ["tspconfig-go-mgmt-generate-fakes-true", "generate-fakes", true, true, undefined], - ["tspconfig-go-mgmt-head-as-boolean-true", "head-as-boolean", true, true, undefined], - ["tspconfig-go-mgmt-inject-spans-true", "inject-spans", true, true, undefined], + { + rule: "tspconfig-go-mgmt-service-dir-match-pattern", + key: "service-dir", + type: KeyType.EmitterOption, + expectedValue: /^sdk\/resourcemanager\/[^\/]*$/, + exampleValue: "sdk/resourcemanager/aaa", + extraExplanation: + "The 'service-dir' should be a string that starts with 'sdk/resourcemanager/', followed by zero or more characters that are not a '/', and ends there", + condition: (_: TypeSpecConfig, _1: Rule.RuleContext) => true, + }, + { + rule: "tspconfig-go-mgmt-package-dir-match-pattern", + key: "package-dir", + type: KeyType.EmitterOption, + expectedValue: /^arm[^\/]*$/, + exampleValue: "armaaa", + extraExplanation: + "The 'package-dir' should be a string that starts with 'arm' and do not contain a forward slash (/) after it", + condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => + isManagementSDK(tspconfig, context, emitters.go), + }, + { + rule: "tspconfig-go-mgmt-module-equal-string", + key: "module", + type: KeyType.EmitterOption, + expectedValue: "github.com/Azure/azure-sdk-for-go/{service-dir}/{package-dir}", + exampleValue: "github.com/Azure/azure-sdk-for-go/{service-dir}/{package-dir}", + condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => + isManagementSDK(tspconfig, context, emitters.go), + }, + { + rule: "tspconfig-go-mgmt-fix-const-stuttering-true", + key: "fix-const-stuttering", + type: KeyType.EmitterOption, + expectedValue: true, + exampleValue: true, + condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => + isManagementSDK(tspconfig, context, emitters.go), + }, + { + rule: "tspconfig-go-mgmt-generate-examples-true", + key: "generate-examples", + type: KeyType.EmitterOption, + expectedValue: true, + exampleValue: true, + condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => + isManagementSDK(tspconfig, context, emitters.go), + }, + { + rule: "tspconfig-go-mgmt-generate-fakes-true", + key: "generate-fakes", + type: KeyType.EmitterOption, + expectedValue: true, + exampleValue: true, + condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => + isManagementSDK(tspconfig, context, emitters.go), + }, + { + rule: "tspconfig-go-mgmt-head-as-boolean-true", + key: "head-as-boolean", + type: KeyType.EmitterOption, + expectedValue: true, + exampleValue: true, + condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => + isManagementSDK(tspconfig, context, emitters.go), + }, + { + rule: "tspconfig-go-mgmt-inject-spans-true", + key: "inject-spans", + type: KeyType.EmitterOption, + expectedValue: true, + exampleValue: true, + condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => + isManagementSDK(tspconfig, context, emitters.go), + }, + // java + { + rule: "tspconfig-java-mgmt-package-dir-match-pattern", + key: "package-dir", + type: KeyType.EmitterOption, + expectedValue: /^azure(-\w+)+$/, + exampleValue: "azure-aaa", + extraExplanation: + "The 'package-dir' should be a string that starts with 'azure', followed by one or more '-' segments. Each segment can contains letters, digits, or underscores", + condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => + isManagementSDK(tspconfig, context, emitters.java), + }, + // python + { + rule: "tspconfig-python-mgmt-package-dir-match-pattern", + key: "package-dir", + type: KeyType.EmitterOption, + expectedValue: /^azure-mgmt(-[a-z]+){1,2}$/, + exampleValue: "azure-mgmt-aaa", + extraExplanation: + "The 'package-dir' should be a string that starts with 'azure-mgmt', followed by 1 or 2 hyphen-separated lowercase alphabetic segments", + condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => + isManagementSDK(tspconfig, context, emitters.python), + }, + { + rule: "tspconfig-python-mgmt-package-name-equal-string", + key: "package-name", + type: KeyType.EmitterOption, + expectedValue: "{package-dir}", + exampleValue: "{package-dir}", + condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => + isManagementSDK(tspconfig, context, emitters.python), + }, + { + rule: "tspconfig-python-mgmt-generate-test-true", + key: "generate-test", + type: KeyType.EmitterOption, + expectedValue: true, + exampleValue: true, + condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => + isManagementSDK(tspconfig, context, emitters.python), + }, + { + rule: "tspconfig-python-mgmt-generate-sample-true", + key: "generate-sample", + type: KeyType.EmitterOption, + expectedValue: true, + exampleValue: true, + condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => + isManagementSDK(tspconfig, context, emitters.python), + }, + // csharp + { + rule: "tspconfig-csharp-az-package-dir-match-pattern", + key: "package-dir", + type: KeyType.EmitterOption, + expectedValue: /^Azure\./, + exampleValue: "Azure.aaa", + extraExplanation: "The 'package-dir' should be a string that starts with 'Azure.'", + condition: (tspconfig: TypeSpecConfig, _: Rule.RuleContext) => + isAzureSDK(tspconfig, emitters.csharp), + }, + { + rule: "tspconfig-csharp-az-namespace-equal-string", + key: "namespace", + type: KeyType.EmitterOption, + expectedValue: "{package-dir}", + exampleValue: "{package-dir}", + condition: (tspconfig: TypeSpecConfig, _: Rule.RuleContext) => + isAzureSDK(tspconfig, emitters.csharp), + }, + { + rule: "tspconfig-csharp-az-clear-output-folder-true", + key: "clear-output-folder", + type: KeyType.EmitterOption, + expectedValue: true, + exampleValue: true, + condition: (tspconfig: TypeSpecConfig, _: Rule.RuleContext) => + isAzureSDK(tspconfig, emitters.csharp), + }, + { + rule: "tspconfig-csharp-mgmt-package-dir-match-pattern", + key: "package-dir", + type: KeyType.EmitterOption, + expectedValue: /^Azure\.ResourceManager\./, + exampleValue: "Azure.ResourceManager.aaa", + extraExplanation: + "The 'package-dir' should be a string that starts with 'Azure.ResourceManager.'", + condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => + isManagementSDK(tspconfig, context, emitters.csharp), + }, ]; -export default args.map((a) => createManagementClientRule(...a)); +export default args.map((a) => createCodeGenSDKRule(a)); diff --git a/eng/tools/eslint-plugin-tsv/src/utils/rule-creator.ts b/eng/tools/eslint-plugin-tsv/src/utils/rule-creator.ts new file mode 100644 index 000000000000..ab921cbd8a07 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/utils/rule-creator.ts @@ -0,0 +1,128 @@ +import { Rule } from "eslint"; +import { TypeSpecConfig } from "../config/types.js"; +import { CreateCodeGenSDKRuleArgs, KeyType, RuleDocument, RuleInfo } from "../interfaces/rule-interfaces.js"; +import { defaultMessageId, defaultRuleType, emitters } from "./constants.js"; +import { NamedRule } from "../interfaces/named-eslint.js"; +import { AST, getStaticYAMLValue } from "yaml-eslint-parser"; +import { createRuleDocument } from "./rule-doc.js"; + +export function createRule(ruleContext: RuleInfo): NamedRule.RuleModule { + const rule: NamedRule.RuleModule = { + name: ruleContext.name, + meta: { + type: defaultRuleType, + docs: { + description: ruleContext.documentation.description, + }, + schema: [], + messages: ruleContext.functions.messages(), + }, + create(context) { + return { + YAMLDocument(node: Rule.Node) { + const yamlDocument = node as unknown as AST.YAMLDocument; + const rawConfig = getStaticYAMLValue(yamlDocument) || {}; + const config = rawConfig as unknown as TypeSpecConfig; + + if (!ruleContext.functions.condition(config, context)) return; + ruleContext.functions.validation(config, context, node); + }, + }; + }, + }; + return rule; +} + +export function createRuleMessages(messageId: string, docs: RuleDocument) { + return { + [messageId]: `${docs.error}.\n${docs.action}.\n${docs.example}`, + }; +} + +export function isAzureSDK(tspconfig: TypeSpecConfig, emitterName: string) { + const flavor = tspconfig.options?.[emitterName]?.flavor as string; + return flavor === "azure"; +} + +export function isManagementSDK( + tspconfig: TypeSpecConfig, + context: Rule.RuleContext, + emitterName: string, +) { + const filename = context.filename; + return isAzureSDK(tspconfig, emitterName) && filename.includes(".Management"); +} + +function validateValue( + context: Rule.RuleContext, + node: Rule.Node, + actual: string | boolean | undefined, + expected: boolean | string | RegExp, +) { + switch (typeof expected) { + case "boolean": + case "string": + if (actual !== expected) + context.report({ node, messageId: defaultMessageId }); + break; + case "object": + if (typeof actual !== "string" || !expected.test(actual)) + context.report({ node, messageId: defaultMessageId }); + break; + case "undefined": + context.report({ node, messageId: defaultMessageId }); + break; + default: + // TODO: log not supported + break; + } +} + +// TODO: add logs +export function createCodeGenSDKRule(args: CreateCodeGenSDKRuleArgs): NamedRule.RuleModule { + const language = args.rule.split("-")[1]! as keyof typeof emitters; + const emitterName = emitters[language]; + const documentation = createRuleDocument( + emitterName, + args.type, + args.key, + args.expectedValue, + args.exampleValue, + args.extraExplanation ?? "", + ); + + const ruleInfo: RuleInfo = { + name: args.rule, + documentation: documentation!, + functions: { + messages: () => createRuleMessages(defaultMessageId, documentation), + condition: (tspconfig, context) => { + if (args.condition) return args.condition(tspconfig, context); + return true; + }, + validation: (tspconfig, context, node) => { + switch (args.type) { + case KeyType.EmitterOption: { + let option: Record | undefined = tspconfig.options?.[emitterName]; + for (const segment of args.key.split(".")) { + if (option && segment in option) option = option![segment]; + } + validateValue(context, node, option as undefined | string | boolean, args.expectedValue); + break; + } + case KeyType.Parameter: { + const parameter = tspconfig.parameters?.[args.key].default; + validateValue(context, node, parameter, args.expectedValue); + break; + } + default: + // TODO: log not supported + break; + } + + }, + }, + }; + + return createRule(ruleInfo); +} diff --git a/eng/tools/eslint-plugin-tsv/src/utils/rule-doc.ts b/eng/tools/eslint-plugin-tsv/src/utils/rule-doc.ts new file mode 100644 index 000000000000..f1525db38605 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/utils/rule-doc.ts @@ -0,0 +1,114 @@ +import { KeyType, RuleDocument } from "../interfaces/rule-interfaces.js"; +import { emitters } from "./constants.js"; +import { stringify } from "yaml"; + +function createDescriptionDocumentBlock( + displayName: string, + expectedValue: string | boolean | RegExp, + extraExplanation: string, +): string { + switch (typeof expectedValue) { + case "object": + return `Validate whether '${displayName}' matches regex pattern '${expectedValue}' in tspconfig.yaml. ${extraExplanation}`; + default: + case "string": + case "boolean": + return `Validate whether '${displayName}' is set to '${expectedValue}' in tspconfig.yaml`; + } +} + +function createErrorDocumentBlock( + displayName: string, + expectedValue: string | boolean | RegExp, +): string { + switch (typeof expectedValue) { + case "object": + return `'${displayName}' does NOT match regex pattern '${expectedValue}' in tspconfig.yaml`; + default: + case "string": + case "boolean": + return `'${displayName}' is NOT set to '${expectedValue}' in tspconfig.yaml`; + } +} + +function createActionDocumentBlock( + displayName: string, + expectedValue: string | boolean | RegExp, +): string { + switch (typeof expectedValue) { + case "object": + return `Set '${displayName}' to a value that matches regex pattern '${expectedValue}' in tspconfig.yaml`; + default: + case "string": + case "boolean": + return `Set '${displayName}' to '${expectedValue}' in tspconfig.yaml`; + } +} + +export function createRuleDocument( + emitterName: string, + keyType: KeyType, + key: string, + expectedValue: string | boolean | RegExp, + exampleValue: string | boolean, + extraExplanation: string, +): RuleDocument { + let displayName = key; + let example = ""; + switch (keyType) { + case KeyType.EmitterOption: + displayName = `options.${emitters.ts}.${key}`; + example = createEmitterOptionExample(emitterName, { key: key, value: exampleValue }); + break; + case KeyType.Parameter: + displayName = `parameters.${key}`; + example = createParameterExample({ key: key, value: exampleValue }); + break; + default: + // TODO: log not supported + displayName = key; + } + const description = createDescriptionDocumentBlock(displayName, expectedValue, extraExplanation); + const error = createErrorDocumentBlock(displayName, expectedValue); + const action = createActionDocumentBlock(displayName, expectedValue); + + const document: RuleDocument = { + description, + error, + action, + example, + }; + return document; +} + +export function createParameterExample(...pairs: { key: string; value: string | boolean | {} }[]) { + const obj: Record = { parameters: {} }; + for (const pair of pairs) { + obj.parameters[pair.key] = { default: pair.value }; + } + const content = stringify(obj); + return content; +} + +export function createEmitterOptionExample( + emitter: string, + ...pairs: { key: string; value: string | boolean | {} }[] +) { + const obj = { options: { [emitter]: {} } }; + for (const pair of pairs) { + const segments = pair.key.split("."); + let cur: Record = obj.options[emitter]; + for (const [i, segment] of segments.entries()) { + if (i === segments.length - 1) { + cur[segment] = pair.value; + break; + } + if (!(segment in cur)) { + cur[segment] = {}; + } + cur = cur[segment]; + } + } + const content = stringify(obj); + return content; +} diff --git a/eng/tools/eslint-plugin-tsv/src/utils/rule.ts b/eng/tools/eslint-plugin-tsv/src/utils/rule.ts deleted file mode 100644 index c3d1cd5972af..000000000000 --- a/eng/tools/eslint-plugin-tsv/src/utils/rule.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { Rule } from "eslint"; -import { TypeSpecConfig } from "../config/types.js"; -import { RuleDocument, RuleInfo } from "../interfaces/rule-interfaces.js"; -import { defaultMessageId, defaultRuleType, emitters } from "./constants.js"; -import { NamedRule } from "../interfaces/named-eslint.js"; -import { AST, getStaticYAMLValue } from "yaml-eslint-parser"; -import { stringify } from "yaml"; -import { resolveValues } from "./config-interpolation.js"; - -function createManagementRuleDocument( - emitterName: string, - optionName: string, - expectedOptionValue: string | boolean | RegExp, - exampleValue: string | boolean, - extraExplanation: string, -): RuleDocument { - let description: string = ""; - switch (typeof expectedOptionValue) { - case "string": - case "boolean": - `Validate whether 'options.${emitters.ts}.${optionName}' is set to '${expectedOptionValue}' in tspconfig.yaml when generating modular clients`; - break; - case "object": - `Validate whether 'options.${emitters.ts}.${optionName}' matches regex pattern '${expectedOptionValue}' in tspconfig.yaml when generating modular clients. ${extraExplanation}`; - break; - } - - const document: RuleDocument = { - description, - error: `'options.${emitters.ts}.${optionName}' is NOT set to '${expectedOptionValue}' in tspconfig.yaml when generating modular clients`, - action: `Set 'options.${emitters.ts}.${optionName}' to '${expectedOptionValue}' in tspconfig.yaml when generating modular clients`, - example: createEmitterOptions(emitterName, { key: optionName, value: exampleValue }), - }; - return document; -} - -export function createRule(ruleContext: RuleInfo): NamedRule.RuleModule { - const rule: NamedRule.RuleModule = { - name: ruleContext.name, - meta: { - type: defaultRuleType, - docs: { - description: ruleContext.documentation.description, - }, - schema: [], - messages: ruleContext.functions.messages(), - }, - create(context) { - return { - YAMLDocument(node: Rule.Node) { - const yamlDocument = node as unknown as AST.YAMLDocument; - const rawConfig = getStaticYAMLValue(yamlDocument) || {}; - const config = rawConfig as unknown as TypeSpecConfig; - - if (!ruleContext.functions.condition(config, context)) return; - ruleContext.functions.validation(config, context, node); - }, - }; - }, - }; - return rule; -} - -export function createRuleMessages(messageId: string, docs: RuleDocument) { - return { - [messageId]: `${docs.error}.\n${docs.action}.\n${docs.example}`, - }; -} - -export function isManagementSDK( - tspconfig: TypeSpecConfig, - context: Rule.RuleContext, - emitterName: string, -) { - const flavor = tspconfig.options?.[emitterName]?.flavor as string; - const filename = context.filename; - return flavor === "azure" && filename.includes(".Management"); -} - -export function createEmitterOptions( - emitter: string, - ...pairs: { key: string; value: string | boolean | {} }[] -) { - const obj = { options: { [emitter]: {} } }; - for (const pair of pairs) { - const segments = pair.key.split("."); - let cur: { [id: string]: any } = obj.options[emitter]; - for (const [i, segment] of segments.entries()) { - if (i === segments.length - 1) { - cur[segment] = pair.value; - break; - } - if (!(segment in cur)) { - cur[segment] = {}; - } - cur = cur[segment]; - } - } - const content = stringify(obj); - return content; -} - -export function createManagementClientRule( - ruleName: string, - optionName: string, - expectedOptionValue: string | boolean | RegExp, - exampleValue: string | boolean, - extraExplanation: string = "", -): NamedRule.RuleModule { - const language = ruleName.split("-")[1]! as keyof typeof emitters; - const emitterName = emitters[language]; - const documentation = createManagementRuleDocument( - emitterName, - optionName, - expectedOptionValue, - exampleValue, - extraExplanation, - ); - - const ruleInfo: RuleInfo = { - name: ruleName, - documentation, - functions: { - messages: () => createRuleMessages(defaultMessageId, documentation), - condition: (tspconfig, context) => { - const isManagement = isManagementSDK(tspconfig, context, emitterName); - if (!isManagement) return false; - - if (emitterName === emitters.ts) { - const isModularLibrary = tspconfig.options?.[emitters.ts]?.isModularLibrary as - | boolean - | undefined; - return isModularLibrary === undefined || isModularLibrary === true; - } - return true; - }, - validation: (tspconfig, context, node) => { - let option: any = tspconfig.options?.[emitterName]; - for (const segment of optionName.split(".")) { - if (segment in option) option = option[segment]; - } - switch (typeof expectedOptionValue) { - case "boolean": - case "string": - if (option !== expectedOptionValue) - context.report({ node, messageId: defaultMessageId }); - break; - case "object": - if (typeof option !== "string" || !expectedOptionValue.test(option)) - context.report({ node, messageId: defaultMessageId }); - break; - } - }, - }, - }; - - return createRule(ruleInfo); -} diff --git a/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts b/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts index 47506385e566..a2456cefe83f 100644 --- a/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts @@ -2,8 +2,8 @@ import { Rule, RuleTester } from "eslint"; import { describe, it } from "vitest"; import parser from "yaml-eslint-parser"; import { defaultMessageId, emitters } from "../../src/utils/constants.js"; -import { createEmitterOptions } from "../../src/utils/rule.js"; import { NamedRule } from "../../src/interfaces/named-eslint.js"; +import { createEmitterOptionExample, createParameterExample } from "../../src/utils/rule-doc.js"; interface Case { description: string; @@ -17,7 +17,16 @@ interface Case { const managementTspconfigPath = "contosowidgetmanager/Contoso.Management/tspconfig.yaml"; const rulePath = "../../src/rules/tspconfig-validation-rules.js"; -const tsManagementGenerateMetadataTestCases = generateManagementClientTestCases( +const commonAzureServiceDirTestCases = createParameterTestCases( + rulePath, + "tspconfig-common-az-service-dir-match-pattern", + "", + "service-dir", + "sdk/aaa", + "sdka/aaa", +); + +const tsManagementGenerateMetadataTestCases = createEmitterOptionTestCases( emitters.ts, rulePath, "tspconfig-ts-mgmt-modular-generate-metadata-true", @@ -27,7 +36,7 @@ const tsManagementGenerateMetadataTestCases = generateManagementClientTestCases( false, ); -const tsManagementHierarchyClientTestCases = generateManagementClientTestCases( +const tsManagementHierarchyClientTestCases = createEmitterOptionTestCases( emitters.ts, rulePath, "tspconfig-ts-mgmt-modular-hierarchy-client-false", @@ -37,7 +46,7 @@ const tsManagementHierarchyClientTestCases = generateManagementClientTestCases( true, ); -const tsManagementExperimentalExtensibleEnumsTestCases = generateManagementClientTestCases( +const tsManagementExperimentalExtensibleEnumsTestCases = createEmitterOptionTestCases( emitters.ts, rulePath, "tspconfig-ts-mgmt-modular-experimental-extensible-enums-true", @@ -47,7 +56,7 @@ const tsManagementExperimentalExtensibleEnumsTestCases = generateManagementClien false, ); -const tsManagementEnableOperationGroupTestCases = generateManagementClientTestCases( +const tsManagementEnableOperationGroupTestCases = createEmitterOptionTestCases( emitters.ts, rulePath, "tspconfig-ts-mgmt-modular-enable-operation-group-true", @@ -57,7 +66,7 @@ const tsManagementEnableOperationGroupTestCases = generateManagementClientTestCa false, ); -const tsManagementPackageDirTestCases = generateManagementClientTestCases( +const tsManagementPackageDirTestCases = createEmitterOptionTestCases( emitters.ts, rulePath, "tspconfig-ts-mgmt-modular-package-dir-match-pattern", @@ -67,7 +76,7 @@ const tsManagementPackageDirTestCases = generateManagementClientTestCases( "aaa-bbb", ); -const tsManagementPackageNameTestCases = generateManagementClientTestCases( +const tsManagementPackageNameTestCases = createEmitterOptionTestCases( emitters.ts, rulePath, "tspconfig-ts-mgmt-modular-package-name-match-pattern", @@ -77,7 +86,7 @@ const tsManagementPackageNameTestCases = generateManagementClientTestCases( "@azure/aaa-bbb", ); -const goManagementServiceDirTestCases = generateManagementClientTestCases( +const goManagementServiceDirTestCases = createEmitterOptionTestCases( emitters.go, rulePath, "tspconfig-go-mgmt-service-dir-match-pattern", @@ -87,7 +96,7 @@ const goManagementServiceDirTestCases = generateManagementClientTestCases( "sdk/manager/aaa", ); -const goManagementPackageDirTestCases = generateManagementClientTestCases( +const goManagementPackageDirTestCases = createEmitterOptionTestCases( emitters.go, rulePath, "tspconfig-go-mgmt-package-dir-match-pattern", @@ -97,7 +106,7 @@ const goManagementPackageDirTestCases = generateManagementClientTestCases( "aaa", ); -const goManagementModuleTestCases = generateManagementClientTestCases( +const goManagementModuleTestCases = createEmitterOptionTestCases( emitters.go, rulePath, "tspconfig-go-mgmt-module-equal-string", @@ -107,7 +116,7 @@ const goManagementModuleTestCases = generateManagementClientTestCases( "github.com/Azure/azure-sdk-for-java/{service-dir}/{package-dir}", ); -const goManagementFixConstStutteringTestCases = generateManagementClientTestCases( +const goManagementFixConstStutteringTestCases = createEmitterOptionTestCases( emitters.go, rulePath, "tspconfig-go-mgmt-fix-const-stuttering-true", @@ -117,7 +126,7 @@ const goManagementFixConstStutteringTestCases = generateManagementClientTestCase false, ); -const goManagementGenerateExamplesTestCases = generateManagementClientTestCases( +const goManagementGenerateExamplesTestCases = createEmitterOptionTestCases( emitters.go, rulePath, "tspconfig-go-mgmt-generate-examples-true", @@ -127,7 +136,7 @@ const goManagementGenerateExamplesTestCases = generateManagementClientTestCases( false, ); -const goManagementGenerateFakesTestCases = generateManagementClientTestCases( +const goManagementGenerateFakesTestCases = createEmitterOptionTestCases( emitters.go, rulePath, "tspconfig-go-mgmt-generate-fakes-true", @@ -137,7 +146,7 @@ const goManagementGenerateFakesTestCases = generateManagementClientTestCases( false, ); -const goManagementHeadAsBooleanTestCases = generateManagementClientTestCases( +const goManagementHeadAsBooleanTestCases = createEmitterOptionTestCases( emitters.go, rulePath, "tspconfig-go-mgmt-head-as-boolean-true", @@ -147,7 +156,7 @@ const goManagementHeadAsBooleanTestCases = generateManagementClientTestCases( false, ); -const goManagementInjectSpansTestCases = generateManagementClientTestCases( +const goManagementInjectSpansTestCases = createEmitterOptionTestCases( emitters.go, rulePath, "tspconfig-go-mgmt-inject-spans-true", @@ -157,54 +166,100 @@ const goManagementInjectSpansTestCases = generateManagementClientTestCases( false, ); -function generateManagementClientTestCases( - emitterName: string, - rulePath: string, - ruleName: string, - fileName: string, - optionName: string, - validOptionValue: boolean | string, - invalidOptionValue: boolean | string, -): Case[] { - const managementGenerateMetadataTestCases: Case[] = [ - { - description: `valid: ${optionName} is ${validOptionValue}`, - rulePath, - ruleName, - fileName, - yamlContent: createEmitterOptions( - emitterName, - { key: optionName, value: validOptionValue }, - { key: "flavor", value: "azure" }, - ), - shouldReportError: false, - }, - { - description: `invalid: ${optionName} is ${invalidOptionValue}`, - rulePath, - ruleName, - fileName, - yamlContent: createEmitterOptions( - emitterName, - { key: optionName, value: invalidOptionValue }, - { key: "flavor", value: "azure" }, - ), - shouldReportError: true, - }, - { - description: `invalid: ${optionName} is undefined`, - rulePath, - ruleName, - fileName, - yamlContent: createEmitterOptions(emitterName, { key: "flavor", value: "azure" }), - shouldReportError: true, - }, - ]; - return managementGenerateMetadataTestCases; -} +const javaManagementPackageDirTestCases = createEmitterOptionTestCases( + emitters.java, + rulePath, + "tspconfig-java-mgmt-package-dir-match-pattern", + managementTspconfigPath, + "package-dir", + "azure-aaa", + "aaa", +); + +const pythonManagementPackageDirTestCases = createEmitterOptionTestCases( + emitters.python, + rulePath, + "tspconfig-python-mgmt-package-dir-match-pattern", + managementTspconfigPath, + "package-dir", + "azure-mgmt-aaa", + "azure-aaa", +); + +const pythonManagementPackageNameTestCases = createEmitterOptionTestCases( + emitters.python, + rulePath, + "tspconfig-python-mgmt-package-name-equal-string", + managementTspconfigPath, + "package-name", + "{package-dir}", + "aaa", +); + +const pythonManagementGenerateTestTestCases = createEmitterOptionTestCases( + emitters.python, + rulePath, + "tspconfig-python-mgmt-generate-test-true", + managementTspconfigPath, + "generate-test", + true, + false, +); + +const pythonManagementGenerateSampleTestCases = createEmitterOptionTestCases( + emitters.python, + rulePath, + "tspconfig-python-mgmt-generate-sample-true", + managementTspconfigPath, + "generate-sample", + true, + false, +); + +const csharpAzPackageDirTestCases = createEmitterOptionTestCases( + emitters.csharp, + rulePath, + "tspconfig-csharp-az-package-dir-match-pattern", + "", + "package-dir", + "Azure.AAA", + "AAA", +); + +const csharpAzNamespaceTestCases = createEmitterOptionTestCases( + emitters.csharp, + rulePath, + "tspconfig-csharp-az-namespace-equal-string", + "", + "namespace", + "{package-dir}", + "AAA", +); + +const csharpAzClearOutputFolderTestCases = createEmitterOptionTestCases( + emitters.csharp, + rulePath, + "tspconfig-csharp-az-clear-output-folder-true", + "", + "clear-output-folder", + true, + false, +); + +const csharpMgmtPackageDirTestCases = createEmitterOptionTestCases( + emitters.csharp, + rulePath, + "tspconfig-csharp-mgmt-package-dir-match-pattern", + managementTspconfigPath, + "package-dir", + "Azure.ResourceManager.AAA", + "Azure.Management.AAA", +); describe("Tspconfig emitter options validation", () => { it.each([ + // common + ...commonAzureServiceDirTestCases, // ts ...tsManagementGenerateMetadataTestCases, ...tsManagementHierarchyClientTestCases, @@ -221,6 +276,18 @@ describe("Tspconfig emitter options validation", () => { ...goManagementGenerateFakesTestCases, ...goManagementHeadAsBooleanTestCases, ...goManagementInjectSpansTestCases, + // java + ...javaManagementPackageDirTestCases, + // python + ...pythonManagementPackageDirTestCases, + ...pythonManagementPackageNameTestCases, + ...pythonManagementGenerateTestTestCases, + ...pythonManagementGenerateSampleTestCases, + // csharp + ...csharpAzPackageDirTestCases, + ...csharpAzNamespaceTestCases, + ...csharpAzClearOutputFolderTestCases, + ...csharpMgmtPackageDirTestCases, ])("$ruleName - $description", async (c: Case) => { const ruleTester = new RuleTester({ languageOptions: { @@ -253,3 +320,86 @@ describe("Tspconfig emitter options validation", () => { ruleTester.run(rule.name, rule as Rule.RuleModule, tests); }); }); + +function createEmitterOptionTestCases( + emitterName: string, + rulePath: string, + ruleName: string, + fileName: string, + key: string, + validValue: boolean | string, + invalidValue: boolean | string, +): Case[] { + const managementGenerateMetadataTestCases: Case[] = [ + { + description: `valid: ${key} is ${validValue}`, + rulePath, + ruleName, + fileName, + yamlContent: createEmitterOptionExample( + emitterName, + { key: key, value: validValue }, + { key: "flavor", value: "azure" }, + ), + shouldReportError: false, + }, + { + description: `invalid: ${key} is ${invalidValue}`, + rulePath, + ruleName, + fileName, + yamlContent: createEmitterOptionExample( + emitterName, + { key: key, value: invalidValue }, + { key: "flavor", value: "azure" }, + ), + shouldReportError: true, + }, + { + description: `invalid: ${key} is undefined`, + rulePath, + ruleName, + fileName, + yamlContent: createEmitterOptionExample(emitterName, { key: "flavor", value: "azure" }), + shouldReportError: true, + }, + ]; + return managementGenerateMetadataTestCases; +} + +function createParameterTestCases( + rulePath: string, + ruleName: string, + fileName: string, + key: string, + validValue: boolean | string, + invalidValue: boolean | string, +): Case[] { + const managementGenerateMetadataTestCases: Case[] = [ + { + description: `valid: ${key} is ${validValue}`, + rulePath, + ruleName, + fileName, + yamlContent: createParameterExample({ key: key, value: validValue }), + shouldReportError: false, + }, + { + description: `invalid: ${key} is ${invalidValue}`, + rulePath, + ruleName, + fileName, + yamlContent: createParameterExample({ key: key, value: invalidValue }), + shouldReportError: true, + }, + { + description: `invalid: ${key} is undefined`, + rulePath, + ruleName, + fileName, + yamlContent: "", + shouldReportError: true, + }, + ]; + return managementGenerateMetadataTestCases; +} From 3c767cfec373e503808ce845668508e44e8915e8 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Thu, 23 Jan 2025 10:42:41 +0800 Subject: [PATCH 066/106] update rule setup --- eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts | 6 ++++++ eng/tools/eslint-plugin-tsv/test/e2e.test.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts index 3e1867522c3f..027cee59ab7a 100644 --- a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts +++ b/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts @@ -2,6 +2,7 @@ import parser from "yaml-eslint-parser"; import { NamedESLint } from "./interfaces/named-eslint.js"; import emitAutorest from "./rules/emit-autorest.js"; import kebabCaseOrg from "./rules/kebab-case-org.js"; +import tspconfigValidationRules from "./rules/tspconfig-validation-rules.js"; const plugin: NamedESLint.Plugin = { configs: { recommended: {} }, @@ -26,4 +27,9 @@ plugin.configs.recommended = { }, }; +tspconfigValidationRules.forEach((rule) => { + plugin.rules![rule.name] = rule; + plugin.configs.recommended.rules![`${plugin.name}/${rule.name}`] = "error"; +}); + export default plugin; diff --git a/eng/tools/eslint-plugin-tsv/test/e2e.test.ts b/eng/tools/eslint-plugin-tsv/test/e2e.test.ts index 7c8ed1376584..e6da7e107a9d 100644 --- a/eng/tools/eslint-plugin-tsv/test/e2e.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/e2e.test.ts @@ -5,7 +5,7 @@ import eslintPluginTsv from "../src/eslint-plugin-tsv.js"; function createESLint() { return new ESLint({ - cwd: "/", + cwd: join(__dirname, "../../../../"), overrideConfig: eslintPluginTsv.configs.recommended, overrideConfigFile: true, }); From 68aa3950b16e6c2616d0e5f590e7033c059e0f88 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Thu, 23 Jan 2025 15:37:13 +0800 Subject: [PATCH 067/106] remove workflow --- .github/workflows/eslint-plugin-tsv-test.yaml | 25 ------------------- 1 file changed, 25 deletions(-) delete mode 100644 .github/workflows/eslint-plugin-tsv-test.yaml diff --git a/.github/workflows/eslint-plugin-tsv-test.yaml b/.github/workflows/eslint-plugin-tsv-test.yaml deleted file mode 100644 index 9fcdc7b47f46..000000000000 --- a/.github/workflows/eslint-plugin-tsv-test.yaml +++ /dev/null @@ -1,25 +0,0 @@ -name: ESLint Plugin for TypeSpec Validation - Test - -on: - push: - branches: - - main - - typespec-next - pull_request: - paths: - - package-lock.json - - package.json - - tsconfig.json - - .github/workflows/_reusable-eng-tools-test.yaml - - .github/workflows/eslint-plugin-tsv-test.yaml - - eng/tools/package.json - - eng/tools/tsconfig.json - - eng/tools/eslint-plugin-tsv/** - - specification/contosowidgetmanager - workflow_dispatch: - -jobs: - eslint-plugin-tsv: - uses: ./.github/workflows/_reusable-eng-tools-test.yaml - with: - package: eslint-plugin-tsv From cac050447a03320ef151eddaf3257db7f2b031f8 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Thu, 23 Jan 2025 15:39:29 +0800 Subject: [PATCH 068/106] remove an unused file --- .../src/utils/config-interpolation.ts | 221 ------------------ 1 file changed, 221 deletions(-) delete mode 100644 eng/tools/eslint-plugin-tsv/src/utils/config-interpolation.ts diff --git a/eng/tools/eslint-plugin-tsv/src/utils/config-interpolation.ts b/eng/tools/eslint-plugin-tsv/src/utils/config-interpolation.ts deleted file mode 100644 index cd7113383d03..000000000000 --- a/eng/tools/eslint-plugin-tsv/src/utils/config-interpolation.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { - ConfigParameter, - EmitterOptions, - ConfigEnvironmentVariable, - TypeSpecConfig, -} from "../config/types.js"; - -// dummy -interface Diagnostic {} -type DiagnosticResult = [T, readonly Diagnostic[]]; -const NoTarget = 0; -function createDiagnosticCollector() { - return { - pipe: (x: DiagnosticResult): T => { - return x[0]; - }, - wrap: (x: T): [T, [Diagnostic]] => { - return [x, [{}]]; - }, - }; -} -function ignoreDiagnostics(result: DiagnosticResult): T { - return result[0]; -} -function createDiagnostic(diag: any): Diagnostic { - return {}; -} -// - -// Copied from https://github.com/microsoft/typespec/blob/main/packages/compiler/src/config/config-interpolation.ts -export interface ExpandConfigOptions { - readonly cwd: string; - readonly outputDir?: string; - readonly env?: Record; - readonly args?: Record; -} - -export function expandConfigVariables( - config: TypeSpecConfig, - expandOptions: ExpandConfigOptions, -): [TypeSpecConfig, readonly Diagnostic[]] { - const diagnostics = createDiagnosticCollector(); - const builtInVars = { - "project-root": config.projectRoot, - cwd: expandOptions.cwd, - }; - - const resolvedArgsParameters = diagnostics.pipe( - resolveArgs(config.parameters, expandOptions.args, builtInVars), - ); - const commonVars = { - ...builtInVars, - ...resolvedArgsParameters, - ...diagnostics.pipe(resolveArgs(config.options, {}, resolvedArgsParameters)), - env: diagnostics.pipe( - resolveArgs(config.environmentVariables, expandOptions.env, builtInVars, true), - ), - }; - const outputDir = diagnostics.pipe( - resolveValue(expandOptions.outputDir ?? config.outputDir, commonVars), - ); - - const result = { ...config, outputDir }; - if (config.options) { - const options: Record = {}; - for (const [name, emitterOptions] of Object.entries(config.options)) { - const emitterVars = { ...commonVars, "output-dir": outputDir, "emitter-name": name }; - options[name] = diagnostics.pipe(resolveValues(emitterOptions, emitterVars)); - } - result.options = options; - } - - return diagnostics.wrap(result); -} - -function resolveArgs( - declarations: - | Record - | undefined, - args: Record | undefined, - predefinedVariables: Record>, - allowUnspecified = false, -): [Record, readonly Diagnostic[]] { - function tryGetValue(value: any): string | undefined { - return typeof value === "string" ? value : undefined; - } - const unmatchedArgs = new Set(Object.keys(args ?? {})); - const result: Record = {}; - - function resolveNestedArgs( - parentName: string, - declarations: [string, ConfigParameter | ConfigEnvironmentVariable | EmitterOptions][], - ) { - for (const [declarationName, definition] of declarations) { - const name = parentName ? `${parentName}.${declarationName}` : declarationName; - if (hasNestedValues(definition)) { - resolveNestedArgs(name, Object.entries(definition ?? {})); - } - unmatchedArgs.delete(name); - result[name] = ignoreDiagnostics( - resolveValue( - args?.[name] ?? tryGetValue(definition.default) ?? tryGetValue(definition) ?? "", - predefinedVariables, - ), - ); - } - } - - if (declarations !== undefined) { - resolveNestedArgs("", Object.entries(declarations ?? {})); - } - - if (!allowUnspecified) { - const diagnostics: Diagnostic[] = [...unmatchedArgs].map((unmatchedArg) => { - return createDiagnostic({ - code: "config-invalid-argument", - format: { name: unmatchedArg }, - target: NoTarget, - }); - }); - return [result, diagnostics]; - } - return [result, []]; -} - -function hasNestedValues(value: any): boolean { - return ( - value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length > 0 - ); -} - -const VariableInterpolationRegex = /{([a-zA-Z-_.]+)}/g; - -function resolveValue( - value: string, - predefinedVariables: Record>, -): [string, readonly Diagnostic[]] { - const [result, diagnostics] = resolveValues({ value }, predefinedVariables); - return [result.value, diagnostics]; -} - -export function resolveValues>( - values: T, - predefinedVariables: Record> = {}, -): [T, readonly Diagnostic[]] { - const diagnostics: Diagnostic[] = []; - const resolvedValues: Record = {}; - const resolvingValues = new Set(); - - function resolveValue(keys: string[]): unknown { - resolvingValues.add(keys[0]); - let value: any = values; - value = keys.reduce((acc, key) => acc?.[key], value); - - if (typeof value !== "string") { - if (hasNestedValues(value)) { - value = value as Record; - const resultObject: Record = {}; - for (const [nestedKey] of Object.entries(value)) { - resolvingValues.add(nestedKey); - resultObject[nestedKey] = resolveValue(keys.concat(nestedKey)) as any; - } - return resultObject; - } - return value; - } - return value.replace(VariableInterpolationRegex, (match, expression) => { - return (resolveExpression(expression) as string) ?? `{${expression}}`; - }); - } - - function resolveExpression(expression: string): unknown | undefined { - if (expression in resolvedValues) { - return resolvedValues[expression]; - } - - if (resolvingValues.has(expression)) { - diagnostics.push( - createDiagnostic({ - code: "config-circular-variable", - target: NoTarget, - format: { name: expression }, - }), - ); - return undefined; - } - - if (expression in values) { - return resolveValue([expression]) as any; - } - - let resolved: any = predefinedVariables; - if (expression in resolved) { - return resolved[expression]; - } - - const segments = expression.split("."); - for (const segment of segments) { - resolved = resolved[segment]; - if (resolved === undefined) { - return undefined; - } - } - - if (typeof resolved === "string") { - return resolved; - } else { - return undefined; - } - } - - for (const key of Object.keys(values)) { - resolvingValues.clear(); - if (key in resolvedValues) { - continue; - } - resolvedValues[key] = resolveValue([key]) as any; - } - - return [resolvedValues as any, diagnostics]; -} From a0dbd75d45a90a3d99ac900824684bf02976ec42 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Thu, 23 Jan 2025 17:17:50 +0800 Subject: [PATCH 069/106] converting --- eng/tools/eslint-plugin-tsv/src/index.ts | 3 + eng/tools/typespec-validation/package.json | 1 + .../src/rules/tspconfig-validation-rules.ts | 35 + .../test/tspconfig.test.ts | 5 + package-lock.json | 995 ++++++++++++++++++ 5 files changed, 1039 insertions(+) create mode 100644 eng/tools/eslint-plugin-tsv/src/index.ts create mode 100644 eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts diff --git a/eng/tools/eslint-plugin-tsv/src/index.ts b/eng/tools/eslint-plugin-tsv/src/index.ts new file mode 100644 index 000000000000..24e0bf0e1925 --- /dev/null +++ b/eng/tools/eslint-plugin-tsv/src/index.ts @@ -0,0 +1,3 @@ +import tsvPlugin from "./eslint-plugin-tsv.js"; + +export default tsvPlugin; diff --git a/eng/tools/typespec-validation/package.json b/eng/tools/typespec-validation/package.json index c4cdbafaa805..c2516ce524f2 100644 --- a/eng/tools/typespec-validation/package.json +++ b/eng/tools/typespec-validation/package.json @@ -10,6 +10,7 @@ "globby": "^14.0.1", "simple-git": "^3.24.0", "suppressions": "file:../suppressions", + "eslint-plugin-tsv": "file:../eslint-plugin-tsv", "yaml": "^2.4.2" }, "devDependencies": { diff --git a/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts b/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts new file mode 100644 index 000000000000..61ebf2dad014 --- /dev/null +++ b/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts @@ -0,0 +1,35 @@ +import { join } 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"; + +import tsvPlugin from "eslint-plugin-tsv" + +function convertToOldRules() { + console.log('plugin name', tsvPlugin); + + let newRules = []; + for (const [_, rule] of Object.entries(tsvPlugin.rules??{})) { + if (!rule.name.startsWith("tspconfig-")) continue; + const oldRule: Rule = { + name: rule.name, + description: rule.meta?.docs?.description ?? "", + async execute(host: TsvHost, folder: string): Promise { + const configText = await host.readTspConfig(folder); + const config = yamlParse(configText); + + const ruleListener = rule.create(context); + return { + }; + } + } + newRules.push(oldRule); + } + +} + +const rules = convertToOldRules(); + + +export default rules; \ No newline at end of file diff --git a/eng/tools/typespec-validation/test/tspconfig.test.ts b/eng/tools/typespec-validation/test/tspconfig.test.ts index 3be4e4f1deb3..6c09cbe1bfea 100644 --- a/eng/tools/typespec-validation/test/tspconfig.test.ts +++ b/eng/tools/typespec-validation/test/tspconfig.test.ts @@ -1,6 +1,7 @@ import { describe, it } from "vitest"; import { join } from "path"; import { TspConfigJavaPackageDirectoryRule } from "../src/rules/tspconfig-java-package-dir.js"; +import tspconfigRules from "../src/rules/tspconfig-validation-rules.js"; import { TsvTestHost } from "./tsv-test-host.js"; import { strict as assert, strictEqual } from "node:assert"; import { Rule } from "../src/rule.js"; @@ -98,3 +99,7 @@ describe("tspconfig", function () { }, ); }); + +describe("convert new ruls to old rules", function () { + +}) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f1ffe4f00aa3..e2e388ec99ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "dev": true, "hasInstallScript": true, "devDependencies": { + "@azure-tools/eslint-plugin-tsv": "file:eslint-plugin-tsv", "@azure-tools/sdk-suppressions": "file:sdk-suppressions", "@azure-tools/specs-model": "file:specs-model", "@azure-tools/suppressions": "file:suppressions", @@ -48,6 +49,798 @@ "@azure-tools/typespec-validation": "file:typespec-validation" } }, + "eng/tools/eslint-plugin-tsv": { + "dev": true, + "dependencies": { + "ajv": "^8.17.1", + "yaml-eslint-parser": "^1.2.3" + }, + "devDependencies": { + "@types/node": "^18.19.31", + "@vitest/coverage-v8": "^2.0.4", + "eslint": "^9.17.0", + "memfs": "^4.15.0", + "rimraf": "^5.0.10", + "typescript": "~5.6.2", + "vitest": "^2.0.4" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0" + } + }, + "eng/tools/eslint-plugin-tsv/node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "eng/tools/eslint-plugin-tsv/node_modules/@vitest/coverage-v8": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.8.tgz", + "integrity": "sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.7", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", + "std-env": "^3.8.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "2.1.8", + "vitest": "2.1.8" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "eng/tools/eslint-plugin-tsv/node_modules/@vitest/expect": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.8.tgz", + "integrity": "sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==", + "dev": true, + "dependencies": { + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "eng/tools/eslint-plugin-tsv/node_modules/@vitest/pretty-format": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz", + "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==", + "dev": true, + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "eng/tools/eslint-plugin-tsv/node_modules/@vitest/runner": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.8.tgz", + "integrity": "sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==", + "dev": true, + "dependencies": { + "@vitest/utils": "2.1.8", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "eng/tools/eslint-plugin-tsv/node_modules/@vitest/snapshot": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.8.tgz", + "integrity": "sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.1.8", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "eng/tools/eslint-plugin-tsv/node_modules/@vitest/spy": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.8.tgz", + "integrity": "sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==", + "dev": true, + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "eng/tools/eslint-plugin-tsv/node_modules/@vitest/utils": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.8.tgz", + "integrity": "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.1.8", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "eng/tools/eslint-plugin-tsv/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "eng/tools/eslint-plugin-tsv/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "eng/tools/eslint-plugin-tsv/node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "eng/tools/eslint-plugin-tsv/node_modules/vite-node": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.8.tgz", + "integrity": "sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "eng/tools/eslint-plugin-tsv/node_modules/vite-node/node_modules/vite": { + "version": "5.4.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", + "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "eng/tools/eslint-plugin-tsv/node_modules/vitest": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.8.tgz", + "integrity": "sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==", + "dev": true, + "dependencies": { + "@vitest/expect": "2.1.8", + "@vitest/mocker": "2.1.8", + "@vitest/pretty-format": "^2.1.8", + "@vitest/runner": "2.1.8", + "@vitest/snapshot": "2.1.8", + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.8", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.8", + "@vitest/ui": "2.1.8", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "eng/tools/eslint-plugin-tsv/node_modules/vitest/node_modules/@vitest/mocker": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.8.tgz", + "integrity": "sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==", + "dev": true, + "dependencies": { + "@vitest/spy": "2.1.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "eng/tools/eslint-plugin-tsv/node_modules/vitest/node_modules/vite": { + "version": "5.4.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", + "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "eng/tools/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "eng/tools/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "eng/tools/node_modules/@types/node": { "version": "18.19.71", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.71.tgz", @@ -587,6 +1380,10 @@ "node": ">=12.0.0" } }, + "node_modules/@azure-tools/eslint-plugin-tsv": { + "resolved": "eng/tools/eslint-plugin-tsv", + "link": true + }, "node_modules/@azure-tools/openapi-tools-common": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@azure-tools/openapi-tools-common/-/openapi-tools-common-1.2.2.tgz", @@ -2360,6 +3157,60 @@ "jsep": "^0.4.0||^1.0.0" } }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.1.tgz", + "integrity": "sha512-osjeBqMJ2lb/j/M8NCPjs1ylqWIcTRTycIhVB5pt6LgzgeRSb0YRZ7j9RfA8wIUrsr/medIuhVyonXRZWLyfdw==", + "dev": true, + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", + "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/@kwsites/file-exists": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", @@ -5992,6 +6843,15 @@ "dev": true, "license": "Unlicense" }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "engines": { + "node": ">=10.18" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -6821,6 +7681,25 @@ "dev": true, "license": "MIT" }, + "node_modules/memfs": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.0.tgz", + "integrity": "sha512-4eirfZ7thblFmqFjywlTmuWVSvccHAJbn1r8qQLzmTO11qcqpohOjmY2mFce6x7x7WtskzRqApPD0hv+Oa74jg==", + "dev": true, + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -8372,6 +9251,65 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rollup": { "version": "4.31.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.31.0.tgz", @@ -9047,6 +9985,18 @@ "dev": true, "license": "MIT" }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "dev": true, + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -9128,6 +10078,22 @@ "dev": true, "license": "MIT" }, + "node_modules/tree-dump": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", + "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", @@ -9872,6 +10838,35 @@ "node": ">= 14" } }, + "node_modules/yaml-eslint-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/yaml-eslint-parser/-/yaml-eslint-parser-1.2.3.tgz", + "integrity": "sha512-4wZWvE398hCP7O8n3nXKu/vdq1HcH01ixYlCREaJL5NUMwQ0g3MaGFUBNSlmBtKmhbtVG/Cm6lyYmSVTEVil8A==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.0.0", + "lodash": "^4.17.21", + "yaml": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, + "node_modules/yaml-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", From e7dd065a160c31aa3e2341654fde2de9f8444b64 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 24 Jan 2025 00:23:20 +0800 Subject: [PATCH 070/106] WIP --- eng/tools/eslint-plugin-tsv/src/index.ts | 3 + .../src/rules/tspconfig-validation-rules.ts | 46 +++++-- .../test/tspconfig.test.ts | 123 +++++++++--------- 3 files changed, 101 insertions(+), 71 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/src/index.ts b/eng/tools/eslint-plugin-tsv/src/index.ts index 24e0bf0e1925..1e0a6b8bfcfe 100644 --- a/eng/tools/eslint-plugin-tsv/src/index.ts +++ b/eng/tools/eslint-plugin-tsv/src/index.ts @@ -1,3 +1,6 @@ +import { Rule, ESLint, Linter } from "eslint"; import tsvPlugin from "./eslint-plugin-tsv.js"; +import { parseForESLint, parseYAML } from "yaml-eslint-parser"; +export { parseForESLint, parseYAML, Rule as ESRule, ESLint, Linter }; export default tsvPlugin; diff --git a/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts b/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts index 61ebf2dad014..5bfabe7ed285 100644 --- a/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts +++ b/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts @@ -4,32 +4,52 @@ import { Rule } from "../rule.js"; import { RuleResult } from "../rule-result.js"; import { TsvHost } from "../tsv-host.js"; -import tsvPlugin from "eslint-plugin-tsv" +import tsvPlugin, { ESLint, ESRule, parseForESLint } from "eslint-plugin-tsv"; + +async function runESLint(content: string) { + const eslint = new ESLint({ + cwd: join(__dirname, "../../../../"), + overrideConfig: tsvPlugin.configs.recommended, + overrideConfigFile: true, + }); + const results = await eslint.lintText(content, {filePath: 'tspconfig.yaml'}); + return results; +} function convertToOldRules() { - console.log('plugin name', tsvPlugin); - - let newRules = []; - for (const [_, rule] of Object.entries(tsvPlugin.rules??{})) { + let oldRules = []; + for (const [_, rule] of Object.entries(tsvPlugin.rules ?? {})) { if (!rule.name.startsWith("tspconfig-")) continue; const oldRule: Rule = { name: rule.name, description: rule.meta?.docs?.description ?? "", async execute(host: TsvHost, folder: string): Promise { const configText = await host.readTspConfig(folder); - const config = yamlParse(configText); - - const ruleListener = rule.create(context); + // const parsed = parseForESLint(configText, {location: true, }); + // const node = parsed as unknown as ESRule.Node; + // console.log('---node', node) + // const context = createFakeRuleContext(folder); + // const ruleListener = rule.create(context); + // const runTspConfigRule = ruleListener.YAMLDocument as (node: ESRule.Node) => void; + // if (runTspConfigRule) runTspConfigRule(node); + console.log('---configText', configText); + const results = await runESLint(configText); + console.log('---messages', results.map(r => r.messages)); return { + stdOutput: "", + success: true, }; - } - } - newRules.push(oldRule); + }, + }; + oldRules.push(oldRule); } - + return oldRules; } const rules = convertToOldRules(); +export default function () { + return convertToOldRules(); +} -export default rules; \ No newline at end of file +// export default rules; diff --git a/eng/tools/typespec-validation/test/tspconfig.test.ts b/eng/tools/typespec-validation/test/tspconfig.test.ts index 6c09cbe1bfea..89fb455910e5 100644 --- a/eng/tools/typespec-validation/test/tspconfig.test.ts +++ b/eng/tools/typespec-validation/test/tspconfig.test.ts @@ -15,65 +15,76 @@ interface TestCase { } const testCases: TestCase[] = [ +// { +// rule: new TspConfigJavaPackageDirectoryRule(), +// folder: TsvTestHost.folder, +// when: "package-dir \"azure-abc\" is valid", +// tspconfig: ` +// options: +// "@azure-tools/typespec-java": +// package-dir: azure-abc +// `, +// expectedResult: true, +// }, +// { +// rule: new TspConfigJavaPackageDirectoryRule(), +// folder: TsvTestHost.folder, +// when: "tspconfig.yaml is not a valid yaml", +// tspconfig: `aaa`, +// expectedResult: false, +// }, +// { +// rule: new TspConfigJavaPackageDirectoryRule(), +// folder: TsvTestHost.folder, +// when: "java emitter has no options", +// tspconfig: ` +// options: +// "@azure-tools/typespec-ts": +// package-dir: com.azure.test +// `, +// expectedResult: false, +// }, +// { +// rule: new TspConfigJavaPackageDirectoryRule(), +// folder: TsvTestHost.folder, +// when: "java emitter options have no package-dir", +// tspconfig: ` +// options: +// "@azure-tools/typespec-java": +// x: com.azure.test +// `, +// expectedResult: false, +// }, +// { +// rule: new TspConfigJavaPackageDirectoryRule(), +// folder: TsvTestHost.folder, +// when: "package-dir \"azure.test\" is invalid", +// tspconfig: ` +// options: +// "@azure-tools/typespec-java": +// package-dir: azure.test +// `, +// expectedResult: false, +// }, +// { +// rule: new TspConfigJavaPackageDirectoryRule(), +// folder: TsvTestHost.folder, +// when: "package-dir \"azure-\" is invalid", +// tspconfig: ` +// options: +// "@azure-tools/typespec-java": +// package-dir: azure- +// `, +// expectedResult: false, +// }, { - rule: new TspConfigJavaPackageDirectoryRule(), - folder: TsvTestHost.folder, - when: "package-dir \"azure-abc\" is valid", - tspconfig: ` -options: - "@azure-tools/typespec-java": - package-dir: azure-abc -`, - expectedResult: true, - }, - { - rule: new TspConfigJavaPackageDirectoryRule(), - folder: TsvTestHost.folder, - when: "tspconfig.yaml is not a valid yaml", - tspconfig: `aaa`, - expectedResult: false, - }, - { - rule: new TspConfigJavaPackageDirectoryRule(), - folder: TsvTestHost.folder, - when: "java emitter has no options", - tspconfig: ` -options: - "@azure-tools/typespec-ts": - package-dir: com.azure.test -`, - expectedResult: false, - }, - { - rule: new TspConfigJavaPackageDirectoryRule(), - folder: TsvTestHost.folder, - when: "java emitter options have no package-dir", - tspconfig: ` -options: - "@azure-tools/typespec-java": - x: com.azure.test -`, - expectedResult: false, - }, - { - rule: new TspConfigJavaPackageDirectoryRule(), - folder: TsvTestHost.folder, - when: "package-dir \"azure.test\" is invalid", - tspconfig: ` -options: - "@azure-tools/typespec-java": - package-dir: azure.test -`, - expectedResult: false, - }, - { - rule: new TspConfigJavaPackageDirectoryRule(), + rule: tspconfigRules()[0], folder: TsvTestHost.folder, when: "package-dir \"azure-\" is invalid", tspconfig: ` options: "@azure-tools/typespec-java": - package-dir: azure- + package-dir: azure-aaa `, expectedResult: false, }, @@ -98,8 +109,4 @@ describe("tspconfig", function () { } }, ); -}); - -describe("convert new ruls to old rules", function () { - -}) \ No newline at end of file +}); \ No newline at end of file From 91c2f288f6d5c830a0b08c435aa5af9f4eb083b9 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 24 Jan 2025 00:52:46 +0800 Subject: [PATCH 071/106] fix doc --- eng/tools/eslint-plugin-tsv/src/utils/constants.ts | 1 + eng/tools/eslint-plugin-tsv/src/utils/rule-doc.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/eng/tools/eslint-plugin-tsv/src/utils/constants.ts b/eng/tools/eslint-plugin-tsv/src/utils/constants.ts index 2ca356ba4f48..aff4758de554 100644 --- a/eng/tools/eslint-plugin-tsv/src/utils/constants.ts +++ b/eng/tools/eslint-plugin-tsv/src/utils/constants.ts @@ -5,6 +5,7 @@ export const emitters = { python: "@azure-tools/typespec-python", go: "@azure-tools/typespec-go", autorest: "@azure-tools/typespec-autorest", + common: "", }; export const defaultMessageId = "problem"; diff --git a/eng/tools/eslint-plugin-tsv/src/utils/rule-doc.ts b/eng/tools/eslint-plugin-tsv/src/utils/rule-doc.ts index f1525db38605..4a62792f0c11 100644 --- a/eng/tools/eslint-plugin-tsv/src/utils/rule-doc.ts +++ b/eng/tools/eslint-plugin-tsv/src/utils/rule-doc.ts @@ -57,7 +57,7 @@ export function createRuleDocument( let example = ""; switch (keyType) { case KeyType.EmitterOption: - displayName = `options.${emitters.ts}.${key}`; + displayName = `options.${emitterName}.${key}`; example = createEmitterOptionExample(emitterName, { key: key, value: exampleValue }); break; case KeyType.Parameter: From 06003ea3f22a6dfee2d3837c8f8736f2077e3673 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 24 Jan 2025 00:57:46 +0800 Subject: [PATCH 072/106] WIP --- eng/tools/typespec-validation/test/tspconfig.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/tools/typespec-validation/test/tspconfig.test.ts b/eng/tools/typespec-validation/test/tspconfig.test.ts index 89fb455910e5..7e4983f13b47 100644 --- a/eng/tools/typespec-validation/test/tspconfig.test.ts +++ b/eng/tools/typespec-validation/test/tspconfig.test.ts @@ -90,7 +90,7 @@ options: }, ]; -describe("tspconfig", function () { +describe("tspconfig-xxx", function () { it.each(testCases)( `should be $expectedResult for rule $rule.name when $when`, async (c: TestCase) => { From 81508d0c6b1a79334dbfba3891c1f9cd5baae0ad Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 24 Jan 2025 00:59:40 +0800 Subject: [PATCH 073/106] fix --- .../src/rules/tspconfig-validation-rules.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts index 719e370ac560..97a1fb10eea7 100644 --- a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts +++ b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts @@ -1,10 +1,6 @@ import { Rule } from "eslint"; import { TypeSpecConfig } from "../config/types.js"; -import { - createCodeGenSDKRule, - isAzureSDK, - isManagementSDK, -} from "../utils/rule-creator.js"; +import { createCodeGenSDKRule, isAzureSDK, isManagementSDK } from "../utils/rule-creator.js"; import { emitters } from "../utils/constants.js"; import { CreateCodeGenSDKRuleArgs, KeyType } from "../interfaces/rule-interfaces.js"; @@ -90,7 +86,8 @@ const args: CreateCodeGenSDKRuleArgs[] = [ exampleValue: "sdk/resourcemanager/aaa", extraExplanation: "The 'service-dir' should be a string that starts with 'sdk/resourcemanager/', followed by zero or more characters that are not a '/', and ends there", - condition: (_: TypeSpecConfig, _1: Rule.RuleContext) => true, + condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => + isManagementSDK(tspconfig, context, emitters.go), }, { rule: "tspconfig-go-mgmt-package-dir-match-pattern", From 73a85623aeb5a38701b83d8c89d36b4c9aa548bd Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 24 Jan 2025 10:03:48 +0800 Subject: [PATCH 074/106] add simple test --- eng/tools/eslint-plugin-tsv/src/index.ts | 5 +- .../src/rules/tspconfig-validation-rules.ts | 44 ++--- .../test/tspconfig.test.ts | 165 ++++++++++-------- 3 files changed, 119 insertions(+), 95 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/src/index.ts b/eng/tools/eslint-plugin-tsv/src/index.ts index 1e0a6b8bfcfe..6e6e166c1421 100644 --- a/eng/tools/eslint-plugin-tsv/src/index.ts +++ b/eng/tools/eslint-plugin-tsv/src/index.ts @@ -1,6 +1,5 @@ -import { Rule, ESLint, Linter } from "eslint"; +import { ESLint } from "eslint"; import tsvPlugin from "./eslint-plugin-tsv.js"; -import { parseForESLint, parseYAML } from "yaml-eslint-parser"; -export { parseForESLint, parseYAML, Rule as ESRule, ESLint, Linter }; +export { ESLint }; export default tsvPlugin; diff --git a/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts b/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts index 5bfabe7ed285..f5628dac4f3e 100644 --- a/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts +++ b/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts @@ -1,21 +1,28 @@ +// Note: temporary workaround to convert new rules to old rules to provides suggestion to correct tspconfig + import { join } 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"; -import tsvPlugin, { ESLint, ESRule, parseForESLint } from "eslint-plugin-tsv"; +import tsvPlugin, { ESLint } from "eslint-plugin-tsv"; -async function runESLint(content: string) { +async function runESLint(content: string, folder: string, ruleName: string) { + const config = tsvPlugin.configs.recommended; + for (const key in config.rules) { + if (key !== "tsv/" + ruleName) delete config.rules[key]; + } const eslint = new ESLint({ cwd: join(__dirname, "../../../../"), overrideConfig: tsvPlugin.configs.recommended, overrideConfigFile: true, }); - const results = await eslint.lintText(content, {filePath: 'tspconfig.yaml'}); - return results; + const results = await eslint.lintText(content, { filePath: join(folder, "tspconfig.yaml") }); + return results; } +// NOTE: This is a workaround to convert the new rules to old rules +// To be removed when the new TSV framework is ready function convertToOldRules() { let oldRules = []; for (const [_, rule] of Object.entries(tsvPlugin.rules ?? {})) { @@ -25,18 +32,17 @@ function convertToOldRules() { description: rule.meta?.docs?.description ?? "", async execute(host: TsvHost, folder: string): Promise { const configText = await host.readTspConfig(folder); - // const parsed = parseForESLint(configText, {location: true, }); - // const node = parsed as unknown as ESRule.Node; - // console.log('---node', node) - // const context = createFakeRuleContext(folder); - // const ruleListener = rule.create(context); - // const runTspConfigRule = ruleListener.YAMLDocument as (node: ESRule.Node) => void; - // if (runTspConfigRule) runTspConfigRule(node); - console.log('---configText', configText); - const results = await runESLint(configText); - console.log('---messages', results.map(r => r.messages)); + const results = await runESLint(configText, folder, rule.name); + if (results.length > 0 && results[0].messages.length > 0) { + return { + errorOutput: results[0].messages[0].message, + // Only used to provide suggestion to correct tspconfig + success: true, + }; + } + return { - stdOutput: "", + stdOutput: `[${rule.name}]: validation passed.`, success: true, }; }, @@ -48,8 +54,4 @@ function convertToOldRules() { const rules = convertToOldRules(); -export default function () { - return convertToOldRules(); -} - -// export default rules; +export default rules; diff --git a/eng/tools/typespec-validation/test/tspconfig.test.ts b/eng/tools/typespec-validation/test/tspconfig.test.ts index 7e4983f13b47..ffb53cd0958c 100644 --- a/eng/tools/typespec-validation/test/tspconfig.test.ts +++ b/eng/tools/typespec-validation/test/tspconfig.test.ts @@ -15,82 +15,60 @@ interface TestCase { } const testCases: TestCase[] = [ -// { -// rule: new TspConfigJavaPackageDirectoryRule(), -// folder: TsvTestHost.folder, -// when: "package-dir \"azure-abc\" is valid", -// tspconfig: ` -// options: -// "@azure-tools/typespec-java": -// package-dir: azure-abc -// `, -// expectedResult: true, -// }, -// { -// rule: new TspConfigJavaPackageDirectoryRule(), -// folder: TsvTestHost.folder, -// when: "tspconfig.yaml is not a valid yaml", -// tspconfig: `aaa`, -// expectedResult: false, -// }, -// { -// rule: new TspConfigJavaPackageDirectoryRule(), -// folder: TsvTestHost.folder, -// when: "java emitter has no options", -// tspconfig: ` -// options: -// "@azure-tools/typespec-ts": -// package-dir: com.azure.test -// `, -// expectedResult: false, -// }, -// { -// rule: new TspConfigJavaPackageDirectoryRule(), -// folder: TsvTestHost.folder, -// when: "java emitter options have no package-dir", -// tspconfig: ` -// options: -// "@azure-tools/typespec-java": -// x: com.azure.test -// `, -// expectedResult: false, -// }, -// { -// rule: new TspConfigJavaPackageDirectoryRule(), -// folder: TsvTestHost.folder, -// when: "package-dir \"azure.test\" is invalid", -// tspconfig: ` -// options: -// "@azure-tools/typespec-java": -// package-dir: azure.test -// `, -// expectedResult: false, -// }, -// { -// rule: new TspConfigJavaPackageDirectoryRule(), -// folder: TsvTestHost.folder, -// when: "package-dir \"azure-\" is invalid", -// tspconfig: ` -// options: -// "@azure-tools/typespec-java": -// package-dir: azure- -// `, -// expectedResult: false, -// }, { - rule: tspconfigRules()[0], + rule: new TspConfigJavaPackageDirectoryRule(), folder: TsvTestHost.folder, - when: "package-dir \"azure-\" is invalid", + when: 'package-dir "azure-abc" is valid', tspconfig: ` -options: - "@azure-tools/typespec-java": - package-dir: azure-aaa -`, + options: + "@azure-tools/typespec-java": + package-dir: azure-abc + `, + expectedResult: true, + }, + { + rule: new TspConfigJavaPackageDirectoryRule(), + folder: TsvTestHost.folder, + when: "tspconfig.yaml is not a valid yaml", + tspconfig: `aaa`, + expectedResult: false, + }, + { + rule: new TspConfigJavaPackageDirectoryRule(), + folder: TsvTestHost.folder, + when: "java emitter has no options", + tspconfig: ` + options: + "@azure-tools/typespec-ts": + package-dir: com.azure.test + `, + expectedResult: false, + }, + { + rule: new TspConfigJavaPackageDirectoryRule(), + folder: TsvTestHost.folder, + when: "java emitter options have no package-dir", + tspconfig: ` + options: + "@azure-tools/typespec-java": + x: com.azure.test + `, + expectedResult: false, + }, + { + rule: new TspConfigJavaPackageDirectoryRule(), + folder: TsvTestHost.folder, + when: 'package-dir "azure.test" is invalid', + tspconfig: ` + options: + "@azure-tools/typespec-java": + package-dir: azure.test + `, expectedResult: false, }, ]; -describe("tspconfig-xxx", function () { +describe("tspconfig", function () { it.each(testCases)( `should be $expectedResult for rule $rule.name when $when`, async (c: TestCase) => { @@ -99,7 +77,7 @@ describe("tspconfig-xxx", function () { return file === join(TsvTestHost.folder, "tspconfig.yaml"); }; host.readTspConfig = async (_folder: string) => c.tspconfig; - const result = await c.rule.execute(host, TsvTestHost.folder); + const result = await c.rule.execute(host, c.folder); strictEqual(result.success, c.expectedResult); if (!c.expectedResult) { // TODO: assert link when ready @@ -109,4 +87,49 @@ describe("tspconfig-xxx", function () { } }, ); -}); \ No newline at end of file + + it.each([ + { + rule: tspconfigRules.find((r) => r.name === "tspconfig-java-mgmt-package-dir-match-pattern")!, + folder: "aaa/aaa.Management/", + when: 'package-dir "azure-" is invalid', + tspconfig: ` + options: + "@azure-tools/typespec-java": + package-dir: xxxxx + flavor: azure + `, + expectedResult: false, + }, + { + rule: tspconfigRules.find((r) => r.name === "tspconfig-java-mgmt-package-dir-match-pattern")!, + folder: "aaa/aaa.Management/", + when: 'package-dir "azure-" is invalid', + tspconfig: ` + options: + "@azure-tools/typespec-java": + package-dir: azure-test + flavor: azure + `, + expectedResult: true, + }, + ])(`should be $expectedResult for new rule $rule.name when $when`, async (c: TestCase) => { + let host = new TsvTestHost(); + host.checkFileExists = async (file: string) => { + return file === join(TsvTestHost.folder, "tspconfig.yaml"); + }; + host.readTspConfig = async (_folder: string) => c.tspconfig; + const result = await c.rule.execute(host, c.folder); + strictEqual(result.success, true); + assert( + (c.expectedResult && + result.stdOutput && + result.stdOutput.length > 0 && + result.errorOutput === undefined) || + (!c.expectedResult && + result.stdOutput === undefined && + result.errorOutput && + result.errorOutput.length > 0), + ); + }); +}); From f84257bed3d878f169bcf6787a79a606d53e0ff3 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 24 Jan 2025 10:07:30 +0800 Subject: [PATCH 075/106] update java rule --- .../eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts | 4 ++-- .../test/rules/tspconfig-options-validation.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts index 97a1fb10eea7..55b79b10b597 100644 --- a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts +++ b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts @@ -156,7 +156,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ }, // java { - rule: "tspconfig-java-mgmt-package-dir-match-pattern", + rule: "tspconfig-java-az-package-dir-match-pattern", key: "package-dir", type: KeyType.EmitterOption, expectedValue: /^azure(-\w+)+$/, @@ -164,7 +164,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ extraExplanation: "The 'package-dir' should be a string that starts with 'azure', followed by one or more '-' segments. Each segment can contains letters, digits, or underscores", condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => - isManagementSDK(tspconfig, context, emitters.java), + isAzureSDK(tspconfig, emitters.java), }, // python { diff --git a/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts b/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts index a2456cefe83f..aecc83dc8b96 100644 --- a/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts @@ -169,8 +169,8 @@ const goManagementInjectSpansTestCases = createEmitterOptionTestCases( const javaManagementPackageDirTestCases = createEmitterOptionTestCases( emitters.java, rulePath, - "tspconfig-java-mgmt-package-dir-match-pattern", - managementTspconfigPath, + "tspconfig-java-az-package-dir-match-pattern", + "", "package-dir", "azure-aaa", "aaa", From 4d2c8f3aa2aec50b492f0a21c40efdca4dffe4fa Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 24 Jan 2025 10:15:18 +0800 Subject: [PATCH 076/106] update test --- eng/tools/typespec-validation/test/tspconfig.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/tools/typespec-validation/test/tspconfig.test.ts b/eng/tools/typespec-validation/test/tspconfig.test.ts index ffb53cd0958c..2104d9811db6 100644 --- a/eng/tools/typespec-validation/test/tspconfig.test.ts +++ b/eng/tools/typespec-validation/test/tspconfig.test.ts @@ -90,7 +90,7 @@ describe("tspconfig", function () { it.each([ { - rule: tspconfigRules.find((r) => r.name === "tspconfig-java-mgmt-package-dir-match-pattern")!, + rule: tspconfigRules.find((r) => r.name === "tspconfig-java-az-package-dir-match-pattern")!, folder: "aaa/aaa.Management/", when: 'package-dir "azure-" is invalid', tspconfig: ` @@ -102,7 +102,7 @@ describe("tspconfig", function () { expectedResult: false, }, { - rule: tspconfigRules.find((r) => r.name === "tspconfig-java-mgmt-package-dir-match-pattern")!, + rule: tspconfigRules.find((r) => r.name === "tspconfig-java-az-package-dir-match-pattern")!, folder: "aaa/aaa.Management/", when: 'package-dir "azure-" is invalid', tspconfig: ` From db989a653a3679321fc2859bddf0917f56032037 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 24 Jan 2025 10:48:34 +0800 Subject: [PATCH 077/106] add new rules --- eng/tools/typespec-validation/src/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/eng/tools/typespec-validation/src/index.ts b/eng/tools/typespec-validation/src/index.ts index b72ea7616531..98192b48420b 100755 --- a/eng/tools/typespec-validation/src/index.ts +++ b/eng/tools/typespec-validation/src/index.ts @@ -8,6 +8,8 @@ import { LinterRulesetRule } from "./rules/linter-ruleset.js"; import { NpmPrefixRule } from "./rules/npm-prefix.js"; import { TsvRunnerHost } from "./tsv-runner-host.js"; import { getSuppressions, Suppression } from "suppressions"; +import tspconfigRules from "./rules/tspconfig-validation-rules.js"; +import { Rule } from "./rule.js"; export async function main() { const host = new TsvRunnerHost(); @@ -39,7 +41,7 @@ export async function main() { return; } - const rules = [ + let rules: Rule[] = [ new FolderStructureRule(), new NpmPrefixRule(), new EmitAutorestRule(), @@ -48,6 +50,8 @@ export async function main() { new CompileRule(), new FormatRule(), ]; + rules.push(...tspconfigRules); + let success = true; for (let i = 0; i < rules.length; i++) { const rule = rules[i]; From 2ba3d591d1899c84402dc35222d39ed6a3aacb13 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 24 Jan 2025 10:51:33 +0800 Subject: [PATCH 078/106] remove files --- .../src/rules/tspconfig-java-package-dir.ts | 54 ------------- .../test/tspconfig.test.ts | 78 +------------------ 2 files changed, 2 insertions(+), 130 deletions(-) delete mode 100644 eng/tools/typespec-validation/src/rules/tspconfig-java-package-dir.ts diff --git a/eng/tools/typespec-validation/src/rules/tspconfig-java-package-dir.ts b/eng/tools/typespec-validation/src/rules/tspconfig-java-package-dir.ts deleted file mode 100644 index 28c8d544f13b..000000000000 --- a/eng/tools/typespec-validation/src/rules/tspconfig-java-package-dir.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { join } 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"; - -export class TspConfigJavaPackageDirectoryRule implements Rule { - pattern = new RegExp(/^azure(-\w+)+$/); - - readonly name = "tspconfig-java-package-dir"; - readonly description = `"options.@azure-tools/typespec-java.package-dir" must match ${this.pattern}.`; - readonly action = `Please update "options.@azure-tools/typespec-java.package-dir" to start with "azure", followed by one or more "-" segments. Each segment can contains letters, digits, or underscores. For example: "azure-test".`; - // TODO: provide link to the rule details and full sample - readonly link = ""; - async execute(host: TsvHost, folder: string): Promise { - const tspconfigExists = await host.checkFileExists(join(folder, "tspconfig.yaml")); - if (!tspconfigExists) - return this.createFailedResult(`Failed to find ${join(folder, "tspconfig.yaml")}`); - - let config = undefined; - try { - const configText = await host.readTspConfig(folder); - config = yamlParse(configText); - } catch (error) { - // TODO: append content " Check tpsconfig-file-exists rule for more details." when it's ready - return this.createFailedResult(`Failed to parse ${join(folder, "tspconfig.yaml")}`); - } - - const javaEmitterOptions = config?.options?.["@azure-tools/typespec-java"]; - - if (!javaEmitterOptions) - return this.createFailedResult(`Failed to find "options.@azure-tools/typespec-java"`); - - const packageDir = javaEmitterOptions?.["package-dir"]; - if (!packageDir) - return this.createFailedResult( - `Failed to find "options.@azure-tools/typespec-java.package-dir"`, - ); - - if (!this.pattern.test(packageDir)) { - return this.createFailedResult( - `package-dir "${packageDir}" does not match "${this.pattern}"`, - ); - } - return { success: true, stdOutput: `[${this.name}]: validation passed.` }; - } - - createFailedResult(errorMessage: string): RuleResult { - return { - success: false, - errorOutput: `[${this.name}]: ${errorMessage}. ${this.description} ${this.action} For more information and full samples, see ${this.link}.`, - }; - } -} diff --git a/eng/tools/typespec-validation/test/tspconfig.test.ts b/eng/tools/typespec-validation/test/tspconfig.test.ts index 2104d9811db6..e7716d587c9a 100644 --- a/eng/tools/typespec-validation/test/tspconfig.test.ts +++ b/eng/tools/typespec-validation/test/tspconfig.test.ts @@ -1,6 +1,5 @@ import { describe, it } from "vitest"; import { join } from "path"; -import { TspConfigJavaPackageDirectoryRule } from "../src/rules/tspconfig-java-package-dir.js"; import tspconfigRules from "../src/rules/tspconfig-validation-rules.js"; import { TsvTestHost } from "./tsv-test-host.js"; import { strict as assert, strictEqual } from "node:assert"; @@ -14,84 +13,11 @@ interface TestCase { folder: string; } -const testCases: TestCase[] = [ - { - rule: new TspConfigJavaPackageDirectoryRule(), - folder: TsvTestHost.folder, - when: 'package-dir "azure-abc" is valid', - tspconfig: ` - options: - "@azure-tools/typespec-java": - package-dir: azure-abc - `, - expectedResult: true, - }, - { - rule: new TspConfigJavaPackageDirectoryRule(), - folder: TsvTestHost.folder, - when: "tspconfig.yaml is not a valid yaml", - tspconfig: `aaa`, - expectedResult: false, - }, - { - rule: new TspConfigJavaPackageDirectoryRule(), - folder: TsvTestHost.folder, - when: "java emitter has no options", - tspconfig: ` - options: - "@azure-tools/typespec-ts": - package-dir: com.azure.test - `, - expectedResult: false, - }, - { - rule: new TspConfigJavaPackageDirectoryRule(), - folder: TsvTestHost.folder, - when: "java emitter options have no package-dir", - tspconfig: ` - options: - "@azure-tools/typespec-java": - x: com.azure.test - `, - expectedResult: false, - }, - { - rule: new TspConfigJavaPackageDirectoryRule(), - folder: TsvTestHost.folder, - when: 'package-dir "azure.test" is invalid', - tspconfig: ` - options: - "@azure-tools/typespec-java": - package-dir: azure.test - `, - expectedResult: false, - }, -]; - -describe("tspconfig", function () { - it.each(testCases)( - `should be $expectedResult for rule $rule.name when $when`, - async (c: TestCase) => { - let host = new TsvTestHost(); - host.checkFileExists = async (file: string) => { - return file === join(TsvTestHost.folder, "tspconfig.yaml"); - }; - host.readTspConfig = async (_folder: string) => c.tspconfig; - const result = await c.rule.execute(host, c.folder); - strictEqual(result.success, c.expectedResult); - if (!c.expectedResult) { - // TODO: assert link when ready - assert(result.errorOutput?.includes(c.rule.name)); - assert(result.errorOutput?.includes(c.rule.description)); - assert(result.errorOutput?.includes(c.rule.action!)); - } - }, - ); - +describe("tspconfig rules", () => { it.each([ { rule: tspconfigRules.find((r) => r.name === "tspconfig-java-az-package-dir-match-pattern")!, - folder: "aaa/aaa.Management/", + folder: "aaa/bbb/", when: 'package-dir "azure-" is invalid', tspconfig: ` options: From 317a827afb717047b252e4778dbd6d6d579a3c4d Mon Sep 17 00:00:00 2001 From: v-tianxi Date: Fri, 24 Jan 2025 11:02:51 +0800 Subject: [PATCH 079/106] remove net-track2 --- specificationRepositoryConfiguration.json | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/specificationRepositoryConfiguration.json b/specificationRepositoryConfiguration.json index 9c6ad529e741..0b2c1930d549 100644 --- a/specificationRepositoryConfiguration.json +++ b/specificationRepositoryConfiguration.json @@ -17,10 +17,6 @@ "mainRepository": "Azure/azure-sdk-for-js" }, "azure-sdk-for-net": { - "integrationRepository": "azure-sdk/azure-sdk-for-net", - "mainRepository": "Azure/azure-sdk-for-net" - }, - "azure-sdk-for-net-track2": { "integrationRepository": "azure-sdk/azure-sdk-for-net", "mainRepository": "Azure/azure-sdk-for-net", "configFilePath": "eng/swagger_to_sdk_config.json" @@ -52,10 +48,6 @@ "mainRepository": "Azure/azure-sdk-for-js-pr" }, "azure-sdk-for-net": { - "integrationRepository": "azure-sdk/azure-sdk-for-net-pr", - "mainRepository": "Azure/azure-sdk-for-net-pr" - }, - "azure-sdk-for-net-track2": { "integrationRepository": "azure-sdk/azure-sdk-for-net-pr", "mainRepository": "Azure/azure-sdk-for-net-pr", "configFilePath": "eng/swagger_to_sdk_config.json" @@ -74,7 +66,7 @@ "typespecEmitterToSdkRepositoryMapping": { "@azure-tools/typespec-python": "azure-sdk-for-python", "@azure-tools/typespec-java": "azure-sdk-for-java", - "@azure-tools/typespec-csharp": "azure-sdk-for-net-track2", + "@azure-tools/typespec-csharp": "azure-sdk-for-net", "@azure-tools/typespec-ts": "azure-sdk-for-js", "@azure-tools/typespec-go": "azure-sdk-for-go" } From 9f84497737d8b19bc603f33ca62e25a00fa25755 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 24 Jan 2025 11:02:55 +0800 Subject: [PATCH 080/106] fix lint error --- .../eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts | 2 +- eng/tools/eslint-plugin-tsv/src/utils/rule-doc.ts | 1 - package-lock.json | 2 ++ 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts index 55b79b10b597..f4c2610ff76d 100644 --- a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts +++ b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts @@ -163,7 +163,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ exampleValue: "azure-aaa", extraExplanation: "The 'package-dir' should be a string that starts with 'azure', followed by one or more '-' segments. Each segment can contains letters, digits, or underscores", - condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => + condition: (tspconfig: TypeSpecConfig, _: Rule.RuleContext) => isAzureSDK(tspconfig, emitters.java), }, // python diff --git a/eng/tools/eslint-plugin-tsv/src/utils/rule-doc.ts b/eng/tools/eslint-plugin-tsv/src/utils/rule-doc.ts index 4a62792f0c11..1ffef72bee77 100644 --- a/eng/tools/eslint-plugin-tsv/src/utils/rule-doc.ts +++ b/eng/tools/eslint-plugin-tsv/src/utils/rule-doc.ts @@ -1,5 +1,4 @@ import { KeyType, RuleDocument } from "../interfaces/rule-interfaces.js"; -import { emitters } from "./constants.js"; import { stringify } from "yaml"; function createDescriptionDocumentBlock( diff --git a/package-lock.json b/package-lock.json index 821663bc1e52..6791bde87d86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,10 +52,12 @@ "name": "@azure-tools/eslint-plugin-tsv", "dev": true, "dependencies": { + "ajv": "^8.17.1", "yaml-eslint-parser": "^1.2.3" }, "devDependencies": { "@types/node": "^18.19.31", + "@vitest/coverage-v8": "^2.0.4", "eslint": "^9.17.0", "memfs": "^4.15.0", "rimraf": "^5.0.10", From 4d1359c0ebcbfa70d406e8531ad85a06e70cd4b2 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 24 Jan 2025 11:05:48 +0800 Subject: [PATCH 081/106] update lock file --- package-lock.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package-lock.json b/package-lock.json index e2e388ec99ce..7037acc8b484 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,6 +50,7 @@ } }, "eng/tools/eslint-plugin-tsv": { + "name": "@azure-tools/eslint-plugin-tsv", "dev": true, "dependencies": { "ajv": "^8.17.1", @@ -1179,6 +1180,7 @@ "name": "@azure-tools/typespec-validation", "dev": true, "dependencies": { + "eslint-plugin-tsv": "file:../eslint-plugin-tsv", "globby": "^14.0.1", "simple-git": "^3.24.0", "suppressions": "file:../suppressions", @@ -5690,6 +5692,10 @@ } } }, + "node_modules/eslint-plugin-tsv": { + "resolved": "eng/tools/eslint-plugin-tsv", + "link": true + }, "node_modules/eslint-plugin-unicorn": { "version": "56.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-56.0.1.tgz", From 4e7e4eec74f8eca998cbf49b5082c2934cc3fe1e Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 24 Jan 2025 15:29:29 +0800 Subject: [PATCH 082/106] delete flavor rule --- .../src/rules/tspconfig-validation-rules.ts | 55 +++++------- .../src/utils/rule-creator.ts | 31 ++++--- .../tspconfig-options-validation.test.ts | 14 +--- eng/tools/typespec-validation/src/index.ts | 2 - .../src/rules/flavor-azure.ts | 53 ------------ .../test/flavor-azure.test.ts | 84 ------------------- 6 files changed, 37 insertions(+), 202 deletions(-) delete mode 100644 eng/tools/typespec-validation/src/rules/flavor-azure.ts delete mode 100644 eng/tools/typespec-validation/test/flavor-azure.test.ts diff --git a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts index f4c2610ff76d..83f193b825f7 100644 --- a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts +++ b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts @@ -1,6 +1,6 @@ import { Rule } from "eslint"; import { TypeSpecConfig } from "../config/types.js"; -import { createCodeGenSDKRule, isAzureSDK, isManagementSDK } from "../utils/rule-creator.js"; +import { createCodeGenSDKRule, isManagementSDK } from "../utils/rule-creator.js"; import { emitters } from "../utils/constants.js"; import { CreateCodeGenSDKRuleArgs, KeyType } from "../interfaces/rule-interfaces.js"; @@ -9,7 +9,7 @@ const tsIsManagementCondition = (tspconfig: TypeSpecConfig, context: Rule.RuleCo const isModularLibrary = tspconfig.options?.[emitterName]?.isModularLibrary as | boolean | undefined; - return isManagementSDK(tspconfig, context, emitterName) && isModularLibrary !== false; + return isManagementSDK(context) && isModularLibrary !== false; }; const args: CreateCodeGenSDKRuleArgs[] = [ @@ -86,8 +86,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ exampleValue: "sdk/resourcemanager/aaa", extraExplanation: "The 'service-dir' should be a string that starts with 'sdk/resourcemanager/', followed by zero or more characters that are not a '/', and ends there", - condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => - isManagementSDK(tspconfig, context, emitters.go), + condition: (_: TypeSpecConfig, context: Rule.RuleContext) => isManagementSDK(context), }, { rule: "tspconfig-go-mgmt-package-dir-match-pattern", @@ -97,8 +96,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ exampleValue: "armaaa", extraExplanation: "The 'package-dir' should be a string that starts with 'arm' and do not contain a forward slash (/) after it", - condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => - isManagementSDK(tspconfig, context, emitters.go), + condition: (_: TypeSpecConfig, context: Rule.RuleContext) => isManagementSDK(context), }, { rule: "tspconfig-go-mgmt-module-equal-string", @@ -106,8 +104,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ type: KeyType.EmitterOption, expectedValue: "github.com/Azure/azure-sdk-for-go/{service-dir}/{package-dir}", exampleValue: "github.com/Azure/azure-sdk-for-go/{service-dir}/{package-dir}", - condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => - isManagementSDK(tspconfig, context, emitters.go), + condition: (_: TypeSpecConfig, context: Rule.RuleContext) => isManagementSDK(context), }, { rule: "tspconfig-go-mgmt-fix-const-stuttering-true", @@ -115,8 +112,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ type: KeyType.EmitterOption, expectedValue: true, exampleValue: true, - condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => - isManagementSDK(tspconfig, context, emitters.go), + condition: (_: TypeSpecConfig, context: Rule.RuleContext) => isManagementSDK(context), }, { rule: "tspconfig-go-mgmt-generate-examples-true", @@ -124,8 +120,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ type: KeyType.EmitterOption, expectedValue: true, exampleValue: true, - condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => - isManagementSDK(tspconfig, context, emitters.go), + condition: (_: TypeSpecConfig, context: Rule.RuleContext) => isManagementSDK(context), }, { rule: "tspconfig-go-mgmt-generate-fakes-true", @@ -133,8 +128,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ type: KeyType.EmitterOption, expectedValue: true, exampleValue: true, - condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => - isManagementSDK(tspconfig, context, emitters.go), + condition: (_: TypeSpecConfig, context: Rule.RuleContext) => isManagementSDK(context), }, { rule: "tspconfig-go-mgmt-head-as-boolean-true", @@ -142,8 +136,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ type: KeyType.EmitterOption, expectedValue: true, exampleValue: true, - condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => - isManagementSDK(tspconfig, context, emitters.go), + condition: (_: TypeSpecConfig, context: Rule.RuleContext) => isManagementSDK(context), }, { rule: "tspconfig-go-mgmt-inject-spans-true", @@ -151,8 +144,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ type: KeyType.EmitterOption, expectedValue: true, exampleValue: true, - condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => - isManagementSDK(tspconfig, context, emitters.go), + condition: (_: TypeSpecConfig, context: Rule.RuleContext) => isManagementSDK(context), }, // java { @@ -163,8 +155,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ exampleValue: "azure-aaa", extraExplanation: "The 'package-dir' should be a string that starts with 'azure', followed by one or more '-' segments. Each segment can contains letters, digits, or underscores", - condition: (tspconfig: TypeSpecConfig, _: Rule.RuleContext) => - isAzureSDK(tspconfig, emitters.java), + condition: (_: TypeSpecConfig, _1: Rule.RuleContext) => true, }, // python { @@ -175,8 +166,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ exampleValue: "azure-mgmt-aaa", extraExplanation: "The 'package-dir' should be a string that starts with 'azure-mgmt', followed by 1 or 2 hyphen-separated lowercase alphabetic segments", - condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => - isManagementSDK(tspconfig, context, emitters.python), + condition: (_: TypeSpecConfig, context: Rule.RuleContext) => isManagementSDK(context), }, { rule: "tspconfig-python-mgmt-package-name-equal-string", @@ -184,8 +174,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ type: KeyType.EmitterOption, expectedValue: "{package-dir}", exampleValue: "{package-dir}", - condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => - isManagementSDK(tspconfig, context, emitters.python), + condition: (_: TypeSpecConfig, context: Rule.RuleContext) => isManagementSDK(context), }, { rule: "tspconfig-python-mgmt-generate-test-true", @@ -193,8 +182,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ type: KeyType.EmitterOption, expectedValue: true, exampleValue: true, - condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => - isManagementSDK(tspconfig, context, emitters.python), + condition: (_: TypeSpecConfig, context: Rule.RuleContext) => isManagementSDK(context), }, { rule: "tspconfig-python-mgmt-generate-sample-true", @@ -202,8 +190,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ type: KeyType.EmitterOption, expectedValue: true, exampleValue: true, - condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => - isManagementSDK(tspconfig, context, emitters.python), + condition: (_: TypeSpecConfig, context: Rule.RuleContext) => isManagementSDK(context), }, // csharp { @@ -213,8 +200,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ expectedValue: /^Azure\./, exampleValue: "Azure.aaa", extraExplanation: "The 'package-dir' should be a string that starts with 'Azure.'", - condition: (tspconfig: TypeSpecConfig, _: Rule.RuleContext) => - isAzureSDK(tspconfig, emitters.csharp), + condition: (_: TypeSpecConfig, _1: Rule.RuleContext) => true, }, { rule: "tspconfig-csharp-az-namespace-equal-string", @@ -222,8 +208,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ type: KeyType.EmitterOption, expectedValue: "{package-dir}", exampleValue: "{package-dir}", - condition: (tspconfig: TypeSpecConfig, _: Rule.RuleContext) => - isAzureSDK(tspconfig, emitters.csharp), + condition: (_: TypeSpecConfig, _1: Rule.RuleContext) => true, }, { rule: "tspconfig-csharp-az-clear-output-folder-true", @@ -231,8 +216,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ type: KeyType.EmitterOption, expectedValue: true, exampleValue: true, - condition: (tspconfig: TypeSpecConfig, _: Rule.RuleContext) => - isAzureSDK(tspconfig, emitters.csharp), + condition: (_: TypeSpecConfig, _1: Rule.RuleContext) => true, }, { rule: "tspconfig-csharp-mgmt-package-dir-match-pattern", @@ -242,8 +226,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ exampleValue: "Azure.ResourceManager.aaa", extraExplanation: "The 'package-dir' should be a string that starts with 'Azure.ResourceManager.'", - condition: (tspconfig: TypeSpecConfig, context: Rule.RuleContext) => - isManagementSDK(tspconfig, context, emitters.csharp), + condition: (_: TypeSpecConfig, context: Rule.RuleContext) => isManagementSDK(context), }, ]; diff --git a/eng/tools/eslint-plugin-tsv/src/utils/rule-creator.ts b/eng/tools/eslint-plugin-tsv/src/utils/rule-creator.ts index ab921cbd8a07..5040e54b07bd 100644 --- a/eng/tools/eslint-plugin-tsv/src/utils/rule-creator.ts +++ b/eng/tools/eslint-plugin-tsv/src/utils/rule-creator.ts @@ -1,6 +1,11 @@ import { Rule } from "eslint"; import { TypeSpecConfig } from "../config/types.js"; -import { CreateCodeGenSDKRuleArgs, KeyType, RuleDocument, RuleInfo } from "../interfaces/rule-interfaces.js"; +import { + CreateCodeGenSDKRuleArgs, + KeyType, + RuleDocument, + RuleInfo, +} from "../interfaces/rule-interfaces.js"; import { defaultMessageId, defaultRuleType, emitters } from "./constants.js"; import { NamedRule } from "../interfaces/named-eslint.js"; import { AST, getStaticYAMLValue } from "yaml-eslint-parser"; @@ -39,18 +44,9 @@ export function createRuleMessages(messageId: string, docs: RuleDocument) { }; } -export function isAzureSDK(tspconfig: TypeSpecConfig, emitterName: string) { - const flavor = tspconfig.options?.[emitterName]?.flavor as string; - return flavor === "azure"; -} - -export function isManagementSDK( - tspconfig: TypeSpecConfig, - context: Rule.RuleContext, - emitterName: string, -) { +export function isManagementSDK(context: Rule.RuleContext) { const filename = context.filename; - return isAzureSDK(tspconfig, emitterName) && filename.includes(".Management"); + return filename.includes(".Management"); } function validateValue( @@ -62,8 +58,7 @@ function validateValue( switch (typeof expected) { case "boolean": case "string": - if (actual !== expected) - context.report({ node, messageId: defaultMessageId }); + if (actual !== expected) context.report({ node, messageId: defaultMessageId }); break; case "object": if (typeof actual !== "string" || !expected.test(actual)) @@ -107,7 +102,12 @@ export function createCodeGenSDKRule(args: CreateCodeGenSDKRuleArgs): NamedRule. for (const segment of args.key.split(".")) { if (option && segment in option) option = option![segment]; } - validateValue(context, node, option as undefined | string | boolean, args.expectedValue); + validateValue( + context, + node, + option as undefined | string | boolean, + args.expectedValue, + ); break; } case KeyType.Parameter: { @@ -119,7 +119,6 @@ export function createCodeGenSDKRule(args: CreateCodeGenSDKRuleArgs): NamedRule. // TODO: log not supported break; } - }, }, }; diff --git a/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts b/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts index aecc83dc8b96..9e0bb1ea2228 100644 --- a/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts @@ -336,11 +336,7 @@ function createEmitterOptionTestCases( rulePath, ruleName, fileName, - yamlContent: createEmitterOptionExample( - emitterName, - { key: key, value: validValue }, - { key: "flavor", value: "azure" }, - ), + yamlContent: createEmitterOptionExample(emitterName, { key: key, value: validValue }), shouldReportError: false, }, { @@ -348,11 +344,7 @@ function createEmitterOptionTestCases( rulePath, ruleName, fileName, - yamlContent: createEmitterOptionExample( - emitterName, - { key: key, value: invalidValue }, - { key: "flavor", value: "azure" }, - ), + yamlContent: createEmitterOptionExample(emitterName, { key: key, value: invalidValue }), shouldReportError: true, }, { @@ -360,7 +352,7 @@ function createEmitterOptionTestCases( rulePath, ruleName, fileName, - yamlContent: createEmitterOptionExample(emitterName, { key: "flavor", value: "azure" }), + yamlContent: createEmitterOptionExample(emitterName), shouldReportError: true, }, ]; diff --git a/eng/tools/typespec-validation/src/index.ts b/eng/tools/typespec-validation/src/index.ts index b72ea7616531..b50531a18f32 100755 --- a/eng/tools/typespec-validation/src/index.ts +++ b/eng/tools/typespec-validation/src/index.ts @@ -1,7 +1,6 @@ import { parseArgs, ParseArgsConfig } from "node:util"; import { CompileRule } from "./rules/compile.js"; import { EmitAutorestRule } from "./rules/emit-autorest.js"; -import { FlavorAzureRule } from "./rules/flavor-azure.js"; import { FolderStructureRule } from "./rules/folder-structure.js"; import { FormatRule } from "./rules/format.js"; import { LinterRulesetRule } from "./rules/linter-ruleset.js"; @@ -43,7 +42,6 @@ export async function main() { new FolderStructureRule(), new NpmPrefixRule(), new EmitAutorestRule(), - new FlavorAzureRule(), new LinterRulesetRule(), new CompileRule(), new FormatRule(), diff --git a/eng/tools/typespec-validation/src/rules/flavor-azure.ts b/eng/tools/typespec-validation/src/rules/flavor-azure.ts deleted file mode 100644 index 1c01955448b3..000000000000 --- a/eng/tools/typespec-validation/src/rules/flavor-azure.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { parse as yamlParse } from "yaml"; -import { Rule } from "../rule.js"; -import { RuleResult } from "../rule-result.js"; -import { TsvHost } from "../tsv-host.js"; - -export class FlavorAzureRule implements Rule { - readonly name = "FlavorAzure"; - - readonly description = "Client emitters must set 'flavor:azure'"; - - async execute(host: TsvHost, folder: string): Promise { - let success = true; - let stdOutput = ""; - let errorOutput = ""; - - const configText = await host.readTspConfig(folder); - const config = yamlParse(configText); - - const options = config?.options; - for (const emitter in options) { - if (this.isClientEmitter(emitter)) { - const flavor = options[emitter]?.flavor; - - stdOutput += `"${emitter}":\n`; - stdOutput += ` flavor: ${flavor}\n`; - - if (flavor !== "azure") { - success = false; - errorOutput += - "tspconfig.yaml must define the following property:\n" + - "\n" + - "options:\n" + - ` "${emitter}":\n` + - " flavor: azure\n\n"; - } - } - } - - return { - success: success, - stdOutput: stdOutput, - errorOutput: errorOutput, - }; - } - - isClientEmitter(name: string): boolean { - const regex = new RegExp( - "^(@azure-tools/typespec-(csharp|java|python|ts)|@typespec/http-client-.+)$", - ); - - return regex.test(name); - } -} diff --git a/eng/tools/typespec-validation/test/flavor-azure.test.ts b/eng/tools/typespec-validation/test/flavor-azure.test.ts deleted file mode 100644 index e282d9364989..000000000000 --- a/eng/tools/typespec-validation/test/flavor-azure.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { describe, it } from "vitest"; -import { FlavorAzureRule } from "../src/rules/flavor-azure.js"; -import { TsvTestHost } from "./tsv-test-host.js"; -import { strict as assert } from "node:assert"; - -describe("flavor-azure", function () { - const clientEmitterNames = [ - "@azure-tools/typespec-csharp", - "@azure-tools/typespec-java", - "@azure-tools/typespec-python", - "@azure-tools/typespec-ts", - "@typespec/http-client-foo", - ]; - - const nonClientEmitterNames = ["@azure-tools/typespec-autorest", "@typespec/openapi3"]; - - clientEmitterNames.forEach(function (emitter) { - it(`should fail if "${emitter}" is missing flavor`, async function () { - let host = new TsvTestHost(); - - host.readTspConfig = async (_folder: string) => ` - options: - "${emitter}": - package-dir: "foo" - `; - const result = await new FlavorAzureRule().execute(host, TsvTestHost.folder); - assert(!result.success); - }); - - it(`should fail if "${emitter}" flavor is not "azure"`, async function () { - let host = new TsvTestHost(); - host.readTspConfig = async (_folder: string) => ` - options: - "${emitter}": - package-dir: "foo" - flavor: not-azure - `; - const result = await new FlavorAzureRule().execute(host, TsvTestHost.folder); - assert(!result.success); - }); - - it(`should succeed if ${emitter} flavor is "azure"`, async function () { - let host = new TsvTestHost(); - host.readTspConfig = async (_folder: string) => ` - options: - "${emitter}": - package-dir: "foo" - flavor: azure - `; - const result = await new FlavorAzureRule().execute(host, TsvTestHost.folder); - assert(result.success); - }); - }); - - nonClientEmitterNames.forEach(function (emitter) { - it(`should succeed if ${emitter} is missing flavor`, async function () { - let host = new TsvTestHost(); - host.readTspConfig = async (_folder: string) => ` - options: - "${emitter}": - azure-resource-provider-folder: "data-plane" - `; - const result = await new FlavorAzureRule().execute(host, TsvTestHost.folder); - assert(result.success); - }); - }); - - it("should succeed if config is empty", async function () { - let host = new TsvTestHost(); - host.readTspConfig = async (_folder: string) => ""; - const result = await new FlavorAzureRule().execute(host, TsvTestHost.folder); - assert(result.success); - }); - - it("should succeed if config has no options", async function () { - let host = new TsvTestHost(); - host.readTspConfig = async (_folder: string) => ` -emit: - - "@azure-tools/typespec-autorest" -`; - const result = await new FlavorAzureRule().execute(host, TsvTestHost.folder); - assert(result.success); - }); -}); From fa6c65a772a608d6117aa088a864094f5d4ad7f4 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 24 Jan 2025 16:17:52 +0800 Subject: [PATCH 083/106] update tsconfig --- eng/tools/typespec-validation/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eng/tools/typespec-validation/tsconfig.json b/eng/tools/typespec-validation/tsconfig.json index c1eaa0805646..81ede7fd4b23 100644 --- a/eng/tools/typespec-validation/tsconfig.json +++ b/eng/tools/typespec-validation/tsconfig.json @@ -4,6 +4,7 @@ "outDir": "./dist", }, "references": [ - { "path": "../suppressions" } + { "path": "../suppressions" }, + { "path": "../eslint-plugin-tsv" }, ] } From 593970c43231d5d60d83fc7c9df14e0525624f92 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 24 Jan 2025 17:34:19 +0800 Subject: [PATCH 084/106] fix --- eng/tools/eslint-plugin-tsv/package.json | 2 +- .../src/rules/tspconfig-validation-rules.ts | 16 +++++++++------- .../typespec-validation/test/tspconfig.test.ts | 11 +++-------- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index b124f469f81f..5c8ca554c3b7 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -2,7 +2,7 @@ "name": "@azure-tools/eslint-plugin-tsv", "private": true, "type": "module", - "main": "src/index.js", + "main": "dist/src/index.js", "dependencies": { "ajv": "^8.17.1", "yaml-eslint-parser": "^1.2.3" diff --git a/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts b/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts index f5628dac4f3e..11174619046f 100644 --- a/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts +++ b/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts @@ -8,13 +8,15 @@ import { TsvHost } from "../tsv-host.js"; import tsvPlugin, { ESLint } from "eslint-plugin-tsv"; async function runESLint(content: string, folder: string, ruleName: string) { - const config = tsvPlugin.configs.recommended; - for (const key in config.rules) { - if (key !== "tsv/" + ruleName) delete config.rules[key]; - } + const cwd = process.cwd(); const eslint = new ESLint({ - cwd: join(__dirname, "../../../../"), - overrideConfig: tsvPlugin.configs.recommended, + cwd, + overrideConfig: { + ...tsvPlugin.configs.recommended, + rules: { + [`${tsvPlugin.name}/${ruleName}`]: "error", + } + }, overrideConfigFile: true, }); const results = await eslint.lintText(content, { filePath: join(folder, "tspconfig.yaml") }); @@ -35,7 +37,7 @@ function convertToOldRules() { const results = await runESLint(configText, folder, rule.name); if (results.length > 0 && results[0].messages.length > 0) { return { - errorOutput: results[0].messages[0].message, + stdOutput: 'Validation failed. ' + results[0].messages[0].message, // Only used to provide suggestion to correct tspconfig success: true, }; diff --git a/eng/tools/typespec-validation/test/tspconfig.test.ts b/eng/tools/typespec-validation/test/tspconfig.test.ts index e7716d587c9a..9b1b3644b261 100644 --- a/eng/tools/typespec-validation/test/tspconfig.test.ts +++ b/eng/tools/typespec-validation/test/tspconfig.test.ts @@ -47,15 +47,10 @@ describe("tspconfig rules", () => { host.readTspConfig = async (_folder: string) => c.tspconfig; const result = await c.rule.execute(host, c.folder); strictEqual(result.success, true); + assert(result.stdOutput && result.stdOutput.length > 0 && result.errorOutput === undefined); assert( - (c.expectedResult && - result.stdOutput && - result.stdOutput.length > 0 && - result.errorOutput === undefined) || - (!c.expectedResult && - result.stdOutput === undefined && - result.errorOutput && - result.errorOutput.length > 0), + (c.expectedResult && result.stdOutput.includes("validation passed")) || + (!c.expectedResult && result.stdOutput.includes("Validation failed. ")), ); }); }); From 8550e880f86a711cbf86eb77ecc1c66d3bedd4ab Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 24 Jan 2025 17:44:07 +0800 Subject: [PATCH 085/106] update --- eng/tools/eslint-plugin-tsv/package.json | 2 +- eng/tools/eslint-plugin-tsv/test/e2e.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index b124f469f81f..5c8ca554c3b7 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -2,7 +2,7 @@ "name": "@azure-tools/eslint-plugin-tsv", "private": true, "type": "module", - "main": "src/index.js", + "main": "dist/src/index.js", "dependencies": { "ajv": "^8.17.1", "yaml-eslint-parser": "^1.2.3" diff --git a/eng/tools/eslint-plugin-tsv/test/e2e.test.ts b/eng/tools/eslint-plugin-tsv/test/e2e.test.ts index e6da7e107a9d..7c8ed1376584 100644 --- a/eng/tools/eslint-plugin-tsv/test/e2e.test.ts +++ b/eng/tools/eslint-plugin-tsv/test/e2e.test.ts @@ -5,7 +5,7 @@ import eslintPluginTsv from "../src/eslint-plugin-tsv.js"; function createESLint() { return new ESLint({ - cwd: join(__dirname, "../../../../"), + cwd: "/", overrideConfig: eslintPluginTsv.configs.recommended, overrideConfigFile: true, }); From 71fec2e9eaae2949f471534da029165f1ab32cbf Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 24 Jan 2025 17:49:09 +0800 Subject: [PATCH 086/106] improve placeholder --- .../src/rules/tspconfig-validation-rules.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts index 83f193b825f7..9eae374011e1 100644 --- a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts +++ b/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts @@ -19,7 +19,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ key: "service-dir", type: KeyType.Parameter, expectedValue: /^sdk\/[^\/]*$/, - exampleValue: "sdk/aaa", + exampleValue: "sdk/placeholder", extraExplanation: "The 'service-dir' should be a string that starts with 'sdk/', followed by zero or more characters that are not a '/', and ends there", condition: (_: TypeSpecConfig, _1: Rule.RuleContext) => true, @@ -62,7 +62,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ key: "package-dir", type: KeyType.EmitterOption, expectedValue: /^arm(?:-[a-z]+)+$/, - exampleValue: "arm-aaa-bbb", + exampleValue: "arm-placeholder-placeholder", extraExplanation: "The 'package-dir' should be a string that starts with 'arm' and is followed by one or more groups of a hyphen (-) and lowercase letters", condition: tsIsManagementCondition, @@ -72,7 +72,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ key: "packageDetails.name", type: KeyType.EmitterOption, expectedValue: /^\@azure\/arm(?:-[a-z]+)+$/, - exampleValue: "@azure/arm-aaa-bbb", + exampleValue: "@azure/arm-placeholder-placeholder", extraExplanation: "The package name should be a string that starts with '@azure/arm' and is followed by one or more groups of a hyphen (-) and lowercase letters", condition: tsIsManagementCondition, @@ -83,7 +83,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ key: "service-dir", type: KeyType.EmitterOption, expectedValue: /^sdk\/resourcemanager\/[^\/]*$/, - exampleValue: "sdk/resourcemanager/aaa", + exampleValue: "sdk/resourcemanager/placeholder", extraExplanation: "The 'service-dir' should be a string that starts with 'sdk/resourcemanager/', followed by zero or more characters that are not a '/', and ends there", condition: (_: TypeSpecConfig, context: Rule.RuleContext) => isManagementSDK(context), @@ -93,7 +93,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ key: "package-dir", type: KeyType.EmitterOption, expectedValue: /^arm[^\/]*$/, - exampleValue: "armaaa", + exampleValue: "armplaceholder", extraExplanation: "The 'package-dir' should be a string that starts with 'arm' and do not contain a forward slash (/) after it", condition: (_: TypeSpecConfig, context: Rule.RuleContext) => isManagementSDK(context), @@ -152,7 +152,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ key: "package-dir", type: KeyType.EmitterOption, expectedValue: /^azure(-\w+)+$/, - exampleValue: "azure-aaa", + exampleValue: "azure-placeholder", extraExplanation: "The 'package-dir' should be a string that starts with 'azure', followed by one or more '-' segments. Each segment can contains letters, digits, or underscores", condition: (_: TypeSpecConfig, _1: Rule.RuleContext) => true, @@ -163,7 +163,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ key: "package-dir", type: KeyType.EmitterOption, expectedValue: /^azure-mgmt(-[a-z]+){1,2}$/, - exampleValue: "azure-mgmt-aaa", + exampleValue: "azure-mgmt-placeholder", extraExplanation: "The 'package-dir' should be a string that starts with 'azure-mgmt', followed by 1 or 2 hyphen-separated lowercase alphabetic segments", condition: (_: TypeSpecConfig, context: Rule.RuleContext) => isManagementSDK(context), @@ -198,7 +198,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ key: "package-dir", type: KeyType.EmitterOption, expectedValue: /^Azure\./, - exampleValue: "Azure.aaa", + exampleValue: "Azure.placeholder", extraExplanation: "The 'package-dir' should be a string that starts with 'Azure.'", condition: (_: TypeSpecConfig, _1: Rule.RuleContext) => true, }, @@ -223,7 +223,7 @@ const args: CreateCodeGenSDKRuleArgs[] = [ key: "package-dir", type: KeyType.EmitterOption, expectedValue: /^Azure\.ResourceManager\./, - exampleValue: "Azure.ResourceManager.aaa", + exampleValue: "Azure.ResourceManager.Placeholder", extraExplanation: "The 'package-dir' should be a string that starts with 'Azure.ResourceManager.'", condition: (_: TypeSpecConfig, context: Rule.RuleContext) => isManagementSDK(context), From 9a378a02cfc1e9f593384f791e2dbb22889a6b10 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 24 Jan 2025 17:59:45 +0800 Subject: [PATCH 087/106] improve message --- eng/tools/eslint-plugin-tsv/package.json | 1 - eng/tools/eslint-plugin-tsv/src/utils/rule-creator.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index 5c8ca554c3b7..9d1d2e017739 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -2,7 +2,6 @@ "name": "@azure-tools/eslint-plugin-tsv", "private": true, "type": "module", - "main": "dist/src/index.js", "dependencies": { "ajv": "^8.17.1", "yaml-eslint-parser": "^1.2.3" diff --git a/eng/tools/eslint-plugin-tsv/src/utils/rule-creator.ts b/eng/tools/eslint-plugin-tsv/src/utils/rule-creator.ts index 5040e54b07bd..36f9571934f3 100644 --- a/eng/tools/eslint-plugin-tsv/src/utils/rule-creator.ts +++ b/eng/tools/eslint-plugin-tsv/src/utils/rule-creator.ts @@ -40,7 +40,7 @@ export function createRule(ruleContext: RuleInfo): NamedRule.RuleModule { export function createRuleMessages(messageId: string, docs: RuleDocument) { return { - [messageId]: `${docs.error}.\n${docs.action}.\n${docs.example}`, + [messageId]: `Error: ${docs.error}.\nAction: ${docs.action}.\nExample: ${docs.example}`, }; } From 01ec98c82745d35f9b5048ab3243dc8d5d44a4ee Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 24 Jan 2025 18:02:20 +0800 Subject: [PATCH 088/106] update --- eng/tools/eslint-plugin-tsv/package.json | 1 + eng/tools/eslint-plugin-tsv/src/index.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json index 9d1d2e017739..5c8ca554c3b7 100644 --- a/eng/tools/eslint-plugin-tsv/package.json +++ b/eng/tools/eslint-plugin-tsv/package.json @@ -2,6 +2,7 @@ "name": "@azure-tools/eslint-plugin-tsv", "private": true, "type": "module", + "main": "dist/src/index.js", "dependencies": { "ajv": "^8.17.1", "yaml-eslint-parser": "^1.2.3" diff --git a/eng/tools/eslint-plugin-tsv/src/index.ts b/eng/tools/eslint-plugin-tsv/src/index.ts index 6e6e166c1421..166204f4a9e6 100644 --- a/eng/tools/eslint-plugin-tsv/src/index.ts +++ b/eng/tools/eslint-plugin-tsv/src/index.ts @@ -1,3 +1,4 @@ +// Note: This file is a tempory workaround for converting new rules to old rules import { ESLint } from "eslint"; import tsvPlugin from "./eslint-plugin-tsv.js"; From d770c0d5f5b8a3be54c45d81e1b38fd36010280e Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Fri, 24 Jan 2025 18:15:08 +0800 Subject: [PATCH 089/106] improve message --- .../typespec-validation/src/rules/tspconfig-validation-rules.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts b/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts index 11174619046f..6a13f28dd6fa 100644 --- a/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts +++ b/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts @@ -37,7 +37,7 @@ function convertToOldRules() { const results = await runESLint(configText, folder, rule.name); if (results.length > 0 && results[0].messages.length > 0) { return { - stdOutput: 'Validation failed. ' + results[0].messages[0].message, + stdOutput: 'Validation failed.\n' + results[0].messages[0].message, // Only used to provide suggestion to correct tspconfig success: true, }; From bd9cacc52c429dec6b311058d35f1ea3ede319aa Mon Sep 17 00:00:00 2001 From: chipate Date: Fri, 24 Jan 2025 12:04:25 -0800 Subject: [PATCH 090/106] Add new resource type VMSize recommendations API and new version for DiagnosticRP --- specification/compute/cspell.yaml | 1 + .../2025-02-01-preview/diagnostic.json | 1478 +++++++++++++++++ ...nerateAttributeBasedVMSizeRecommender.json | 190 +++ .../examples/GenerateSpotPlacementScores.json | 85 + .../examples/GetDiagnosticOperation.json | 29 + .../examples/GetDiskInspection.json | 23 + .../examples/GetSpotPlacementRecommender.json | 23 + .../examples/GetSpotPlacementScores.json | 23 + .../examples/ListDiagnostics.json | 47 + .../RegisterStorageConfiguration.json | 17 + .../examples/RunDiskInspection.json | 20 + .../examples/SpotPlacementRecommender.json | 85 + .../ValidateStorageConfiguration.json | 17 + .../Microsoft.Compute/suppressions.yaml | 6 + .../compute/resource-manager/readme.md | 9 + 15 files changed, 2053 insertions(+) create mode 100644 specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json create mode 100644 specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json create mode 100644 specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateSpotPlacementScores.json create mode 100644 specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GetDiagnosticOperation.json create mode 100644 specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GetDiskInspection.json create mode 100644 specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GetSpotPlacementRecommender.json create mode 100644 specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GetSpotPlacementScores.json create mode 100644 specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/ListDiagnostics.json create mode 100644 specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/RegisterStorageConfiguration.json create mode 100644 specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/RunDiskInspection.json create mode 100644 specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/SpotPlacementRecommender.json create mode 100644 specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/ValidateStorageConfiguration.json diff --git a/specification/compute/cspell.yaml b/specification/compute/cspell.yaml index ccaf4546626a..1ebdb93297a0 100644 --- a/specification/compute/cspell.yaml +++ b/specification/compute/cspell.yaml @@ -21,6 +21,7 @@ words: - osdisk - osdiskforlinuxsimple - osdiskforwindowssimple + - rdma - reimage - reimageall - reimaged diff --git a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json new file mode 100644 index 000000000000..0a39040491bf --- /dev/null +++ b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json @@ -0,0 +1,1478 @@ +{ + "swagger": "2.0", + "info": { + "title": "ComputeDiagnosticResourceProviderClient", + "description": "The Compute Diagnostic Resource Provider Client", + "version": "2025-02-01-preview" + }, + "host": "management.azure.com", + "schemes": [ + "https" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "security": [ + { + "azure_auth": [ + "user_impersonation" + ] + } + ], + "securityDefinitions": { + "azure_auth": { + "type": "oauth2", + "authorizationUrl": "https://login.microsoftonline.com/common/oauth2/authorize", + "flow": "implicit", + "description": "Azure Active Directory OAuth2 Flow", + "scopes": { + "user_impersonation": "impersonate your user account" + } + } + }, + "paths": { + "/subscriptions/{subscriptionId}/providers/Microsoft.Compute/locations/{location}/diagnosticOperations/{operationId}": { + "get": { + "tags": [ + "diagnosticOperations" + ], + "operationId": "DiagnosticOperations_Read", + "description": "Gets status of a Diagnostic operation issued for Disk Inspection.", + "parameters": [ + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/LocationParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/OperationIdParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter" + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/ComputeDiagnosticsOperationResult" + } + }, + "202": { + "description": "Accepted", + "headers": { + "Location": { + "type": "string" + } + } + }, + "default": { + "description": "Error response describing why the operation failed.", + "schema": { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-examples": { + "Gets the status of a scheduled disk inspection request.": { + "$ref": "./examples/GetDiagnosticOperation.json" + } + } + } + }, + "/subscriptions/{subscriptionId}/providers/Microsoft.Compute/locations/{location}/diagnostics/diskInspection/run": { + "post": { + "tags": [ + "diagnostics" + ], + "operationId": "DiskInspection_Create", + "x-ms-long-running-operation": true, + "x-ms-long-running-operation-options": { + "final-state-via": "location" + }, + "description": "Creates a request for executing disk Inspection.", + "consumes": [ + "application/json" + ], + "parameters": [ + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/LocationParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "name": "runDiskInspectionInput", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/RunDiskInspectionInput" + }, + "description": "RunDiskInspection object supplied in the body of the Post run disk inspection operation." + } + ], + "responses": { + "202": { + "description": "Accepted", + "headers": { + "Location": { + "type": "string" + } + } + }, + "default": { + "description": "Error response describing why the diagnostic run failed.", + "schema": { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-examples": { + "Create a request for disk inspection.": { + "$ref": "./examples/RunDiskInspection.json" + } + } + } + }, + "/subscriptions/{subscriptionId}/providers/Microsoft.Compute/locations/{location}/diagnostics": { + "get": { + "tags": [ + "diagnostics" + ], + "operationId": "Diagnostics_List", + "description": "Lists all available Compute diagnostics for a subscription in a location.", + "x-ms-pageable": { + "nextLinkName": "nextLink", + "itemName": "value" + }, + "parameters": [ + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/LocationParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter" + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/ComputeDiagnosticsList" + } + }, + "default": { + "description": "Error response describing why the listing call failed.", + "schema": { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-examples": { + "Gets the status of a scheduled disk inspection request.": { + "$ref": "./examples/ListDiagnostics.json" + } + } + } + }, + "/subscriptions/{subscriptionId}/providers/Microsoft.Compute/locations/{location}/diagnostics/diskInspection": { + "get": { + "tags": [ + "diagnostics_DiskInspection" + ], + "operationId": "DiskInspection_Get", + "description": "Gets a specific Compute diagnostic for a subscription in a location.", + "parameters": [ + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/LocationParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter" + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/ComputeDiagnosticBase" + } + }, + "default": { + "description": "Error response describing why the get call failed.", + "schema": { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-examples": { + "Gets the status of a scheduled disk inspection request.": { + "$ref": "./examples/GetDiskInspection.json" + } + } + } + }, + "/subscriptions/{subscriptionId}/providers/Microsoft.Compute/locations/{location}/diagnostics/diskInspection/registerStorageConfiguration": { + "post": { + "tags": [ + "registerStorageConfiguration" + ], + "operationId": "DiskInspectionStorageConfiguration_Register", + "x-ms-long-running-operation": false, + "description": "Register a storageAccount for a subscription used for DiskInspection", + "consumes": [ + "application/json" + ], + "parameters": [ + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/LocationParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "name": "storageConfigurationInput", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/StorageConfigurationInput" + }, + "description": "Storage Configuration object supplied in the body of the Post to cache storageAccount for a given subscription." + } + ], + "responses": { + "200": { + "description": "Succeeded" + }, + "default": { + "description": "Error response describing why the register operation failed.", + "schema": { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-examples": { + "Create a request for registering a storageAccount information.": { + "$ref": "./examples/RegisterStorageConfiguration.json" + } + } + } + }, + "/subscriptions/{subscriptionId}/providers/Microsoft.Compute/locations/{location}/diagnostics/diskInspection/validateStorageConfiguration": { + "post": { + "tags": [ + "validateStorageConfiguration" + ], + "operationId": "DiskInspectionStorageConfiguration_Validate", + "x-ms-long-running-operation": false, + "description": "Validate if a storageAccount configuration exists for a subscription used for DiskInspection.", + "consumes": [ + "application/json" + ], + "parameters": [ + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/LocationParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter" + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/StorageConfigurationResponse" + } + }, + "default": { + "description": "Error response describing why the get call failed.", + "schema": { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-examples": { + "Returns storageAccount value for an existing configuration entry": { + "$ref": "./examples/ValidateStorageConfiguration.json" + } + } + } + }, + "/subscriptions/{subscriptionId}/providers/Microsoft.Compute/locations/{location}/diagnostics/spotPlacementRecommender": { + "get": { + "tags": [ + "diagnostics_SpotPlacementRecommender" + ], + "operationId": "SpotPlacementRecommender_Get", + "description": "[SOON TO BE DEPRECATED] Gets Spot Placement Recommender diagnostic metadata.", + "parameters": [ + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/LocationParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter" + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/ComputeDiagnosticBase" + } + }, + "default": { + "description": "Error response describing why the get call failed.", + "schema": { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-examples": { + "Gets the metadata of Spot Placement Recommender diagnostic.": { + "$ref": "./examples/GetSpotPlacementRecommender.json" + } + } + } + }, + "/subscriptions/{subscriptionId}/providers/Microsoft.Compute/locations/{location}/diagnostics/spotPlacementRecommender/generate": { + "post": { + "tags": [ + "spotPlacementRecommender" + ], + "operationId": "SpotPlacementRecommender_Post", + "description": "[SOON TO BE DEPRECATED] Generates placement scores for Spot VM skus.", + "parameters": [ + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/LocationParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "name": "spotPlacementRecommenderInput", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/SpotPlacementRecommenderInput" + }, + "description": "SpotPlacementRecommender object supplied in the body of the Post spot placement recommender operation." + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/SpotPlacementRecommenderResponse" + } + }, + "default": { + "description": "Error response describing why the operation failed.", + "schema": { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-examples": { + "Returns spot VM placement scores for given configurations.": { + "$ref": "./examples/SpotPlacementRecommender.json" + } + } + } + }, + "/subscriptions/{subscriptionId}/providers/Microsoft.Compute/locations/{location}/placementScores/spot": { + "get": { + "tags": [ + "placementScores_spot" + ], + "operationId": "SpotPlacementScores_Get", + "description": "Gets Spot Placement Scores metadata.", + "parameters": [ + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/LocationParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter" + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/ComputeDiagnosticBase" + } + }, + "default": { + "description": "Error response describing why the get call failed.", + "schema": { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-examples": { + "Gets the metadata of Spot Placement Scores.": { + "$ref": "./examples/GetSpotPlacementScores.json" + } + } + } + }, + "/subscriptions/{subscriptionId}/providers/Microsoft.Compute/locations/{location}/placementScores/spot/generate": { + "post": { + "tags": [ + "spotPlacementScores" + ], + "operationId": "SpotPlacementScores_Post", + "description": "Generates placement scores for Spot VM skus.", + "parameters": [ + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/LocationParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "name": "spotPlacementScoresInput", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/SpotPlacementScoresInput" + }, + "description": "SpotPlacementScores object supplied in the body of the Post spot placement scores operation." + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/SpotPlacementScoresResponse" + } + }, + "default": { + "description": "Error response describing why the operation failed.", + "schema": { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-examples": { + "Returns spot VM placement scores for given configurations.": { + "$ref": "./examples/GenerateSpotPlacementScores.json" + } + } + } + }, + "/subscriptions/{subscriptionId}/providers/Microsoft.Compute/locations/{location}/vmSizeRecommendations/vmAttributeBased/generate": { + "post": { + "tags": [ + "attributeBasedVMSizeRecommender" + ], + "operationId": "AttributeBasedVMSizeRecommender_Post", + "description": "Generates attribute based VM Size Recommendations.", + "parameters": [ + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/LocationParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "name": "attributeBasedVMSizeRecommenderInput", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/AttributeBasedVMSizeRecommenderInput" + }, + "description": "AttributeBasedVMSizeRecommender object supplied in the body of the Post attribute based VMSize recommender operation." + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/AttributeBasedVMSizeRecommenderResponse" + } + }, + "default": { + "description": "Error response describing why the operation failed.", + "schema": { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-examples": { + "Returns VMSize recommendations for given configurations.": { + "$ref": "./examples/GenerateAttributeBasedVMSizeRecommender.json" + } + } + } + } + }, + "definitions": { + "ComputeDiagnosticsList": { + "description": "Lists all available Compute diagnostics for a subscription in a location.", + "type": "object", + "properties": { + "value": { + "type": "array", + "items": { + "$ref": "#/definitions/ComputeDiagnosticBase" + }, + "description": "The collection of available Compute diagnostics returned by the listing operation." + }, + "nextLink": { + "type": "string", + "description": "The continuation token." + } + } + }, + "ComputeDiagnosticBase": { + "description": "Contains metadata of a diagnostic type", + "type": "object", + "properties": { + "properties": { + "$ref": "#/definitions/DiagnosticProperties" + } + }, + "allOf": [ + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/definitions/ProxyResource" + } + ] + }, + "DiagnosticProperties": { + "description": "Contains additional properties of a diagnostic", + "type": "object", + "properties": { + "supportedResourceTypes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Describes what are the supported resource types for a diagnostic." + } + } + }, + "RunDiskInspectionInput": { + "type": "object", + "properties": { + "resourceId": { + "description": "Qualified ID of the resource.", + "type": "string" + }, + "manifest": { + "description": "Name of manifest in order to trigger Disk Inspection.", + "type": "string" + }, + "uploadSasUri": { + "description": "SAS uri to the blob where results will be uploaded.", + "type": "string", + "x-ms-secret": true + } + }, + "required": [ + "resourceId", + "manifest", + "uploadSasUri" + ], + "description": "Data used for requesting a Disk Inspection execution." + }, + "ComputeDiagnosticsOperationResult": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "The result of the disk inspection operation." + }, + "responseFields": { + "type": "string", + "description": "The response fields of the disk inspection operation." + }, + "resultStatus": { + "type": "string", + "enum": [ + "Success", + "Failed" + ], + "x-ms-enum": { + "name": "resultStatus", + "modelAsString": true + }, + "description": "Result status of the async operation." + }, + "errorDetail": { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorDetail", + "description": "The API error details." + }, + "createdUTC": { + "type": "string", + "format": "date-time", + "description": "The time when the disk inspection was completed." + } + }, + "description": "Api output result when Compute Diagnostic operation is completed." + }, + "StorageConfigurationInput": { + "type": "object", + "properties": { + "storageAccountId": { + "description": "Fully qualified storage account Id. Example: \"/subscriptions/{subId}/resourceGroups/{rgName}/providers/Microsoft.Storage/storageAccounts/{storageAccountName}\"", + "type": "string" + } + }, + "required": [ + "storageAccountId" + ], + "description": "Data used for registering a Storage Account for a Subscription." + }, + "StorageConfigurationResponse": { + "type": "object", + "properties": { + "storageAccountId": { + "type": "string", + "description": "Fully qualified storage account Id. Example: \"/subscriptions/{subId}/resourceGroups/{rgName}/providers/Microsoft.Storage/storageAccounts/{storageAccountName}\"" + } + }, + "description": "Api output result when there is an existing storage configuration entry." + }, + "ErrorResponse": { + "type": "object", + "properties": { + "error": { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorDetail" + } + }, + "description": "An error response from the Compute Diagnostic Resource Provider service." + }, + "ErrorDetail": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "The error code." + }, + "target": { + "type": "string", + "description": "The target of the particular error." + }, + "message": { + "type": "string", + "description": "User friendly error message." + }, + "details": { + "type": "array", + "items": { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorDetail" + }, + "description": "The Api error details" + }, + "innerError": { + "$ref": "#/definitions/InnerError", + "description": "The Api inner error" + } + }, + "description": "Error Detail message." + }, + "InnerError": { + "type": "object", + "properties": { + "exceptiontype": { + "type": "string", + "description": "The exception type." + }, + "errordetail": { + "type": "string", + "description": "The internal error message or exception dump." + } + }, + "description": "Inner error details." + }, + "SpotPlacementRecommenderInput": { + "type": "object", + "properties": { + "desiredLocations": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The desired regions" + }, + "desiredSizes": { + "type": "array", + "items": { + "$ref": "#/definitions/ResourceSize" + }, + "description": "The desired resource SKUs." + }, + "desiredCount": { + "type": "integer", + "format": "int32", + "description": "Desired instance count per region/zone based on the scope." + }, + "availabilityZones": { + "type": "boolean", + "description": "Defines if the scope is zonal or regional." + } + }, + "description": "SpotPlacementRecommender API Input." + }, + "SpotPlacementRecommenderResponse": { + "type": "object", + "properties": { + "desiredLocations": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The desired regions" + }, + "desiredSizes": { + "type": "array", + "items": { + "$ref": "#/definitions/ResourceSize" + }, + "description": "The desired resource SKUs." + }, + "desiredCount": { + "type": "integer", + "format": "int32", + "description": "Desired instance count per region/zone based on the scope." + }, + "availabilityZones": { + "type": "boolean", + "description": "Defines if the scope is zonal or regional." + }, + "placementScores": { + "type": "array", + "items": { + "$ref": "#/definitions/PlacementScore" + }, + "description": "The spot placement scores." + } + }, + "description": "SpotPlacementRecommender API response." + }, + "SpotPlacementScoresInput": { + "type": "object", + "properties": { + "desiredLocations": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The desired regions" + }, + "desiredSizes": { + "type": "array", + "items": { + "$ref": "#/definitions/ResourceSize" + }, + "description": "The desired resource SKUs." + }, + "desiredCount": { + "type": "integer", + "format": "int32", + "description": "Desired instance count per region/zone based on the scope." + }, + "availabilityZones": { + "type": "boolean", + "description": "Defines if the scope is zonal or regional." + } + }, + "description": "SpotPlacementScores API Input." + }, + "SpotPlacementScoresResponse": { + "type": "object", + "properties": { + "desiredLocations": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The desired regions" + }, + "desiredSizes": { + "type": "array", + "items": { + "$ref": "#/definitions/ResourceSize" + }, + "description": "The desired resource SKUs." + }, + "desiredCount": { + "type": "integer", + "format": "int32", + "description": "Desired instance count per region/zone based on the scope." + }, + "availabilityZones": { + "type": "boolean", + "description": "Defines if the scope is zonal or regional." + }, + "placementScores": { + "type": "array", + "items": { + "$ref": "#/definitions/PlacementScore" + }, + "description": "The spot placement scores." + } + }, + "description": "SpotPlacementScores API response." + }, + "ResourceSize": { + "type": "object", + "properties": { + "sku": { + "type": "string", + "description": "The resource's CRP virtual machine SKU size." + } + }, + "description": "SpotPlacementRecommender API response." + }, + "PlacementScore": { + "type": "object", + "properties": { + "sku": { + "type": "string", + "description": "The resource's CRP virtual machine SKU size." + }, + "region": { + "type": "string", + "description": "The region." + }, + "availabilityZone": { + "type": "string", + "description": "The availability region." + }, + "score": { + "type": "string", + "description": "The placement score." + }, + "isQuotaAvailable": { + "type": "boolean", + "description": "Whether the desired quota is available." + } + }, + "description": "The spot placement score for sku/region/zone combination." + }, + "AttributeBasedVMSizeRecommenderInput": { + "type": "object", + "properties": { + "regularPriorityProfile": { + "type": "object", + "items": { + "$ref": "#/definitions/RegularPriorityProfile" + }, + "description": "The regular priority VM profile." + }, + "spotPriorityProfile": { + "type": "object", + "items": { + "$ref": "#/definitions/SpotPriorityProfile" + }, + "description": "The spot priority VM profile." + }, + "recommendationProperties": { + "type": "object", + "items": { + "$ref": "#/definitions/RecommendationProperties" + }, + "description": "The recommendation properties." + }, + "resourceProperties": { + "type": "object", + "items": { + "$ref": "#/definitions/ResourceProperties" + }, + "description": "The resource properties." + } + }, + "description": "AttributeBasedVMSizeRecommender API Input." + }, + "AttributeBasedVMSizeRecommenderResponse": { + "type": "object", + "properties": { + "recommendedVMSizes": { + "type": "object", + "items": { + "$ref": "#/definitions/RecommendedVMSizes" + }, + "description": "The recommended VMSizes." + } + }, + "description": "AttributeBasedVMSizeRecommender API response." + }, + "RegularPriorityProfile": { + "type": "object", + "properties": { + "targetCapacity": { + "type": "integer", + "format": "int32", + "description": "The target capacity." + }, + "capacityUnitType": { + "type": "string", + "description": "The capacity unit type." + } + }, + "description": "Regular Priority Profile for AttributeBasedVmSizeRecommender." + }, + "SpotPriorityProfile": { + "type": "object", + "properties": { + "targetCapacity": { + "type": "integer", + "format": "int32", + "description": "The target capacity." + }, + "capacityUnitType": { + "type": "string", + "description": "The capacity unit type." + }, + "maxPricePerVM": { + "type": "number", + "format": "double", + "description": "The maximum price per spot vm instance." + } + }, + "description": "Spot Priority Profile for AttributeBasedVmSizeRecommender." + }, + "RecommendationProperties": { + "type": "object", + "properties": { + "quotaAndOfferRestrictionsCheckRequired": { + "type": "boolean", + "description": "The quota and offer restrictions check required flag." + } + }, + "description": "Recommendations Properties for AttributeBasedVmSizeRecommender." + }, + "ResourceProperties": { + "type": "object", + "properties": { + "vmAttributes": { + "type": "object", + "items": { + "$ref": "#/definitions/VmAttributes" + }, + "description": "The VM attributes." + } + }, + "description": "Resource Properties for AttributeBasedVmSizeRecommender." + }, + "VmAttributes": { + "type": "object", + "properties": { + "vCpuCount": { + "type": "object", + "items": { + "$ref": "#/definitions/VMAttributeMinMaxInteger" + }, + "description": "The vCPU count." + }, + "memoryInGiB": { + "type": "object", + "items": { + "$ref": "#/definitions/VMAttributeMinMaxDouble" + }, + "description": "The memory in GiB." + }, + "memoryInGiBPerVCpu": { + "type": "object", + "items": { + "$ref": "#/definitions/VMAttributeMinMaxDouble" + }, + "description": "The memory in GiB per vCPU." + }, + "vmCategories": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The vm categories." + }, + "architectureTypes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The cpu architecture types." + }, + "cpuManufacturers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The cpu manufacturers." + }, + "localStorageSupport": { + "type": "string", + "description": "The local storage support filter.", + "enum": [ + "Included", + "Required", + "Excluded" + ], + "x-ms-enum": { + "name": "FilterType", + "modelAsString": false + } + }, + "localStorageDiskTypes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The local storage disk types." + }, + "localStorageInGiB": { + "type": "object", + "items": { + "$ref": "#/definitions/VMAttributeMinMaxDouble" + }, + "description": "The local storage in GiB." + }, + "dataDiskCount": { + "type": "object", + "items": { + "$ref": "#/definitions/VMAttributeMinMaxInteger" + }, + "description": "The data disk count." + }, + "ultraSSDSupport": { + "type": "string", + "description": "The ultra ssd support filter.", + "enum": [ + "Included", + "Required", + "Excluded" + ], + "x-ms-enum": { + "name": "FilterType", + "modelAsString": false + } + }, + "hibernationSupport": { + "type": "string", + "description": "The hibernation support filter.", + "enum": [ + "Included", + "Required", + "Excluded" + ], + "x-ms-enum": { + "name": "FilterType", + "modelAsString": false + } + }, + "hyperVGenerations": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The Hyper-V Generations." + }, + "networkInterfaceCount": { + "type": "object", + "items": { + "$ref": "#/definitions/VMAttributeMinMaxInteger" + }, + "description": "The network interface count." + }, + "networkBandwidthInMbps": { + "type": "object", + "items": { + "$ref": "#/definitions/VMAttributeMinMaxDouble" + }, + "description": "The network bandwidth in Mbps." + }, + "burstableSupport": { + "type": "string", + "description": "The burstable support filter.", + "enum": [ + "Included", + "Required", + "Excluded" + ], + "x-ms-enum": { + "name": "FilterType", + "modelAsString": false + } + }, + "acceleratorSupport": { + "type": "string", + "description": "The accelerator support filter.", + "enum": [ + "Included", + "Required", + "Excluded" + ], + "x-ms-enum": { + "name": "FilterType", + "modelAsString": false + } + }, + "acceleratorTypes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The accelerator types." + }, + "acceleratorManufacturers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The accelerator manufacturers." + }, + "acceleratorCount": { + "type": "object", + "items": { + "$ref": "#/definitions/VMAttributeMinMaxInteger" + }, + "description": "The accelerator count." + }, + "rdmaSupport": { + "type": "string", + "description": "The rdma support filter.", + "enum": [ + "Included", + "Required", + "Excluded" + ], + "x-ms-enum": { + "name": "FilterType", + "modelAsString": false + } + }, + "rdmaNetworkInterfaceCount": { + "type": "object", + "items": { + "$ref": "#/definitions/VMAttributeMinMaxInteger" + }, + "description": "The rdma network interface count." + }, + "acceleratedNetworkingSupport": { + "type": "string", + "description": "The accelerated networking support filter.", + "enum": [ + "Included", + "Required", + "Excluded" + ], + "x-ms-enum": { + "name": "FilterType", + "modelAsString": false + } + }, + "confidentialVMSupport": { + "type": "string", + "description": "The confidential VM support filter.", + "enum": [ + "Included", + "Required", + "Excluded" + ], + "x-ms-enum": { + "name": "FilterType", + "modelAsString": false + } + }, + "osTypes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of Operation Systems." + }, + "allowedVMSizes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of allowed VMSizes." + }, + "excludedVMSizes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of excluded VMSizes." + } + }, + "description": "VM attributes for AttributeBasedVmSizeRecommender." + }, + "VMAttributeMinMaxInteger": { + "type": "object", + "properties": { + "min": { + "type": "integer", + "format": "int32", + "description": "The minimum value for vm attribute in integer." + }, + "max": { + "type": "integer", + "format": "int32", + "description": "The maximum value for vm attribute in integer." + } + }, + "description": "MinMax values in integer for AttributeBasedVmSizeRecommender." + }, + "VMAttributeMinMaxDouble": { + "type": "object", + "properties": { + "min": { + "type": "number", + "format": "double", + "description": "The minimum value for vm attribute in double." + }, + "max": { + "type": "number", + "format": "double", + "description": "The maximum value for vm attribute in double." + } + }, + "description": "Priority Profile for AttributeBasedVmSizeRecommender." + }, + "RecommendedVMSizes": { + "type": "object", + "properties": { + "regularVMSizes": { + "type": "array", + "items": { + "$ref": "#/definitions/RecommendedVMSizeProperties" + }, + "description": "The regular priority VMSizes." + }, + "spotVMSizes": { + "type": "array", + "items": { + "$ref": "#/definitions/RecommendedVMSizeProperties" + }, + "description": "The spot priority VMSizes." + } + }, + "description": "The regular and spot priority recommended VMSizes." + }, + "RecommendedVMSizeProperties": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the VMSize." + }, + "size": { + "type": "string", + "description": "The vm size." + }, + "family": { + "type": "string", + "description": "The family of VMSize." + }, + "attributes": { + "type": "object", + "items": { + "$ref": "#/definitions/RecommendedVMSizeAttributes" + }, + "description": "The recommended VMSize attributes." + }, + "restrictions": { + "type": "array", + "items": { + "$ref": "#/definitions/RecommendedVMSizeRestrictions" + }, + "description": "The recommended VMSize restrictions." + } + }, + "description": "The recommended VMSize properties." + }, + "RecommendedVMSizeAttributes": { + "type": "object", + "properties": { + "vCpu": { + "type": "integer", + "format": "int32", + "description": "The vCPU count." + }, + "memoryInGiB": { + "type": "number", + "format": "double", + "description": "The memory in GiB." + }, + "cpuArchitecture": { + "type": "string", + "description": "The cpu architecture of VMSize." + }, + "cpuManufacturer": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The cpu manufacturers." + }, + "networkBandwidthInMbps": { + "type": "number", + "format": "double", + "description": "The network bandwidth in Mbps." + }, + "localStorageSupport": { + "type": "boolean", + "description": "The local storage support flag." + }, + "localStorageType": { + "type": "string", + "description": "The local storage type of VMSize." + }, + "localStorageInGiB": { + "type": "number", + "format": "double", + "description": "The local storage in GiB." + }, + "dataDiskCount": { + "type": "integer", + "format": "int32", + "description": "The data disk count." + }, + "networkInterfaceCount": { + "type": "integer", + "format": "int32", + "description": "The network interface count." + } + }, + "description": "The recommended VMSize attributes." + }, + "RecommendedVMSizeRestrictions": { + "type": "object", + "properties": { + "offerRestrictions": { + "type": "array", + "items": { + "$ref": "#/definitions/VMSizeOfferRestriction" + }, + "description": "The offer restrictions on VMSize." + }, + "quotaRestrictions": { + "type": "array", + "items": { + "$ref": "#/definitions/VMSizeQuotaRestriction" + }, + "description": "The quota restrictions on VMSize." + } + }, + "description": "The restrictions recommended VMSizes." + }, + "VMSizeOfferRestriction": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "The type of offer restriction on VMSize.", + "enum": [ + "NotSpecified", + "Location", + "Zone" + ], + "x-ms-enum": { + "name": "ResourceSkuRestrictionType", + "modelAsString": false + } + }, + "values": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of restriction values." + }, + "restrictionInfo": { + "type": "object", + "items": { + "$ref": "#/definitions/VMSizeOfferRestrictionInfo" + }, + "description": "The offer restrictions on VMSize." + }, + "reasonCode": { + "type": "string", + "description": "The offer restriction reason code.", + "enum": [ + "NotSpecified", + "QuotaId", + "NotAvailableForSubscription" + ], + "x-ms-enum": { + "name": "ResourceSkuRestrictionReasonCode", + "modelAsString": false + } + } + }, + "description": "The offer restrictions on recommended VMSize." + }, + "VMSizeOfferRestrictionInfo": { + "type": "object", + "properties": { + "locations": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of restriction locations." + }, + "zones": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of restriction zones." + } + }, + "description": "The offer restriction info on recommended VMSize." + }, + "VMSizeQuotaRestriction": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "The type of quota restriction on VMSize.", + "enum": [ + "RegionalVCpu", + "VMFamilyVCpu" + ], + "x-ms-enum": { + "name": "QuotaRestrictionsType", + "modelAsString": false + } + } + }, + "description": "The quota restrictions on recommended VMSize." + } + } +} diff --git a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json new file mode 100644 index 000000000000..2e76964c8d49 --- /dev/null +++ b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json @@ -0,0 +1,190 @@ +{ + "parameters": { + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "location": "eastus", + "api-version": "2025-02-01-preview", + "attributeBasedVMSizeRecommenderInput": { + "regularPriorityProfile": { + "targetCapacity": 1, + "capacityUnitType": "VMInstanceCount" + }, + "spotPriorityProfile": { + "targetCapacity": 1, + "capacityUnitType": "VMInstanceCount", + "maxPricePerVM": 0.200 + }, + "recommendationProperties": { + "quotaAndOfferRestrictionsCheckRequired": true + }, + "resourceProperties": { + "vmAttributes": { + "vCpuCount": { + "Min": 0, + "Max": 0 + }, + "memoryInMiB": { + "Min": 0, + "Max": 0 + }, + "vmCategories": [ + "General Purpose" + ], + "architectureTypes": [ + "x86", + "arm64" + ], + "cpuManufacturers": [ + "Intel", + "AMD" + ], + "localStorageSupport": "included", + "ultraSSDSupport": "included", + "localStorageTypes": [ + "SSD", + "HDD" + ], + "localStorageInMiB": { + "Min": 0, + "Max": 0 + }, + "dataDiskCount": { + "Min": 0, + "Max": 4 + }, + "hibernationSupport": "included", + "hyperVGenerations": [ + "Gen1", + "Gen2" + ], + "networkInterfaceCount": { + "Min": 0, + "Max": 4 + }, + "networkBandwidthInMbps": { + "Min": 0, + "Max": 0 + }, + "confidentialVMSupport": "included", + "burstableSupport": "included", + "acceleratorSupport": "included", + "acceleratorTypes": [ + "FPGA", + "GPU" + ], + "acceleratorManufacturers": [ + "Intel", + "AMD", + "NVidia" + ], + "acceleratorCount": { + "Min": 0, + "Max": 0 + }, + "acceleratedNetworkingSupport": "included", + "rdmaSupport": "included", + "rdmaNetworkInterfaceCount": { + "Min": 0, + "Max": 0 + }, + "osType": [ + "Windows", + "Linux" + ], + "allowedVMSizes": [ + ], + + "excludeVMSizes": [ + ] + } + } + } + }, + "responses": { + "200": { + "body": { + "recommendedVMSizes": { + "regularVMSizes": [ + { + "name": "Standard_D4ds_v5", + "size": "D4ds_v5", + "family": "standardDDSv5Family", + "attributes": { + "vCPUs": 4, + "memoryGB": 16, + "cpuArchitecture": "x86", + "cpuManufacturer": "Intel", + "networkBandwidthInMbps": "12500", + "localStorageSupport": "Included", + "localStorageType": "SSD", + "localStorageInGb": 150, + "dataDiskCount": 4, + "networkInterfaceCount": 4 + }, + "restrictions": + { + "offerRestrictions": [ + { + "type": "Location", + "values": [ "westus" ], + "restrictionInfo": { + "location": [ "westus" ], + "zones": [ "1", "2", "3" ] + }, + "reasonCode": "NotAvailableForSubscription" + } + ], + "quotaRestrictions": [ + { + "type": "RegionalVCpu" + } + ] + } + } + ], + "spotVMSizes": [ + { + "name": "Standard_D4ds_v6", + "size": "D4ds_v5", + "family": "standardDDSv6Family", + "attributes": { + "vCPUs": 4, + "memoryGB": 16, + "cpuArchitecture": "x86", + "cpuManufacturer": "Intel", + "networkBandwidthInMbps": "12500", + "localStorageSupport": "Included", + "localStorageType": "SSD", + "localStorageInGb": 150, + "dataDiskCount": 4, + "networkInterfaceCount": 4 + }, + "restrictions": + { + "offerRestrictions": [ + { + "type": "Location", + "values": [ "westus" ], + "restrictionInfo": { + "location": [ "westus" ], + "zones": [ "1", "2", "3" ] + }, + "reasonCode": "NotAvailableForSubscription" + } + ], + "quotaRestrictions": [ + { + "type": "RegionalVCpu" + } + ] + } + } + ] + } + }, + "headers": { + "x-ms-request-id": "57b891ab-1fb7-4f5a-b002-71eb6854961f", + "location": "https://management.azure.com/subscriptions/be23ca13-8eb4-4d0e-be10-b00451817956/providers/Microsoft.Compute/locations/eastus/vmSizeRecommendations/vmAttributeBased/generate?api-version=2025-02-01-preview" + } + } + } +} diff --git a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateSpotPlacementScores.json b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateSpotPlacementScores.json new file mode 100644 index 000000000000..a74a41cf2459 --- /dev/null +++ b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateSpotPlacementScores.json @@ -0,0 +1,85 @@ +{ + "parameters": { + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "location": "eastus", + "api-version": "2025-02-01-preview", + "spotPlacementScoresInput": { + "desiredLocations": [ + "eastus", + "eastus2" + ], + "desiredSizes": [ + { + "sku": "Standard_D2_v2" + } + ], + "desiredCount": 1, + "availabilityZones": true + } + }, + "responses": { + "200": { + "body": { + "desiredLocations": [ + "eastus", + "eastus2" + ], + "desiredSizes": [ + { + "sku": "Standard_D2_v2" + } + ], + "desiredCount": 1, + "availabilityZones": true, + "placementScores": [ + { + "sku": "Standard_D2_v2", + "region": "eastus", + "availabilityZone": "1", + "score": "High", + "isQuotaAvailable": true + }, + { + "sku": "Standard_D2_v2", + "region": "eastus", + "availabilityZone": "2", + "score": "High", + "isQuotaAvailable": true + }, + { + "sku": "Standard_D2_v2", + "region": "eastus", + "availabilityZone": "3", + "score": "High", + "isQuotaAvailable": true + }, + { + "sku": "Standard_D2_v2", + "region": "eastus2", + "availabilityZone": "1", + "score": "DataNotFoundOrStale", + "isQuotaAvailable": true + }, + { + "sku": "Standard_D2_v2", + "region": "eastus2", + "availabilityZone": "2", + "score": "High", + "isQuotaAvailable": true + }, + { + "sku": "Standard_D2_v2", + "region": "eastus2", + "availabilityZone": "3", + "score": "High", + "isQuotaAvailable": true + } + ] + }, + "headers": { + "x-ms-request-id": "57b891ab-1fb7-4f5a-b002-71eb6854961f", + "location": "https://management.azure.com/subscriptions/be23ca13-8eb4-4d0e-be10-b00451817956/providers/Microsoft.Compute/locations/eastus/placementScores/spot/generate?api-version=2025-02-01-preview" + } + } + } +} diff --git a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GetDiagnosticOperation.json b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GetDiagnosticOperation.json new file mode 100644 index 000000000000..d16a6301fe62 --- /dev/null +++ b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GetDiagnosticOperation.json @@ -0,0 +1,29 @@ +{ + "parameters": { + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "location": "eastus", + "operationId": "{operation-id}", + "api-version": "2025-02-01-preview" + }, + "responses": { + "200": { + "body": { + "message": "DiskInspection for selected resource succeeded.", + "createdUTC": "2024-03-05T22:15:44.2986354Z", + "resultStatus": "Success", + "errorDetail": null + }, + "headers": { + "x-ms-request-id": "57b891ab-1fb7-4f5a-b002-71eb6854961f", + "location": "https://westcentralus.diagnostic.compute.azure.com/subscriptions/88fd8cb2-8248-499e-9a2d-4929a4b0133c/providers/Microsoft.Compute/locations/southcentralus/diagnosticOperations/57b891ab-1fb7-4f5a-b002-71eb6854961f" + } + }, + "202": { + "headers": { + "Retry-After": "120000", + "x-ms-request-id": "57b891ab-1fb7-4f5a-b002-71eb6854961f", + "location": "https://westcentralus.diagnostic.compute.azure.com/subscriptions/88fd8cb2-8248-499e-9a2d-4929a4b0133c/providers/Microsoft.Compute/locations/southcentralus/diagnosticOperations/57b891ab-1fb7-4f5a-b002-71eb6854961f" + } + } + } +} diff --git a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GetDiskInspection.json b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GetDiskInspection.json new file mode 100644 index 000000000000..0556ca00e927 --- /dev/null +++ b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GetDiskInspection.json @@ -0,0 +1,23 @@ +{ + "parameters": { + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "location": "eastus" + }, + "responses": { + "200": { + "headers": { + "x-ms-request-id": "27b7c568-16ec-46f3-bcf1-5bea3f2529b1" + }, + "body": { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Compute/locations/eastus/diagnostics/diskInspection", + "name": "diskInspection", + "type": "Microsoft.Compute/locations/diagnostics", + "properties": { + "supportedResourceTypes": [ + "Microsoft.Compute/virtualMachines" + ] + } + } + } + } +} diff --git a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GetSpotPlacementRecommender.json b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GetSpotPlacementRecommender.json new file mode 100644 index 000000000000..c62e24870102 --- /dev/null +++ b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GetSpotPlacementRecommender.json @@ -0,0 +1,23 @@ +{ + "parameters": { + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "location": "eastus" + }, + "responses": { + "200": { + "headers": { + "x-ms-request-id": "27b7c568-16ec-46f3-bcf1-5bea3f2529b1" + }, + "body": { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Compute/locations/eastus/diagnostics/spotPlacementRecommender", + "name": "spotPlacementRecommender", + "type": "Microsoft.Compute/locations/diagnostics", + "properties": { + "supportedResourceTypes": [ + "Microsoft.Compute/virtualMachines" + ] + } + } + } + } +} diff --git a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GetSpotPlacementScores.json b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GetSpotPlacementScores.json new file mode 100644 index 000000000000..b7ab7d0e7bbd --- /dev/null +++ b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GetSpotPlacementScores.json @@ -0,0 +1,23 @@ +{ + "parameters": { + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "location": "eastus" + }, + "responses": { + "200": { + "headers": { + "x-ms-request-id": "27b7c568-16ec-46f3-bcf1-5bea3f2529b1" + }, + "body": { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Compute/placementScores/spot", + "name": "spotPlacementScores", + "type": "Microsoft.Compute/placementScores", + "properties": { + "supportedResourceTypes": [ + "Microsoft.Compute/virtualMachines" + ] + } + } + } + } +} diff --git a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/ListDiagnostics.json b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/ListDiagnostics.json new file mode 100644 index 000000000000..e81b89d721e5 --- /dev/null +++ b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/ListDiagnostics.json @@ -0,0 +1,47 @@ +{ + "parameters": { + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "location": "eastus" + }, + "responses": { + "200": { + "headers": { + "x-ms-request-id": "27b7c568-16ec-46f3-bcf1-5bea3f2529b1" + }, + "body": { + "value": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Compute/locations/eastus/diagnostics/diskInspection", + "name": "diskInspection", + "type": "Microsoft.Compute/locations/diagnostics", + "properties": { + "supportedResourceTypes": [ + "Microsoft.Compute/virtualMachines" + ] + } + }, + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Compute/locations/{location}/diagnostics/spotPlacementRecommender", + "name": "spotPlacementRecommender", + "type": "Microsoft.Compute/locations/diagnostics", + "properties": { + "supportedResourceTypes": [ + "Microsoft.Compute/virtualMachines" + ] + } + }, + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Compute/locations/{location}/placementScores/spot", + "name": "spotPlacementRecommender", + "type": "Microsoft.Compute/placementScores", + "properties": { + "supportedResourceTypes": [ + "Microsoft.Compute/virtualMachines" + ] + } + } + ] + } + } + } +} diff --git a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/RegisterStorageConfiguration.json b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/RegisterStorageConfiguration.json new file mode 100644 index 000000000000..c53b212af6b3 --- /dev/null +++ b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/RegisterStorageConfiguration.json @@ -0,0 +1,17 @@ +{ + "parameters": { + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "location": "eastus", + "api-version": "2025-02-01-preview", + "storageConfigurationInput": { + "storageAccountId": "/subscriptions/88fd8cb2-8248-499e-9a2d-4929a4b0133c/resourceGroups/az-iid-blobtest/providers/Microsoft.Storage/storageAccounts/aziidblobtest" + } + }, + "responses": { + "200": { + "headers": { + "x-ms-request-id": "fa55b700-8218-42b1-a3e3-1794cfd37629" + } + } + } +} diff --git a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/RunDiskInspection.json b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/RunDiskInspection.json new file mode 100644 index 000000000000..6374d356fba2 --- /dev/null +++ b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/RunDiskInspection.json @@ -0,0 +1,20 @@ +{ + "parameters": { + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "location": "eastus", + "api-version": "2025-02-01-preview", + "runDiskInspectionInput": { + "resourceId": "/subscriptions/88fd8cb2-8248-499e-9a2d-4929a4b0133c/resourceGroups/cladtest1/providers/Microsoft.Compute/virtualMachines/adVM", + "manifest": "diagnostic", + "uploadSasUri": "testSasUri" + } + }, + "responses": { + "202": { + "headers": { + "x-ms-request-id": "57b891ab-1fb7-4f5a-b002-71eb6854961f", + "location": "https://westcentralus.diagnostic.compute.azure.com/subscriptions/88fd8cb2-8248-499e-9a2d-4929a4b0133c/providers/Microsoft.Compute/locations/southcentralus/diagnosticOperations/57b891ab-1fb7-4f5a-b002-71eb6854961f" + } + } + } +} diff --git a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/SpotPlacementRecommender.json b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/SpotPlacementRecommender.json new file mode 100644 index 000000000000..ae5e81e1bcb1 --- /dev/null +++ b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/SpotPlacementRecommender.json @@ -0,0 +1,85 @@ +{ + "parameters": { + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "location": "eastus", + "api-version": "2025-02-01-preview", + "spotPlacementRecommenderInput": { + "desiredLocations": [ + "eastus", + "eastus2" + ], + "desiredSizes": [ + { + "sku": "Standard_D2_v2" + } + ], + "desiredCount": 1, + "availabilityZones": true + } + }, + "responses": { + "200": { + "body": { + "desiredLocations": [ + "eastus", + "eastus2" + ], + "desiredSizes": [ + { + "sku": "Standard_D2_v2" + } + ], + "desiredCount": 1, + "availabilityZones": true, + "placementScores": [ + { + "sku": "Standard_D2_v2", + "region": "eastus", + "availabilityZone": "1", + "score": "High", + "isQuotaAvailable": true + }, + { + "sku": "Standard_D2_v2", + "region": "eastus", + "availabilityZone": "2", + "score": "High", + "isQuotaAvailable": true + }, + { + "sku": "Standard_D2_v2", + "region": "eastus", + "availabilityZone": "3", + "score": "High", + "isQuotaAvailable": true + }, + { + "sku": "Standard_D2_v2", + "region": "eastus2", + "availabilityZone": "1", + "score": "DataNotFoundOrStale", + "isQuotaAvailable": true + }, + { + "sku": "Standard_D2_v2", + "region": "eastus2", + "availabilityZone": "2", + "score": "High", + "isQuotaAvailable": true + }, + { + "sku": "Standard_D2_v2", + "region": "eastus2", + "availabilityZone": "3", + "score": "High", + "isQuotaAvailable": true + } + ] + }, + "headers": { + "x-ms-request-id": "57b891ab-1fb7-4f5a-b002-71eb6854961f", + "location": "https://management.azure.com/subscriptions/be23ca13-8eb4-4d0e-be10-b00451817956/providers/Microsoft.Compute/locations/eastus/diagnostics/spotplacementrecommender/generate?api-version=2025-02-01-preview" + } + } + } +} diff --git a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/ValidateStorageConfiguration.json b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/ValidateStorageConfiguration.json new file mode 100644 index 000000000000..39066f0063f4 --- /dev/null +++ b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/ValidateStorageConfiguration.json @@ -0,0 +1,17 @@ +{ + "parameters": { + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "location": "eastus", + "api-version": "2025-02-01-preview" + }, + "responses": { + "200": { + "headers": { + "x-ms-request-id": "27b7c568-16ec-46f3-bcf1-5bea3f2529b1" + }, + "body": { + "storageAccountId": "/subscriptions/88fd8cb2-8248-499e-9a2d-4929a4b0133c/resourceGroups/az-iid-blobtest/providers/Microsoft.Storage/storageAccounts/aziidblobtest" + } + } + } +} diff --git a/specification/compute/resource-manager/Microsoft.Compute/suppressions.yaml b/specification/compute/resource-manager/Microsoft.Compute/suppressions.yaml index a801dff0e896..0fc378eb7f39 100644 --- a/specification/compute/resource-manager/Microsoft.Compute/suppressions.yaml +++ b/specification/compute/resource-manager/Microsoft.Compute/suppressions.yaml @@ -1,3 +1,9 @@ - tool: TypeSpecRequirement path: DiagnosticRP/preview/2024-06-01-preview/**/*.json reason: CDRP service not yet ready to migrate to typespec +- tool: TypeSpecRequirement + path: DiagnosticRP/preview/2025-02-01-preview/**/*.json + reason: CDRP service not yet ready to migrate to typespec +- code: DefinitionsPropertiesNamesCamelCase + reason: The property name contains abbreviations and need to keep it as upper case. + from: diagnostic.json diff --git a/specification/compute/resource-manager/readme.md b/specification/compute/resource-manager/readme.md index 5cd125cb6f5b..ae5b099b0cf0 100644 --- a/specification/compute/resource-manager/readme.md +++ b/specification/compute/resource-manager/readme.md @@ -288,6 +288,15 @@ suppressions: reason: Existing issue from last version. ``` +### Tag: package-2025-02-01-preview + +These settings apply only when `--tag=package-2025-02-01-preview` is specified on the command line. + +```yaml $(tag) == 'package-2025-02-01-preview' +input-file: + - Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json +``` + ### Tag: package-2024-11-04 These settings apply only when `--tag=package-2024-11-04` is specified on the command line. From 40dfa24555a432e58d34d5b5491437810e1ee257 Mon Sep 17 00:00:00 2001 From: chipate Date: Fri, 24 Jan 2025 12:45:46 -0800 Subject: [PATCH 091/106] Apply pretties and suppression --- ...nerateAttributeBasedVMSizeRecommender.json | 49 ++++++++++++------- .../Microsoft.Compute/suppressions.yaml | 3 -- .../compute/resource-manager/readme.md | 6 +++ 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json index 2e76964c8d49..82fbb97498ae 100644 --- a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json +++ b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json @@ -90,11 +90,8 @@ "Windows", "Linux" ], - "allowedVMSizes": [ - ], - - "excludeVMSizes": [ - ] + "allowedVMSizes": [], + "excludeVMSizes": [] } } } @@ -120,15 +117,22 @@ "dataDiskCount": 4, "networkInterfaceCount": 4 }, - "restrictions": - { + "restrictions": { "offerRestrictions": [ { "type": "Location", - "values": [ "westus" ], + "values": [ + "westus" + ], "restrictionInfo": { - "location": [ "westus" ], - "zones": [ "1", "2", "3" ] + "location": [ + "westus" + ], + "zones": [ + "1", + "2", + "3" + ] }, "reasonCode": "NotAvailableForSubscription" } @@ -143,10 +147,10 @@ ], "spotVMSizes": [ { - "name": "Standard_D4ds_v6", - "size": "D4ds_v5", - "family": "standardDDSv6Family", - "attributes": { + "name": "Standard_D4ds_v6", + "size": "D4ds_v5", + "family": "standardDDSv6Family", + "attributes": { "vCPUs": 4, "memoryGB": 16, "cpuArchitecture": "x86", @@ -158,15 +162,22 @@ "dataDiskCount": 4, "networkInterfaceCount": 4 }, - "restrictions": - { + "restrictions": { "offerRestrictions": [ { "type": "Location", - "values": [ "westus" ], + "values": [ + "westus" + ], "restrictionInfo": { - "location": [ "westus" ], - "zones": [ "1", "2", "3" ] + "location": [ + "westus" + ], + "zones": [ + "1", + "2", + "3" + ] }, "reasonCode": "NotAvailableForSubscription" } diff --git a/specification/compute/resource-manager/Microsoft.Compute/suppressions.yaml b/specification/compute/resource-manager/Microsoft.Compute/suppressions.yaml index 0fc378eb7f39..ee80746fcc21 100644 --- a/specification/compute/resource-manager/Microsoft.Compute/suppressions.yaml +++ b/specification/compute/resource-manager/Microsoft.Compute/suppressions.yaml @@ -4,6 +4,3 @@ - tool: TypeSpecRequirement path: DiagnosticRP/preview/2025-02-01-preview/**/*.json reason: CDRP service not yet ready to migrate to typespec -- code: DefinitionsPropertiesNamesCamelCase - reason: The property name contains abbreviations and need to keep it as upper case. - from: diagnostic.json diff --git a/specification/compute/resource-manager/readme.md b/specification/compute/resource-manager/readme.md index ae5b099b0cf0..68979f08d5fd 100644 --- a/specification/compute/resource-manager/readme.md +++ b/specification/compute/resource-manager/readme.md @@ -269,6 +269,12 @@ suppressions: from: diagnostic.json - code: XmsPageableForListCalls reason: False positive error as API Path does not match ARM Lint check formatting, requesting to suppress due to approval from reviewer. + from: diagnostic.json + - code: DefinitionsPropertiesNamesCamelCase + reason: The property name contains abbreviations and need to keep it as upper case. + from: diagnostic.json + - code: BodyTopLevelProperties + reason: The is the additional property bag to introduce new nonbreaking properties. from: diagnostic.json - code: PatchResponseCodes reason: PATCH and PUT follow the same behavior and response codes in Compute. Keeping it for legacy reasons. From 5158a331789b1b33ff9d3f98e8cfb0db6b910ea6 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Sun, 26 Jan 2025 13:27:19 +0800 Subject: [PATCH 092/106] remove new tool and put new code into current TSV --- eng/tools/eslint-plugin-tsv/package.json | 32 --------- eng/tools/eslint-plugin-tsv/src/index.ts | 6 -- .../src/rules/emit-autorest.ts | 59 ----------------- .../src/rules/kebab-case-org.ts | 55 ---------------- eng/tools/eslint-plugin-tsv/src/utils/npm.ts | 32 --------- eng/tools/eslint-plugin-tsv/test/e2e.test.ts | 66 ------------------- .../test/rules/emit-autorest.test.ts | 32 --------- .../test/rules/kebab-case-org.test.ts | 35 ---------- eng/tools/eslint-plugin-tsv/tsconfig.json | 10 --- eng/tools/eslint-plugin-tsv/vitest.config.ts | 10 --- eng/tools/package.json | 1 - eng/tools/typespec-validation/package.json | 8 ++- .../src/config/config-schema.ts | 0 .../eslint-plugin-tsv/src/config/types.ts | 0 .../src/eslint-plugin-tsv.ts | 0 .../src/interfaces/named-eslint.ts | 0 .../src/interfaces/rule-interfaces.ts | 0 .../src/rules/tspconfig-validation-rules.ts | 0 .../eslint-plugin-tsv/src/utils/constants.ts | 0 .../src/utils/rule-creator.ts | 22 ++++--- .../eslint-plugin-tsv/src/utils/rule-doc.ts | 0 .../src}/eslint-plugin-tsv/src/yaml/types.ts | 0 .../tspconfig-options-validation.test.ts | 0 .../eslint-plugin-tsv/test/utils/npm.test.ts | 0 .../src/rules/tspconfig-validation-rules.ts | 4 +- .../test/tspconfig.test.ts | 2 +- eng/tools/typespec-validation/tsconfig.json | 3 +- 27 files changed, 22 insertions(+), 355 deletions(-) delete mode 100644 eng/tools/eslint-plugin-tsv/package.json delete mode 100644 eng/tools/eslint-plugin-tsv/src/index.ts delete mode 100644 eng/tools/eslint-plugin-tsv/src/rules/emit-autorest.ts delete mode 100644 eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts delete mode 100644 eng/tools/eslint-plugin-tsv/src/utils/npm.ts delete mode 100644 eng/tools/eslint-plugin-tsv/test/e2e.test.ts delete mode 100644 eng/tools/eslint-plugin-tsv/test/rules/emit-autorest.test.ts delete mode 100644 eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts delete mode 100644 eng/tools/eslint-plugin-tsv/tsconfig.json delete mode 100644 eng/tools/eslint-plugin-tsv/vitest.config.ts rename eng/tools/{ => typespec-validation/src}/eslint-plugin-tsv/src/config/config-schema.ts (100%) rename eng/tools/{ => typespec-validation/src}/eslint-plugin-tsv/src/config/types.ts (100%) rename eng/tools/{ => typespec-validation/src}/eslint-plugin-tsv/src/eslint-plugin-tsv.ts (100%) rename eng/tools/{ => typespec-validation/src}/eslint-plugin-tsv/src/interfaces/named-eslint.ts (100%) rename eng/tools/{ => typespec-validation/src}/eslint-plugin-tsv/src/interfaces/rule-interfaces.ts (100%) rename eng/tools/{ => typespec-validation/src}/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts (100%) rename eng/tools/{ => typespec-validation/src}/eslint-plugin-tsv/src/utils/constants.ts (100%) rename eng/tools/{ => typespec-validation/src}/eslint-plugin-tsv/src/utils/rule-creator.ts (81%) rename eng/tools/{ => typespec-validation/src}/eslint-plugin-tsv/src/utils/rule-doc.ts (100%) rename eng/tools/{ => typespec-validation/src}/eslint-plugin-tsv/src/yaml/types.ts (100%) rename eng/tools/{ => typespec-validation/src}/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts (100%) rename eng/tools/{ => typespec-validation/src}/eslint-plugin-tsv/test/utils/npm.test.ts (100%) diff --git a/eng/tools/eslint-plugin-tsv/package.json b/eng/tools/eslint-plugin-tsv/package.json deleted file mode 100644 index 5c8ca554c3b7..000000000000 --- a/eng/tools/eslint-plugin-tsv/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "@azure-tools/eslint-plugin-tsv", - "private": true, - "type": "module", - "main": "dist/src/index.js", - "dependencies": { - "ajv": "^8.17.1", - "yaml-eslint-parser": "^1.2.3" - }, - "peerDependencies": { - "eslint": ">=9.0.0" - }, - "devDependencies": { - "@types/node": "^18.19.31", - "@vitest/coverage-v8": "^2.0.4", - "eslint": "^9.17.0", - "memfs": "^4.15.0", - "rimraf": "^5.0.10", - "typescript": "~5.6.2", - "vitest": "^2.0.4" - }, - "scripts": { - "build": "tsc --build", - "cbt": "npm run clean && npm run build && npm run test:ci", - "clean": "rimraf ./dist ./temp", - "test": "vitest", - "test:ci": "vitest run --coverage --reporter=verbose" - }, - "engines": { - "node": ">= 18.0.0" - } -} diff --git a/eng/tools/eslint-plugin-tsv/src/index.ts b/eng/tools/eslint-plugin-tsv/src/index.ts deleted file mode 100644 index 166204f4a9e6..000000000000 --- a/eng/tools/eslint-plugin-tsv/src/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Note: This file is a tempory workaround for converting new rules to old rules -import { ESLint } from "eslint"; -import tsvPlugin from "./eslint-plugin-tsv.js"; - -export { ESLint }; -export default tsvPlugin; diff --git a/eng/tools/eslint-plugin-tsv/src/rules/emit-autorest.ts b/eng/tools/eslint-plugin-tsv/src/rules/emit-autorest.ts deleted file mode 100644 index b64953d1ccbf..000000000000 --- a/eng/tools/eslint-plugin-tsv/src/rules/emit-autorest.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Ajv } from "ajv"; -import { Rule } from "eslint"; -import { AST, getStaticYAMLValue } from "yaml-eslint-parser"; -import { TypeSpecConfigJsonSchema } from "../config/config-schema.js"; -import { TypeSpecConfig } from "../config/types.js"; -import { NamedRule } from "../interfaces/named-eslint.js"; - -export const rule: NamedRule.RuleModule = { - name: "emit-autorest", - meta: { - type: "problem", - docs: { - description: - "Requires emitter 'typespec-autorest' to be enabled by default, and requires emitted autorest to match content in repo", - }, - schema: [], - messages: { - invalid: "tspconfig.yaml is invalid per the schema: {{errors}}", - missing: - 'tspconfig.yaml must include the following emitter by default:\n\nemit:\n - "@azure-tools/typespec-autorest"', - // disabled: "Path does not match format '.*/specification/{orgName}/': ''{{filename}}'", - // autorestDiff: "Emitted autorest does not match content in repo", - }, - }, - create(context) { - return { - YAMLDocument(node: Rule.Node) { - const yamlDocument = node as unknown as AST.YAMLDocument; - - // If config yaml is empty, use empty object instead of "null" - const config = getStaticYAMLValue(yamlDocument) || {}; - - const ajv = new Ajv(); - const valid = ajv.validate(TypeSpecConfigJsonSchema, config); - - if (!valid) { - context.report({ - node, - messageId: "invalid", - data: { errors: ajv.errorsText(ajv.errors) }, - }); - return; - } - - const typedConfig = config as unknown as TypeSpecConfig; - if (!typedConfig.emit?.includes("@azure-tools/typespec-autorest")) { - // TODO: Move error message to "emit:" node - context.report({ - node, - messageId: "missing", - }); - return; - } - }, - }; - }, -}; - -export default rule; diff --git a/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts b/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts deleted file mode 100644 index fcc81c116138..000000000000 --- a/eng/tools/eslint-plugin-tsv/src/rules/kebab-case-org.ts +++ /dev/null @@ -1,55 +0,0 @@ -import path from "path"; -import { NamedRule } from "../interfaces/named-eslint.js"; - -// Valid: /specification/kebab-case/Kebab.Case/tspconfig.yaml -// Invalid: /specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml - -export const rule: NamedRule.RuleModule = { - name: "kebab-case-org", - meta: { - type: "problem", - docs: { - description: - "Requires kebab-case for'organization' name (first path segment after 'specification')", - }, - schema: [], - messages: { - invalid: "Path does not match format '.*/specification/{orgName}/': ''{{filename}}'", - kebab: - "Organization name (first path segment after 'specification') does not use kebab-case: '{{orgName}}'", - }, - }, - create(context) { - return { - Program(node) { - const filename = path.resolve(context.filename as string); - const pathSegments = filename.split(path.sep); - const specificationIndex = pathSegments.indexOf("specification"); - const pathValid = specificationIndex >= 0 && specificationIndex < pathSegments.length - 1; - - if (!pathValid) { - context.report({ - node, - messageId: "invalid", - data: { filename: filename }, - }); - return; - } - - const orgName = pathSegments[specificationIndex + 1]; - const kebabCaseRegex = /^[a-z0-9]+(-[a-z0-9]+)*$/; - const orgNameKebabCase = orgName.match(kebabCaseRegex); - - if (!orgNameKebabCase) { - context.report({ - node, - messageId: "kebab", - data: { orgName: orgName }, - }); - } - }, - }; - }, -}; - -export default rule; diff --git a/eng/tools/eslint-plugin-tsv/src/utils/npm.ts b/eng/tools/eslint-plugin-tsv/src/utils/npm.ts deleted file mode 100644 index 0d36838a1f8b..000000000000 --- a/eng/tools/eslint-plugin-tsv/src/utils/npm.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { dirname, join, resolve } from "path"; -import { stat, access } from "fs/promises"; - -export class Npm { - // Simulates `npm prefix` by finding the nearest parent directory containing `package.json` or `node_modules`. - // If neither exist in any parent directories, returns the directory containing the path itself. - // Always returns an absolute path. - static async prefix(path: string): Promise { - path = resolve(path); - - const initialDir = (await stat(path)).isDirectory() ? path : dirname(path); - - for ( - var currentDir = initialDir; - dirname(currentDir) != currentDir; - currentDir = dirname(currentDir) - ) { - try { - await access(join(currentDir, "package.json")); - return currentDir; - } catch {} - - try { - await access(join(currentDir, "node_modules")); - return currentDir; - } catch {} - } - - // Neither found in an parent dir - return initialDir; - } -} diff --git a/eng/tools/eslint-plugin-tsv/test/e2e.test.ts b/eng/tools/eslint-plugin-tsv/test/e2e.test.ts deleted file mode 100644 index 7c8ed1376584..000000000000 --- a/eng/tools/eslint-plugin-tsv/test/e2e.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { ESLint } from "eslint"; -import { join, resolve } from "path"; -import { describe, expect, it } from "vitest"; -import eslintPluginTsv from "../src/eslint-plugin-tsv.js"; - -function createESLint() { - return new ESLint({ - cwd: "/", - overrideConfig: eslintPluginTsv.configs.recommended, - overrideConfigFile: true, - }); -} - -describe("lint-text", () => { - it("Not-Kebab-Case/Not.KebabCase", async () => { - const filePath = "/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml"; - const eslint = createESLint(); - - const results = await eslint.lintText("", { filePath: filePath }); - - expect(results).toHaveLength(1); - expect(results[0].filePath).toBe(filePath); - expect(results[0].messages[0].ruleId).toBe("tsv/kebab-case-org"); - expect(results[0].messages[0].messageId).toBe("kebab"); - }); - - it("Not-Kebab-Case-Disabled/Not.KebabCase", async () => { - const filePath = "/specification/Not-Kebab-Case-Disabled/Not.KebabCase/tspconfig.yaml"; - const eslint = createESLint(); - - const results = await eslint.lintText( - "# eslint-disable tsv/kebab-case-org, tsv/emit-autorest\n", - { - filePath: filePath, - }, - ); - - expect(results).toHaveLength(1); - expect(results[0].filePath).toBe(filePath); - expect(results[0].messages).toHaveLength(0); - }); -}); - -describe("lint-files", () => { - const specsFolder = resolve(__filename, "../../../../../specification"); - - it("contosowidgetmanager/Contso.WidgetManager", async () => { - const eslint = createESLint(); - const filePath = join(specsFolder, "contosowidgetmanager/Contoso.WidgetManager/tspconfig.yaml"); - const results = await eslint.lintFiles(filePath); - - expect(results).toHaveLength(1); - expect(results[0].filePath).toBe(filePath); - expect(results[0].messages).toHaveLength(0); - }); - - it("contosowidgetmanager/Contso.Management", async () => { - const eslint = createESLint(); - const filePath = join(specsFolder, "contosowidgetmanager/Contoso.Management/tspconfig.yaml"); - const results = await eslint.lintFiles(filePath); - - expect(results).toHaveLength(1); - expect(results[0].filePath).toBe(filePath); - expect(results[0].messages).toHaveLength(0); - }); -}); diff --git a/eng/tools/eslint-plugin-tsv/test/rules/emit-autorest.test.ts b/eng/tools/eslint-plugin-tsv/test/rules/emit-autorest.test.ts deleted file mode 100644 index 7f8938378634..000000000000 --- a/eng/tools/eslint-plugin-tsv/test/rules/emit-autorest.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Rule, RuleTester } from "eslint"; -import { test } from "vitest"; -import parser from "yaml-eslint-parser"; - -import emitAutorest from "../../src/rules/emit-autorest.js"; - -test("RuleTester", () => { - const ruleTester = new RuleTester({ - languageOptions: { - parser: parser, - }, - }); - - ruleTester.run(emitAutorest.name, emitAutorest as Rule.RuleModule, { - valid: [ - { - code: 'emit:\n - "@azure-tools/typespec-autorest"', - }, - ], - invalid: [ - { - code: "", - errors: [{ messageId: "missing" }], - }, - { - code: "emit:\n - foo", - errors: [{ messageId: "missing" }], - }, - { code: "not: valid", errors: [{ messageId: "invalid" }] }, - ], - }); -}); diff --git a/eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts b/eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts deleted file mode 100644 index b7a0092f3282..000000000000 --- a/eng/tools/eslint-plugin-tsv/test/rules/kebab-case-org.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Rule, RuleTester } from "eslint"; -import { test } from "vitest"; -import parser from "yaml-eslint-parser"; - -import kebabCaseOrg from "../../src/rules/kebab-case-org.js"; - -test("RuleTester", () => { - const ruleTester = new RuleTester({ - languageOptions: { - parser: parser, - }, - }); - - ruleTester.run(kebabCaseOrg.name, kebabCaseOrg as Rule.RuleModule, { - valid: [ - { code: "", filename: "/specification/contoso/Contoso.WidgetManager/tspconfig.yaml" }, - { - code: `# eslint-disable rule-to-test/${kebabCaseOrg.name}`, - filename: "/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml", - }, - ], - invalid: [ - { - code: "", - filename: "/specification/Not-Kebab-Case/Not.KebabCase/tspconfig.yaml", - errors: [{ messageId: "kebab" }], - }, - { - code: "", - filename: "tspconfig.yaml", - errors: [{ messageId: "invalid" }], - }, - ], - }); -}); diff --git a/eng/tools/eslint-plugin-tsv/tsconfig.json b/eng/tools/eslint-plugin-tsv/tsconfig.json deleted file mode 100644 index c16578a92bf1..000000000000 --- a/eng/tools/eslint-plugin-tsv/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "./dist" - }, - "include": [ - "src/**/*.ts", - "test/**/*.ts" - ] -} diff --git a/eng/tools/eslint-plugin-tsv/vitest.config.ts b/eng/tools/eslint-plugin-tsv/vitest.config.ts deleted file mode 100644 index 785acc9b7335..000000000000 --- a/eng/tools/eslint-plugin-tsv/vitest.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - coverage: { - include: ["src"], - exclude: ["src/interfaces"], - }, - }, -}); diff --git a/eng/tools/package.json b/eng/tools/package.json index 4e1e559da5a0..1385766802eb 100644 --- a/eng/tools/package.json +++ b/eng/tools/package.json @@ -1,7 +1,6 @@ { "name": "azure-rest-api-specs-eng-tools", "devDependencies": { - "@azure-tools/eslint-plugin-tsv": "file:eslint-plugin-tsv", "@azure-tools/specs-model": "file:specs-model", "@azure-tools/suppressions": "file:suppressions", "@azure-tools/tsp-client-tests": "file:tsp-client-tests", diff --git a/eng/tools/typespec-validation/package.json b/eng/tools/typespec-validation/package.json index c2516ce524f2..b8fcfa146932 100644 --- a/eng/tools/typespec-validation/package.json +++ b/eng/tools/typespec-validation/package.json @@ -10,14 +10,16 @@ "globby": "^14.0.1", "simple-git": "^3.24.0", "suppressions": "file:../suppressions", - "eslint-plugin-tsv": "file:../eslint-plugin-tsv", - "yaml": "^2.4.2" + "yaml": "^2.4.2", + "ajv": "^8.17.1", + "yaml-eslint-parser": "^1.2.3" }, "devDependencies": { "@types/node": "^18.19.31", "@vitest/coverage-v8": "^3.0.2", "typescript": "~5.6.2", - "vitest": "^3.0.2" + "vitest": "^3.0.2", + "eslint": "^9.17.0" }, "scripts": { "build": "tsc --build", diff --git a/eng/tools/eslint-plugin-tsv/src/config/config-schema.ts b/eng/tools/typespec-validation/src/eslint-plugin-tsv/src/config/config-schema.ts similarity index 100% rename from eng/tools/eslint-plugin-tsv/src/config/config-schema.ts rename to eng/tools/typespec-validation/src/eslint-plugin-tsv/src/config/config-schema.ts diff --git a/eng/tools/eslint-plugin-tsv/src/config/types.ts b/eng/tools/typespec-validation/src/eslint-plugin-tsv/src/config/types.ts similarity index 100% rename from eng/tools/eslint-plugin-tsv/src/config/types.ts rename to eng/tools/typespec-validation/src/eslint-plugin-tsv/src/config/types.ts diff --git a/eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts b/eng/tools/typespec-validation/src/eslint-plugin-tsv/src/eslint-plugin-tsv.ts similarity index 100% rename from eng/tools/eslint-plugin-tsv/src/eslint-plugin-tsv.ts rename to eng/tools/typespec-validation/src/eslint-plugin-tsv/src/eslint-plugin-tsv.ts diff --git a/eng/tools/eslint-plugin-tsv/src/interfaces/named-eslint.ts b/eng/tools/typespec-validation/src/eslint-plugin-tsv/src/interfaces/named-eslint.ts similarity index 100% rename from eng/tools/eslint-plugin-tsv/src/interfaces/named-eslint.ts rename to eng/tools/typespec-validation/src/eslint-plugin-tsv/src/interfaces/named-eslint.ts diff --git a/eng/tools/eslint-plugin-tsv/src/interfaces/rule-interfaces.ts b/eng/tools/typespec-validation/src/eslint-plugin-tsv/src/interfaces/rule-interfaces.ts similarity index 100% rename from eng/tools/eslint-plugin-tsv/src/interfaces/rule-interfaces.ts rename to eng/tools/typespec-validation/src/eslint-plugin-tsv/src/interfaces/rule-interfaces.ts diff --git a/eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts b/eng/tools/typespec-validation/src/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts similarity index 100% rename from eng/tools/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts rename to eng/tools/typespec-validation/src/eslint-plugin-tsv/src/rules/tspconfig-validation-rules.ts diff --git a/eng/tools/eslint-plugin-tsv/src/utils/constants.ts b/eng/tools/typespec-validation/src/eslint-plugin-tsv/src/utils/constants.ts similarity index 100% rename from eng/tools/eslint-plugin-tsv/src/utils/constants.ts rename to eng/tools/typespec-validation/src/eslint-plugin-tsv/src/utils/constants.ts diff --git a/eng/tools/eslint-plugin-tsv/src/utils/rule-creator.ts b/eng/tools/typespec-validation/src/eslint-plugin-tsv/src/utils/rule-creator.ts similarity index 81% rename from eng/tools/eslint-plugin-tsv/src/utils/rule-creator.ts rename to eng/tools/typespec-validation/src/eslint-plugin-tsv/src/utils/rule-creator.ts index 36f9571934f3..889c4efbbd0e 100644 --- a/eng/tools/eslint-plugin-tsv/src/utils/rule-creator.ts +++ b/eng/tools/typespec-validation/src/eslint-plugin-tsv/src/utils/rule-creator.ts @@ -25,12 +25,16 @@ export function createRule(ruleContext: RuleInfo): NamedRule.RuleModule { create(context) { return { YAMLDocument(node: Rule.Node) { - const yamlDocument = node as unknown as AST.YAMLDocument; - const rawConfig = getStaticYAMLValue(yamlDocument) || {}; - const config = rawConfig as unknown as TypeSpecConfig; - - if (!ruleContext.functions.condition(config, context)) return; - ruleContext.functions.validation(config, context, node); + // TODO: remove try-catch block when ESLint based TSV is ready, and have confidence for this + try { + const yamlDocument = node as unknown as AST.YAMLDocument; + const rawConfig = getStaticYAMLValue(yamlDocument) || {}; + const config = rawConfig as unknown as TypeSpecConfig; + if (!ruleContext.functions.condition(config, context)) return; + ruleContext.functions.validation(config, context, node); + } catch (error) { + console.error(`Failed to validate rule '${ruleContext.name}' due to error: ${error}`); + } }, }; }, @@ -40,7 +44,7 @@ export function createRule(ruleContext: RuleInfo): NamedRule.RuleModule { export function createRuleMessages(messageId: string, docs: RuleDocument) { return { - [messageId]: `Error: ${docs.error}.\nAction: ${docs.action}.\nExample: ${docs.example}`, + [messageId]: `Error: ${docs.error}.\nAction: ${docs.action}.\nExample:\n\`\`\`\n${docs.example}\n\`\`\``, }; } @@ -68,7 +72,7 @@ function validateValue( context.report({ node, messageId: defaultMessageId }); break; default: - // TODO: log not supported + console.warn("Unsupported expected-value-type for tspconfig.yaml"); break; } } @@ -116,7 +120,7 @@ export function createCodeGenSDKRule(args: CreateCodeGenSDKRuleArgs): NamedRule. break; } default: - // TODO: log not supported + console.warn("Unsupported key type in tspconfig.yaml"); break; } }, diff --git a/eng/tools/eslint-plugin-tsv/src/utils/rule-doc.ts b/eng/tools/typespec-validation/src/eslint-plugin-tsv/src/utils/rule-doc.ts similarity index 100% rename from eng/tools/eslint-plugin-tsv/src/utils/rule-doc.ts rename to eng/tools/typespec-validation/src/eslint-plugin-tsv/src/utils/rule-doc.ts diff --git a/eng/tools/eslint-plugin-tsv/src/yaml/types.ts b/eng/tools/typespec-validation/src/eslint-plugin-tsv/src/yaml/types.ts similarity index 100% rename from eng/tools/eslint-plugin-tsv/src/yaml/types.ts rename to eng/tools/typespec-validation/src/eslint-plugin-tsv/src/yaml/types.ts diff --git a/eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts b/eng/tools/typespec-validation/src/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts similarity index 100% rename from eng/tools/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts rename to eng/tools/typespec-validation/src/eslint-plugin-tsv/test/rules/tspconfig-options-validation.test.ts diff --git a/eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts b/eng/tools/typespec-validation/src/eslint-plugin-tsv/test/utils/npm.test.ts similarity index 100% rename from eng/tools/eslint-plugin-tsv/test/utils/npm.test.ts rename to eng/tools/typespec-validation/src/eslint-plugin-tsv/test/utils/npm.test.ts diff --git a/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts b/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts index 6a13f28dd6fa..0929c2699065 100644 --- a/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts +++ b/eng/tools/typespec-validation/src/rules/tspconfig-validation-rules.ts @@ -4,8 +4,8 @@ import { join } from "path"; import { Rule } from "../rule.js"; import { RuleResult } from "../rule-result.js"; import { TsvHost } from "../tsv-host.js"; - -import tsvPlugin, { ESLint } from "eslint-plugin-tsv"; +import { ESLint } from "eslint"; +import tsvPlugin from "../eslint-plugin-tsv/src/eslint-plugin-tsv.js"; async function runESLint(content: string, folder: string, ruleName: string) { const cwd = process.cwd(); diff --git a/eng/tools/typespec-validation/test/tspconfig.test.ts b/eng/tools/typespec-validation/test/tspconfig.test.ts index 9b1b3644b261..9f34671e4f1b 100644 --- a/eng/tools/typespec-validation/test/tspconfig.test.ts +++ b/eng/tools/typespec-validation/test/tspconfig.test.ts @@ -50,7 +50,7 @@ describe("tspconfig rules", () => { assert(result.stdOutput && result.stdOutput.length > 0 && result.errorOutput === undefined); assert( (c.expectedResult && result.stdOutput.includes("validation passed")) || - (!c.expectedResult && result.stdOutput.includes("Validation failed. ")), + (!c.expectedResult && result.stdOutput.includes("Validation failed.")) ); }); }); diff --git a/eng/tools/typespec-validation/tsconfig.json b/eng/tools/typespec-validation/tsconfig.json index 81ede7fd4b23..c1eaa0805646 100644 --- a/eng/tools/typespec-validation/tsconfig.json +++ b/eng/tools/typespec-validation/tsconfig.json @@ -4,7 +4,6 @@ "outDir": "./dist", }, "references": [ - { "path": "../suppressions" }, - { "path": "../eslint-plugin-tsv" }, + { "path": "../suppressions" } ] } From 718cb300baf0370707b66ce1f2b5216840c9beb8 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Sun, 26 Jan 2025 13:32:00 +0800 Subject: [PATCH 093/106] revert tsconfig --- eng/tools/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/eng/tools/tsconfig.json b/eng/tools/tsconfig.json index 44787ca9f682..eca3d4cdace7 100644 --- a/eng/tools/tsconfig.json +++ b/eng/tools/tsconfig.json @@ -11,7 +11,6 @@ "composite": true, }, "references": [ - { "path": "./eslint-plugin-tsv" }, { "path": "./specs-model" }, { "path": "./suppressions" }, { "path": "./tsp-client-tests" }, From 06435c36dfb1be966d575c796e19bb8f31429914 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Sun, 26 Jan 2025 13:39:13 +0800 Subject: [PATCH 094/106] removed unnecessary rules --- .../src/eslint-plugin-tsv.ts | 12 +--- .../eslint-plugin-tsv/test/utils/npm.test.ts | 56 ------------------- 2 files changed, 2 insertions(+), 66 deletions(-) delete mode 100644 eng/tools/typespec-validation/src/eslint-plugin-tsv/test/utils/npm.test.ts diff --git a/eng/tools/typespec-validation/src/eslint-plugin-tsv/src/eslint-plugin-tsv.ts b/eng/tools/typespec-validation/src/eslint-plugin-tsv/src/eslint-plugin-tsv.ts index 027cee59ab7a..581472b85320 100644 --- a/eng/tools/typespec-validation/src/eslint-plugin-tsv/src/eslint-plugin-tsv.ts +++ b/eng/tools/typespec-validation/src/eslint-plugin-tsv/src/eslint-plugin-tsv.ts @@ -1,16 +1,11 @@ import parser from "yaml-eslint-parser"; import { NamedESLint } from "./interfaces/named-eslint.js"; -import emitAutorest from "./rules/emit-autorest.js"; -import kebabCaseOrg from "./rules/kebab-case-org.js"; import tspconfigValidationRules from "./rules/tspconfig-validation-rules.js"; const plugin: NamedESLint.Plugin = { configs: { recommended: {} }, name: "tsv", - rules: { - [kebabCaseOrg.name]: kebabCaseOrg, - [emitAutorest.name]: emitAutorest, - }, + rules: {}, }; plugin.configs.recommended = { @@ -18,10 +13,7 @@ plugin.configs.recommended = { [plugin.name]: plugin, }, files: ["*.yaml", "**/*.yaml"], - rules: { - [`${plugin.name}/${kebabCaseOrg.name}`]: "error", - [`${plugin.name}/${emitAutorest.name}`]: "error", - }, + rules: {}, languageOptions: { parser: parser, }, diff --git a/eng/tools/typespec-validation/src/eslint-plugin-tsv/test/utils/npm.test.ts b/eng/tools/typespec-validation/src/eslint-plugin-tsv/test/utils/npm.test.ts deleted file mode 100644 index 28e54924170a..000000000000 --- a/eng/tools/typespec-validation/src/eslint-plugin-tsv/test/utils/npm.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; -import { vol } from "memfs"; -import { resolve } from "path"; -import { Npm } from "../../src/utils/npm.js"; - -vi.mock("fs/promises", async () => { - const memfs = await import("memfs"); - return { - ...memfs.fs.promises, - }; -}); - -describe("prefix", () => { - beforeEach(() => { - vol.reset(); - }); - - describe("returns current directory if no match", () => { - it.each([ - ["/foo/bar/tspconfig.yaml", "/foo/bar"], - ["/foo/bar", "/foo/bar"], - ])("%s", async (path, expected) => { - vol.fromJSON({ - "/foo/bar/tspconfig.yaml": "", - }); - - expect(await Npm.prefix(path)).toBe(resolve(expected)); - }); - }); - - describe("returns first match", () => { - it.each([ - ["/pj", "/pj"], - ["/pj/none", "/pj"], - ["/pj/none/none/none", "/pj"], - ["/pj/nm", "/pj/nm"], - ["/pj/nm/none", "/pj/nm"], - ["/pj/pj", "/pj/pj"], - ["/pj/nm/pj", "/pj/nm/pj"], - ["/pj/pj/nm", "/pj/pj/nm"], - ])("%s", async (path, expected) => { - vol.fromJSON({ - "/pj/package.json": "", - "/pj/none": null, - "/pj/none/none/none": null, - "/pj/nm/node_modules": null, - "/pj/nm/none": null, - "/pj/pj/package.json": "", - "/pj/nm/pj/package.json": "", - "/pj/pj/nm/node_modules": null, - }); - - expect(await Npm.prefix(path)).toBe(resolve(expected)); - }); - }); -}); From ae8f5dc90a1d226bece2e1c09db92be3c1d6a1ea Mon Sep 17 00:00:00 2001 From: chipate Date: Mon, 27 Jan 2025 10:53:31 -0800 Subject: [PATCH 095/106] Revert the new API change and only keeping adding new version for existing APIs --- specification/compute/cspell.yaml | 1 - .../2025-02-01-preview/diagnostic.json | 639 ------------------ ...nerateAttributeBasedVMSizeRecommender.json | 201 ------ .../Microsoft.Compute/suppressions.yaml | 3 - .../compute/resource-manager/readme.md | 15 - 5 files changed, 859 deletions(-) delete mode 100644 specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json diff --git a/specification/compute/cspell.yaml b/specification/compute/cspell.yaml index 1ebdb93297a0..ccaf4546626a 100644 --- a/specification/compute/cspell.yaml +++ b/specification/compute/cspell.yaml @@ -21,7 +21,6 @@ words: - osdisk - osdiskforlinuxsimple - osdiskforwindowssimple - - rdma - reimage - reimageall - reimaged diff --git a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json index 0a39040491bf..19af2748570f 100644 --- a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json +++ b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json @@ -488,54 +488,6 @@ } } } - }, - "/subscriptions/{subscriptionId}/providers/Microsoft.Compute/locations/{location}/vmSizeRecommendations/vmAttributeBased/generate": { - "post": { - "tags": [ - "attributeBasedVMSizeRecommender" - ], - "operationId": "AttributeBasedVMSizeRecommender_Post", - "description": "Generates attribute based VM Size Recommendations.", - "parameters": [ - { - "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/SubscriptionIdParameter" - }, - { - "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/LocationParameter" - }, - { - "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter" - }, - { - "name": "attributeBasedVMSizeRecommenderInput", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/AttributeBasedVMSizeRecommenderInput" - }, - "description": "AttributeBasedVMSizeRecommender object supplied in the body of the Post attribute based VMSize recommender operation." - } - ], - "responses": { - "200": { - "description": "Success", - "schema": { - "$ref": "#/definitions/AttributeBasedVMSizeRecommenderResponse" - } - }, - "default": { - "description": "Error response describing why the operation failed.", - "schema": { - "$ref": "../../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse" - } - } - }, - "x-ms-examples": { - "Returns VMSize recommendations for given configurations.": { - "$ref": "./examples/GenerateAttributeBasedVMSizeRecommender.json" - } - } - } } }, "definitions": { @@ -882,597 +834,6 @@ } }, "description": "The spot placement score for sku/region/zone combination." - }, - "AttributeBasedVMSizeRecommenderInput": { - "type": "object", - "properties": { - "regularPriorityProfile": { - "type": "object", - "items": { - "$ref": "#/definitions/RegularPriorityProfile" - }, - "description": "The regular priority VM profile." - }, - "spotPriorityProfile": { - "type": "object", - "items": { - "$ref": "#/definitions/SpotPriorityProfile" - }, - "description": "The spot priority VM profile." - }, - "recommendationProperties": { - "type": "object", - "items": { - "$ref": "#/definitions/RecommendationProperties" - }, - "description": "The recommendation properties." - }, - "resourceProperties": { - "type": "object", - "items": { - "$ref": "#/definitions/ResourceProperties" - }, - "description": "The resource properties." - } - }, - "description": "AttributeBasedVMSizeRecommender API Input." - }, - "AttributeBasedVMSizeRecommenderResponse": { - "type": "object", - "properties": { - "recommendedVMSizes": { - "type": "object", - "items": { - "$ref": "#/definitions/RecommendedVMSizes" - }, - "description": "The recommended VMSizes." - } - }, - "description": "AttributeBasedVMSizeRecommender API response." - }, - "RegularPriorityProfile": { - "type": "object", - "properties": { - "targetCapacity": { - "type": "integer", - "format": "int32", - "description": "The target capacity." - }, - "capacityUnitType": { - "type": "string", - "description": "The capacity unit type." - } - }, - "description": "Regular Priority Profile for AttributeBasedVmSizeRecommender." - }, - "SpotPriorityProfile": { - "type": "object", - "properties": { - "targetCapacity": { - "type": "integer", - "format": "int32", - "description": "The target capacity." - }, - "capacityUnitType": { - "type": "string", - "description": "The capacity unit type." - }, - "maxPricePerVM": { - "type": "number", - "format": "double", - "description": "The maximum price per spot vm instance." - } - }, - "description": "Spot Priority Profile for AttributeBasedVmSizeRecommender." - }, - "RecommendationProperties": { - "type": "object", - "properties": { - "quotaAndOfferRestrictionsCheckRequired": { - "type": "boolean", - "description": "The quota and offer restrictions check required flag." - } - }, - "description": "Recommendations Properties for AttributeBasedVmSizeRecommender." - }, - "ResourceProperties": { - "type": "object", - "properties": { - "vmAttributes": { - "type": "object", - "items": { - "$ref": "#/definitions/VmAttributes" - }, - "description": "The VM attributes." - } - }, - "description": "Resource Properties for AttributeBasedVmSizeRecommender." - }, - "VmAttributes": { - "type": "object", - "properties": { - "vCpuCount": { - "type": "object", - "items": { - "$ref": "#/definitions/VMAttributeMinMaxInteger" - }, - "description": "The vCPU count." - }, - "memoryInGiB": { - "type": "object", - "items": { - "$ref": "#/definitions/VMAttributeMinMaxDouble" - }, - "description": "The memory in GiB." - }, - "memoryInGiBPerVCpu": { - "type": "object", - "items": { - "$ref": "#/definitions/VMAttributeMinMaxDouble" - }, - "description": "The memory in GiB per vCPU." - }, - "vmCategories": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The vm categories." - }, - "architectureTypes": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The cpu architecture types." - }, - "cpuManufacturers": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The cpu manufacturers." - }, - "localStorageSupport": { - "type": "string", - "description": "The local storage support filter.", - "enum": [ - "Included", - "Required", - "Excluded" - ], - "x-ms-enum": { - "name": "FilterType", - "modelAsString": false - } - }, - "localStorageDiskTypes": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The local storage disk types." - }, - "localStorageInGiB": { - "type": "object", - "items": { - "$ref": "#/definitions/VMAttributeMinMaxDouble" - }, - "description": "The local storage in GiB." - }, - "dataDiskCount": { - "type": "object", - "items": { - "$ref": "#/definitions/VMAttributeMinMaxInteger" - }, - "description": "The data disk count." - }, - "ultraSSDSupport": { - "type": "string", - "description": "The ultra ssd support filter.", - "enum": [ - "Included", - "Required", - "Excluded" - ], - "x-ms-enum": { - "name": "FilterType", - "modelAsString": false - } - }, - "hibernationSupport": { - "type": "string", - "description": "The hibernation support filter.", - "enum": [ - "Included", - "Required", - "Excluded" - ], - "x-ms-enum": { - "name": "FilterType", - "modelAsString": false - } - }, - "hyperVGenerations": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The Hyper-V Generations." - }, - "networkInterfaceCount": { - "type": "object", - "items": { - "$ref": "#/definitions/VMAttributeMinMaxInteger" - }, - "description": "The network interface count." - }, - "networkBandwidthInMbps": { - "type": "object", - "items": { - "$ref": "#/definitions/VMAttributeMinMaxDouble" - }, - "description": "The network bandwidth in Mbps." - }, - "burstableSupport": { - "type": "string", - "description": "The burstable support filter.", - "enum": [ - "Included", - "Required", - "Excluded" - ], - "x-ms-enum": { - "name": "FilterType", - "modelAsString": false - } - }, - "acceleratorSupport": { - "type": "string", - "description": "The accelerator support filter.", - "enum": [ - "Included", - "Required", - "Excluded" - ], - "x-ms-enum": { - "name": "FilterType", - "modelAsString": false - } - }, - "acceleratorTypes": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The accelerator types." - }, - "acceleratorManufacturers": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The accelerator manufacturers." - }, - "acceleratorCount": { - "type": "object", - "items": { - "$ref": "#/definitions/VMAttributeMinMaxInteger" - }, - "description": "The accelerator count." - }, - "rdmaSupport": { - "type": "string", - "description": "The rdma support filter.", - "enum": [ - "Included", - "Required", - "Excluded" - ], - "x-ms-enum": { - "name": "FilterType", - "modelAsString": false - } - }, - "rdmaNetworkInterfaceCount": { - "type": "object", - "items": { - "$ref": "#/definitions/VMAttributeMinMaxInteger" - }, - "description": "The rdma network interface count." - }, - "acceleratedNetworkingSupport": { - "type": "string", - "description": "The accelerated networking support filter.", - "enum": [ - "Included", - "Required", - "Excluded" - ], - "x-ms-enum": { - "name": "FilterType", - "modelAsString": false - } - }, - "confidentialVMSupport": { - "type": "string", - "description": "The confidential VM support filter.", - "enum": [ - "Included", - "Required", - "Excluded" - ], - "x-ms-enum": { - "name": "FilterType", - "modelAsString": false - } - }, - "osTypes": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The list of Operation Systems." - }, - "allowedVMSizes": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The list of allowed VMSizes." - }, - "excludedVMSizes": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The list of excluded VMSizes." - } - }, - "description": "VM attributes for AttributeBasedVmSizeRecommender." - }, - "VMAttributeMinMaxInteger": { - "type": "object", - "properties": { - "min": { - "type": "integer", - "format": "int32", - "description": "The minimum value for vm attribute in integer." - }, - "max": { - "type": "integer", - "format": "int32", - "description": "The maximum value for vm attribute in integer." - } - }, - "description": "MinMax values in integer for AttributeBasedVmSizeRecommender." - }, - "VMAttributeMinMaxDouble": { - "type": "object", - "properties": { - "min": { - "type": "number", - "format": "double", - "description": "The minimum value for vm attribute in double." - }, - "max": { - "type": "number", - "format": "double", - "description": "The maximum value for vm attribute in double." - } - }, - "description": "Priority Profile for AttributeBasedVmSizeRecommender." - }, - "RecommendedVMSizes": { - "type": "object", - "properties": { - "regularVMSizes": { - "type": "array", - "items": { - "$ref": "#/definitions/RecommendedVMSizeProperties" - }, - "description": "The regular priority VMSizes." - }, - "spotVMSizes": { - "type": "array", - "items": { - "$ref": "#/definitions/RecommendedVMSizeProperties" - }, - "description": "The spot priority VMSizes." - } - }, - "description": "The regular and spot priority recommended VMSizes." - }, - "RecommendedVMSizeProperties": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the VMSize." - }, - "size": { - "type": "string", - "description": "The vm size." - }, - "family": { - "type": "string", - "description": "The family of VMSize." - }, - "attributes": { - "type": "object", - "items": { - "$ref": "#/definitions/RecommendedVMSizeAttributes" - }, - "description": "The recommended VMSize attributes." - }, - "restrictions": { - "type": "array", - "items": { - "$ref": "#/definitions/RecommendedVMSizeRestrictions" - }, - "description": "The recommended VMSize restrictions." - } - }, - "description": "The recommended VMSize properties." - }, - "RecommendedVMSizeAttributes": { - "type": "object", - "properties": { - "vCpu": { - "type": "integer", - "format": "int32", - "description": "The vCPU count." - }, - "memoryInGiB": { - "type": "number", - "format": "double", - "description": "The memory in GiB." - }, - "cpuArchitecture": { - "type": "string", - "description": "The cpu architecture of VMSize." - }, - "cpuManufacturer": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The cpu manufacturers." - }, - "networkBandwidthInMbps": { - "type": "number", - "format": "double", - "description": "The network bandwidth in Mbps." - }, - "localStorageSupport": { - "type": "boolean", - "description": "The local storage support flag." - }, - "localStorageType": { - "type": "string", - "description": "The local storage type of VMSize." - }, - "localStorageInGiB": { - "type": "number", - "format": "double", - "description": "The local storage in GiB." - }, - "dataDiskCount": { - "type": "integer", - "format": "int32", - "description": "The data disk count." - }, - "networkInterfaceCount": { - "type": "integer", - "format": "int32", - "description": "The network interface count." - } - }, - "description": "The recommended VMSize attributes." - }, - "RecommendedVMSizeRestrictions": { - "type": "object", - "properties": { - "offerRestrictions": { - "type": "array", - "items": { - "$ref": "#/definitions/VMSizeOfferRestriction" - }, - "description": "The offer restrictions on VMSize." - }, - "quotaRestrictions": { - "type": "array", - "items": { - "$ref": "#/definitions/VMSizeQuotaRestriction" - }, - "description": "The quota restrictions on VMSize." - } - }, - "description": "The restrictions recommended VMSizes." - }, - "VMSizeOfferRestriction": { - "type": "object", - "properties": { - "type": { - "type": "string", - "description": "The type of offer restriction on VMSize.", - "enum": [ - "NotSpecified", - "Location", - "Zone" - ], - "x-ms-enum": { - "name": "ResourceSkuRestrictionType", - "modelAsString": false - } - }, - "values": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The list of restriction values." - }, - "restrictionInfo": { - "type": "object", - "items": { - "$ref": "#/definitions/VMSizeOfferRestrictionInfo" - }, - "description": "The offer restrictions on VMSize." - }, - "reasonCode": { - "type": "string", - "description": "The offer restriction reason code.", - "enum": [ - "NotSpecified", - "QuotaId", - "NotAvailableForSubscription" - ], - "x-ms-enum": { - "name": "ResourceSkuRestrictionReasonCode", - "modelAsString": false - } - } - }, - "description": "The offer restrictions on recommended VMSize." - }, - "VMSizeOfferRestrictionInfo": { - "type": "object", - "properties": { - "locations": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The list of restriction locations." - }, - "zones": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The list of restriction zones." - } - }, - "description": "The offer restriction info on recommended VMSize." - }, - "VMSizeQuotaRestriction": { - "type": "object", - "properties": { - "type": { - "type": "string", - "description": "The type of quota restriction on VMSize.", - "enum": [ - "RegionalVCpu", - "VMFamilyVCpu" - ], - "x-ms-enum": { - "name": "QuotaRestrictionsType", - "modelAsString": false - } - } - }, - "description": "The quota restrictions on recommended VMSize." } } } diff --git a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json deleted file mode 100644 index 82fbb97498ae..000000000000 --- a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json +++ /dev/null @@ -1,201 +0,0 @@ -{ - "parameters": { - "subscriptionId": "00000000-0000-0000-0000-000000000000", - "location": "eastus", - "api-version": "2025-02-01-preview", - "attributeBasedVMSizeRecommenderInput": { - "regularPriorityProfile": { - "targetCapacity": 1, - "capacityUnitType": "VMInstanceCount" - }, - "spotPriorityProfile": { - "targetCapacity": 1, - "capacityUnitType": "VMInstanceCount", - "maxPricePerVM": 0.200 - }, - "recommendationProperties": { - "quotaAndOfferRestrictionsCheckRequired": true - }, - "resourceProperties": { - "vmAttributes": { - "vCpuCount": { - "Min": 0, - "Max": 0 - }, - "memoryInMiB": { - "Min": 0, - "Max": 0 - }, - "vmCategories": [ - "General Purpose" - ], - "architectureTypes": [ - "x86", - "arm64" - ], - "cpuManufacturers": [ - "Intel", - "AMD" - ], - "localStorageSupport": "included", - "ultraSSDSupport": "included", - "localStorageTypes": [ - "SSD", - "HDD" - ], - "localStorageInMiB": { - "Min": 0, - "Max": 0 - }, - "dataDiskCount": { - "Min": 0, - "Max": 4 - }, - "hibernationSupport": "included", - "hyperVGenerations": [ - "Gen1", - "Gen2" - ], - "networkInterfaceCount": { - "Min": 0, - "Max": 4 - }, - "networkBandwidthInMbps": { - "Min": 0, - "Max": 0 - }, - "confidentialVMSupport": "included", - "burstableSupport": "included", - "acceleratorSupport": "included", - "acceleratorTypes": [ - "FPGA", - "GPU" - ], - "acceleratorManufacturers": [ - "Intel", - "AMD", - "NVidia" - ], - "acceleratorCount": { - "Min": 0, - "Max": 0 - }, - "acceleratedNetworkingSupport": "included", - "rdmaSupport": "included", - "rdmaNetworkInterfaceCount": { - "Min": 0, - "Max": 0 - }, - "osType": [ - "Windows", - "Linux" - ], - "allowedVMSizes": [], - "excludeVMSizes": [] - } - } - } - }, - "responses": { - "200": { - "body": { - "recommendedVMSizes": { - "regularVMSizes": [ - { - "name": "Standard_D4ds_v5", - "size": "D4ds_v5", - "family": "standardDDSv5Family", - "attributes": { - "vCPUs": 4, - "memoryGB": 16, - "cpuArchitecture": "x86", - "cpuManufacturer": "Intel", - "networkBandwidthInMbps": "12500", - "localStorageSupport": "Included", - "localStorageType": "SSD", - "localStorageInGb": 150, - "dataDiskCount": 4, - "networkInterfaceCount": 4 - }, - "restrictions": { - "offerRestrictions": [ - { - "type": "Location", - "values": [ - "westus" - ], - "restrictionInfo": { - "location": [ - "westus" - ], - "zones": [ - "1", - "2", - "3" - ] - }, - "reasonCode": "NotAvailableForSubscription" - } - ], - "quotaRestrictions": [ - { - "type": "RegionalVCpu" - } - ] - } - } - ], - "spotVMSizes": [ - { - "name": "Standard_D4ds_v6", - "size": "D4ds_v5", - "family": "standardDDSv6Family", - "attributes": { - "vCPUs": 4, - "memoryGB": 16, - "cpuArchitecture": "x86", - "cpuManufacturer": "Intel", - "networkBandwidthInMbps": "12500", - "localStorageSupport": "Included", - "localStorageType": "SSD", - "localStorageInGb": 150, - "dataDiskCount": 4, - "networkInterfaceCount": 4 - }, - "restrictions": { - "offerRestrictions": [ - { - "type": "Location", - "values": [ - "westus" - ], - "restrictionInfo": { - "location": [ - "westus" - ], - "zones": [ - "1", - "2", - "3" - ] - }, - "reasonCode": "NotAvailableForSubscription" - } - ], - "quotaRestrictions": [ - { - "type": "RegionalVCpu" - } - ] - } - } - ] - } - }, - "headers": { - "x-ms-request-id": "57b891ab-1fb7-4f5a-b002-71eb6854961f", - "location": "https://management.azure.com/subscriptions/be23ca13-8eb4-4d0e-be10-b00451817956/providers/Microsoft.Compute/locations/eastus/vmSizeRecommendations/vmAttributeBased/generate?api-version=2025-02-01-preview" - } - } - } -} diff --git a/specification/compute/resource-manager/Microsoft.Compute/suppressions.yaml b/specification/compute/resource-manager/Microsoft.Compute/suppressions.yaml index ee80746fcc21..a801dff0e896 100644 --- a/specification/compute/resource-manager/Microsoft.Compute/suppressions.yaml +++ b/specification/compute/resource-manager/Microsoft.Compute/suppressions.yaml @@ -1,6 +1,3 @@ - tool: TypeSpecRequirement path: DiagnosticRP/preview/2024-06-01-preview/**/*.json reason: CDRP service not yet ready to migrate to typespec -- tool: TypeSpecRequirement - path: DiagnosticRP/preview/2025-02-01-preview/**/*.json - reason: CDRP service not yet ready to migrate to typespec diff --git a/specification/compute/resource-manager/readme.md b/specification/compute/resource-manager/readme.md index 68979f08d5fd..5cd125cb6f5b 100644 --- a/specification/compute/resource-manager/readme.md +++ b/specification/compute/resource-manager/readme.md @@ -269,12 +269,6 @@ suppressions: from: diagnostic.json - code: XmsPageableForListCalls reason: False positive error as API Path does not match ARM Lint check formatting, requesting to suppress due to approval from reviewer. - from: diagnostic.json - - code: DefinitionsPropertiesNamesCamelCase - reason: The property name contains abbreviations and need to keep it as upper case. - from: diagnostic.json - - code: BodyTopLevelProperties - reason: The is the additional property bag to introduce new nonbreaking properties. from: diagnostic.json - code: PatchResponseCodes reason: PATCH and PUT follow the same behavior and response codes in Compute. Keeping it for legacy reasons. @@ -294,15 +288,6 @@ suppressions: reason: Existing issue from last version. ``` -### Tag: package-2025-02-01-preview - -These settings apply only when `--tag=package-2025-02-01-preview` is specified on the command line. - -```yaml $(tag) == 'package-2025-02-01-preview' -input-file: - - Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json -``` - ### Tag: package-2024-11-04 These settings apply only when `--tag=package-2024-11-04` is specified on the command line. From 95533742f679e92fd86a9bc476a6537d9fb1e76a Mon Sep 17 00:00:00 2001 From: chipate Date: Mon, 27 Jan 2025 11:10:45 -0800 Subject: [PATCH 096/106] Add suppression and new version into readme --- .../resource-manager/Microsoft.Compute/suppressions.yaml | 3 +++ specification/compute/resource-manager/readme.md | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/specification/compute/resource-manager/Microsoft.Compute/suppressions.yaml b/specification/compute/resource-manager/Microsoft.Compute/suppressions.yaml index a801dff0e896..ee80746fcc21 100644 --- a/specification/compute/resource-manager/Microsoft.Compute/suppressions.yaml +++ b/specification/compute/resource-manager/Microsoft.Compute/suppressions.yaml @@ -1,3 +1,6 @@ - tool: TypeSpecRequirement path: DiagnosticRP/preview/2024-06-01-preview/**/*.json reason: CDRP service not yet ready to migrate to typespec +- tool: TypeSpecRequirement + path: DiagnosticRP/preview/2025-02-01-preview/**/*.json + reason: CDRP service not yet ready to migrate to typespec diff --git a/specification/compute/resource-manager/readme.md b/specification/compute/resource-manager/readme.md index 5cd125cb6f5b..ae5b099b0cf0 100644 --- a/specification/compute/resource-manager/readme.md +++ b/specification/compute/resource-manager/readme.md @@ -288,6 +288,15 @@ suppressions: reason: Existing issue from last version. ``` +### Tag: package-2025-02-01-preview + +These settings apply only when `--tag=package-2025-02-01-preview` is specified on the command line. + +```yaml $(tag) == 'package-2025-02-01-preview' +input-file: + - Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json +``` + ### Tag: package-2024-11-04 These settings apply only when `--tag=package-2024-11-04` is specified on the command line. From 086bf78862049f7a58f18f07ff032e0dba371257 Mon Sep 17 00:00:00 2001 From: chipate Date: Mon, 27 Jan 2025 11:27:52 -0800 Subject: [PATCH 097/106] Suppress swagger lint for new API version --- specification/compute/resource-manager/readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/specification/compute/resource-manager/readme.md b/specification/compute/resource-manager/readme.md index ae5b099b0cf0..c6931e3ffd8d 100644 --- a/specification/compute/resource-manager/readme.md +++ b/specification/compute/resource-manager/readme.md @@ -270,6 +270,9 @@ suppressions: - code: XmsPageableForListCalls reason: False positive error as API Path does not match ARM Lint check formatting, requesting to suppress due to approval from reviewer. from: diagnostic.json + - code: BodyTopLevelProperties + reason: The is the additional property bag to introduce new nonbreaking properties. + from: diagnostic.json - code: PatchResponseCodes reason: PATCH and PUT follow the same behavior and response codes in Compute. Keeping it for legacy reasons. from: gallery.json From 32453bac0402922b48476ea6aebc8f4279a1dcd1 Mon Sep 17 00:00:00 2001 From: chipate Date: Mon, 27 Jan 2025 11:28:49 -0800 Subject: [PATCH 098/106] Add new API vmsizerecommendation in diagnosticRP --- specification/compute/cspell.yaml | 1 + .../2025-02-01-preview/diagnostic.json | 639 ++++++++++++++++++ ...nerateAttributeBasedVMSizeRecommender.json | 201 ++++++ .../compute/resource-manager/readme.md | 3 + 4 files changed, 844 insertions(+) create mode 100644 specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json diff --git a/specification/compute/cspell.yaml b/specification/compute/cspell.yaml index ccaf4546626a..1ebdb93297a0 100644 --- a/specification/compute/cspell.yaml +++ b/specification/compute/cspell.yaml @@ -21,6 +21,7 @@ words: - osdisk - osdiskforlinuxsimple - osdiskforwindowssimple + - rdma - reimage - reimageall - reimaged diff --git a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json index 19af2748570f..0a39040491bf 100644 --- a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json +++ b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json @@ -488,6 +488,54 @@ } } } + }, + "/subscriptions/{subscriptionId}/providers/Microsoft.Compute/locations/{location}/vmSizeRecommendations/vmAttributeBased/generate": { + "post": { + "tags": [ + "attributeBasedVMSizeRecommender" + ], + "operationId": "AttributeBasedVMSizeRecommender_Post", + "description": "Generates attribute based VM Size Recommendations.", + "parameters": [ + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/LocationParameter" + }, + { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "name": "attributeBasedVMSizeRecommenderInput", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/AttributeBasedVMSizeRecommenderInput" + }, + "description": "AttributeBasedVMSizeRecommender object supplied in the body of the Post attribute based VMSize recommender operation." + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/AttributeBasedVMSizeRecommenderResponse" + } + }, + "default": { + "description": "Error response describing why the operation failed.", + "schema": { + "$ref": "../../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-examples": { + "Returns VMSize recommendations for given configurations.": { + "$ref": "./examples/GenerateAttributeBasedVMSizeRecommender.json" + } + } + } } }, "definitions": { @@ -834,6 +882,597 @@ } }, "description": "The spot placement score for sku/region/zone combination." + }, + "AttributeBasedVMSizeRecommenderInput": { + "type": "object", + "properties": { + "regularPriorityProfile": { + "type": "object", + "items": { + "$ref": "#/definitions/RegularPriorityProfile" + }, + "description": "The regular priority VM profile." + }, + "spotPriorityProfile": { + "type": "object", + "items": { + "$ref": "#/definitions/SpotPriorityProfile" + }, + "description": "The spot priority VM profile." + }, + "recommendationProperties": { + "type": "object", + "items": { + "$ref": "#/definitions/RecommendationProperties" + }, + "description": "The recommendation properties." + }, + "resourceProperties": { + "type": "object", + "items": { + "$ref": "#/definitions/ResourceProperties" + }, + "description": "The resource properties." + } + }, + "description": "AttributeBasedVMSizeRecommender API Input." + }, + "AttributeBasedVMSizeRecommenderResponse": { + "type": "object", + "properties": { + "recommendedVMSizes": { + "type": "object", + "items": { + "$ref": "#/definitions/RecommendedVMSizes" + }, + "description": "The recommended VMSizes." + } + }, + "description": "AttributeBasedVMSizeRecommender API response." + }, + "RegularPriorityProfile": { + "type": "object", + "properties": { + "targetCapacity": { + "type": "integer", + "format": "int32", + "description": "The target capacity." + }, + "capacityUnitType": { + "type": "string", + "description": "The capacity unit type." + } + }, + "description": "Regular Priority Profile for AttributeBasedVmSizeRecommender." + }, + "SpotPriorityProfile": { + "type": "object", + "properties": { + "targetCapacity": { + "type": "integer", + "format": "int32", + "description": "The target capacity." + }, + "capacityUnitType": { + "type": "string", + "description": "The capacity unit type." + }, + "maxPricePerVM": { + "type": "number", + "format": "double", + "description": "The maximum price per spot vm instance." + } + }, + "description": "Spot Priority Profile for AttributeBasedVmSizeRecommender." + }, + "RecommendationProperties": { + "type": "object", + "properties": { + "quotaAndOfferRestrictionsCheckRequired": { + "type": "boolean", + "description": "The quota and offer restrictions check required flag." + } + }, + "description": "Recommendations Properties for AttributeBasedVmSizeRecommender." + }, + "ResourceProperties": { + "type": "object", + "properties": { + "vmAttributes": { + "type": "object", + "items": { + "$ref": "#/definitions/VmAttributes" + }, + "description": "The VM attributes." + } + }, + "description": "Resource Properties for AttributeBasedVmSizeRecommender." + }, + "VmAttributes": { + "type": "object", + "properties": { + "vCpuCount": { + "type": "object", + "items": { + "$ref": "#/definitions/VMAttributeMinMaxInteger" + }, + "description": "The vCPU count." + }, + "memoryInGiB": { + "type": "object", + "items": { + "$ref": "#/definitions/VMAttributeMinMaxDouble" + }, + "description": "The memory in GiB." + }, + "memoryInGiBPerVCpu": { + "type": "object", + "items": { + "$ref": "#/definitions/VMAttributeMinMaxDouble" + }, + "description": "The memory in GiB per vCPU." + }, + "vmCategories": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The vm categories." + }, + "architectureTypes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The cpu architecture types." + }, + "cpuManufacturers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The cpu manufacturers." + }, + "localStorageSupport": { + "type": "string", + "description": "The local storage support filter.", + "enum": [ + "Included", + "Required", + "Excluded" + ], + "x-ms-enum": { + "name": "FilterType", + "modelAsString": false + } + }, + "localStorageDiskTypes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The local storage disk types." + }, + "localStorageInGiB": { + "type": "object", + "items": { + "$ref": "#/definitions/VMAttributeMinMaxDouble" + }, + "description": "The local storage in GiB." + }, + "dataDiskCount": { + "type": "object", + "items": { + "$ref": "#/definitions/VMAttributeMinMaxInteger" + }, + "description": "The data disk count." + }, + "ultraSSDSupport": { + "type": "string", + "description": "The ultra ssd support filter.", + "enum": [ + "Included", + "Required", + "Excluded" + ], + "x-ms-enum": { + "name": "FilterType", + "modelAsString": false + } + }, + "hibernationSupport": { + "type": "string", + "description": "The hibernation support filter.", + "enum": [ + "Included", + "Required", + "Excluded" + ], + "x-ms-enum": { + "name": "FilterType", + "modelAsString": false + } + }, + "hyperVGenerations": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The Hyper-V Generations." + }, + "networkInterfaceCount": { + "type": "object", + "items": { + "$ref": "#/definitions/VMAttributeMinMaxInteger" + }, + "description": "The network interface count." + }, + "networkBandwidthInMbps": { + "type": "object", + "items": { + "$ref": "#/definitions/VMAttributeMinMaxDouble" + }, + "description": "The network bandwidth in Mbps." + }, + "burstableSupport": { + "type": "string", + "description": "The burstable support filter.", + "enum": [ + "Included", + "Required", + "Excluded" + ], + "x-ms-enum": { + "name": "FilterType", + "modelAsString": false + } + }, + "acceleratorSupport": { + "type": "string", + "description": "The accelerator support filter.", + "enum": [ + "Included", + "Required", + "Excluded" + ], + "x-ms-enum": { + "name": "FilterType", + "modelAsString": false + } + }, + "acceleratorTypes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The accelerator types." + }, + "acceleratorManufacturers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The accelerator manufacturers." + }, + "acceleratorCount": { + "type": "object", + "items": { + "$ref": "#/definitions/VMAttributeMinMaxInteger" + }, + "description": "The accelerator count." + }, + "rdmaSupport": { + "type": "string", + "description": "The rdma support filter.", + "enum": [ + "Included", + "Required", + "Excluded" + ], + "x-ms-enum": { + "name": "FilterType", + "modelAsString": false + } + }, + "rdmaNetworkInterfaceCount": { + "type": "object", + "items": { + "$ref": "#/definitions/VMAttributeMinMaxInteger" + }, + "description": "The rdma network interface count." + }, + "acceleratedNetworkingSupport": { + "type": "string", + "description": "The accelerated networking support filter.", + "enum": [ + "Included", + "Required", + "Excluded" + ], + "x-ms-enum": { + "name": "FilterType", + "modelAsString": false + } + }, + "confidentialVMSupport": { + "type": "string", + "description": "The confidential VM support filter.", + "enum": [ + "Included", + "Required", + "Excluded" + ], + "x-ms-enum": { + "name": "FilterType", + "modelAsString": false + } + }, + "osTypes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of Operation Systems." + }, + "allowedVMSizes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of allowed VMSizes." + }, + "excludedVMSizes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of excluded VMSizes." + } + }, + "description": "VM attributes for AttributeBasedVmSizeRecommender." + }, + "VMAttributeMinMaxInteger": { + "type": "object", + "properties": { + "min": { + "type": "integer", + "format": "int32", + "description": "The minimum value for vm attribute in integer." + }, + "max": { + "type": "integer", + "format": "int32", + "description": "The maximum value for vm attribute in integer." + } + }, + "description": "MinMax values in integer for AttributeBasedVmSizeRecommender." + }, + "VMAttributeMinMaxDouble": { + "type": "object", + "properties": { + "min": { + "type": "number", + "format": "double", + "description": "The minimum value for vm attribute in double." + }, + "max": { + "type": "number", + "format": "double", + "description": "The maximum value for vm attribute in double." + } + }, + "description": "Priority Profile for AttributeBasedVmSizeRecommender." + }, + "RecommendedVMSizes": { + "type": "object", + "properties": { + "regularVMSizes": { + "type": "array", + "items": { + "$ref": "#/definitions/RecommendedVMSizeProperties" + }, + "description": "The regular priority VMSizes." + }, + "spotVMSizes": { + "type": "array", + "items": { + "$ref": "#/definitions/RecommendedVMSizeProperties" + }, + "description": "The spot priority VMSizes." + } + }, + "description": "The regular and spot priority recommended VMSizes." + }, + "RecommendedVMSizeProperties": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the VMSize." + }, + "size": { + "type": "string", + "description": "The vm size." + }, + "family": { + "type": "string", + "description": "The family of VMSize." + }, + "attributes": { + "type": "object", + "items": { + "$ref": "#/definitions/RecommendedVMSizeAttributes" + }, + "description": "The recommended VMSize attributes." + }, + "restrictions": { + "type": "array", + "items": { + "$ref": "#/definitions/RecommendedVMSizeRestrictions" + }, + "description": "The recommended VMSize restrictions." + } + }, + "description": "The recommended VMSize properties." + }, + "RecommendedVMSizeAttributes": { + "type": "object", + "properties": { + "vCpu": { + "type": "integer", + "format": "int32", + "description": "The vCPU count." + }, + "memoryInGiB": { + "type": "number", + "format": "double", + "description": "The memory in GiB." + }, + "cpuArchitecture": { + "type": "string", + "description": "The cpu architecture of VMSize." + }, + "cpuManufacturer": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The cpu manufacturers." + }, + "networkBandwidthInMbps": { + "type": "number", + "format": "double", + "description": "The network bandwidth in Mbps." + }, + "localStorageSupport": { + "type": "boolean", + "description": "The local storage support flag." + }, + "localStorageType": { + "type": "string", + "description": "The local storage type of VMSize." + }, + "localStorageInGiB": { + "type": "number", + "format": "double", + "description": "The local storage in GiB." + }, + "dataDiskCount": { + "type": "integer", + "format": "int32", + "description": "The data disk count." + }, + "networkInterfaceCount": { + "type": "integer", + "format": "int32", + "description": "The network interface count." + } + }, + "description": "The recommended VMSize attributes." + }, + "RecommendedVMSizeRestrictions": { + "type": "object", + "properties": { + "offerRestrictions": { + "type": "array", + "items": { + "$ref": "#/definitions/VMSizeOfferRestriction" + }, + "description": "The offer restrictions on VMSize." + }, + "quotaRestrictions": { + "type": "array", + "items": { + "$ref": "#/definitions/VMSizeQuotaRestriction" + }, + "description": "The quota restrictions on VMSize." + } + }, + "description": "The restrictions recommended VMSizes." + }, + "VMSizeOfferRestriction": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "The type of offer restriction on VMSize.", + "enum": [ + "NotSpecified", + "Location", + "Zone" + ], + "x-ms-enum": { + "name": "ResourceSkuRestrictionType", + "modelAsString": false + } + }, + "values": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of restriction values." + }, + "restrictionInfo": { + "type": "object", + "items": { + "$ref": "#/definitions/VMSizeOfferRestrictionInfo" + }, + "description": "The offer restrictions on VMSize." + }, + "reasonCode": { + "type": "string", + "description": "The offer restriction reason code.", + "enum": [ + "NotSpecified", + "QuotaId", + "NotAvailableForSubscription" + ], + "x-ms-enum": { + "name": "ResourceSkuRestrictionReasonCode", + "modelAsString": false + } + } + }, + "description": "The offer restrictions on recommended VMSize." + }, + "VMSizeOfferRestrictionInfo": { + "type": "object", + "properties": { + "locations": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of restriction locations." + }, + "zones": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of restriction zones." + } + }, + "description": "The offer restriction info on recommended VMSize." + }, + "VMSizeQuotaRestriction": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "The type of quota restriction on VMSize.", + "enum": [ + "RegionalVCpu", + "VMFamilyVCpu" + ], + "x-ms-enum": { + "name": "QuotaRestrictionsType", + "modelAsString": false + } + } + }, + "description": "The quota restrictions on recommended VMSize." } } } diff --git a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json new file mode 100644 index 000000000000..82fbb97498ae --- /dev/null +++ b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json @@ -0,0 +1,201 @@ +{ + "parameters": { + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "location": "eastus", + "api-version": "2025-02-01-preview", + "attributeBasedVMSizeRecommenderInput": { + "regularPriorityProfile": { + "targetCapacity": 1, + "capacityUnitType": "VMInstanceCount" + }, + "spotPriorityProfile": { + "targetCapacity": 1, + "capacityUnitType": "VMInstanceCount", + "maxPricePerVM": 0.200 + }, + "recommendationProperties": { + "quotaAndOfferRestrictionsCheckRequired": true + }, + "resourceProperties": { + "vmAttributes": { + "vCpuCount": { + "Min": 0, + "Max": 0 + }, + "memoryInMiB": { + "Min": 0, + "Max": 0 + }, + "vmCategories": [ + "General Purpose" + ], + "architectureTypes": [ + "x86", + "arm64" + ], + "cpuManufacturers": [ + "Intel", + "AMD" + ], + "localStorageSupport": "included", + "ultraSSDSupport": "included", + "localStorageTypes": [ + "SSD", + "HDD" + ], + "localStorageInMiB": { + "Min": 0, + "Max": 0 + }, + "dataDiskCount": { + "Min": 0, + "Max": 4 + }, + "hibernationSupport": "included", + "hyperVGenerations": [ + "Gen1", + "Gen2" + ], + "networkInterfaceCount": { + "Min": 0, + "Max": 4 + }, + "networkBandwidthInMbps": { + "Min": 0, + "Max": 0 + }, + "confidentialVMSupport": "included", + "burstableSupport": "included", + "acceleratorSupport": "included", + "acceleratorTypes": [ + "FPGA", + "GPU" + ], + "acceleratorManufacturers": [ + "Intel", + "AMD", + "NVidia" + ], + "acceleratorCount": { + "Min": 0, + "Max": 0 + }, + "acceleratedNetworkingSupport": "included", + "rdmaSupport": "included", + "rdmaNetworkInterfaceCount": { + "Min": 0, + "Max": 0 + }, + "osType": [ + "Windows", + "Linux" + ], + "allowedVMSizes": [], + "excludeVMSizes": [] + } + } + } + }, + "responses": { + "200": { + "body": { + "recommendedVMSizes": { + "regularVMSizes": [ + { + "name": "Standard_D4ds_v5", + "size": "D4ds_v5", + "family": "standardDDSv5Family", + "attributes": { + "vCPUs": 4, + "memoryGB": 16, + "cpuArchitecture": "x86", + "cpuManufacturer": "Intel", + "networkBandwidthInMbps": "12500", + "localStorageSupport": "Included", + "localStorageType": "SSD", + "localStorageInGb": 150, + "dataDiskCount": 4, + "networkInterfaceCount": 4 + }, + "restrictions": { + "offerRestrictions": [ + { + "type": "Location", + "values": [ + "westus" + ], + "restrictionInfo": { + "location": [ + "westus" + ], + "zones": [ + "1", + "2", + "3" + ] + }, + "reasonCode": "NotAvailableForSubscription" + } + ], + "quotaRestrictions": [ + { + "type": "RegionalVCpu" + } + ] + } + } + ], + "spotVMSizes": [ + { + "name": "Standard_D4ds_v6", + "size": "D4ds_v5", + "family": "standardDDSv6Family", + "attributes": { + "vCPUs": 4, + "memoryGB": 16, + "cpuArchitecture": "x86", + "cpuManufacturer": "Intel", + "networkBandwidthInMbps": "12500", + "localStorageSupport": "Included", + "localStorageType": "SSD", + "localStorageInGb": 150, + "dataDiskCount": 4, + "networkInterfaceCount": 4 + }, + "restrictions": { + "offerRestrictions": [ + { + "type": "Location", + "values": [ + "westus" + ], + "restrictionInfo": { + "location": [ + "westus" + ], + "zones": [ + "1", + "2", + "3" + ] + }, + "reasonCode": "NotAvailableForSubscription" + } + ], + "quotaRestrictions": [ + { + "type": "RegionalVCpu" + } + ] + } + } + ] + } + }, + "headers": { + "x-ms-request-id": "57b891ab-1fb7-4f5a-b002-71eb6854961f", + "location": "https://management.azure.com/subscriptions/be23ca13-8eb4-4d0e-be10-b00451817956/providers/Microsoft.Compute/locations/eastus/vmSizeRecommendations/vmAttributeBased/generate?api-version=2025-02-01-preview" + } + } + } +} diff --git a/specification/compute/resource-manager/readme.md b/specification/compute/resource-manager/readme.md index c6931e3ffd8d..68979f08d5fd 100644 --- a/specification/compute/resource-manager/readme.md +++ b/specification/compute/resource-manager/readme.md @@ -269,6 +269,9 @@ suppressions: from: diagnostic.json - code: XmsPageableForListCalls reason: False positive error as API Path does not match ARM Lint check formatting, requesting to suppress due to approval from reviewer. + from: diagnostic.json + - code: DefinitionsPropertiesNamesCamelCase + reason: The property name contains abbreviations and need to keep it as upper case. from: diagnostic.json - code: BodyTopLevelProperties reason: The is the additional property bag to introduce new nonbreaking properties. From 3476f2a18f7e7e567bc3b993d1efe05935ca9413 Mon Sep 17 00:00:00 2001 From: chipate Date: Tue, 28 Jan 2025 08:14:20 -0800 Subject: [PATCH 099/106] Make restrictionsFilter as enum instead of Boolean --- .../preview/2025-02-01-preview/diagnostic.json | 16 +++++++++++++--- .../GenerateAttributeBasedVMSizeRecommender.json | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json index 0a39040491bf..7077da709f3b 100644 --- a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json +++ b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json @@ -968,9 +968,19 @@ "RecommendationProperties": { "type": "object", "properties": { - "quotaAndOfferRestrictionsCheckRequired": { - "type": "boolean", - "description": "The quota and offer restrictions check required flag." + "restrictionsFilter": { + "type": "string", + "description": "The restrictions filters to apply on VMSize recommendations.", + "enum": [ + "QuotaAndOfferRestrictions", + "QuotaRestrictions", + "OfferRestrictions", + "None" + ], + "x-ms-enum": { + "name": "RestrictionsFilterType", + "modelAsString": false + } } }, "description": "Recommendations Properties for AttributeBasedVmSizeRecommender." diff --git a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json index 82fbb97498ae..d4c7a3211d30 100644 --- a/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json +++ b/specification/compute/resource-manager/Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/examples/GenerateAttributeBasedVMSizeRecommender.json @@ -14,7 +14,7 @@ "maxPricePerVM": 0.200 }, "recommendationProperties": { - "quotaAndOfferRestrictionsCheckRequired": true + "restrictionsFilter": "QuotaAndOfferRestrictions" }, "resourceProperties": { "vmAttributes": { From f409467f8ad290bf3aba60b324b8dcca63cf3ad1 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Wed, 5 Feb 2025 18:11:23 +0800 Subject: [PATCH 100/106] Create branch.txt --- .js/branch.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 .js/branch.txt diff --git a/.js/branch.txt b/.js/branch.txt new file mode 100644 index 000000000000..8de1c3245328 --- /dev/null +++ b/.js/branch.txt @@ -0,0 +1 @@ +origin/wanl/fix-migration-sdk-type-detection From e71a62d85f1696af963a75f0cfdb3cb27d829cfd Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Thu, 6 Feb 2025 14:39:56 +0800 Subject: [PATCH 101/106] Fix markdown horizontal rule syntax --- specification/compute/resource-manager/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specification/compute/resource-manager/readme.md b/specification/compute/resource-manager/readme.md index 68979f08d5fd..d903cf79b588 100644 --- a/specification/compute/resource-manager/readme.md +++ b/specification/compute/resource-manager/readme.md @@ -10,7 +10,7 @@ Hence, each sub-service has its own swagger spec. All of them are tied together using this configuration and are packaged together into one compute client library. This makes it easier for customers to download one (NuGet/npm/pip/maven/gem) compute client library package rather than installing individual packages for each sub service. ---- +--- ## Getting Started From 8e7c98b91dfda6fbf5a228e13e14b0bf36295f5b Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Thu, 6 Feb 2025 15:10:01 +0800 Subject: [PATCH 102/106] Fix markdown horizontal rule in readme.md... --- specification/compute/resource-manager/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specification/compute/resource-manager/readme.md b/specification/compute/resource-manager/readme.md index d903cf79b588..68979f08d5fd 100644 --- a/specification/compute/resource-manager/readme.md +++ b/specification/compute/resource-manager/readme.md @@ -10,7 +10,7 @@ Hence, each sub-service has its own swagger spec. All of them are tied together using this configuration and are packaged together into one compute client library. This makes it easier for customers to download one (NuGet/npm/pip/maven/gem) compute client library package rather than installing individual packages for each sub service. ---- +--- ## Getting Started From ffd2af99f5a7c93a1128c0218820790f531cc916 Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Thu, 6 Feb 2025 16:25:51 +0800 Subject: [PATCH 103/106] Fix typo in AutoRest configuration file. --- specification/compute/resource-manager/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specification/compute/resource-manager/readme.md b/specification/compute/resource-manager/readme.md index 68979f08d5fd..ced256f6b2d7 100644 --- a/specification/compute/resource-manager/readme.md +++ b/specification/compute/resource-manager/readme.md @@ -2,7 +2,7 @@ > see https://aka.ms/autorest -This is the AutoRest configuration file for Compute. +This is the AutoRest configuration file for Compute.. The compute RP comprises of small services where each service has its own tag. Hence, each sub-service has its own swagger spec. From ff88a6616519d35f79c90489da880031398a45ca Mon Sep 17 00:00:00 2001 From: Wanpeng Li Date: Mon, 10 Feb 2025 13:38:33 +0800 Subject: [PATCH 104/106] Update specificationRepositoryConfiguration.json --- specificationRepositoryConfiguration.json | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/specificationRepositoryConfiguration.json b/specificationRepositoryConfiguration.json index 0b2c1930d549..c25c57f95e6e 100644 --- a/specificationRepositoryConfiguration.json +++ b/specificationRepositoryConfiguration.json @@ -13,8 +13,9 @@ "configFilePath": "eng/automation/swagger_to_sdk_config.json" }, "azure-sdk-for-js": { - "integrationRepository": "azure-sdk/azure-sdk-for-js", - "mainRepository": "Azure/azure-sdk-for-js" + "integrationRepository": "test-repo-billy/azure-sdk-for-js", + "mainBranch": "test_repo_breaking_change_mlc", + "mainRepository": "test-repo-billy/azure-sdk-for-js" }, "azure-sdk-for-net": { "integrationRepository": "azure-sdk/azure-sdk-for-net", @@ -44,8 +45,9 @@ "configFilePath": "eng/automation/swagger_to_sdk_config.json" }, "azure-sdk-for-js": { - "integrationRepository": "azure-sdk/azure-sdk-for-js-pr", - "mainRepository": "Azure/azure-sdk-for-js-pr" + "integrationRepository": "test-repo-billy/azure-sdk-for-js", + "mainBranch": "test_repo_breaking_change_mlc", + "mainRepository": "test-repo-billy/azure-sdk-for-js" }, "azure-sdk-for-net": { "integrationRepository": "azure-sdk/azure-sdk-for-net-pr", From df2b859c1f2e7c902a7c3b9ac4d126dbad87e0fb Mon Sep 17 00:00:00 2001 From: Pan Shao <97225342+pshao25@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:19:09 +0800 Subject: [PATCH 105/106] Update specificationRepositoryConfiguration.json --- specificationRepositoryConfiguration.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specificationRepositoryConfiguration.json b/specificationRepositoryConfiguration.json index c25c57f95e6e..7a0e48278243 100644 --- a/specificationRepositoryConfiguration.json +++ b/specificationRepositoryConfiguration.json @@ -19,7 +19,8 @@ }, "azure-sdk-for-net": { "integrationRepository": "azure-sdk/azure-sdk-for-net", - "mainRepository": "Azure/azure-sdk-for-net", + "mainRepository": "pshao25/azure-sdk-for-net", + "mainBranch": "computeMigrationReadmeUpdate", "configFilePath": "eng/swagger_to_sdk_config.json" }, "azure-sdk-for-python": { From 66ffabf6830b51c8131c8fee515f160c9450a247 Mon Sep 17 00:00:00 2001 From: ZiWei Chen <98569699+kazrael2119@users.noreply.github.com> Date: Tue, 11 Feb 2025 14:02:03 +0800 Subject: [PATCH 106/106] Update tag and input files in readme --- .../compute/resource-manager/readme.md | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/specification/compute/resource-manager/readme.md b/specification/compute/resource-manager/readme.md index ced256f6b2d7..432f4df488f8 100644 --- a/specification/compute/resource-manager/readme.md +++ b/specification/compute/resource-manager/readme.md @@ -34,7 +34,7 @@ These are the global settings for the Compute API. title: ComputeManagementClient description: Compute Client openapi-type: arm -tag: package-2024-11-04 +tag: package-2025-02-01-preview directive: - where: @@ -301,6 +301,33 @@ These settings apply only when `--tag=package-2025-02-01-preview` is specified o ```yaml $(tag) == 'package-2025-02-01-preview' input-file: - Microsoft.Compute/DiagnosticRP/preview/2025-02-01-preview/diagnostic.json + - Microsoft.Compute/common-types/v1/common.json + - Microsoft.Compute/ComputeRP/stable/2024-07-01/computeRPCommon.json + - Microsoft.Compute/ComputeRP/stable/2024-07-01/virtualMachineScaleSet.json + - Microsoft.Compute/ComputeRP/stable/2024-07-01/virtualMachine.json + - Microsoft.Compute/ComputeRP/stable/2024-07-01/virtualMachineImage.json + - Microsoft.Compute/ComputeRP/stable/2024-07-01/virtualMachineExtensionImage.json + - Microsoft.Compute/ComputeRP/stable/2024-07-01/availabilitySet.json + - Microsoft.Compute/ComputeRP/stable/2024-07-01/proximityPlacementGroup.json + - Microsoft.Compute/ComputeRP/stable/2024-07-01/dedicatedHost.json + - Microsoft.Compute/ComputeRP/stable/2024-07-01/sshPublicKey.json + - Microsoft.Compute/ComputeRP/stable/2024-07-01/image.json + - Microsoft.Compute/ComputeRP/stable/2024-07-01/restorePoint.json + - Microsoft.Compute/ComputeRP/stable/2024-07-01/capacityReservation.json + - Microsoft.Compute/ComputeRP/stable/2024-07-01/logAnalytic.json + - Microsoft.Compute/ComputeRP/stable/2024-07-01/runCommand.json + - Microsoft.Compute/DiskRP/stable/2024-03-02/diskRPCommon.json + - Microsoft.Compute/DiskRP/stable/2024-03-02/disk.json + - Microsoft.Compute/DiskRP/stable/2024-03-02/diskAccess.json + - Microsoft.Compute/DiskRP/stable/2024-03-02/diskEncryptionSet.json + - Microsoft.Compute/DiskRP/stable/2024-03-02/diskRestorePoint.json + - Microsoft.Compute/DiskRP/stable/2024-03-02/snapshot.json + - Microsoft.Compute/Skus/stable/2021-07-01/skus.json + - Microsoft.Compute/GalleryRP/stable/2024-03-03/galleryRPCommon.json + - Microsoft.Compute/GalleryRP/stable/2024-03-03/gallery.json + - Microsoft.Compute/GalleryRP/stable/2024-03-03/sharedGallery.json + - Microsoft.Compute/GalleryRP/stable/2024-03-03/communityGallery.json + - Microsoft.Compute/CloudserviceRP/stable/2024-11-04/cloudService.json ``` ### Tag: package-2024-11-04