diff --git a/.editorconfig b/.editorconfig index 4ea6d3bc8ac2..e992d7ca3b56 100644 --- a/.editorconfig +++ b/.editorconfig @@ -28,7 +28,7 @@ insert_final_newline = true # Organize usings dotnet_sort_system_directives_first = true # this. preferences -dotnet_style_qualification_for_field = true:suggestion +dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_property = false:silent dotnet_style_qualification_for_method = false:silent dotnet_style_qualification_for_event = false:silent diff --git a/tools/code-owners-parser/Azure.Sdk.Tools.CodeOwnersParser.Tests/CodeOwnersFileTests.cs b/tools/code-owners-parser/Azure.Sdk.Tools.CodeOwnersParser.Tests/CodeOwnersFileTests.cs index 46eac3803242..75abc455b779 100644 --- a/tools/code-owners-parser/Azure.Sdk.Tools.CodeOwnersParser.Tests/CodeOwnersFileTests.cs +++ b/tools/code-owners-parser/Azure.Sdk.Tools.CodeOwnersParser.Tests/CodeOwnersFileTests.cs @@ -4,13 +4,13 @@ namespace Azure.Sdk.Tools.CodeOwnersParser.Tests; [TestFixture] -public class CodeOwnersFileTests +public class CodeownersFileTests { /// /// A battery of test cases specifying behavior of new logic matching target /// path to CODEOWNERS entries, and comparing it to existing, legacy logic. /// - /// The logic that has changed is located in CodeOwnersFile.FindOwnersForClosestMatch. + /// The logic that has changed is located in CodeownersFile.GetMatchingCodeownersEntry. /// /// In the test case table below, any discrepancy between legacy and new /// matcher expected matches that doesn't pertain to wildcard matching denotes @@ -135,14 +135,14 @@ public class CodeOwnersFileTests }; /// - /// Exercises Azure.Sdk.Tools.CodeOwnersParser.Tests.CodeOwnersFileTests.testCases. + /// Exercises Azure.Sdk.Tools.CodeOwnersParser.Tests.CodeownersFileTests.testCases. /// See comment on that member for details. /// [TestCaseSource(nameof(testCases))] public void TestParseAndFindOwnersForClosestMatch(TestCase testCase) { - List? codeownersEntries = - CodeOwnersFile.ParseContent(testCase.CodeownersPath + "@owner"); + List? codeownersEntries = + CodeownersFile.GetCodeownersEntries(testCase.CodeownersPath + "@owner"); VerifyFindOwnersForClosestMatch(testCase, codeownersEntries, useNewImpl: false, testCase.ExpectedLegacyMatch); VerifyFindOwnersForClosestMatch(testCase, codeownersEntries, useNewImpl: true, testCase.ExpectedNewMatch); @@ -150,18 +150,16 @@ public void TestParseAndFindOwnersForClosestMatch(TestCase testCase) private static void VerifyFindOwnersForClosestMatch( TestCase testCase, - List codeownersEntries, + List codeownersEntries, bool useNewImpl, bool expectedMatch) { - CodeOwnerEntry? entryLegacy = + CodeownersEntry? entryLegacy = // Act - CodeOwnersFile.FindOwnersForClosestMatch( - codeownersEntries, - testCase.TargetPath, - useNewFindOwnersForClosestMatchImpl: useNewImpl); + CodeownersFile.GetMatchingCodeownersEntry(testCase.TargetPath, + codeownersEntries, useNewImpl: useNewImpl); - Assert.That(entryLegacy.Owners.Count, Is.EqualTo(expectedMatch ? 1 : 0)); + Assert.That(entryLegacy.Owners, Has.Count.EqualTo(expectedMatch ? 1 : 0)); } public record TestCase( diff --git a/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/ConsoleOutput.cs b/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/ConsoleOutput.cs index a4de98d76905..434129f87c42 100644 --- a/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/ConsoleOutput.cs +++ b/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/ConsoleOutput.cs @@ -38,6 +38,8 @@ public void Dispose() Console.SetOut(this.originalOutput); this.stringWriter.Dispose(); this.originalOutput.Dispose(); + // https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1816 + GC.SuppressFinalize(this); } /// diff --git a/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/MainTests.cs b/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/ProgramTests.cs similarity index 66% rename from tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/MainTests.cs rename to tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/ProgramTests.cs index 51701637e918..0755b7307b0e 100644 --- a/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/MainTests.cs +++ b/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/ProgramTests.cs @@ -9,9 +9,9 @@ namespace Azure.Sdk.Tools.RetrieveCodeOwners.Tests /// This is test of console app Azure.Sdk.Tools.RetrieveCodeOwners. /// [TestFixture] - public class MainTests + public class ProgramTests { - private const string CodeOwnersFilePath = "CODEOWNERS"; + private const string CodeownersFilePath = "CODEOWNERS"; private static readonly object[] sourceLists = { @@ -27,28 +27,26 @@ public class MainTests [TestCaseSource(nameof(sourceLists))] public void TestOnNormalOutput(string targetDirectory, bool includeUserAliasesOnly, List expectedReturn) { - using (var consoleOutput = new ConsoleOutput()) - { - Program.Main(CodeOwnersFilePath, targetDirectory, includeUserAliasesOnly); - var output = consoleOutput.GetOutput(); - TestExpectResult(expectedReturn, output); - consoleOutput.Dispose(); - } + using var consoleOutput = new ConsoleOutput(); + Program.Main(targetDirectory, CodeownersFilePath, includeUserAliasesOnly); + var output = consoleOutput.GetOutput(); + TestExpectResult(expectedReturn, output); + consoleOutput.Dispose(); } [TestCase("PathNotExist")] [TestCase("http://testLink")] [TestCase("https://testLink")] - public void TestOnError(string codeOwnerPath) + public void TestOnError(string codeownersPath) { - Assert.That(Program.Main(codeOwnerPath, "sdk"), Is.EqualTo(1)); + Assert.That(Program.Main("sdk", codeownersPath), Is.EqualTo(1)); } private static void TestExpectResult(List expectReturn, string output) { - CodeOwnerEntry? codeOwnerEntry = JsonSerializer.Deserialize(output); - List actualReturn = codeOwnerEntry!.Owners; - Assert.That(actualReturn.Count, Is.EqualTo(expectReturn.Count)); + CodeownersEntry? codeownersEntry = JsonSerializer.Deserialize(output); + List actualReturn = codeownersEntry!.Owners; + Assert.That(actualReturn, Has.Count.EqualTo(expectReturn.Count)); for (int i = 0; i < actualReturn.Count; i++) { Assert.That(actualReturn[i], Is.EqualTo(expectReturn[i])); diff --git a/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners/Program.cs b/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners/Program.cs index efc105654b7a..c385eb9d9bd5 100644 --- a/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners/Program.cs +++ b/tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners/Program.cs @@ -5,36 +5,40 @@ namespace Azure.Sdk.Tools.RetrieveCodeOwners { /// - /// The tool command to retrieve code owners. + /// See Program.Main comment. /// public static class Program { /// - /// Retrieves CODEOWNERS information for specific section of the repo + /// Given targetPath and CODEOWNERS file codeownersFilePath, + /// prints out to stdout owners of the targetPath as determined by the CODEOWNERS file. /// - /// The path of CODEOWNERS file in repo - /// The directory whose information is to be retrieved - /// The option to filter out code owner team alias. + /// The path whose owners are to be determined. + /// The path to the CODEOWNERS file with ownership information. + /// Whether owners that aren't users should be excluded from the returned owners. /// Exit code - public static int Main( - string codeOwnerFilePath, - string targetDirectory, - bool filterOutNonUserAliases = false - ) + string targetPath, + string codeownersFilePath, + bool excludeNonUserAliases = false) { - var target = targetDirectory.Trim(); - try { - var codeOwnerEntry = CodeOwnersFile.ParseAndFindOwnersForClosestMatch(codeOwnerFilePath, target); - if (filterOutNonUserAliases) + targetPath = targetPath.Trim(); + try + { + var codeownersEntry = CodeownersFile.GetMatchingCodeownersEntry(targetPath, codeownersFilePath); + if (excludeNonUserAliases) { - codeOwnerEntry.FilterOutNonUserAliases(); + codeownersEntry.ExcludeNonUserAliases(); } - var codeOwnerJson = JsonSerializer.Serialize(codeOwnerEntry, new JsonSerializerOptions { WriteIndented = true }); - Console.WriteLine(codeOwnerJson); + + string codeownersJson = JsonSerializer.Serialize(codeownersEntry, + new JsonSerializerOptions { WriteIndented = true }); + + Console.WriteLine(codeownersJson); return 0; } - catch (Exception e) { + catch (Exception e) + { Console.Error.WriteLine(e.Message); return 1; } diff --git a/tools/code-owners-parser/CodeOwnersParser/CodeOwnersFile.cs b/tools/code-owners-parser/CodeOwnersParser/CodeOwnersFile.cs index 0e7d4285973d..16e4e3d531a5 100644 --- a/tools/code-owners-parser/CodeOwnersParser/CodeOwnersFile.cs +++ b/tools/code-owners-parser/CodeOwnersParser/CodeOwnersFile.cs @@ -1,84 +1,96 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; namespace Azure.Sdk.Tools.CodeOwnersParser { - public static class CodeOwnersFile + public static class CodeownersFile { - public static List ParseFile(string filePathOrUrl) + public static List GetCodeownersEntriesFromFileOrUrl(string codeownersFilePathOrUrl) { - string content = FileHelpers.GetFileContents(filePathOrUrl); - return ParseContent(content); + string content = FileHelpers.GetFileOrUrlContents(codeownersFilePathOrUrl); + return GetCodeownersEntries(content); } - public static List ParseContent(string fileContent) + public static List GetCodeownersEntries(string codeownersContent) { - List entries = new List(); + List entries = new List(); // An entry ends when we get to a path (a real path or a commented dummy path) - using (StringReader sr = new StringReader(fileContent)) + using (StringReader sr = new StringReader(codeownersContent)) { - CodeOwnerEntry entry = new CodeOwnerEntry(); + CodeownersEntry entry = new CodeownersEntry(); // we are going to read line by line until we find a line that is not a comment OR that is using the placeholder entry inside the comment. // while we are trying to find the folder entry, we parse all comment lines to extract the labels from it. // when we find the path or placeholder, we add the completed entry and create a new one. while (sr.ReadLine() is { } line) { - line = NormalizeLine(line); - - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - - if (!line.StartsWith("#") || line.IndexOf(CodeOwnerEntry.MissingFolder, System.StringComparison.OrdinalIgnoreCase) >= 0) - { - // If this is not a comment line OR this is a placeholder entry - - entry.ParseOwnersAndPath(line); - - // only add it if it is a valid entry - if (entry.IsValid) - { - entries.Add(entry); - } - - // create a new entry. - entry = new CodeOwnerEntry(); - } - else if (line.StartsWith("#")) - { - // try to process the line in case there are markers that need to be extracted - entry.ProcessLabelsOnLine(line); - } - + ProcessCodeownersLine(line, entry, entries); } } return entries; } - public static CodeOwnerEntry ParseAndFindOwnersForClosestMatch( - string codeOwnersFilePathOrUrl, + public static CodeownersEntry GetMatchingCodeownersEntry( string targetPath, - bool useNewFindOwnersForClosestMatchImpl = false) + string codeownersFilePathOrUrl, + bool useNewImpl = false) { - var codeOwnerEntries = ParseFile(codeOwnersFilePathOrUrl); - return FindOwnersForClosestMatch(codeOwnerEntries, targetPath, useNewFindOwnersForClosestMatchImpl); + var codeownersEntries = GetCodeownersEntriesFromFileOrUrl(codeownersFilePathOrUrl); + return GetMatchingCodeownersEntry(targetPath, codeownersEntries, useNewImpl); } - public static CodeOwnerEntry FindOwnersForClosestMatch( - List codeOwnerEntries, + public static CodeownersEntry GetMatchingCodeownersEntry( string targetPath, - bool useNewFindOwnersForClosestMatchImpl = false) + List codeownersEntries, + bool useNewImpl = false) + { + Debug.Assert(targetPath != null); + return useNewImpl + ? new MatchedCodeownersEntry(codeownersEntries, targetPath).Value + : GetMatchingCodeownersEntryLegacyImpl(codeownersEntries, targetPath); + } + + private static void ProcessCodeownersLine(string line, CodeownersEntry entry, List entries) { - return useNewFindOwnersForClosestMatchImpl - ? new MatchedCodeOwnerEntry(codeOwnerEntries, targetPath).Value - : FindOwnersForClosestMatchLegacyImpl(codeOwnerEntries, targetPath); + line = NormalizeLine(line); + + if (string.IsNullOrWhiteSpace(line)) + { + return; + } + + if (!IsCommentLine(line) || IsPlaceholderEntry(line)) + { + entry.ParseOwnersAndPath(line); + + // only add it if it is a valid entry + if (entry.IsValid) + entries.Add(entry); + } + else if (line.StartsWith("#")) + { + // try to process the line in case there are markers that need to be extracted + entry.ProcessLabelsOnLine(line); + } } - private static CodeOwnerEntry FindOwnersForClosestMatchLegacyImpl(List codeOwnerEntries, + private static bool IsPlaceholderEntry(string line) + => line.Contains(CodeownersEntry.MissingFolder, StringComparison.OrdinalIgnoreCase); + + private static bool IsCommentLine(string line) + => line.StartsWith("#"); + + private static string NormalizeLine(string line) + => !string.IsNullOrEmpty(line) + // Remove tabs and trim extra whitespace + ? line.Replace('\t', ' ').Trim() + : line; + + private static CodeownersEntry GetMatchingCodeownersEntryLegacyImpl( + List codeownersEntries, string targetPath) { // Normalize the start and end of the paths by trimming slash @@ -86,24 +98,18 @@ private static CodeOwnerEntry FindOwnersForClosestMatchLegacyImpl(List= 0; i--) + for (int i = codeownersEntries.Count - 1; i >= 0; i--) { - string codeOwnerPath = codeOwnerEntries[i].PathExpression.Trim('/'); + string codeownersPath = codeownersEntries[i].PathExpression.Trim('/'); // Note that this only matches on paths without glob patterns which is good enough // for our current scenarios but in the future might need to support globs - if (targetPath.StartsWith(codeOwnerPath, StringComparison.OrdinalIgnoreCase)) + if (targetPath.StartsWith(codeownersPath, StringComparison.OrdinalIgnoreCase)) { - return codeOwnerEntries[i]; + return codeownersEntries[i]; } } - return new CodeOwnerEntry(); + return new CodeownersEntry(); } - - private static string NormalizeLine(string line) - => !string.IsNullOrEmpty(line) - // Remove tabs and trim extra whitespace - ? line.Replace('\t', ' ').Trim() - : line; } } diff --git a/tools/code-owners-parser/CodeOwnersParser/CodeOwnerEntry.cs b/tools/code-owners-parser/CodeOwnersParser/CodeownersEntry.cs similarity index 77% rename from tools/code-owners-parser/CodeOwnersParser/CodeOwnerEntry.cs rename to tools/code-owners-parser/CodeOwnersParser/CodeownersEntry.cs index c2fa426734ac..f471c84fcd2a 100644 --- a/tools/code-owners-parser/CodeOwnersParser/CodeOwnerEntry.cs +++ b/tools/code-owners-parser/CodeOwnersParser/CodeownersEntry.cs @@ -10,7 +10,7 @@ namespace Azure.Sdk.Tools.CodeOwnersParser /// # ServiceLabel: %Label /// path @owner @owner /// - public class CodeOwnerEntry + public class CodeownersEntry { const char LabelSeparator = '%'; const char OwnerSeparator = '@'; @@ -20,7 +20,7 @@ public class CodeOwnerEntry public string PathExpression { get; set; } = ""; - public bool ContainsWildcard => PathExpression.Contains("*"); + public bool ContainsWildcard => PathExpression.Contains('*'); public List Owners { get; set; } = new List(); @@ -28,18 +28,10 @@ public class CodeOwnerEntry public List ServiceLabels { get; set; } = new List(); - public bool IsValid - { - get - { - return !string.IsNullOrEmpty(PathExpression); - } - } + public bool IsValid => !string.IsNullOrWhiteSpace(PathExpression); private static string[] SplitLine(string line, char splitOn) - { - return line.Split(new char[] { splitOn }, StringSplitOptions.RemoveEmptyEntries); - } + => line.Split(new char[] { splitOn }, StringSplitOptions.RemoveEmptyEntries); public override string ToString() { @@ -48,12 +40,12 @@ public override string ToString() public bool ProcessLabelsOnLine(string line) { - if (line.IndexOf(PRLabelMoniker, StringComparison.OrdinalIgnoreCase) >= 0) + if (line.Contains(PRLabelMoniker, StringComparison.OrdinalIgnoreCase)) { PRLabels.AddRange(ParseLabels(line, PRLabelMoniker)); return true; } - else if (line.IndexOf(ServiceLabelMoniker, StringComparison.OrdinalIgnoreCase) >= 0) + else if (line.Contains(ServiceLabelMoniker, StringComparison.OrdinalIgnoreCase)) { ServiceLabels.AddRange(ParseLabels(line, ServiceLabelMoniker)); return true; @@ -61,10 +53,10 @@ public bool ProcessLabelsOnLine(string line) return false; } - private IEnumerable ParseLabels(string line, string moniker) + private static IEnumerable ParseLabels(string line, string moniker) { // Parse a line that looks like # PRLabel: %Label, %Label - if (line.IndexOf(moniker, StringComparison.OrdinalIgnoreCase) == -1) + if (!line.Contains(moniker, StringComparison.OrdinalIgnoreCase)) { yield break; } @@ -76,7 +68,7 @@ private IEnumerable ParseLabels(string line, string moniker) yield break; } - line = line.Substring(colonPosition + 1).Trim(); + line = line[(colonPosition + 1)..].Trim(); foreach (string label in SplitLine(line, LabelSeparator).ToList()) { if (!string.IsNullOrWhiteSpace(label)) @@ -89,31 +81,36 @@ private IEnumerable ParseLabels(string line, string moniker) public void ParseOwnersAndPath(string line) { if (string.IsNullOrEmpty(line) || - (line.StartsWith("#") && !(line.IndexOf(CodeOwnerEntry.MissingFolder, StringComparison.OrdinalIgnoreCase) >= 0))) + (IsComment(line) && !(line.Contains(CodeownersEntry.MissingFolder, StringComparison.OrdinalIgnoreCase)))) { return; } line = ParsePath(line); - - //remove any comments from the line, if any. - // this is the case when we have something like @user #comment - int commentIndex = line.IndexOf("#"); - - if (commentIndex >= 0) - { - line = line.Substring(0, commentIndex).Trim(); - } + line = RemoveCommentIfAny(line); foreach (string author in SplitLine(line, OwnerSeparator).ToList()) { if (!string.IsNullOrWhiteSpace(author)) - { Owners.Add(author.Trim()); - } } } + private static bool IsComment(string line) + => line.StartsWith("#"); + + private static string RemoveCommentIfAny(string line) + { + // this is the case when we have something like @user #comment + + int commentIndex = line.IndexOf("#", StringComparison.OrdinalIgnoreCase); + + if (commentIndex >= 0) + line = line[..commentIndex].Trim(); + + return line; + } + private string ParsePath(string line) { // Get the start of the owner in the string @@ -123,18 +120,18 @@ private string ParsePath(string line) return line; } - string path = line.Substring(0, ownerStartPosition).Trim(); + string path = line[..ownerStartPosition].Trim(); // the first entry is the path/regex PathExpression = path; // remove the path from the string. - return line.Substring(ownerStartPosition); + return line[ownerStartPosition..]; } /// /// Remove all code owners which are not github alias. /// - public void FilterOutNonUserAliases() + public void ExcludeNonUserAliases() { Owners.RemoveAll(r => !IsGitHubUserAlias(r)); } diff --git a/tools/code-owners-parser/CodeOwnersParser/FileHelpers.cs b/tools/code-owners-parser/CodeOwnersParser/FileHelpers.cs index 2c9b0ff58e24..91db19aa3e19 100644 --- a/tools/code-owners-parser/CodeOwnersParser/FileHelpers.cs +++ b/tools/code-owners-parser/CodeOwnersParser/FileHelpers.cs @@ -6,25 +6,26 @@ namespace Azure.Sdk.Tools.CodeOwnersParser { public static class FileHelpers { - public static string GetFileContents(string fileOrUri) + public static string GetFileOrUrlContents(string fileOrUrl) { - // try to parse it as an Uri - if (fileOrUri.StartsWith("https")) - { - using (HttpClient client = new HttpClient()) - { - HttpResponseMessage response = client.GetAsync(fileOrUri).ConfigureAwait(false).GetAwaiter().GetResult(); - return response.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult(); - } - } + if (fileOrUrl.StartsWith("https")) + return GetUrlContents(fileOrUrl); - string fullPath = Path.GetFullPath(fileOrUri); + string fullPath = Path.GetFullPath(fileOrUrl); if (File.Exists(fullPath)) - { return File.ReadAllText(fullPath); - } - throw new ArgumentException($"The path provided is neither local path nor https link. Please check your path: {fileOrUri} resolved to {fullPath}."); + throw new ArgumentException( + "The path provided is neither local path nor https link. " + + $"Please check your path: '{fileOrUrl}' resolved to '{fullPath}'."); + } + + private static string GetUrlContents(string url) + { + using HttpClient client = new HttpClient(); + HttpResponseMessage response = + client.GetAsync(url).ConfigureAwait(false).GetAwaiter().GetResult(); + return response.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } } } diff --git a/tools/code-owners-parser/CodeOwnersParser/MatchedCodeOwnerEntry.cs b/tools/code-owners-parser/CodeOwnersParser/MatchedCodeownersEntry.cs similarity index 91% rename from tools/code-owners-parser/CodeOwnersParser/MatchedCodeOwnerEntry.cs rename to tools/code-owners-parser/CodeOwnersParser/MatchedCodeownersEntry.cs index 76f3cd297eec..703f70363826 100644 --- a/tools/code-owners-parser/CodeOwnersParser/MatchedCodeOwnerEntry.cs +++ b/tools/code-owners-parser/CodeOwnersParser/MatchedCodeownersEntry.cs @@ -11,7 +11,7 @@ namespace Azure.Sdk.Tools.CodeOwnersParser /// /// This is a new matcher, compared to the old one, located in: /// - /// CodeOwnersFile.FindOwnersForClosestMatchLegacyImpl() + /// CodeownersFile.FindOwnersForClosestMatchLegacyImpl() /// /// This new matcher supports matching against wildcards, while the old one doesn't. /// This new matcher is designed to work with CODEOWNERS file validation: @@ -25,7 +25,7 @@ namespace Azure.Sdk.Tools.CodeOwnersParser /// https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-syntax /// https://git-scm.com/docs/gitignore#_pattern_format /// - internal class MatchedCodeOwnerEntry + internal class MatchedCodeownersEntry { /// /// Token for temporarily substituting "**" in regex, to avoid it being escaped when @@ -38,7 +38,7 @@ internal class MatchedCodeOwnerEntry /// private const string SingleStar = "_SINGLE_STAR_"; - public readonly CodeOwnerEntry Value; + public readonly CodeownersEntry Value; /// /// See comment on IsCodeOwnersPathValid @@ -52,29 +52,29 @@ internal class MatchedCodeOwnerEntry /// private static readonly char[] unsupportedChars = { '[', ']', '!', '?' }; - private readonly ILogger log; + private readonly ILogger log; - public MatchedCodeOwnerEntry(List entries, string targetPath) + public MatchedCodeownersEntry(List entries, string targetPath) { this.log = CreateLog(); this.Value = FindOwnersForClosestMatch(entries, targetPath); } - private ILogger CreateLog() + private ILogger CreateLog() { var loggerFactory = LoggerFactory.Create(builder => { builder.AddSimpleConsole(); }); - return loggerFactory.CreateLogger(); + return loggerFactory.CreateLogger(); } /// - /// Returns a CodeOwnerEntry from codeOwnerEntries that matches targetPath + /// Returns a CodeownersEntry from codeOwnerEntries that matches targetPath /// per algorithm described in the GitHub CODEOWNERS reference, /// as linked to in this class comment. /// - /// If there is no match, returns "new CodeOwnerEntry()". + /// If there is no match, returns "new CodeownersEntry()". /// - private CodeOwnerEntry FindOwnersForClosestMatch( - List codeownersEntries, + private CodeownersEntry FindOwnersForClosestMatch( + List codeownersEntries, string targetPath) { // targetPath is assumed to be absolute w.r.t. repository root, hence we ensure @@ -87,7 +87,7 @@ private CodeOwnerEntry FindOwnersForClosestMatch( // so it can not match against a CODEOWNERS entry that is guaranteed to be a file, // by the virtue of not ending with "/". - CodeOwnerEntry matchedEntry = codeownersEntries + CodeownersEntry matchedEntry = codeownersEntries .Where(entry => !ContainsUnsupportedCharacters(entry.PathExpression)) // Entries listed in CODEOWNERS file below take precedence, hence we read the file from the bottom up. // By convention, entries in CODEOWNERS should be sorted top-down in the order of: @@ -99,7 +99,7 @@ private CodeOwnerEntry FindOwnersForClosestMatch( .FirstOrDefault( entry => Matches(targetPath, entry), // assert: none of the codeownersEntries matched targetPath - new CodeOwnerEntry()); + new CodeownersEntry()); return matchedEntry; } @@ -124,7 +124,7 @@ private bool ContainsUnsupportedCharacters(string codeownersPath) /// Returns true if the regex expression representing the PathExpression /// of CODEOWNERS entry matches a prefix of targetPath. /// - private bool Matches(string targetPath, CodeOwnerEntry entry) + private bool Matches(string targetPath, CodeownersEntry entry) { string codeownersPath = entry.PathExpression; @@ -226,7 +226,7 @@ private string NormalizePath(string codeownersPath) /// /// The entry is valid if it obeys following conditions: /// - The Value was obtained with a call to - /// Azure.Sdk.Tools.CodeOwnersParser.CodeOwnersFile.ParseContent(). + /// Azure.Sdk.Tools.CodeOwnersParser.CodeownersFile.GetCodeownersEntries(). /// - As a consequence, in the case of no match, the entry is not valid. /// - It does not contain unsupported characters (see "unsupportedChars"). /// - the Value.PathExpression starts with "/". diff --git a/tools/identity-resolution/Services/GitHubService.cs b/tools/identity-resolution/Services/GitHubService.cs index bbd40d23e3f9..0e4bb73ab7d8 100644 --- a/tools/identity-resolution/Services/GitHubService.cs +++ b/tools/identity-resolution/Services/GitHubService.cs @@ -16,8 +16,8 @@ public class GitHubService { private static readonly HttpClient httpClient = new HttpClient(); - private static readonly ConcurrentDictionary> - codeownersFileCache = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> + codeownersFileCache = new ConcurrentDictionary>(); private readonly ILogger logger; @@ -35,9 +35,9 @@ public GitHubService(ILogger logger) /// /// GitHub repository URL /// Contents fo the located CODEOWNERS file - public async Task> GetCodeownersFile(Uri repoUrl) + public async Task> GetCodeownersFile(Uri repoUrl) { - List result; + List result; if (codeownersFileCache.TryGetValue(repoUrl.ToString(), out result)) { return result; @@ -53,7 +53,7 @@ public async Task> GetCodeownersFile(Uri repoUrl) /// /// /// - private async Task> GetCodeownersFileImpl(Uri repoUrl) + private async Task> GetCodeownersFileImpl(Uri repoUrl) { // Gets the repo path from the URL var relevantPathParts = repoUrl.Segments.Skip(1).Take(2); @@ -64,7 +64,7 @@ private async Task> GetCodeownersFileImpl(Uri repoUrl) if (result.IsSuccessStatusCode) { logger.LogInformation("Retrieved CODEOWNERS file URL = {0}", codeOwnersUrl); - return CodeOwnersFile.ParseContent(await result.Content.ReadAsStringAsync()); + return CodeownersFile.GetCodeownersEntries(await result.Content.ReadAsStringAsync()); } logger.LogWarning("Could not retrieve CODEOWNERS file URL = {0} ResponseCode = {1}", codeOwnersUrl, result.StatusCode); diff --git a/tools/notification-configuration/notification-configuration.sln.DotSettings b/tools/notification-configuration/notification-configuration.sln.DotSettings index 1e20132c27cb..8c1c0ec65f15 100644 --- a/tools/notification-configuration/notification-configuration.sln.DotSettings +++ b/tools/notification-configuration/notification-configuration.sln.DotSettings @@ -1,3 +1,4 @@  + AAD PR True \ No newline at end of file diff --git a/tools/notification-configuration/notification-creator/NotificationConfigurator.cs b/tools/notification-configuration/notification-creator/NotificationConfigurator.cs index 386e56a3f282..adecdc32fcbd 100644 --- a/tools/notification-configuration/notification-creator/NotificationConfigurator.cs +++ b/tools/notification-configuration/notification-creator/NotificationConfigurator.cs @@ -209,8 +209,8 @@ private async Task SyncTeamWithCodeOwnerFile(BuildDefinition pipeline, WebApiTea logger.LogInformation("Searching CODEOWNERS for matching path for {0}", process.YamlFilename); - var codeOwnerEntry = CodeOwnersFile.FindOwnersForClosestMatch(codeOwnerEntries, process.YamlFilename); - codeOwnerEntry.FilterOutNonUserAliases(); + var codeOwnerEntry = CodeownersFile.GetMatchingCodeownersEntry(process.YamlFilename, codeOwnerEntries); + codeOwnerEntry.ExcludeNonUserAliases(); logger.LogInformation("Matching Contacts Path = {0}, NumContacts = {1}", process.YamlFilename, codeOwnerEntry.Owners.Count); diff --git a/tools/notification-configuration/notification-creator/YamlHelper.cs b/tools/notification-configuration/notification-creator/YamlHelper.cs index bdad08f876ca..a8bf28802da1 100644 --- a/tools/notification-configuration/notification-creator/YamlHelper.cs +++ b/tools/notification-configuration/notification-creator/YamlHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; @@ -6,10 +6,10 @@ namespace Azure.Sdk.Tools.NotificationConfiguration { static class YamlHelper { - private static ISerializer serializer = + private static readonly ISerializer serializer = new SerializerBuilder().WithNamingConvention(new CamelCaseNamingConvention()).Build(); - private static IDeserializer deserializer = + private static readonly IDeserializer deserializer = new DeserializerBuilder().WithNamingConvention(new CamelCaseNamingConvention()).Build();