eng/scripts/lint-resourcemanager.js (129 lines of code) (raw):
import { exec } from "child_process";
import * as yaml from "js-yaml";
import { scanSwaggers } from "./helpers.js";
class LintErrorParser {
results;
AutoRestErrors = [
'{\n "Channel": "warning"',
'{\n "Channel": "error"',
'{\n "Channel": "fatal"',
"Process() cancelled due to exception",
];
constructor(output) {
this.results = this.cleanUp(output);
}
cleanUp(s) {
let resultString = s.replace(/}\nProcessing batch task - {\"package-(.*).\n{/g, "},{");
resultString = resultString.replace(/{"package-(.*)} .\n/, "");
resultString = resultString.replace(/\nProcessing batch task(.*)./g, "");
return s;
}
getLintResult() {
const regexLintResult = /\{\n "type": "[\s\S]*?\n\}/gi;
let results = [];
let matches;
while ((matches = regexLintResult.exec(this.results))) {
const oneMessage = yaml.load(matches[0]);
if (oneMessage) {
results.push(oneMessage);
} else {
console.log("The linter output message has invalid format:", matches[0]);
}
}
return JSON.stringify(results);
}
isAutorestFailed() {
return this.AutoRestErrors.some((error) => this.results.indexOf(error) !== -1);
}
getAutoRestError() {
if (this.isAutorestFailed()) {
const regexLintResult = /\{\n "type": "[\s\S]*?\n\}/gi;
return this.results.replace(regexLintResult, "");
}
return "";
}
}
const suppressedErrors = [
{
id: "D5001",
code: "XmsExamplesRequired",
},
{
id: "R2026",
code: "AvoidAnonymousTypes",
},
];
async function lintInternal(swagger) {
const cmd = [
"npx",
"autorest",
"--validation",
"--azure-validator",
"--message-format=json",
"--openapi-type=arm",
"--use=@microsoft.azure/classic-openapi-validator@latest",
"--use=@microsoft.azure/openapi-validator@latest",
"--openapi-subtype=providerHub",
"--input-file=" + swagger,
].join(" ");
const { err, stdout, stderr } = await new Promise((res) =>
exec(cmd, { encoding: "utf-8", maxBuffer: 1024 * 1024 * 64 }, (err, stdout, stderr) =>
res({ err, stdout, stderr }),
),
);
let resultString = stderr + stdout;
if (resultString.indexOf("{") !== -1) {
resultString = resultString.replace(/Processing batch task - {.*} \.\n/g, "");
}
const parser = new LintErrorParser(resultString);
return parser;
}
function filterSuppressed(results) {
const isSuppressed = (x) => suppressedErrors.some((y) => x.id === y.id && x.code === y.code);
return results.filter((x) => !isSuppressed(x));
}
async function lintSwagger(swagger) {
const r = await lintInternal(swagger);
let success = true;
if (r.isAutorestFailed()) {
console.log("autorest errors:");
console.log(r.getAutoRestError());
success = false;
} else {
const lintResults = JSON.parse(r.getLintResult());
const res = filterSuppressed(lintResults);
const errors = res.filter((x) => x.type === "Error");
const warnings = res.filter((x) => x.type === "Warning");
if (errors.length > 0) {
console.error(errors.length, " errors detected as below:");
console.log(errors);
success = false;
}
console.log("\n\n");
if (warnings.length > 0) {
console.warn(warnings.length, " warnings detected as below:");
console.warn(warnings);
}
}
return success;
}
async function main() {
const roots = process.argv[2].split(";");
const paths = roots.flatMap((root) => scanSwaggers(root));
console.log("Scanned following swaggers:", paths);
const errorPaths = [];
for (const p of paths) {
console.log("Run LintProviderHub for", p);
const success = await lintSwagger(p);
if (!success) {
errorPaths.push(p);
}
console.log("\n\n\n");
}
if (errorPaths.length > 0) {
console.error("Please fix errors for following files:");
console.error(errorPaths);
process.exit(1);
}
}
main();