in tools/codeowners-utils/Azure.Sdk.Tools.CodeownersUtils/Verification/CodeownersLinter.cs [102:265]
public static void VerifyBlock(DirectoryUtils directoryUtils,
OwnerDataUtils ownerData,
RepoLabelDataUtils repoLabelData,
List<BaseError> errors,
int startBlockLineNumber,
int endBlockLineNumber,
List<string> codeownersFile,
bool singleLineVerification = true)
{
List<string> blockErrorStrings = new List<string>();
// The codeownersFile as a list<string> is 0 based, for reporting purposes it needs
// to be 1 based to match the exact line in the CODEOWNERS file.
int startLineNumberForReporting = startBlockLineNumber + 1;
int endLineNumberForReporting = endBlockLineNumber + 1;
bool endsWithSourceOwnerLine = ParsingUtils.IsSourcePathOwnerLine(codeownersFile[endBlockLineNumber]);
// Booleans for every moniker, will be set to true when found, are used to verify the block
// contains what it needs to contain for the monikers found within it.
bool blockHasAzureSdkOwners = false;
bool blockHasMissingFolder = false;
bool blockHasPRLabel = false;
bool blockHasServiceLabel = false;
bool blockHasServiceOwners = false;
for (int blockLine = startBlockLineNumber; blockLine <= endBlockLineNumber; blockLine++)
{
string line = codeownersFile[blockLine];
int lineNumberForReporting = blockLine + 1;
bool isSourcePathOwnerLine = ParsingUtils.IsSourcePathOwnerLine(line);
if (isSourcePathOwnerLine)
{
if (singleLineVerification)
{
VerifySingleLine(directoryUtils,
ownerData,
repoLabelData,
errors,
lineNumberForReporting,
line,
isSourcePathOwnerLine,
!endsWithSourceOwnerLine);
}
}
else
{
string moniker = MonikerUtils.ParseMonikerFromLine(line);
// This can happen if there's a comment line in the block, skip the line
if (null == moniker)
{
continue;
}
switch (moniker)
{
case MonikerConstants.AzureSdkOwners:
if (blockHasAzureSdkOwners)
{
blockErrorStrings.Add($"{MonikerConstants.AzureSdkOwners}{ErrorMessageConstants.DuplicateMonikerInBlockPartial}");
}
blockHasAzureSdkOwners = true;
break;
case MonikerConstants.PRLabel:
if (blockHasPRLabel)
{
blockErrorStrings.Add($"{MonikerConstants.PRLabel}{ErrorMessageConstants.DuplicateMonikerInBlockPartial}");
}
blockHasPRLabel = true;
break;
case MonikerConstants.MissingFolder:
if (blockHasMissingFolder)
{
blockErrorStrings.Add($"{MonikerConstants.MissingFolder}{ErrorMessageConstants.DuplicateMonikerInBlockPartial}");
}
blockHasMissingFolder = true;
break;
case MonikerConstants.ServiceLabel:
if (blockHasServiceLabel)
{
blockErrorStrings.Add($"{MonikerConstants.ServiceLabel}{ErrorMessageConstants.DuplicateMonikerInBlockPartial}");
}
blockHasServiceLabel = true;
break;
case MonikerConstants.ServiceOwners:
if (blockHasServiceOwners)
{
blockErrorStrings.Add($"{MonikerConstants.ServiceOwners}{ErrorMessageConstants.DuplicateMonikerInBlockPartial}");
}
blockHasServiceOwners = true;
break;
default:
// This shouldn't get here unless someone adds a new moniker and forgets to add it to the switch statement
throw new ArgumentException($"Unexpected moniker '{moniker}' found on line {lineNumberForReporting}\nLine={line}");
}
if (singleLineVerification)
{
VerifySingleLine(directoryUtils,
ownerData,
repoLabelData,
errors,
lineNumberForReporting,
line,
isSourcePathOwnerLine,
!endsWithSourceOwnerLine, // If the block ends in a source path/owner line then we don't expect owners on moniker lines
moniker);
}
}
}
// After the block has been processed, ensure that any monikers are paired correctly with other
// monikers or source path/owners
// If the block is a single source path/owners line then there's nothing else to be done since there
// can't be any block errors.
if (startBlockLineNumber == endBlockLineNumber && endsWithSourceOwnerLine)
{
return;
}
// AzureSdkOwners must be part of a block of that a ServiceLabel entry as the AzureSdkOwners are associated with
// that ServiceLabel
if (blockHasAzureSdkOwners && !blockHasServiceLabel)
{
blockErrorStrings.Add(ErrorMessageConstants.AzureSdkOwnersMustBeWithServiceLabel);
}
if (blockHasServiceOwners && !blockHasServiceLabel)
{
blockErrorStrings.Add(ErrorMessageConstants.ServiceOwnersMustBeWithServiceLabel);
}
// PRLabel moniker must be in a block that ends with a source path/owner line
if (blockHasPRLabel && !endsWithSourceOwnerLine)
{
blockErrorStrings.Add($"{MonikerConstants.PRLabel}{ErrorMessageConstants.NeedsToEndWithSourceOwnerPartial}");
}
// ServiceLabel needs to be part of a block that has AzureSdkOwners or one of; ServiceOwners or #/<NotInRepo>/
// (MonikerConstants.MissingFolder), or ends in a source path/owner line both not both.
if (blockHasServiceLabel)
{
if (!endsWithSourceOwnerLine && !blockHasServiceOwners && !blockHasMissingFolder && !blockHasAzureSdkOwners)
{
blockErrorStrings.Add(ErrorMessageConstants.ServiceLabelNeedsOwners);
}
else if (endsWithSourceOwnerLine && (blockHasServiceOwners || blockHasMissingFolder))
{
blockErrorStrings.Add(ErrorMessageConstants.ServiceLabelHasTooManyOwners);
}
else if (blockHasServiceOwners && blockHasMissingFolder)
{
blockErrorStrings.Add(ErrorMessageConstants.ServiceLabelHasTooManyOwnerMonikers);
}
}
if (blockErrorStrings.Count > 0)
{
List<string> blockLines = new List<string>();
blockLines.AddRange(codeownersFile.GetRange(startBlockLineNumber, (endBlockLineNumber - startBlockLineNumber) + 1));
errors.Add(new BlockFormattingError(startLineNumberForReporting,
endLineNumberForReporting,
blockLines,
blockErrorStrings));
}
}