diff --git a/src/Extension/Extension.csproj b/src/Extension/Extension.csproj index dd65de9..7f6fb2a 100644 --- a/src/Extension/Extension.csproj +++ b/src/Extension/Extension.csproj @@ -78,8 +78,10 @@ + + + - diff --git a/src/Extension/Helpers/SolutionHelper.cs b/src/Extension/Helpers/SolutionHelper.cs index 6dd7483..d31c8c9 100644 --- a/src/Extension/Helpers/SolutionHelper.cs +++ b/src/Extension/Helpers/SolutionHelper.cs @@ -1,4 +1,6 @@ using System.IO; +using System.Threading.Tasks; +using Community.VisualStudio.Toolkit; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; @@ -9,6 +11,29 @@ namespace Extension.Helpers /// internal static class SolutionHelper { + /// + /// Returns the solution's root dir, or in Open Folder mode, the open folder's path. + /// + /// + internal static async Task GetSolutionDir() + { + var serviceProvider = await GetServiceProvider(); + return serviceProvider == null ? null : GetSolutionDir(serviceProvider); + } + + /// + /// Returns the that is used to retrieve the solution directory. + /// + /// + internal static async Task GetServiceProvider() + { + return await ThreadHelper.JoinableTaskFactory.RunAsync(async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + return await VS.GetMefServiceAsync(); + }); + } + /// /// Returns the solution's root dir, or in Open Folder mode, the open folder's path. /// diff --git a/src/Extension/Rosie/CodigaCodeAnalysisConfig.cs b/src/Extension/Rosie/CodigaCodeAnalysisConfig.cs deleted file mode 100644 index ee3caf7..0000000 --- a/src/Extension/Rosie/CodigaCodeAnalysisConfig.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Extension.Rosie -{ - /// - /// Stores the ruleset names deserialized from the Codiga config file. - /// - /// - public class CodigaCodeAnalysisConfig - { - public List? Rulesets { get; set; } - - /// - /// Returns the ruleset names having filtered out null values from them. - ///
- /// This is for covering the case where the config file is configured like this: - /// - /// rulesets: - /// - - /// - ///
- public List? GetRulesets() - { - return Rulesets?.Where(ruleset => ruleset != null).ToList(); - } - } -} diff --git a/src/Extension/Rosie/CodigaConfigFileUtil.cs b/src/Extension/Rosie/CodigaConfigFileUtil.cs index 730e5aa..a41a427 100644 --- a/src/Extension/Rosie/CodigaConfigFileUtil.cs +++ b/src/Extension/Rosie/CodigaConfigFileUtil.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text.RegularExpressions; using Extension.Helpers; +using Extension.Rosie.Model.Codiga; using Extension.SnippetFormats; using Microsoft.VisualStudio.Shell; using YamlDotNet.Serialization; @@ -34,6 +35,8 @@ public static class CodigaConfigFileUtil private static readonly Regex CodigaRulesetNamePattern = new Regex("^[a-z0-9][a-z0-9-]{4,31}$"); private const string CodigaConfigFileName = "codiga.yml"; + private const string RULESETS = "rulesets"; + private const string IGNORE = "ignore"; /// /// Looks up the Codiga config file in the provided Solution's root directory, @@ -76,64 +79,77 @@ public static void CreateCodigaConfigFile(LanguageUtils.LanguageEnumeration lang } /// - /// Collects the list of valid ruleset names from the provided configuration object. + /// Deserializes the provided raw YAML string (the content of the Codiga config file) to a + /// object, so that ruleset names, ignore prefixes, etc. can be accessed later. + ///
+ /// It deserializes it using dynamic typing. ///
- /// The configuration containing the rulesets. - /// The list of ruleset names or empty list if there is no config or no ruleset. - public static List CollectRulesetNames(CodigaCodeAnalysisConfig? config) + /// The content of the Codiga config file + /// The deserialized config file, or in case deserialization failed. + public static CodigaCodeAnalysisConfig DeserializeConfig(string rawYamlConfig) { - return config?.Rulesets == null - ? new List() - : config.Rulesets + if (string.IsNullOrWhiteSpace(rawYamlConfig)) + return CodigaCodeAnalysisConfig.EMPTY; + + try + { + var codigaConfig = new CodigaCodeAnalysisConfig(); + var semiRawConfig = ConfigDeserializer.Deserialize(rawYamlConfig); + if (semiRawConfig is Dictionary properties) + { + SetRulesets(properties, codigaConfig); + SetIgnore(properties, codigaConfig); + } + + return codigaConfig; + } + catch + { + return CodigaCodeAnalysisConfig.EMPTY; + } + } + + /// + /// Configures the rulesets in the argument based on the deserialized config data. + /// + /// The codiga.yml file as a dictionary of string-object entries + /// The Codiga config in which rulesets are being configured + private static void SetRulesets(Dictionary semiRawConfig, CodigaCodeAnalysisConfig codigaConfig) + { + if (semiRawConfig.ContainsKey(RULESETS) && semiRawConfig[RULESETS] is List rulesetNames) + { + codigaConfig.Rulesets = rulesetNames + .OfType() //Filter out non-string value, and null and empty ruleset names .Where(name => !string.IsNullOrEmpty(name)) //Filter out invalid ruleset names .Where(IsRulesetNameValid) .ToList(); + } } /// - /// Deserializes the provided raw YAML string (the content of the Codiga config file) to a - /// object, so that ruleset names can be accessed later. - /// - /// First, it tries to deserialize directly into a CodigaCodeAnalysisConfig instance, and if that fails, - /// it tries to deserialize it using dynamic typing. This is necessary because when the config file is configured e.g. like this: - /// - /// rulesets: - /// - my-csharp-ruleset - /// - rules: - /// - some-rule - /// - my-other-ruleset - /// - /// it would fail with an exception on the rules property, and would not return the rest of the ruleset names. + /// Configures the ignores in the argument based on the deserialized config data. /// - /// The content of the Codiga config file - /// The deserialized config file, or null in case deserialization couldn't happen. - public static CodigaCodeAnalysisConfig? DeserializeConfig(string rawYamlConfig) + /// The codiga.yml file as a dictionary of string-object entries + /// The Codiga config in which ignore config is being configured + private static void SetIgnore(Dictionary semiRawConfig, CodigaCodeAnalysisConfig codigaConfig) { - if (string.IsNullOrWhiteSpace(rawYamlConfig)) - return null; - - try - { - return ConfigDeserializer.Deserialize(rawYamlConfig); - } - catch + //List of [ruleset name -> rule ignore config] mappings + if (semiRawConfig.ContainsKey(IGNORE) && semiRawConfig[IGNORE] is List rulesetIgnoreConfigs) { - var semiRawConfigFile = ConfigDeserializer.Deserialize(rawYamlConfig); - if (semiRawConfigFile is Dictionary properties - //If there is one property, and it is called 'rulesets' - && properties.Keys.Count == 1 && properties.ContainsKey("rulesets") - //If the value of 'rulesets' is a non-empty list - && properties["rulesets"] is List rulesetNames && rulesetNames.Count > 0) + foreach (var rulesetIgnoreConfig in rulesetIgnoreConfigs) { - return new CodigaCodeAnalysisConfig + //[ruleset name -> rule ignore config] mappings + if (rulesetIgnoreConfig is Dictionary rulesetIgnoreDict) { - Rulesets = rulesetNames.OfType().ToList() - }; - } + var rulesetIgnore = new RulesetIgnore( + rulesetIgnoreDict.Keys.FirstOrDefault() as string, + rulesetIgnoreDict.Values.FirstOrDefault()); - return null; + codigaConfig.Ignore.Add(rulesetIgnore.RulesetName, rulesetIgnore); + } + } } } diff --git a/src/Extension/Rosie/CodigaDefaultRulesetsInfoBarHelper.cs b/src/Extension/Rosie/CodigaDefaultRulesetsInfoBarHelper.cs index 485f2ac..5ab2b6c 100644 --- a/src/Extension/Rosie/CodigaDefaultRulesetsInfoBarHelper.cs +++ b/src/Extension/Rosie/CodigaDefaultRulesetsInfoBarHelper.cs @@ -65,11 +65,7 @@ internal class InfoBarHolder /// internal static async void ShowDefaultRulesetCreationInfoBarAsync(InfoBarHolder infoBarHolder) { - var serviceProvider = await ThreadHelper.JoinableTaskFactory.RunAsync(async () => - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - return await VS.GetMefServiceAsync(); - }); + var serviceProvider = await SolutionHelper.GetServiceProvider(); if (SolutionSettings.IsShouldNotifyUserToCreateCodigaConfig(serviceProvider) && CodigaConfigFileUtil.FindCodigaConfigFile(serviceProvider) == null) diff --git a/src/Extension/Rosie/Model/Codiga/CodigaCodeAnalysisConfig.cs b/src/Extension/Rosie/Model/Codiga/CodigaCodeAnalysisConfig.cs new file mode 100644 index 0000000..4ed701f --- /dev/null +++ b/src/Extension/Rosie/Model/Codiga/CodigaCodeAnalysisConfig.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; + +namespace Extension.Rosie.Model.Codiga +{ + /// + /// Represents a codiga.yml configuration file. + /// + /// + public class CodigaCodeAnalysisConfig + { + public static readonly CodigaCodeAnalysisConfig EMPTY = new CodigaCodeAnalysisConfig(); + + private List? _rulesets; + + public List Rulesets + { + get => _rulesets ?? new List(); + set => _rulesets = value; + } + + /// + /// Stores [ruleset name -> ruleset ignore configuration] mappings. + ///
+ /// Using a map instead of a List<RulesetIgnore>, so that we can query the ruleset + /// configs by name, without having to filter the list by the ruleset name. + ///
+ public IDictionary Ignore { get; } = new Dictionary(); + } +} \ No newline at end of file diff --git a/src/Extension/Rosie/Model/Codiga/RuleIgnore.cs b/src/Extension/Rosie/Model/Codiga/RuleIgnore.cs new file mode 100644 index 0000000..5898474 --- /dev/null +++ b/src/Extension/Rosie/Model/Codiga/RuleIgnore.cs @@ -0,0 +1,97 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Extension.Rosie.Model.Codiga +{ + /// + /// Represents a rule ignore configuration element in the codiga.yml file. + ///
+ /// This is the element right under a ruleset name property, e.g.: + /// + /// - rule1: + /// - prefix: /path/to/file/to/ignore + /// + /// or + /// + /// - rule2: + /// - prefix: + /// - /path1 + /// - /path2 + /// + ///
+ public class RuleIgnore + { + public string RuleName { get; } + + /// + /// The list of prefix values under the prefix property. + ///
+ /// In case multiple prefix properties are defined under the same rule config, + /// they are all added to this list. + ///
+ /// For example, in case of: + /// + /// ignore: + /// - my-python-ruleset: + /// - rule1: + /// - prefix: + /// - /path1 + /// - /path2 + /// - prefix: /path3 + /// + /// all of /path1, /path2 and /path3 are stored here. + ///
+ /// In case a prefix property contains the same value multiple times, + /// they are deduplicated and only once instance is stored, for example: + /// + /// ignore: + /// - my-python-ruleset: + /// - rule1: + /// - prefix: + /// - /path1 + /// - /path1 + /// + ///
+ public List Prefixes { get; } = new List(); + + public RuleIgnore(string ruleName) + { + RuleName = ruleName; + } + + public RuleIgnore(string ruleName, object ruleIgnore) + { + RuleName = ruleName; + if (ruleIgnore is List prefixIgnores) + { + foreach (var prefixIgnore in prefixIgnores) + { + if (prefixIgnore is Dictionary prefixIgnoreDict) + { + var prefixIgnoreValue = prefixIgnoreDict.Values.FirstOrDefault(); + /* + A 'prefix' property can have a single String value: + - prefix: /path/to/file/to/ignore + */ + if (prefixIgnoreValue is string value) + Prefixes = new List { value }; + + /* + A 'prefix' property can also have multiple String values as a list: + - prefix: + - /path1 + - /path2 + */ + else if (prefixIgnoreValue is List prefixes) + //It filters out null and non-String prefix values + Prefixes = prefixes + .Where(prefix => prefix != null) + .OfType() + .Distinct() + .ToList(); + } + } + } + } + } +} diff --git a/src/Extension/Rosie/Model/Codiga/RulesetIgnore.cs b/src/Extension/Rosie/Model/Codiga/RulesetIgnore.cs new file mode 100644 index 0000000..2fe9e6c --- /dev/null +++ b/src/Extension/Rosie/Model/Codiga/RulesetIgnore.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Extension.Rosie.Model.Codiga +{ + /// + /// Represents a ruleset ignore configuration element in the codiga.yml file. + ///
+ /// This is the element right under the root-level ignore property, e.g.: + /// + /// - my-python-ruleset: + /// - rule1: + /// - prefix: /path/to/file/to/ignore + /// + ///
+ public class RulesetIgnore + { + public string RulesetName { get; } + + /// + /// Stores [rule name -> rule ignore configuration] mappings. + ///
+ /// Using a map instead of a List<RuleIgnore>, so that we can query the ruleset + /// configs by name, without having to filter the list by the ruleset name. + ///
+ public IDictionary RuleIgnores { get; } = new Dictionary(); + + /// + /// Saves the ruleset name and the rule ignore configuration from its value. + /// + /// the ruleset name + /// the value associated to the ruleset name property in codiga.yml + public RulesetIgnore(string rulesetName, object ruleIgnoresConfig) + { + RulesetName = rulesetName; + + if (ruleIgnoresConfig is List ruleIgnores) + { + foreach (var ruleIgnore in ruleIgnores) + { + var ruleIgn = ruleIgnore switch + { + /* + A rule ignore config can be a single rule name without any prefix value: + - rulename + */ + string ruleName => new RuleIgnore(ruleName), + + /* + A rule ignore config can be a Map of the rule name and its object value, + with one or more prefix values: + - rulename: + - prefix: /path/to/file/to/ignore + as a {[rulename -> prefix: /path/to/file/to/ignore]} map + + - rulename2: + - prefix: + - /path1 + - /path2 + as a {[rulename2 -> prefix: /path1, /path2]} map + */ + Dictionary ruleIgnoreDict => + new RuleIgnore( + ruleIgnoreDict.Keys.FirstOrDefault() as string, + ruleIgnoreDict.Values.FirstOrDefault()), + _ => null + }; + + if (ruleIgn != null) + RuleIgnores.Add(ruleIgn.RuleName, ruleIgn); + } + } + } + } +} \ No newline at end of file diff --git a/src/Extension/Rosie/Model/RosieRule.cs b/src/Extension/Rosie/Model/RosieRule.cs index 889e53a..eb1ecf3 100644 --- a/src/Extension/Rosie/Model/RosieRule.cs +++ b/src/Extension/Rosie/Model/RosieRule.cs @@ -4,6 +4,8 @@ namespace Extension.Rosie.Model { public class RosieRule { + public string RulesetName { get; } + public string RuleName { get; } public string Id { get; } public string ContentBase64 { get; } public string Language { get; } @@ -12,6 +14,8 @@ public class RosieRule public string Pattern { get; } public RosieRule(string rulesetName, Rule rule) { + RulesetName = rulesetName; + RuleName = rule.Name; Id = rulesetName + "/" + rule.Name; ContentBase64 = rule.Content; Language = rule.Language; diff --git a/src/Extension/Rosie/RosieClient.cs b/src/Extension/Rosie/RosieClient.cs index 62fda5f..c2ede14 100644 --- a/src/Extension/Rosie/RosieClient.cs +++ b/src/Extension/Rosie/RosieClient.cs @@ -93,7 +93,7 @@ public RosieClient(Func fileNameProvider, Func internal DateTime ConfigFileLastWriteTime { get; set; } = DateTime.MinValue; - /// - /// Ruleset names stored locally in the codiga.yml config file. - /// - public IList RulesetNames { get; set; } + public CodigaCodeAnalysisConfig CodigaConfig { get; set; } /// /// The cache is considered initialized with rules right after the response is received from , @@ -88,11 +88,14 @@ private RosieRulesCache(ICodigaClientProvider clientProvider) { _clientProvider = clientProvider; _cachedRules = new ConcurrentDictionary(); - RulesetNames = new SynchronizedCollection(); + CodigaConfig = CodigaCodeAnalysisConfig.EMPTY; } public static void Initialize(bool startPolling = true) { + TextWriterTraceListener tr1 = new TextWriterTraceListener(Console.Out); + Debug.Listeners.Add(tr1); + Instance = new RosieRulesCache(); if (startPolling) Instance.StartPolling(); @@ -196,22 +199,23 @@ private async Task UpdateCacheFromModifiedCodigaConfigFileAsync(string codigaCon { ConfigFileLastWriteTime = File.GetLastWriteTime(codigaConfigFile); var rawCodigaConfig = File.ReadAllText(codigaConfigFile); - var rulesetNames = CodigaConfigFileUtil.DeserializeConfig(rawCodigaConfig)?.GetRulesets(); + var codigaConfig = CodigaConfigFileUtil.DeserializeConfig(rawCodigaConfig); //If the config file is not configured properly, we clear the cache - if (rulesetNames == null) + if (codigaConfig.Rulesets.Count == 0) { ClearCache(); return; } - RulesetNames = rulesetNames; + CodigaConfig = codigaConfig; //If there is at least on ruleset name, we can make a request with them - if (RulesetNames.Count > 0) + if (CodigaConfig.Rulesets.Count > 0) { try { - var rulesetsForClient = await client.GetRulesetsForClientAsync(rulesetNames); + Debug.WriteLine($"Fetching rulesets last updated timestamp at {DateTime.Now}"); + var rulesetsForClient = await client.GetRulesetsForClientAsync(codigaConfig.Rulesets); IsInitializedWithRules = true; if (rulesetsForClient == null) return; @@ -233,7 +237,8 @@ due to an issue in how the Codiga server collects the rules. codiga.yml was updated locally with a non-existent ruleset, or a ruleset that has an earlier timestamp than the latest updated one, so the rulesets configured don't result in an updated timestamp from the server. */ - long timestampFromServer = await client.GetRulesetsLastUpdatedTimestampAsync(rulesetNames); + Debug.WriteLine($"Fetching rulesets last updated timestamp at {DateTime.Now}"); + long timestampFromServer = await client.GetRulesetsLastUpdatedTimestampAsync(codigaConfig.Rulesets); if (timestampFromServer != RulesetslastUpdatedTimeStamp) RulesetslastUpdatedTimeStamp = timestampFromServer; @@ -257,18 +262,20 @@ due to an issue in how the Codiga server collects the rules. /// private async Task UpdateCacheFromChangesOnServerAsync(ICodigaClient client) { - if (RulesetNames.Count == 0) + if (CodigaConfig.Rulesets.Count == 0) return; try { //Retrieve the last updated timestamp for the rulesets - var timestampFromServer = await client.GetRulesetsLastUpdatedTimestampAsync(RulesetNames.ToImmutableList()); + Debug.WriteLine($"Fetching rulesets last updated timestamp at {DateTime.Now}"); + var timestampFromServer = await client.GetRulesetsLastUpdatedTimestampAsync(CodigaConfig.Rulesets.ToImmutableList()); IsInitializedWithRules = true; //If there was a change on the server, we can get and cache the rulesets if (RulesetslastUpdatedTimeStamp != timestampFromServer) { - var rulesetsForClient = await client.GetRulesetsForClientAsync(RulesetNames.ToImmutableList()); + Debug.WriteLine($"Fetching rulesets at {DateTime.Now}"); + var rulesetsForClient = await client.GetRulesetsForClientAsync(CodigaConfig.Rulesets.ToImmutableList()); if (rulesetsForClient == null) return; @@ -338,21 +345,70 @@ private async Task NotifyActiveDocumentForTagUpdateAsync() #region Get rules /// - /// Returns the s for the provided language. + /// Returns the list of s for the argument language and file path, + /// that will be sent to the Rosie service for analysis. /// - /// The language to get the rules for. + /// The language to get the rules for + /// the absolute path of the file being analyzed. + /// Required to pass in for the ignore configuration. + /// The solution root directory. + /// Null only in case of production code, so we can retrieve the proper root directory. /// The rules for the given language. - public IReadOnlyList GetRosieRulesForLanguage(LanguageEnumeration language) + public async Task> GetRosieRules(LanguageEnumeration language, string pathOfAnalyzedFile, string? solutionDirectory = null) { + var solutionDir = solutionDirectory ?? await SolutionHelper.GetSolutionDir(); + if (solutionDir == null) + return NoRule; + var cachedLanguageType = GetCachedLanguageTypeOf(language); if (_cachedRules.ContainsKey(cachedLanguageType)) { var cachedRules = _cachedRules[cachedLanguageType]; - return cachedRules != null ? cachedRules.RosieRules : NoRule; + var rosieRulesForLanguage = cachedRules != null ? cachedRules.RosieRules : NoRule; + + if (rosieRulesForLanguage.Count > 0) + { + //Replaces backslash '\' symbols with forward slashes '/', so that in case of Windows specific paths, + // we still can compare the relative paths properly. + string relativePathOfAnalyzedFile = pathOfAnalyzedFile.Replace(solutionDir, "").Replace("\\", "/"); + + //Returns the RosieRules that either don't have an ignore rule, or their prefixes don't match the currently analyzed file's path + return rosieRulesForLanguage + .Where(rosieRule => + { + //If there is no ruleset ignore or rule ignore for the current RosieRule, then we keep it/don't ignore it. + if (!CodigaConfig.Ignore.ContainsKey(rosieRule.RulesetName) + || !CodigaConfig.Ignore[rosieRule.RulesetName].RuleIgnores + .ContainsKey(rosieRule.RuleName)) + return true; + + var ruleIgnore = CodigaConfig.Ignore[rosieRule.RulesetName].RuleIgnores[rosieRule.RuleName]; + + //If there is no prefix specified for the current rule ignore config, + // we don't keep the rule/ignore it. + if (ruleIgnore.Prefixes.Count == 0) + return false; + + return ruleIgnore.Prefixes + //Since the leading / is optional, we remove it + .Select(RemoveLeadingSlash) + //./, /. and .. sequences are not allowed in prefixes, therefore we consider them not matching the file path. + //. symbols in general are allowed to be able to target exact file paths with their file extensions. + .All(prefix => + prefix.Contains("..") + || prefix.Contains("./") + || prefix.Contains("/.") + || !RemoveLeadingSlash(relativePathOfAnalyzedFile).StartsWith(prefix)); + }).ToList(); + } } return NoRule; } + private static string RemoveLeadingSlash(string path) { + return path.StartsWith("/") ? path.Substring(1) : path; + } + /// /// Since, besides JavaScript files, rules for TypeScript files are also handled under the same JavaScript Rosie language /// type, we have to return JavaScript rules for TypeScript files as well. @@ -365,7 +421,7 @@ private static LanguageEnumeration GetCachedLanguageTypeOf(LanguageEnumeration f /// /// Returns the cached rules for the provided language and rule id. ///
- /// Null value for non-existent mapping for a language is already handled in . + /// Null value for non-existent mapping for a language is already handled in . ///
/// It should not return null when retrieving the rule for the rule id, since in RosieApiImpl#GetAnnotations() /// the s and their ids are based on the values cached here. @@ -386,8 +442,7 @@ private void ClearCache() { if (_cachedRules.Count > 0) _cachedRules.Clear(); - if (RulesetNames.Count > 0) - RulesetNames.Clear(); + CodigaConfig = CodigaCodeAnalysisConfig.EMPTY; RulesetslastUpdatedTimeStamp = -1L; } diff --git a/src/Extension/source.extension.cs b/src/Extension/source.extension.cs index f6c83ab..769f634 100644 --- a/src/Extension/source.extension.cs +++ b/src/Extension/source.extension.cs @@ -11,7 +11,7 @@ internal sealed partial class Vsix public const string Name = "Codiga"; public const string Description = @"Code Snippets for 15+ languages and Code Analysis with Custom Rules."; public const string Language = "en-US"; - public const string Version = "0.0.14"; + public const string Version = "0.0.15"; public const string Author = "Codiga"; public const string Tags = ""; } diff --git a/src/Extension/source.extension.vsixmanifest b/src/Extension/source.extension.vsixmanifest index 90b67b1..e2e6802 100644 --- a/src/Extension/source.extension.vsixmanifest +++ b/src/Extension/source.extension.vsixmanifest @@ -1,7 +1,7 @@  - + Codiga Code Snippets for 15+ languages and Code Analysis with Custom Rules. https://www.codiga.io/ diff --git a/src/Tests/Rosie/CodigaConfigFileUtilTest.cs b/src/Tests/Rosie/CodigaConfigFileUtilTest.cs index 735350d..71b5256 100644 --- a/src/Tests/Rosie/CodigaConfigFileUtilTest.cs +++ b/src/Tests/Rosie/CodigaConfigFileUtilTest.cs @@ -1,5 +1,6 @@ using System.IO; using Extension.Rosie; +using Extension.Rosie.Model.Codiga; using Extension.SnippetFormats; using NUnit.Framework; using static Tests.ServiceProviderMockSupport; @@ -14,38 +15,52 @@ internal class CodigaConfigFileUtilTest { private string _codigaConfigFile; - #region DeserializeConfig negative cases + #region DeserializeConfig [Test] - public void DeserializeConfig_should_return_null_for_null_raw_config() + public void DeserializeConfig_should_return_empty_config_for_null_raw_config() { var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(null); - Assert.That(codigaConfigFile, Is.Null); + Assert.That(codigaConfigFile, Is.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); } [Test] - public void DeserializeConfig_should_return_null_for_empty_raw_config() + public void DeserializeConfig_should_return_empty_config_for_empty_raw_config() { var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(""); - Assert.That(codigaConfigFile, Is.Null); + Assert.That(codigaConfigFile, Is.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); } [Test] - public void DeserializeConfig_should_return_null_for_whitespace_only_raw_config() + public void DeserializeConfig_should_return_empty_config_for_whitespace_only_raw_config() { var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(" "); - Assert.That(codigaConfigFile, Is.Null); + Assert.That(codigaConfigFile, Is.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); } + + [Test] + public void DeserializeConfig_should_return_empty_config_for_malformed_rulesets_list() + { + var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" +rulesets: +- my-csharp-ruleset +- rules: + - some-rule +- my-other-ruleset"); - #endregion - - #region CollectRulesetNames positive cases + Assert.That(codigaConfigFile, Is.Not.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); + var rulesetNames = codigaConfigFile.Rulesets; + Assert.That(rulesetNames, Has.Count.EqualTo(2)); + Assert.That(rulesetNames, Contains.Item("my-csharp-ruleset")); + Assert.That(rulesetNames, Contains.Item("my-other-ruleset")); + } + [Test] - public void CollectRulesetNames_should_return_non_empty_ruleset_names() + public void DeserializeConfig_should_return_non_empty_ruleset_names() { var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" rulesets: @@ -54,7 +69,7 @@ public void CollectRulesetNames_should_return_non_empty_ruleset_names() - an_Inv@lid-name "); - var rulesetNames = CodigaConfigFileUtil.CollectRulesetNames(codigaConfigFile); + var rulesetNames = codigaConfigFile.Rulesets; Assert.That(rulesetNames, Has.Count.EqualTo(2)); Assert.That(rulesetNames, Contains.Item("my-csharp-ruleset")); @@ -62,20 +77,20 @@ public void CollectRulesetNames_should_return_non_empty_ruleset_names() } [Test] - public void CollectRulesetNames_should_return_no_ruleset_name_for_empty_rulesets_list() + public void DeserializeConfig_should_return_no_ruleset_name_for_empty_rulesets_list() { var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" rulesets: - "); - var rulesetNames = CodigaConfigFileUtil.CollectRulesetNames(codigaConfigFile); + var rulesetNames = codigaConfigFile.Rulesets; Assert.That(rulesetNames, Is.Empty); } [Test] - public void CollectRulesetNames_should_return_filtered_ruleset_names_when_there_is_empty_list_item() + public void DeserializeConfig_should_return_filtered_ruleset_names_when_there_is_empty_list_item() { var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" rulesets: @@ -85,7 +100,7 @@ public void CollectRulesetNames_should_return_filtered_ruleset_names_when_there_ - some-ruleset "); - var rulesetNames = CodigaConfigFileUtil.CollectRulesetNames(codigaConfigFile); + var rulesetNames = codigaConfigFile.Rulesets; Assert.That(rulesetNames, Has.Count.EqualTo(3)); Assert.That(rulesetNames, Contains.Item("my-csharp-ruleset")); @@ -94,7 +109,7 @@ public void CollectRulesetNames_should_return_filtered_ruleset_names_when_there_ } [Test] - public void CollectRulesetNames_should_return_filtered_ruleset_names_when_there_is_non_plain_text_list_item() + public void DeserializeConfig_should_return_filtered_ruleset_names_when_there_is_non_plain_text_list_item() { var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" rulesets: @@ -104,57 +119,370 @@ public void CollectRulesetNames_should_return_filtered_ruleset_names_when_there_ - my-other-ruleset "); - var rulesetNames = CodigaConfigFileUtil.CollectRulesetNames(codigaConfigFile); + var rulesetNames = codigaConfigFile.Rulesets; Assert.That(rulesetNames, Has.Count.EqualTo(2)); Assert.That(rulesetNames, Contains.Item("my-csharp-ruleset")); Assert.That(rulesetNames, Contains.Item("my-other-ruleset")); } - #endregion - - #region CollectRulesetNames negative cases - [Test] - public void CollectRulesetNames_should_return_no_ruleset_name_for_missing_ruleset_property() + public void DeserializeConfig_should_return_no_ruleset_name_for_missing_ruleset_property() { var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" not-rulesets: - my-csharp-ruleset - my-other-ruleset"); - var rulesetNames = CodigaConfigFileUtil.CollectRulesetNames(codigaConfigFile); + var rulesetNames = codigaConfigFile.Rulesets; Assert.That(rulesetNames, Is.Empty); } [Test] - public void CollectRulesetNames_should_return_no_ruleset_name_for_non_sequence_empty_rulesets_list() + public void DeserializeConfig_should_return_no_ruleset_name_for_non_sequence_empty_rulesets_list() { var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" rulesets: "); - var rulesetNames = CodigaConfigFileUtil.CollectRulesetNames(codigaConfigFile); + var rulesetNames = codigaConfigFile.Rulesets; Assert.That(rulesetNames, Is.Empty); } [Test] - public void CollectRulesetNames_should_return_no_ruleset_name_for_non_sequence_rulesets_property() + public void DeserializeConfig_should_return_no_ruleset_name_for_non_sequence_rulesets_property() { var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" rulesets: rules:"); - var rulesetNames = CodigaConfigFileUtil.CollectRulesetNames(codigaConfigFile); + var rulesetNames = codigaConfigFile.Rulesets; Assert.That(rulesetNames, Is.Empty); } - #endregion + [Test] + public void DeserializeConfig_should_return_empty_ignore_config_for_non_property_ignore() { + var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" +rulesets: + - my-python-ruleset + - my-other-ruleset +ignore"); + + Assert.That(codigaConfigFile, Is.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); + } + + [Test] + public void DeserializeConfig_should_return_ignore_config_for_no_ignore_item() { + var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" +rulesets: + - my-python-ruleset + - my-other-ruleset +ignore:"); + + Assert.That(codigaConfigFile, Is.Not.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); + Assert.That(codigaConfigFile.Ignore, Is.Empty); + } + + [Test] + public void DeserializeConfig_should_return_ignore_config_for_blank_ignore_item() { + var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" +rulesets: + - my-python-ruleset + - my-other-ruleset +ignore: + - "); + + Assert.That(codigaConfigFile, Is.Not.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); + Assert.That(codigaConfigFile.Ignore, Is.Empty); + } + + [Test] + public void DeserializeConfig_should_return_empty_ignore_config_for_string_ignore_item() { + var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" +rulesets: + - my-python-ruleset + - my-other-ruleset +ignore: + - my-python-ruleset"); + + Assert.That(codigaConfigFile, Is.Not.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); + Assert.That(codigaConfigFile.Ignore, Is.Empty); + } + + [Test] + public void DeserializeConfig_should_return_ignore_config_for_empty_ruleset_name_ignore_property() { + var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" +rulesets: + - my-python-ruleset + - my-other-ruleset +ignore: + - my-python-ruleset:"); + + Assert.That(codigaConfigFile, Is.Not.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); + Assert.That(codigaConfigFile.Ignore, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores, Is.Empty); + } + + [Test] + public void DeserializeConfig_should_return_ignore_config_for_string_rule_name_ignore_property() { + var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" +rulesets: + - my-python-ruleset + - my-other-ruleset +ignore: + - my-python-ruleset: + - rule1"); + + Assert.That(codigaConfigFile, Is.Not.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); + Assert.That(codigaConfigFile.Ignore, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes, Is.Empty); + } + + [Test] + public void DeserializeConfig_should_return_ignore_config_for_empty_rule_name_ignore_property() { + var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" +rulesets: + - my-python-ruleset + - my-other-ruleset +ignore: + - my-python-ruleset: + - rule1:"); + + Assert.That(codigaConfigFile, Is.Not.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); + Assert.That(codigaConfigFile.Ignore, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes, Is.Empty); + } + + [Test] + public void DeserializeConfig_should_return_ignore_config_for_string_prefix_ignore_property() { + var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" +rulesets: + - my-python-ruleset + - my-other-ruleset +ignore: + - my-python-ruleset: + - rule1: + - prefix"); + + Assert.That(codigaConfigFile, Is.Not.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); + Assert.That(codigaConfigFile.Ignore, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes, Is.Empty); + } + + [Test] + public void DeserializeConfig_should_return_ignore_config_for_empty_prefix_ignore_property() { + var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" +rulesets: + - my-python-ruleset + - my-other-ruleset +ignore: + - my-python-ruleset: + - rule1: + - prefix:"); + + Assert.That(codigaConfigFile, Is.Not.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); + Assert.That(codigaConfigFile.Ignore, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes, Is.Empty); + } + + [Test] + public void DeserializeConfig_should_return_ignore_config_for_blank_prefix() { + var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" +rulesets: + - my-python-ruleset + - my-other-ruleset +ignore: + - my-python-ruleset: + - rule1: + - prefix: "); + + Assert.That(codigaConfigFile, Is.Not.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); + Assert.That(codigaConfigFile.Ignore, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes, Is.Empty); + } + + [Test] + public void DeserializeConfig_should_return_ignore_config_for_single_prefix() { + var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" +rulesets: + - my-python-ruleset + - my-other-ruleset +ignore: + - my-python-ruleset: + - rule1: + - prefix: /path/to/file/to/ignore"); + + Assert.That(codigaConfigFile, Is.Not.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); + Assert.That(codigaConfigFile.Ignore, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes[0], Is.EqualTo("/path/to/file/to/ignore")); + } + + [Test] + public void DeserializeConfig_should_return_ignore_config_for_single_prefix_as_list() { + var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" +rulesets: + - my-python-ruleset + - my-other-ruleset +ignore: + - my-python-ruleset: + - rule1: + - prefix: + - /path/to/file/to/ignore"); + + Assert.That(codigaConfigFile, Is.Not.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); + Assert.That(codigaConfigFile.Ignore, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes[0], Is.EqualTo("/path/to/file/to/ignore")); + } + + [Test] + public void DeserializeConfig_should_return_ignore_config_for_multiple_prefixes_as_list() { + var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" +rulesets: + - my-python-ruleset + - my-other-ruleset +ignore: + - my-python-ruleset: + - rule1: + - prefix: + - /path1 + - /path2"); + + Assert.That(codigaConfigFile, Is.Not.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); + Assert.That(codigaConfigFile.Ignore, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes, Has.Count.EqualTo(2)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes[0], Is.EqualTo("/path1")); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes[1], Is.EqualTo("/path2")); + } + + [Test] + public void DeserializeConfig_should_return_ignore_config_for_multiple_rule_ignores() { + var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" +rulesets: + - my-python-ruleset + - my-other-ruleset +ignore: + - my-python-ruleset: + - rule1: + - prefix: + - /path1 + - /path2 + - rule2"); + + Assert.That(codigaConfigFile, Is.Not.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); + Assert.That(codigaConfigFile.Ignore, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores, Has.Count.EqualTo(2)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes, Has.Count.EqualTo(2)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes[0], Is.EqualTo("/path1")); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes[1], Is.EqualTo("/path2")); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule2"].Prefixes, Is.Empty); + } + + [Test] + public void DeserializeConfig_should_return_ignore_config_without_rulesets() { + var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" +ignore: + - my-python-ruleset: + - rule1: + - prefix: + - /path1 + - /path2"); + + Assert.That(codigaConfigFile, Is.Not.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); + Assert.That(codigaConfigFile.Ignore, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes, Has.Count.EqualTo(2)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes[0], Is.EqualTo("/path1")); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes[1], Is.EqualTo("/path2")); + } + + [Test] + public void DeserializeConfig_should_return_ignore_config_for_duplicate_prefix_properties() { + var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" +rulesets: + - my-python-ruleset + - my-other-ruleset +ignore: + - my-python-ruleset: + - rule1: + - prefix: + - /path1 + - /path2 + - prefix: /path3"); + + Assert.That(codigaConfigFile, Is.Not.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); + Assert.That(codigaConfigFile.Ignore, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes[0], Is.EqualTo("/path3")); + } + + [Test] + public void DeserializeConfig_should_return_ignore_config_for_duplicate_prefix_values() { + var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" +rulesets: + - my-python-ruleset + - my-other-ruleset +ignore: + - my-python-ruleset: + - rule1: + - prefix: + - /path1 + - /path1"); + + Assert.That(codigaConfigFile, Is.Not.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); + Assert.That(codigaConfigFile.Ignore, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes, Has.Count.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes[0], Is.EqualTo("/path1")); + } + + [Test] + public void DeserializeConfig_should_return_ignore_config_for_multiple_ruleset_ignores() { + var codigaConfigFile = CodigaConfigFileUtil.DeserializeConfig(@" +rulesets: + - my-python-ruleset + - my-other-ruleset +ignore: + - my-python-ruleset: + - rule1: + - prefix: + - /path1 + - /path2 + - rule2 + - my-other-ruleset: + - rule3: + - prefix: /another/path"); + + Assert.That(codigaConfigFile, Is.Not.EqualTo(CodigaCodeAnalysisConfig.EMPTY)); + Assert.That(codigaConfigFile.Ignore.Count, Is.EqualTo(2)); + + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores.Count, Is.EqualTo(2)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes.Count, Is.EqualTo(2)); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes[0], Is.EqualTo("/path1")); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule1"].Prefixes[1], Is.EqualTo("/path2")); + Assert.That(codigaConfigFile.Ignore["my-python-ruleset"].RuleIgnores["rule2"].Prefixes, Is.Empty); + + Assert.That(codigaConfigFile.Ignore["my-other-ruleset"].RuleIgnores.Count, Is.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-other-ruleset"].RuleIgnores["rule3"].Prefixes.Count, Is.EqualTo(1)); + Assert.That(codigaConfigFile.Ignore["my-other-ruleset"].RuleIgnores["rule3"].Prefixes[0], Is.EqualTo("/another/path")); + } + + #endregion - #region FindCodigaConfigFile + #region FindCodigaConfigFile [Test] public void FindCodigaConfigFile_should_return_null_for_missing_solution_root() diff --git a/src/Tests/Rosie/RosieRulesCacheTest.cs b/src/Tests/Rosie/RosieRulesCacheTest.cs index 6f1c1db..172233c 100644 --- a/src/Tests/Rosie/RosieRulesCacheTest.cs +++ b/src/Tests/Rosie/RosieRulesCacheTest.cs @@ -1,15 +1,18 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Threading.Tasks; using EnvDTE; using Extension.Caching; using Extension.Rosie; -using Extension.SnippetFormats; +using Extension.Rosie.Model; +using Extension.Rosie.Model.Codiga; using GraphQLClient; using Moq; using NUnit.Framework; +using LanguageEnumeration = Extension.SnippetFormats.LanguageUtils.LanguageEnumeration; namespace Tests.Rosie { @@ -26,6 +29,8 @@ internal class RosieRulesCacheTest private string? _codigaConfigFile; private ICodigaClientProvider _clientProvider; private RosieRulesCache? _cache; + private string? _pythonFile; + private string? _javaScriptFile; /// /// Initializes the test with a Solution directory with a mock Solution, @@ -41,6 +46,8 @@ public void Setup() _solutionDirPath = Path.GetTempPath(); _codigaConfigFile = $"{_solutionDirPath}codiga.yml"; + _pythonFile = $"{_solutionDirPath}python_file.py"; + _javaScriptFile = $"{_solutionDirPath}javascript_file.js"; _solution = new Mock(); //Necessary to mock for CodigaConfigFileUtil.FindCodigaConfigFile() @@ -65,7 +72,7 @@ public async Task HandleCacheUpdateAsync_should_clear_cache_and_return_no_config InitConfigAndCache(@" rulesets: - - singleRulesetSingleLanguage"); + - single-ruleset-single-language"); Assert.That(RosieRulesCache.IsInitializedWithRules, Is.False); @@ -78,11 +85,13 @@ public async Task HandleCacheUpdateAsync_should_clear_cache_and_return_no_config var updateResult = await _cache.HandleCacheUpdateAsync(); Assert.AreEqual(updateResult, RosieRulesCache.UpdateResult.NoConfigFile); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Python, _pythonFile, _solutionDirPath); + Assert.Multiple(() => { - Assert.That(_cache.RulesetNames, Is.Empty); - Assert.That(_cache.GetRosieRulesForLanguage(LanguageUtils.LanguageEnumeration.Python), - Is.Empty); + Assert.That(_cache.CodigaConfig.Rulesets, Is.Empty); + Assert.That(rules, Is.Empty); Assert.That(RosieRulesCache.IsInitializedWithRules, Is.True); }); } @@ -93,14 +102,16 @@ public async Task HandleCacheUpdateAsync_should_populate_empty_cache_from_codiga { InitConfigAndCache(@" rulesets: - - singleRulesetSingleLanguage"); + - single-ruleset-single-language"); var updateResult = await _cache.HandleCacheUpdateAsync(); Assert.AreEqual(updateResult, RosieRulesCache.UpdateResult.Success); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Python, _pythonFile, _solutionDirPath); + Assert.Multiple(() => { - var rules = _cache.GetRosieRulesForLanguage(LanguageUtils.LanguageEnumeration.Python); Assert.That(rules[0].Id, Is.EqualTo($"python-ruleset/{RulesetsForClientTestSupport.PythonRule1.Name}")); Assert.That(rules[1].Id, Is.EqualTo($"python-ruleset/{RulesetsForClientTestSupport.PythonRule2.Name}")); Assert.That(rules[2].Id, Is.EqualTo($"python-ruleset/{RulesetsForClientTestSupport.PythonRule3.Name}")); @@ -113,11 +124,13 @@ public async Task HandleCacheUpdateAsync_should_update_non_empty_cache_from_codi { InitConfigAndCache(@" rulesets: - - singleRulesetSingleLanguage"); + - single-ruleset-single-language"); await _cache.HandleCacheUpdateAsync(); - var rules = _cache.GetRosieRulesForLanguage(LanguageUtils.LanguageEnumeration.Python); + var rules = await _cache.GetRosieRules(LanguageEnumeration.Python, _pythonFile, _solutionDirPath); + + Assert.That(rules, Has.Count.EqualTo(3)); Assert.Multiple(() => { Assert.That(rules[0].Id, Is.EqualTo($"python-ruleset/{RulesetsForClientTestSupport.PythonRule1.Name}")); @@ -127,15 +140,17 @@ public async Task HandleCacheUpdateAsync_should_update_non_empty_cache_from_codi await UpdateCodigaConfig(@" rulesets: - - multipleRulesetsSingleLanguage"); + - multi-rulesets-single-language"); var updateResult = await _cache.HandleCacheUpdateAsync(); Assert.AreEqual(updateResult, RosieRulesCache.UpdateResult.Success); + Assert.That(rules, Has.Count.EqualTo(3)); + + var rulesMulti = await _cache.GetRosieRules(LanguageEnumeration.Python, _pythonFile, _solutionDirPath); Assert.Multiple(() => { - var rulesMulti = _cache.GetRosieRulesForLanguage(LanguageUtils.LanguageEnumeration.Python); Assert.That(rulesMulti[0].Id, Is.EqualTo($"python-ruleset/{RulesetsForClientTestSupport.PythonRule1.Name}")); Assert.That(rulesMulti[1].Id, @@ -151,20 +166,23 @@ public async Task HandleCacheUpdateAsync_should_update_non_empty_cache_from_serv { InitConfigAndCache(@" rulesets: - - singleRulesetSingleLanguage"); + - single-ruleset-single-language"); _cache.UpdateCacheFrom(RulesetsForClientTestSupport.SingleRulesetMultipleLanguages()); _cache.RulesetslastUpdatedTimeStamp = 102L; _cache.ConfigFileLastWriteTime = File.GetLastWriteTime(_codigaConfigFile); - _cache.RulesetNames = new List { "singleRulesetSingleLanguage" }; + _cache.CodigaConfig = new CodigaCodeAnalysisConfig + { + Rulesets = new List { "single-ruleset-single-language" } + }; - var rules = _cache.GetRosieRulesForLanguage(LanguageUtils.LanguageEnumeration.Python); + var rules = await _cache.GetRosieRules(LanguageEnumeration.Python, _pythonFile, _solutionDirPath); Assert.That(rules.Count, Is.EqualTo(2)); Assert.That(_cache.RulesetslastUpdatedTimeStamp, Is.EqualTo(102L)); await _cache.HandleCacheUpdateAsync(); - var updatedRules = _cache.GetRosieRulesForLanguage(LanguageUtils.LanguageEnumeration.Python); + var updatedRules = await _cache.GetRosieRules(LanguageEnumeration.Python, _pythonFile, _solutionDirPath); Assert.That(updatedRules.Count, Is.EqualTo(3)); Assert.That(_cache.RulesetslastUpdatedTimeStamp, Is.EqualTo(101L)); } @@ -179,7 +197,7 @@ public async Task { InitConfigAndCache(@" rulesets: - - singleRulesetSingleLanguage"); + - single-ruleset-single-language"); await _cache.HandleCacheUpdateAsync(); @@ -198,8 +216,8 @@ public async Task { InitConfigAndCache(@" rulesets: - - singleRulesetSingleLanguage"); - + - single-ruleset-single-language"); + await _cache.HandleCacheUpdateAsync(); Assert.IsFalse(_cache.IsEmpty()); @@ -219,11 +237,11 @@ public async Task { InitConfigAndCache(@" rulesets: - - singleRulesetSingleLanguage"); - + - single-ruleset-single-language"); + await _cache.HandleCacheUpdateAsync(); - var rules = _cache.GetRosieRulesForLanguage(LanguageUtils.LanguageEnumeration.Python); + var rules = await _cache.GetRosieRules(LanguageEnumeration.Python, _pythonFile, _solutionDirPath); Assert.Multiple(() => { Assert.That(rules[0].Id, Is.EqualTo($"python-ruleset/{RulesetsForClientTestSupport.PythonRule1.Name}")); @@ -233,11 +251,11 @@ public async Task await UpdateCodigaConfig(@" rulesets: - - erroredRuleset"); - + - errored-ruleset"); + await _cache.HandleCacheUpdateAsync(); - var updatedRules = _cache.GetRosieRulesForLanguage(LanguageUtils.LanguageEnumeration.Python); + var updatedRules = await _cache.GetRosieRules(LanguageEnumeration.Python, _pythonFile, _solutionDirPath); Assert.Multiple(() => { Assert.That(updatedRules[0].Id, @@ -255,15 +273,15 @@ public async Task { InitConfigAndCache(@" rulesets: - - singleRulesetSingleLanguage"); - + - single-ruleset-single-language"); + await _cache.HandleCacheUpdateAsync(); Assert.IsFalse(_cache.IsEmpty()); await UpdateCodigaConfig(@" rulesets: - - nonExistentRuleset"); + - non-existent-ruleset"); await _cache.HandleCacheUpdateAsync(); @@ -276,18 +294,18 @@ public async Task { InitConfigAndCache(@" rulesets: - - singleRulesetSingleLanguage"); - + - single-ruleset-single-language"); + await _cache.HandleCacheUpdateAsync(); _cache.RulesetslastUpdatedTimeStamp = 100L; await UpdateCodigaConfig(@" rulesets: - - singleRulesetMultipleLanguagesDefaultTimestamp"); + - single-set-multi-lang-def-ts"); await _cache.HandleCacheUpdateAsync(); - var updatedRules = _cache.GetRosieRulesForLanguage(LanguageUtils.LanguageEnumeration.Python); + var updatedRules = await _cache.GetRosieRules(LanguageEnumeration.Python, _pythonFile, _solutionDirPath); Assert.That(updatedRules, Has.Count.EqualTo(2)); Assert.That(_cache.RulesetslastUpdatedTimeStamp, Is.EqualTo(100L)); } @@ -298,18 +316,18 @@ public async Task { InitConfigAndCache(@" rulesets: - - singleRulesetSingleLanguage"); - + - single-ruleset-single-language"); + await _cache.HandleCacheUpdateAsync(); _cache.RulesetslastUpdatedTimeStamp = 100L; await UpdateCodigaConfig(@" rulesets: - - singleRulesetMultipleLanguages"); + - single-ruleset-multi-languages"); await _cache.HandleCacheUpdateAsync(); - var updatedRules = _cache.GetRosieRulesForLanguage(LanguageUtils.LanguageEnumeration.Python); + var updatedRules = await _cache.GetRosieRules(LanguageEnumeration.Python, _pythonFile, _solutionDirPath); Assert.That(updatedRules, Has.Count.EqualTo(2)); Assert.That(_cache.RulesetslastUpdatedTimeStamp, Is.EqualTo(102L)); } @@ -342,19 +360,22 @@ public async Task { InitConfigAndCache(@" rulesets: - - singleRulesetSingleLanguage"); + - single-ruleset-single-language"); _cache.UpdateCacheFrom(RulesetsForClientTestSupport.SingleRulesetMultipleLanguages()); - _cache.RulesetNames = new List { "singleRulesetSingleLanguage" }; + _cache.CodigaConfig = new CodigaCodeAnalysisConfig + { + Rulesets = new List { "single-ruleset-single-language" } + }; _cache.RulesetslastUpdatedTimeStamp = 101L; _cache.ConfigFileLastWriteTime = File.GetLastWriteTime(_codigaConfigFile); - var rules = _cache.GetRosieRulesForLanguage(LanguageUtils.LanguageEnumeration.Python); + var rules = await _cache.GetRosieRules(LanguageEnumeration.Python, _pythonFile, _solutionDirPath); Assert.That(rules, Has.Count.EqualTo(2)); Assert.That(_cache.RulesetslastUpdatedTimeStamp, Is.EqualTo(101L)); await _cache.HandleCacheUpdateAsync(); - var updatedRules = _cache.GetRosieRulesForLanguage(LanguageUtils.LanguageEnumeration.Python); + var updatedRules = await _cache.GetRosieRules(LanguageEnumeration.Python, _pythonFile, _solutionDirPath); Assert.That(updatedRules, Has.Count.EqualTo(2)); Assert.That(_cache.RulesetslastUpdatedTimeStamp, Is.EqualTo(101L)); } @@ -364,19 +385,22 @@ public async Task UpdateCacheFromChangesOnServerAsync_should_update_cache() { InitConfigAndCache(@" rulesets: - - singleRulesetSingleLanguage"); + - single-ruleset-single-language"); _cache.UpdateCacheFrom(RulesetsForClientTestSupport.SingleRulesetMultipleLanguages()); - _cache.RulesetNames = new List { "singleRulesetSingleLanguage" }; + _cache.CodigaConfig = new CodigaCodeAnalysisConfig + { + Rulesets = new List { "single-ruleset-single-language" } + }; _cache.RulesetslastUpdatedTimeStamp = 102L; _cache.ConfigFileLastWriteTime = File.GetLastWriteTime(_codigaConfigFile); - var rules = _cache.GetRosieRulesForLanguage(LanguageUtils.LanguageEnumeration.Python); + var rules = await _cache.GetRosieRules(LanguageEnumeration.Python, _pythonFile, _solutionDirPath); Assert.That(rules, Has.Count.EqualTo(2)); Assert.That(_cache.RulesetslastUpdatedTimeStamp, Is.EqualTo(102L)); await _cache.HandleCacheUpdateAsync(); - var updatedRules = _cache.GetRosieRulesForLanguage(LanguageUtils.LanguageEnumeration.Python); + var updatedRules = await _cache.GetRosieRules(LanguageEnumeration.Python, _pythonFile, _solutionDirPath); Assert.That(updatedRules, Has.Count.EqualTo(3)); Assert.That(_cache.RulesetslastUpdatedTimeStamp, Is.EqualTo(101L)); } @@ -390,11 +414,11 @@ public async Task should_store_rules_from_multiple_rulesets_for_multiple_languag { InitConfigAndCache(@" rulesets: - - multipleRulesetsMultipleLanguages"); + - multi-rulesets-multi-languages"); await _cache.HandleCacheUpdateAsync(); - var rules = _cache.GetRosieRulesForLanguage(LanguageUtils.LanguageEnumeration.Python); + var rules = await _cache.GetRosieRules(LanguageEnumeration.Python, _pythonFile, _solutionDirPath); Assert.That(rules, Has.Count.EqualTo(2)); Assert.Multiple(() => { @@ -402,23 +426,397 @@ public async Task should_store_rules_from_multiple_rulesets_for_multiple_languag Assert.That(rules[1].Id, Is.EqualTo($"python-ruleset/{RulesetsForClientTestSupport.PythonRule5.Name}")); }); - var updatedRules = _cache.GetRosieRulesForLanguage(LanguageUtils.LanguageEnumeration.Java); + var updatedRules = await _cache.GetRosieRules(LanguageEnumeration.Java, + $"{_solutionDirPath}JavaFile.java", _solutionDirPath); Assert.That(updatedRules, Has.Count.EqualTo(1)); Assert.That(updatedRules[0].Id, Is.EqualTo($"mixed-ruleset/{RulesetsForClientTestSupport.JavaRule1.Name}")); } - + + #endregion + + #region GetRosieRules + [Test] - public async Task GetRosieRulesForLanguage_should_return_javascript_rules_for_typescript() { + public async Task GetRosieRules_should_return_rules_for_empty_ignore_config() + { InitConfigAndCache(@" rulesets: - - javascriptRuleset"); - + - javascript-ruleset"); + await _cache.HandleCacheUpdateAsync(); - - var rules = _cache.GetRosieRulesForLanguage(LanguageUtils.LanguageEnumeration.Typescript); - Assert.That(rules, Has.Count.EqualTo(3)); - Assert.That(rules, Has.All.Property("Language").EqualTo("Javascript")); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Javascript, _javaScriptFile, _solutionDirPath); + + ValidateRuleCountAndRuleIds(rules, + 3, + "javascript-ruleset/javascript_rule_1", "javascript-ruleset/javascript_rule_2", + "javascript-ruleset/javascript_rule_3"); + } + + [Test] + public async Task GetRosieRules_should_not_filter_rules_for_ignore_config_with_no_ruleset() + { + InitConfigAndCache(@" +rulesets: + - javascript-ruleset +ignore: + "); + + await _cache.HandleCacheUpdateAsync(); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Javascript, _javaScriptFile, _solutionDirPath); + + ValidateRuleCountAndRuleIds(rules, + 3, + "javascript-ruleset/javascript_rule_1", "javascript-ruleset/javascript_rule_2", + "javascript-ruleset/javascript_rule_3"); + } + + [Test] + public async Task GetRosieRules_should_not_filter_rules_for_ignore_config_with_no_rule() + { + InitConfigAndCache(@" +rulesets: + - javascript-ruleset +ignore: + - javascript-ruleset:"); + + await _cache.HandleCacheUpdateAsync(); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Javascript, _javaScriptFile, _solutionDirPath); + + ValidateRuleCountAndRuleIds(rules, + 3, + "javascript-ruleset/javascript_rule_1", "javascript-ruleset/javascript_rule_2", + "javascript-ruleset/javascript_rule_3"); + } + + [Test] + public async Task GetRosieRules_should_filter_rules_for_ignore_config_with_no_prefix() + { + InitConfigAndCache(@" +rulesets: + - javascript-ruleset +ignore: + - javascript-ruleset: + - javascript_rule_2"); + + await _cache.HandleCacheUpdateAsync(); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Javascript, _javaScriptFile, _solutionDirPath); + + ValidateRuleCountAndRuleIds(rules, + 2, + "javascript-ruleset/javascript_rule_1", "javascript-ruleset/javascript_rule_3"); + } + + [Test] + public async Task + GetRosieRules_should_filter_rule_for_ignore_config_with_one_matching_prefix_with_leading_slash() + { + InitConfigAndCache(@" +rulesets: + - javascript-ruleset +ignore: + - javascript-ruleset: + - javascript_rule_2: + - prefix: /javascript"); + + await _cache.HandleCacheUpdateAsync(); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Javascript, _javaScriptFile, _solutionDirPath); + + ValidateRuleCountAndRuleIds(rules, + 2, + "javascript-ruleset/javascript_rule_1", "javascript-ruleset/javascript_rule_3"); + } + + [Test] + public async Task + GetRosieRules_should_filter_rules_with_ignore_config_with_one_matching_prefix_without_leading_slash() + { + InitConfigAndCache(@" +rulesets: + - javascript-ruleset +ignore: + - javascript-ruleset: + - javascript_rule_2: + - prefix: javascript"); + + await _cache.HandleCacheUpdateAsync(); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Javascript, _javaScriptFile, _solutionDirPath); + + ValidateRuleCountAndRuleIds(rules, + 2, + "javascript-ruleset/javascript_rule_1", "javascript-ruleset/javascript_rule_3"); + } + + [Test] + public async Task GetRosieRules_should_filter_rules_with_ignore_config_with_one_matching_file_path_prefix() + { + InitConfigAndCache(@" +rulesets: + - javascript-ruleset +ignore: + - javascript-ruleset: + - javascript_rule_2: + - prefix: /javascript_file.js"); + + await _cache.HandleCacheUpdateAsync(); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Javascript, _javaScriptFile, _solutionDirPath); + + ValidateRuleCountAndRuleIds(rules, + 2, + "javascript-ruleset/javascript_rule_1", "javascript-ruleset/javascript_rule_3"); + } + + [Test] + public async Task GetRosieRules_should_filter_rules_with_ignore_config_with_one_matching_directory_path_prefix() + { + InitConfigAndCache(@" +rulesets: + - javascript-ruleset +ignore: + - javascript-ruleset: + - javascript_rule_2: + - prefix: /directory"); + + await _cache.HandleCacheUpdateAsync(); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Javascript, + $"{_solutionDirPath}directory/javascript_file.js", _solutionDirPath); + + ValidateRuleCountAndRuleIds(rules, + 2, + "javascript-ruleset/javascript_rule_1", "javascript-ruleset/javascript_rule_3"); + } + + [Test] + public async Task GetRosieRules_should_not_filter_rules_with_ignore_config_with_one_prefix_not_matching() + { + InitConfigAndCache(@" +rulesets: + - javascript-ruleset +ignore: + - javascript-ruleset: + - javascript_rule_2: + - prefix: not-matching"); + + await _cache.HandleCacheUpdateAsync(); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Javascript, _javaScriptFile, _solutionDirPath); + + ValidateRuleCountAndRuleIds(rules, + 3, + "javascript-ruleset/javascript_rule_1", "javascript-ruleset/javascript_rule_2", "javascript-ruleset/javascript_rule_3"); + } + + [Test] + public async Task + GetRosieRules_should_not_filter_rules_with_ignore_config_with_one_prefix_containing_double_dots() + { + InitConfigAndCache(@" +rulesets: + - javascript-ruleset +ignore: + - javascript-ruleset: + - javascript_rule_2: + - prefix: javascript_file..js"); + + await _cache.HandleCacheUpdateAsync(); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Javascript, _javaScriptFile, _solutionDirPath); + + ValidateRuleCountAndRuleIds(rules, + 3, + "javascript-ruleset/javascript_rule_1", "javascript-ruleset/javascript_rule_2", "javascript-ruleset/javascript_rule_3"); + } + + [Test] + public async Task + GetRosieRules_should_not_filter_rules_with_ignore_config_with_one_prefix_containing_single_dot_as_folder() + { + InitConfigAndCache(@" +rulesets: + - javascript-ruleset +ignore: + - javascript-ruleset: + - javascript_rule_2: + - prefix: directory/./javascript_file.js"); + + await _cache.HandleCacheUpdateAsync(); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Javascript, + $"{_solutionDirPath}directory/sub/javascript_file.js", _solutionDirPath); + + ValidateRuleCountAndRuleIds(rules, + 3, + "javascript-ruleset/javascript_rule_1", "javascript-ruleset/javascript_rule_2", "javascript-ruleset/javascript_rule_3"); + } + + [Test] + public async Task + GetRosieRules_should_not_filter_rules_with_ignore_config_with_one_prefix_containing_double_dots_as_folder() + { + InitConfigAndCache(@" +rulesets: + - javascript-ruleset +ignore: + - javascript-ruleset: + - javascript_rule_2: + - prefix: directory/../javascript_file.js"); + + await _cache.HandleCacheUpdateAsync(); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Javascript, + $"{_solutionDirPath}directory/sub/javascript_file.js", _solutionDirPath); + + ValidateRuleCountAndRuleIds(rules, + 3, + "javascript-ruleset/javascript_rule_1", "javascript-ruleset/javascript_rule_2", "javascript-ruleset/javascript_rule_3"); + } + + [Test] + public async Task GetRosieRules_should_filter_rules_with_ignore_config_with_one_matching_prefix_of_multiple() + { + InitConfigAndCache(@" +rulesets: + - javascript-ruleset +ignore: + - javascript-ruleset: + - javascript_rule_2: + - prefix: + - not/matching + - javascript_file.js"); + + await _cache.HandleCacheUpdateAsync(); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Javascript, _javaScriptFile, _solutionDirPath); + + ValidateRuleCountAndRuleIds(rules, + 2, + "javascript-ruleset/javascript_rule_1", "javascript-ruleset/javascript_rule_3"); + } + + [Test] + public async Task GetRosieRules_should_filter_rules_with_ignore_config_with_multiple_matching_prefixes() + { + InitConfigAndCache(@" +rulesets: + - javascript-ruleset +ignore: + - javascript-ruleset: + - javascript_rule_2: + - prefix: + - /javascript + - javascript_file.js"); + + await _cache.HandleCacheUpdateAsync(); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Javascript, _javaScriptFile, _solutionDirPath); + + ValidateRuleCountAndRuleIds(rules, + 2, + "javascript-ruleset/javascript_rule_1", "javascript-ruleset/javascript_rule_3"); + } + + [Test] + public async Task GetRosieRules_should_not_filter_rules_with_ignore_config_with_multiple_prefixes_not_matching() + { + InitConfigAndCache(@" +rulesets: + - javascript-ruleset +ignore: + - javascript-ruleset: + - javascript_rule_2: + - prefix: + - not-matching + - also/not/matching"); + + await _cache.HandleCacheUpdateAsync(); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Javascript, _javaScriptFile, _solutionDirPath); + + ValidateRuleCountAndRuleIds(rules, + 3, + "javascript-ruleset/javascript_rule_1", "javascript-ruleset/javascript_rule_2", "javascript-ruleset/javascript_rule_3"); + } + + [Test] + public async Task + GetRosieRules_should_filter_rules_with_ignore_config_with_multiple_rule_ignore_configurations() + { + InitConfigAndCache(@" +rulesets: + - javascript-ruleset +ignore: + - javascript-ruleset: + - javascript_rule_2: + - prefix: javascript_file..js + - javascript_rule_3: + - prefix: + - /javascript_fi"); + + await _cache.HandleCacheUpdateAsync(); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Javascript, _javaScriptFile, _solutionDirPath); + + ValidateRuleCountAndRuleIds(rules, + 2, + "javascript-ruleset/javascript_rule_1", "javascript-ruleset/javascript_rule_2"); + } + + [Test] + public async Task GetRosieRules_should_not_filter_rules_when_rule_doesnt_belong_to_ruleset() + { + InitConfigAndCache(@" +rulesets: + - javascript-ruleset +ignore: + - javascript-ruleset: + - non_javascript_rule: + - prefix: javascript_file..js"); + + await _cache.HandleCacheUpdateAsync(); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Javascript, _javaScriptFile, _solutionDirPath); + + ValidateRuleCountAndRuleIds(rules, + 3, + "javascript-ruleset/javascript_rule_1", "javascript-ruleset/javascript_rule_2", "javascript-ruleset/javascript_rule_3"); + } + + [Test] + public async Task + GetRosieRules_should_not_filter_rules_when_ruleset_ignore_is_not_present_in_rulesets_property() + { + InitConfigAndCache(@" +rulesets: + - javascript-ruleset +ignore: + - not-configured-ruleset: + - javascript_rule_2: + - prefix: javascript_file.js"); + + await _cache.HandleCacheUpdateAsync(); + + var rules = await _cache.GetRosieRules(LanguageEnumeration.Javascript, _javaScriptFile, _solutionDirPath); + + ValidateRuleCountAndRuleIds(rules, + 3, + "javascript-ruleset/javascript_rule_1", "javascript-ruleset/javascript_rule_2", "javascript-ruleset/javascript_rule_3"); + } + + private void ValidateRuleCountAndRuleIds(IReadOnlyList rules, int count, + params string[] expectedRuleIds) + { + Assert.That(rules, Has.Count.EqualTo(count)); + + var actualRuleIds = rules.Select(rule => rule.Id).ToList(); + CollectionAssert.AreEqual(actualRuleIds, expectedRuleIds); } #endregion @@ -450,7 +848,7 @@ private void InitCodigaConfig(string rawConfig) private async Task UpdateCodigaConfig(string rawConfig) { File.Delete(_codigaConfigFile); - + //This delay ensures that the last write time of the new config file is different than the // one in the rules cache. In the CI pipeline the test execution tends to be so fast // that it is executed in the same millisecond, resulting in the exact same last write time. @@ -463,7 +861,7 @@ private async Task UpdateCodigaConfig(string rawConfig) { return; } - + InitCodigaConfig(rawConfig); } diff --git a/src/Tests/Rosie/RulesetsForClientTestSupport.cs b/src/Tests/Rosie/RulesetsForClientTestSupport.cs index c9b965b..dab4b9e 100644 --- a/src/Tests/Rosie/RulesetsForClientTestSupport.cs +++ b/src/Tests/Rosie/RulesetsForClientTestSupport.cs @@ -82,18 +82,18 @@ public static class RulesetsForClientTestSupport switch (rulesetNames.ToList()[0]) { - case "singleRulesetSingleLanguage": + case "single-ruleset-single-language": return SingleRulesetSingleLanguage(); //Python - case "singleRulesetMultipleLanguagesDefaultTimestamp": - case "singleRulesetMultipleLanguages": + case "single-set-multi-lang-def-ts": + case "single-ruleset-multi-languages": return SingleRulesetMultipleLanguages(); //Python, Java - case "multipleRulesetsSingleLanguage": + case "multi-rulesets-single-language": return MultipleRulesetsSingleLanguage(); //Python - case "multipleRulesetsMultipleLanguages": + case "multi-rulesets-multi-languages": return MultipleRulesetsMultipleLanguages(); //Python, Java - case "erroredRuleset": + case "errored-ruleset": return null; - case "javascriptRuleset": + case "javascript-ruleset": return JavascriptRulesets(); default: return ImmutableList.Create(); @@ -104,12 +104,12 @@ public static long GetRulesetsLastTimestamp(IReadOnlyCollection rulesetN { return rulesetNames.ToList()[0] switch { - "singleRulesetSingleLanguage" => 101L, - "singleRulesetMultipleLanguagesDefaultTimestamp" => 100L, - "singleRulesetMultipleLanguages" => 102L, - "multipleRulesetsSingleLanguage" => 103L, - "multipleRulesetsMultipleLanguages" => 104L, - "javascriptRuleset" => 105L, + "single-ruleset-single-language" => 101L, + "single-set-multi-lang-def-ts" => 100L, + "single-ruleset-multi-languages" => 102L, + "multi-rulesets-single-language" => 103L, + "multi-rulesets-multi-languages" => 104L, + "javascript-ruleset" => 105L, _ => -1L }; } @@ -171,7 +171,7 @@ private static IReadOnlyCollection MultipleRulesetsMultipleLa private static IReadOnlyCollection JavascriptRulesets() { var rules = new List { JavaScriptRule1, JavaScriptRule2, JavaScriptRule3}; - var ruleset = new RuleSetsForClient { Id = 5678, Name = "javascriptRuleset", Rules = rules }; + var ruleset = new RuleSetsForClient { Id = 5678, Name = "javascript-ruleset", Rules = rules }; return ImmutableList.Create(ruleset); }