in tools/codeowners-utils/Azure.Sdk.Tools.CodeownersLinter/Program.cs [159:308]
static int LintCodeownersFile(string teamUserBlobStorageUri,
string userOrgVisibilityBlobStorageUri,
string repoLabelBlobStorageUri,
string repoRoot,
string repoName,
bool filterBaselineErrors,
bool generateBaseline,
string baseBranchBaselineFile)
{
// Don't allow someone to create and use a baseline in the same run
if (filterBaselineErrors && generateBaseline)
{
throw new ArgumentException("The --filterBaselineErrors (-fbl) and --generateBaseline (-gbl) options cannot both be set. Either a baseline is being generated or being used to filter but not both.");
}
// Verify that the repoRoot exists
if (!Directory.Exists(repoRoot))
{
throw new ArgumentException($"The repository root '{repoRoot}' is not a valid directory. Please ensure the --repoRoot is set to the root of the repository.");
}
// Verify that the CODEOWNERS file exists in the .github subdirectory of the repository root
string codeownersFileFullPath = Path.Combine(repoRoot, ".github", "CODEOWNERS");
if (!File.Exists(codeownersFileFullPath))
{
throw new ArgumentException($"CODEOWNERS file {codeownersFileFullPath} does not exist. Please ensure the --repoRoot is set to the root of the repository and the CODEOWNERS file exists in the .github subdirectory.");
}
// Verify that label data exists for the repository
RepoLabelDataUtils repoLabelData = new RepoLabelDataUtils(repoLabelBlobStorageUri, repoName);
if (!repoLabelData.RepoLabelDataExists())
{
throw new ArgumentException($"The repository label data for {repoName} does not exist. Should this be running in this repository?");
}
bool useBaseBranchBaselineFile = false;
if (!string.IsNullOrEmpty(baseBranchBaselineFile))
{
if ((filterBaselineErrors && File.Exists(baseBranchBaselineFile)) || generateBaseline)
{
useBaseBranchBaselineFile = true;
}
else
{
throw new ArgumentException($"The base branch baseline file {baseBranchBaselineFile} does not exist.");
}
}
string codeownersBaselineFile = Path.Combine(repoRoot, ".github", BaselineConstants.BaselineErrorFile);
bool codeownersBaselineFileExists = false;
// If the baseline is to be used, verify that it exists.
if (filterBaselineErrors)
{
if (File.Exists(codeownersBaselineFile))
{
codeownersBaselineFileExists = true;
}
else
{
Console.WriteLine($"The CODEOWNERS baseline error file, {codeownersBaselineFile}, file for {repoName} does not exist. No filtering will be done for errors.");
}
}
DirectoryUtils directoryUtils = new DirectoryUtils(repoRoot);
OwnerDataUtils ownerData = new OwnerDataUtils(teamUserBlobStorageUri, userOrgVisibilityBlobStorageUri);
var errors = CodeownersUtils.Verification.CodeownersLinter.LintCodeownersFile(directoryUtils,
ownerData,
repoLabelData,
codeownersFileFullPath);
// Regenerate the baseline file if that option was selected
if (generateBaseline)
{
BaselineUtils baselineUtils = null;
if (useBaseBranchBaselineFile)
{
baselineUtils = new BaselineUtils(baseBranchBaselineFile);
}
else
{
baselineUtils = new BaselineUtils(codeownersBaselineFile);
}
baselineUtils.GenerateBaseline(errors);
}
// If the baseline is being used to filter out known errors, set the list
// of errors to the filtered list.
if (filterBaselineErrors)
{
// Can only filter if the filter file exists, if it doesn't then there's nothing to filter
// and all encountered errors will be output. Also, if the file doesn't exist that's reported
// above and doesn't need to be reported here.
if (codeownersBaselineFileExists)
{
if (errors.Count == 0)
{
Console.WriteLine($"##vso[task.LogIssue type=warning;]There were no CODEOWNERS parsing errors but there is a baseline file {codeownersBaselineFile} for filtering. If the file is empty, or all errors have been fixed, then it should be deleted.");
}
else
{
BaselineUtils baselineUtils = new BaselineUtils(codeownersBaselineFile);
errors = baselineUtils.FilterErrorsUsingBaseline(errors);
}
}
// After the file has been filered with the standard CODEOWNERS baseline file, if there are
// still remaining errors and there is a base branch baseline file, further filter with that
// file.
if (useBaseBranchBaselineFile && errors.Count > 0)
{
BaselineUtils baselineUtils = new BaselineUtils(baseBranchBaselineFile);
errors = baselineUtils.FilterErrorsUsingBaseline(errors);
}
}
bool loggingInDevOps = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SYSTEM_TEAMPROJECTID"));
int returnCode = 0;
// If there are errors, and this isn't a baseline generation, ensure the returnCode is non-zero and output the errors.
if ((errors.Count > 0) && !generateBaseline)
{
returnCode = 1;
// DevOps only adds the first 4 errors to the github checks list so lets always add the generic one first and then as many of the individual ones as can be found afterwards
if (loggingInDevOps)
{
Console.WriteLine($"##vso[task.logissue type=error;]There are linter errors. Please visit {linterErrorsHelpLink} for guidance on how to handle them.");
}
else
{
Console.WriteLine($"There are linter errors. Please visit {linterErrorsHelpLink} for guidance on how to handle them.");
}
// Output the errors sorted ascending by line number and by type. If there's a block
// error with the same starting line number as a single line error, the block error
// should be output first.
var errorsByLineAndType = errors.OrderBy(e => e.LineNumber).ThenBy(e => e.GetType().Name);
foreach (var error in errorsByLineAndType)
{
if (loggingInDevOps)
{
// Environment.NewLine needs to be replaced by an encoded NewLine "%0D%0A" in order to display on GitHub and DevOps checks
Console.WriteLine($"##vso[task.logissue type=error;sourcepath={codeownersFileFullPath};linenumber={error.LineNumber};columnnumber=1;]{error.ToString().Replace(Environment.NewLine,"%0D%0A")}");
}
else
{
Console.WriteLine(error + Environment.NewLine);
}
}
}
return returnCode;
}