diff --git a/src/CTA.FeatureDetection.Common/packages.lock.json b/src/CTA.FeatureDetection.Common/packages.lock.json index 91b782be..5d62cef8 100644 --- a/src/CTA.FeatureDetection.Common/packages.lock.json +++ b/src/CTA.FeatureDetection.Common/packages.lock.json @@ -54,12 +54,12 @@ }, "Codelyzer.Analysis": { "type": "Transitive", - "resolved": "2.4.56-alpha-g89b570063c", - "contentHash": "3u40R9jXqCZ3pLyY3cEFB2WbZIjvF/I+xQcT83oFbJXFAE5n7g1txDjzrv4Vyb9YHQJtlkAIUMkkiFn8dUNt7Q==", + "resolved": "2.4.65-alpha-gd06dae876d", + "contentHash": "8ocAQHFpL7NIVSQ8i518EQWUFujXcHqS/AuQfxHbX64PA6wNAkrBWTsjhKTvI05dLWZ6iHUZm3wQ5uxXU2gPlA==", "dependencies": { - "Codelyzer.Analysis.Build": "2.4.56-alpha-g89b570063c", - "Codelyzer.Analysis.CSharp": "2.4.56-alpha-g89b570063c", - "Codelyzer.Analysis.VisualBasic": "2.4.56-alpha-g89b570063c", + "Codelyzer.Analysis.Build": "2.4.65-alpha-gd06dae876d", + "Codelyzer.Analysis.CSharp": "2.4.65-alpha-gd06dae876d", + "Codelyzer.Analysis.VisualBasic": "2.4.65-alpha-gd06dae876d", "CommandLineParser": "2.8.0", "Microsoft.Build.Utilities.Core": "17.1.0", "Microsoft.Extensions.Logging.Console": "6.0.0", @@ -69,23 +69,23 @@ }, "Codelyzer.Analysis.Build": { "type": "Transitive", - "resolved": "2.4.56-alpha-g89b570063c", - "contentHash": "hvxLNqVU9Yh/hdTNsmT+qtLgy7J+0Nm1diRjHGKH/FI6ymZkvgAkYB2UWge1JkRAWyRm4WNF4ukgwUlB8WboPA==", + "resolved": "2.4.65-alpha-gd06dae876d", + "contentHash": "1EqRE2hIKMtkzzKpTyzDZrqCDcSjr9iJlM8WYQ7evQ1gT9YOGaUB/iO+/DdW4CsKNbFqhq6RvCeyhu32x272hw==", "dependencies": { "Buildalyzer": "4.1.4", "Buildalyzer.Logger": "4.1.4", "Buildalyzer.Workspaces": "4.1.4", - "Codelyzer.Analysis.Common": "2.4.56-alpha-g89b570063c", + "Codelyzer.Analysis.Common": "2.4.65-alpha-gd06dae876d", "Microsoft.Extensions.Logging": "6.0.0", "NuGet.Packaging": "6.0.0" } }, "Codelyzer.Analysis.Common": { "type": "Transitive", - "resolved": "2.4.56-alpha-g89b570063c", - "contentHash": "ZflwAG3tOHPfqHmfnkUTl5RKuCzN7HqhVF7aw6fwUD1uE93HTdB4VBvkm0n3Nj6Ucv6Mv8UK3bYoYGr0+EYYIQ==", + "resolved": "2.4.65-alpha-gd06dae876d", + "contentHash": "igze76koUw5Ao9XrpjjdyLPobF5CCi9uSb26zLcnrnQsiS1k8m58CQJZKCfsP2mAsGb7HhC5IHmkzxUqGIu29g==", "dependencies": { - "Codelyzer.Analysis.Model": "2.4.56-alpha-g89b570063c", + "Codelyzer.Analysis.Model": "2.4.65-alpha-gd06dae876d", "Microsoft.Build": "17.0.0", "Microsoft.VisualStudio.Setup.Configuration.Interop": "3.1.2196", "Newtonsoft.Json": "13.0.1" @@ -93,17 +93,17 @@ }, "Codelyzer.Analysis.CSharp": { "type": "Transitive", - "resolved": "2.4.56-alpha-g89b570063c", - "contentHash": "68Z3EFQpRG7OJk3ImRVXjKxRcqZl9nk7E8rrD5fdfy64qd/I9KD8/eJ0xyEPwhwTzv+OsnZQyZ5YidOd9WLIEg==", + "resolved": "2.4.65-alpha-gd06dae876d", + "contentHash": "HlxgpiPQUw53w1c25F3WpO2NMEm+9ZekHmhxNcspy+P2pIHmhRLvmAhkQGYerk+J6teS7oJTHG31w6NgrxgdqA==", "dependencies": { - "Codelyzer.Analysis.Common": "2.4.56-alpha-g89b570063c", - "Codelyzer.Analysis.Model": "2.4.56-alpha-g89b570063c" + "Codelyzer.Analysis.Common": "2.4.65-alpha-gd06dae876d", + "Codelyzer.Analysis.Model": "2.4.65-alpha-gd06dae876d" } }, "Codelyzer.Analysis.Model": { "type": "Transitive", - "resolved": "2.4.56-alpha-g89b570063c", - "contentHash": "CJCJsGouBGuFe0MCJD3zinuaKQQXfLxT81oqpz2Z+yaTEi420fiHNLvlRzHILnaT3Uu1pBnbkCix5hxHRDWF0A==", + "resolved": "2.4.65-alpha-gd06dae876d", + "contentHash": "E0pzEAkmo1znKadLMIsrINDAffA+7wHaRhSV+IGjayvYx77XAjb54j2U61ysATOz2YXHqU3wQfWU2KhIgGHdZw==", "dependencies": { "Microsoft.CodeAnalysis": "4.1.0", "Microsoft.CodeAnalysis.CSharp": "4.1.0", @@ -114,11 +114,11 @@ }, "Codelyzer.Analysis.VisualBasic": { "type": "Transitive", - "resolved": "2.4.56-alpha-g89b570063c", - "contentHash": "oZ2tGbAMHPjegveNz2hXLEJYxJvOOMjb27W7KNMKpynwLDm+Ck9GloGiGZi/ADpSvrxelYl6JQtYIeACJBu+Iw==", + "resolved": "2.4.65-alpha-gd06dae876d", + "contentHash": "92Y5YpPXOXvlNe8g3ipZ1MQ54yZwl8Ah91OGJEZSTnnb6CwFkgqiIp/mIlFtor/AyUCO/+MEKdqJ7Q/fOgogVQ==", "dependencies": { - "Codelyzer.Analysis.Common": "2.4.56-alpha-g89b570063c", - "Codelyzer.Analysis.Model": "2.4.56-alpha-g89b570063c" + "Codelyzer.Analysis.Common": "2.4.65-alpha-gd06dae876d", + "Codelyzer.Analysis.Model": "2.4.65-alpha-gd06dae876d" } }, "CommandLineParser": { @@ -1711,8 +1711,8 @@ "cta.rules.config": { "type": "Project", "dependencies": { - "Codelyzer.Analysis": "2.4.56-alpha-g89b570063c", - "Codelyzer.Analysis.Model": "2.4.56-alpha-g89b570063c", + "Codelyzer.Analysis": "2.4.65-alpha-gd06dae876d", + "Codelyzer.Analysis.Model": "2.4.65-alpha-gd06dae876d", "Microsoft.Extensions.Logging": "6.0.0", "Microsoft.Extensions.Logging.Abstractions": "6.0.0", "Microsoft.Extensions.Logging.Console": "6.0.0", diff --git a/src/CTA.Rules.Actions/ActionHelpers/FolderUpdate.cs b/src/CTA.Rules.Actions/ActionHelpers/FolderUpdate.cs index f33f879a..f3b751ea 100644 --- a/src/CTA.Rules.Actions/ActionHelpers/FolderUpdate.cs +++ b/src/CTA.Rules.Actions/ActionHelpers/FolderUpdate.cs @@ -1,4 +1,5 @@ using System.IO; +using CTA.Rules.Common.Helpers; using CTA.Rules.Config; using CTA.Rules.Models; @@ -9,12 +10,17 @@ public class FolderUpdate private readonly string _projectDir; private readonly string _projectFile; private readonly ProjectType _projectType; + private readonly string _codeFileExtension; public FolderUpdate(string projectFile, ProjectType projectType) { _projectFile = projectFile; _projectDir = Directory.GetParent(_projectFile).FullName; _projectType = projectType; + _codeFileExtension = + VisualBasicUtils.IsVisualBasicProject(projectFile) + ? FileExtension.VisualBasic + : FileExtension.CSharp; } //TODO Is there a better way to do this? @@ -37,25 +43,25 @@ public string Run() CreateMvcDirs(_projectDir); CreateStartupFiles(_projectDir, _projectType, FileTypeCreation.Program); CreateStartupFiles(_projectDir, _projectType, FileTypeCreation.Startup); - runResult = "Mvc project detected. Created static files, Program.cs, and Startup.cs"; + runResult = $"Mvc project detected. Created static files, Program{_codeFileExtension} and Startup{_codeFileExtension}"; } if (_projectType == ProjectType.WebApi) { CreateStartupFiles(_projectDir, _projectType, FileTypeCreation.Program); CreateStartupFiles(_projectDir, _projectType, FileTypeCreation.Startup); - runResult = "Web API project detected. Created Program.cs and Startup.cs"; + runResult = $"Web API project detected. Created Program{_codeFileExtension} and Startup{_codeFileExtension}"; } if (_projectType == ProjectType.WCFConfigBasedService) { CreateStartupFiles(_projectDir, _projectType, FileTypeCreation.Program); CreateStartupFiles(_projectDir, _projectType, FileTypeCreation.Startup); - runResult = "WCF Config Based Service Project detected. Created Program.cs and Startup.cs"; + runResult = $"WCF Config Based Service Project detected. Created Program{_codeFileExtension} and Startup{_codeFileExtension}"; } if (_projectType == ProjectType.WCFCodeBasedService) { CreateStartupFiles(_projectDir, _projectType, FileTypeCreation.Program); CreateStartupFiles(_projectDir, _projectType, FileTypeCreation.Startup); - runResult = "WCF Code Based Service Project detected. Created Program.cs and Startup.cs"; + runResult = $"WCF Code Based Service Project detected. Created Program{_codeFileExtension} and Startup{_codeFileExtension}"; } return runResult; } @@ -69,14 +75,14 @@ private void CreateStartupFiles(string projectDir, ProjectType projectType, File { string projectNamespace = GetProjectNamespace(); - var file = Path.Combine(projectDir, string.Concat(fileType.ToString(), ".cs")); + var file = Path.Combine(projectDir, string.Concat(fileType.ToString(), _codeFileExtension)); if (File.Exists(file)) { - File.Move(file, string.Concat(file, ".bak")); + File.Move(file, string.Concat(file, FileExtension.Backup)); } - File.WriteAllText(file, GetStartupFileContent(projectNamespace, projectType, fileType)); + File.WriteAllText(file, GetStartupFileContent(projectNamespace, projectType, fileType, _codeFileExtension)); - LogChange(string.Format("Created {0}.cs file using {1} template", fileType.ToString(), projectType.ToString())); + LogChange(string.Format("Created {0}{2} file using {1} template", fileType.ToString(), projectType.ToString(), _codeFileExtension)); } /// @@ -85,10 +91,11 @@ private void CreateStartupFiles(string projectDir, ProjectType projectType, File /// The project namespace /// The project type /// Type of the file to be retrieved + /// Extension of file to be retrieved /// The content of the startup file - private string GetStartupFileContent(string projectNamespace, ProjectType projectType, FileTypeCreation fileType) + private string GetStartupFileContent(string projectNamespace, ProjectType projectType, FileTypeCreation fileType, string fileExtension) { - return TemplateHelper.GetTemplateFileContent(projectNamespace, projectType, fileType.ToString() + ".cs"); + return TemplateHelper.GetTemplateFileContent(projectNamespace, projectType, fileType.ToString() + fileExtension); } /// diff --git a/src/CTA.Rules.Actions/ActionLoaderBase.cs b/src/CTA.Rules.Actions/ActionLoaderBase.cs new file mode 100644 index 00000000..e12b8275 --- /dev/null +++ b/src/CTA.Rules.Actions/ActionLoaderBase.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using CTA.Rules.Models; +using Codelyzer.Analysis; +using CTA.Rules.Config; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editing; +using Newtonsoft.Json; + +namespace CTA.Rules.Actions; + +public class ActionLoaderBase +{ + protected List projectLevelActions, + projectFileActions, + projectTypeActions, + memberAccessActions; + + protected object projectLevelObject, + projectFileObject, + projectTypeObject, + memberAccessObject; + + public Func GetProjectLevelActions(string name, dynamic value) => + GetAction> + (projectLevelActions, projectLevelObject, name, value); + public Func, Dictionary, List, List, string> GetProjectFileActions(string name, dynamic value) => + GetAction, Dictionary, List, List, string>> + (projectFileActions, projectFileObject, name, value); + public Func GetProjectTypeActions(string name, dynamic value) => + GetAction> + (projectTypeActions, projectTypeObject, name, value); + public Func GetMemberAccessExpressionActions(string name, dynamic value) => + GetAction> + (memberAccessActions, memberAccessObject, name, value); + + #region helper functions + + /// + /// Gets the action by invoking the methods that will create it + /// + /// The type of the object + /// List of actions on the type T + /// The object that will be used to retrieve the action + /// Name of the action + /// Parameter(s) of the action + /// + public static T GetAction(List actions, object invokeObject, string name, dynamic value) + { + T val = default; + try + { + string actionName = GetActionName(name); + var method = actions.FirstOrDefault(m => m.Name == actionName); + if (method == null) + { + LogHelper.LogDebug(string.Format("No such action {0}", actionName)); + } + else + { + var parameters = GetParameters(value, method); + + if (parameters != null) + { + val = (T)method.Invoke(invokeObject, parameters); + } + } + } + catch (Exception ex) + { + LogHelper.LogError(ex, "Error while loading action {0}", name); + } + return val; + } + + public static List GetAssemblies(List assemblyPaths) + { + var actionsAssembly = AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => a.FullName.Contains("CTA.Rules.Actions")); + + var assemblies = new List + { + actionsAssembly + }; + + foreach (var path in assemblyPaths) + { + try + { + var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path); + assemblies.Add(assembly); + } + catch (Exception ex) + { + LogHelper.LogError(ex, + string.Format("Error loading assembly from {0}{1}{2}", path, Environment.NewLine, ex.Message)); + } + } + + return assemblies; + } + + private static string GetActionName(string name) + { + return string.Concat("Get", name, "Action"); + } + + protected static List GetFuncMethods(Type t) => t.GetMethods().Where(m => m.ReturnType.ToString().Contains("System.Func")).ToList(); + + protected static bool TryCreateInstance(string actionName, List types, out object obj) + { + obj = null; + var type = types.FirstOrDefault(t => t.Name == actionName); + if (type == null) + { + return false; + } + obj = Activator.CreateInstance(type); + return true; + } + + private static List GetJsonParameters(string value, MethodInfo method) + { + List result = new List(); + + Dictionary jsonParameters = JsonConvert.DeserializeObject>(value); + + var methodParams = method.GetParameters(); + foreach (var p in methodParams) + { + if (jsonParameters.ContainsKey(p.Name)) + { + result.Add(jsonParameters[p.Name]); + } + else if(p.IsOptional) + { + result.Add(p.HasDefaultValue && p.DefaultValue != null ? p.DefaultValue.ToString() : null); + } + else + { + LogHelper.LogDebug(string.Format("Parameter {0} is not available for action {1}", p.Name, method.Name)); + return null; + } + } + return result; + } + /// + /// Gets the parameters for the action. The parameters should match the action signature in the provided rules file + /// + /// The paramter(s) as a string or JSON object + /// The method for these parameters + /// + private static string[] GetParameters(dynamic value, MethodInfo method) + { + List result = new List(); + + try + { + if (value is string) + { + var strValue = value.ToString(); + if (strValue.StartsWith("{")) + { + try + { + result = GetJsonParameters(value.ToString(), method); + } + catch (Exception) + { + result = new List() { value }; + } + } + else + { + result = new List() { value }; + var optionalParameters = method.GetParameters().Where(p => p.IsOptional); + // This should only run if optional parameter was not inlcuded originally. + // TODO: We do not support ONLY optional parameters > 1 at this time, this logic would need to be re-written properly, that scenario would fail at val = (T)method.Invoke(invokeObject, parameters); + if (optionalParameters.Any() && method.GetParameters().Count() > 1) + { + result.AddRange(optionalParameters.Select(p => p.HasDefaultValue && p.DefaultValue != null ? p.DefaultValue.ToString() : null)); + } + } + } + else + { + result = GetJsonParameters(value.ToString(), method); + } + } + catch (Exception ex) + { + LogHelper.LogError(ex, "Error while loading parameters for action {0}", method.Name); + } + return result.ToArray(); + } + + #endregion +} diff --git a/src/CTA.Rules.Actions/ActionsLoader.cs b/src/CTA.Rules.Actions/ActionsLoader.cs index 85d7a59d..2f59a64d 100644 --- a/src/CTA.Rules.Actions/ActionsLoader.cs +++ b/src/CTA.Rules.Actions/ActionsLoader.cs @@ -9,22 +9,39 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -using Newtonsoft.Json; namespace CTA.Rules.Actions { /// /// Loads actions into the current execution context /// - public class ActionsLoader + public class ActionsLoader : ActionLoaderBase { - private readonly List compilationUnitActions, attributeActions, attributeListActions, classActions, - identifierNameActions, invocationExpressionActions, expressionActions, methodDeclarationActions, elementAccessActions, - objectCreationExpressionActions, memberAccessActions, namespaceActions, projectLevelActions, projectFileActions, projectTypeActions, interfaceActions; - - private readonly object attributeObject, attributeListObject, classObject, interfaceObject, compilationUnitObject, identifierNameObject - , invocationExpressionObject, expressionObject, methodDeclarationObject, elementAccessObject, memberAccessObject, objectExpressionObject, namespaceObject, projectLevelObject, - projectFileObject, projectTypeObject; + private readonly List compilationUnitActions, + attributeActions, + attributeListActions, + classActions, + identifierNameActions, + invocationExpressionActions, + expressionActions, + methodDeclarationActions, + elementAccessActions, + objectCreationExpressionActions, + namespaceActions, + interfaceActions; + + private readonly object attributeObject, + attributeListObject, + classObject, + interfaceObject, + compilationUnitObject, + identifierNameObject, + invocationExpressionObject, + expressionObject, + methodDeclarationObject, + elementAccessObject, + objectExpressionObject, + namespaceObject; /// /// Initializes a new ActionLoader that loads the default actions @@ -73,7 +90,11 @@ public ActionsLoader(List assemblyPaths) { try { - var types = assembly.GetTypes().Where(t => t.Name.EndsWith("Actions")); + var types = assembly.GetTypes() + .Where(t => t.Name.EndsWith("Actions") && + (t.Namespace.EndsWith(ProjectLanguage.Csharp.ToString()) || + t.Name.StartsWith("Project") || + t.Name.StartsWith("MemberAccess"))).ToList(); attributeObject = Activator.CreateInstance(types.FirstOrDefault(t => t.Name == Constants.AttributeActions)); attributeListObject = Activator.CreateInstance(types.FirstOrDefault(t => t.Name == Constants.AttributeListActions)); @@ -92,7 +113,6 @@ public ActionsLoader(List assemblyPaths) projectFileObject = Activator.CreateInstance(types.FirstOrDefault(t => t.Name == Constants.ProjectFileActions)); projectTypeObject = Activator.CreateInstance(types.FirstOrDefault(t => t.Name == Constants.ProjectTypeActions)); - foreach (var t in types) { switch (t.Name) @@ -191,13 +211,6 @@ public ActionsLoader(List assemblyPaths) } } } - - private List GetFuncMethods(Type t) => t.GetMethods().Where(m => m.ReturnType.ToString().Contains("System.Func")).ToList(); - - private string GetActionName(string name) - { - return string.Concat("Get", name, "Action"); - } public Func GetCompilationUnitAction(string name, dynamic value) => GetAction> (compilationUnitActions, compilationUnitObject, name, value); @@ -231,134 +244,9 @@ public Func GetObjectCreationExpressionActions(string name, dynamic value) => GetAction> (objectCreationExpressionActions, objectExpressionObject, name, value); - public Func GetProjectLevelActions(string name, dynamic value) => - GetAction> - (projectLevelActions, projectLevelObject, name, value); - public Func, Dictionary, List, List, string> GetProjectFileActions(string name, dynamic value) => - GetAction, Dictionary, List, List, string>> - (projectFileActions, projectFileObject, name, value); - public Func GetProjectTypeActions(string name, dynamic value) => - GetAction> - (projectTypeActions, projectTypeObject, name, value); public Func GetElementAccessExpressionActions(string name, dynamic value) => GetAction> (elementAccessActions, elementAccessObject, name, value); - public Func GetMemberAccessExpressionActions(string name, dynamic value) => - GetAction> - (memberAccessActions, memberAccessObject, name, value); - - /// - /// Gets the action by invoking the methods that will create it - /// - /// The type of the object - /// List of actions on the type T - /// The object that will be used to retrieve the action - /// Name of the action - /// Parameter(s) of the action - /// - private T GetAction(List actions, object invokeObject, string name, dynamic value) - { - T val = default; - try - { - string actionName = GetActionName(name); - var method = actions.Where(m => m.Name == actionName).FirstOrDefault(); - if (method == null) - { - LogHelper.LogDebug(string.Format("No such action {0}", actionName)); - } - else - { - var parameters = GetParameters(value, method); - - if (parameters != null) - { - val = (T)method.Invoke(invokeObject, parameters); - } - } - } - catch (Exception ex) - { - LogHelper.LogError(ex, "Error while loading action {0}", name); - } - return val; - } - - /// - /// Gets the parameters for the action. The parameters should match the action signature in the provided rules file - /// - /// The paramter(s) as a string or JSON object - /// The method for these parameters - /// - private string[] GetParameters(dynamic value, MethodInfo method) - { - List result = new List(); - - try - { - if (value is string) - { - var strValue = value.ToString(); - if (strValue.StartsWith("{")) - { - try - { - result = GetJsonParameters(value.ToString(), method); - } - catch (Exception) - { - result = new List() { value }; - } - } - else - { - result = new List() { value }; - var optionalParameters = method.GetParameters().Where(p => p.IsOptional); - // This should only run if optional parameter was not inlcuded originally. - // TODO: We do not support ONLY optional parameters > 1 at this time, this logic would need to be re-written properly, that scenario would fail at val = (T)method.Invoke(invokeObject, parameters); - if (optionalParameters.Any() && method.GetParameters().Count() > 1) - { - result.AddRange(optionalParameters.Select(p => p.HasDefaultValue && p.DefaultValue != null ? p.DefaultValue.ToString() : null)); - } - } - } - else - { - result = GetJsonParameters(value.ToString(), method); - } - } - catch (Exception ex) - { - LogHelper.LogError(ex, "Error while loading parameters for action {0}", method.Name); - } - return result.ToArray(); - } - - private List GetJsonParameters(string value, MethodInfo method) - { - List result = new List(); - - Dictionary jsonParameters = JsonConvert.DeserializeObject>(value); - - var methodParams = method.GetParameters(); - foreach (var p in methodParams) - { - if (jsonParameters.ContainsKey(p.Name)) - { - result.Add(jsonParameters[p.Name]); - } - else if(p.IsOptional) - { - result.Add(p.HasDefaultValue && p.DefaultValue != null ? p.DefaultValue.ToString() : null); - } - else - { - LogHelper.LogDebug(string.Format("Parameter {0} is not available for action {1}", p.Name, method.Name)); - return null; - } - } - return result; - } } } diff --git a/src/CTA.Rules.Actions/AttributeActions.cs b/src/CTA.Rules.Actions/Csharp/AttributeActions.cs similarity index 95% rename from src/CTA.Rules.Actions/AttributeActions.cs rename to src/CTA.Rules.Actions/Csharp/AttributeActions.cs index b78e1dc0..2ccd87db 100644 --- a/src/CTA.Rules.Actions/AttributeActions.cs +++ b/src/CTA.Rules.Actions/Csharp/AttributeActions.cs @@ -4,7 +4,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -namespace CTA.Rules.Actions +namespace CTA.Rules.Actions.Csharp { /// /// List of actions that can run on attributes diff --git a/src/CTA.Rules.Actions/AttributeListActions.cs b/src/CTA.Rules.Actions/Csharp/AttributeListActions.cs similarity index 96% rename from src/CTA.Rules.Actions/AttributeListActions.cs rename to src/CTA.Rules.Actions/Csharp/AttributeListActions.cs index 016021f0..1ec1fd77 100644 --- a/src/CTA.Rules.Actions/AttributeListActions.cs +++ b/src/CTA.Rules.Actions/Csharp/AttributeListActions.cs @@ -5,7 +5,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -namespace CTA.Rules.Actions +namespace CTA.Rules.Actions.Csharp { /// /// List of actions that can run on attribute lists diff --git a/src/CTA.Rules.Actions/ClassActions.cs b/src/CTA.Rules.Actions/Csharp/ClassActions.cs similarity index 98% rename from src/CTA.Rules.Actions/ClassActions.cs rename to src/CTA.Rules.Actions/Csharp/ClassActions.cs index 85cfffa0..c3cc97bd 100644 --- a/src/CTA.Rules.Actions/ClassActions.cs +++ b/src/CTA.Rules.Actions/Csharp/ClassActions.cs @@ -9,8 +9,9 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; +using CSharpExtensions = Microsoft.CodeAnalysis.CSharp.CSharpExtensions; -namespace CTA.Rules.Actions +namespace CTA.Rules.Actions.Csharp { /// /// List of actions that can run on Class Declarations @@ -214,7 +215,7 @@ public Func Get { ClassDeclarationSyntax RemoveConstructorInitializer(SyntaxGenerator syntaxGenerator, ClassDeclarationSyntax node) { - var constructor = node.ChildNodes().Where(c => Microsoft.CodeAnalysis.CSharp.CSharpExtensions.Kind(c) == SyntaxKind.ConstructorDeclaration).FirstOrDefault(); + var constructor = node.ChildNodes().FirstOrDefault(c => c.IsKind(SyntaxKind.ConstructorDeclaration)); if (constructor != null) { ConstructorDeclarationSyntax constructorNode = (ConstructorDeclarationSyntax)constructor; @@ -249,7 +250,7 @@ public Func Get { ClassDeclarationSyntax AppendConstructorExpression(SyntaxGenerator syntaxGenerator, ClassDeclarationSyntax node) { - var constructor = node.Members.Where(c => Microsoft.CodeAnalysis.CSharp.CSharpExtensions.Kind(c) == SyntaxKind.ConstructorDeclaration).FirstOrDefault(); + var constructor = node.Members.FirstOrDefault(c => c.IsKind(SyntaxKind.ConstructorDeclaration)); if (constructor != null) { ConstructorDeclarationSyntax constructorNode = (ConstructorDeclarationSyntax)constructor; diff --git a/src/CTA.Rules.Actions/CompilationUnitActions.cs b/src/CTA.Rules.Actions/Csharp/CompilationUnitActions.cs similarity index 98% rename from src/CTA.Rules.Actions/CompilationUnitActions.cs rename to src/CTA.Rules.Actions/Csharp/CompilationUnitActions.cs index 96151367..0a0672e0 100644 --- a/src/CTA.Rules.Actions/CompilationUnitActions.cs +++ b/src/CTA.Rules.Actions/Csharp/CompilationUnitActions.cs @@ -6,7 +6,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -namespace CTA.Rules.Actions +namespace CTA.Rules.Actions.Csharp { /// /// List of actions that can run on Compilation Units diff --git a/src/CTA.Rules.Actions/ElementAccessActions.cs b/src/CTA.Rules.Actions/Csharp/ElementAccessActions.cs similarity index 97% rename from src/CTA.Rules.Actions/ElementAccessActions.cs rename to src/CTA.Rules.Actions/Csharp/ElementAccessActions.cs index 16d49cc6..7846a5ba 100644 --- a/src/CTA.Rules.Actions/ElementAccessActions.cs +++ b/src/CTA.Rules.Actions/Csharp/ElementAccessActions.cs @@ -5,7 +5,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -namespace CTA.Rules.Update +namespace CTA.Rules.Update.Csharp { /// /// List of actions that can run on Element Accesses diff --git a/src/CTA.Rules.Actions/ExpressionActions.cs b/src/CTA.Rules.Actions/Csharp/ExpressionActions.cs similarity index 98% rename from src/CTA.Rules.Actions/ExpressionActions.cs rename to src/CTA.Rules.Actions/Csharp/ExpressionActions.cs index deaa5249..fd8fa257 100644 --- a/src/CTA.Rules.Actions/ExpressionActions.cs +++ b/src/CTA.Rules.Actions/Csharp/ExpressionActions.cs @@ -6,7 +6,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -namespace CTA.Rules.Actions +namespace CTA.Rules.Actions.Csharp { public class ExpressionActions { diff --git a/src/CTA.Rules.Actions/IdentifierNameActions.cs b/src/CTA.Rules.Actions/Csharp/IdentifierNameActions.cs similarity index 98% rename from src/CTA.Rules.Actions/IdentifierNameActions.cs rename to src/CTA.Rules.Actions/Csharp/IdentifierNameActions.cs index 739cebbc..81f15d2e 100644 --- a/src/CTA.Rules.Actions/IdentifierNameActions.cs +++ b/src/CTA.Rules.Actions/Csharp/IdentifierNameActions.cs @@ -4,7 +4,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -namespace CTA.Rules.Actions +namespace CTA.Rules.Actions.Csharp { //TODO shouldn't this be inside ClassActions ? /// diff --git a/src/CTA.Rules.Actions/InterfaceActions.cs b/src/CTA.Rules.Actions/Csharp/InterfaceActions.cs similarity index 99% rename from src/CTA.Rules.Actions/InterfaceActions.cs rename to src/CTA.Rules.Actions/Csharp/InterfaceActions.cs index 87f7a1a7..eb73b423 100644 --- a/src/CTA.Rules.Actions/InterfaceActions.cs +++ b/src/CTA.Rules.Actions/Csharp/InterfaceActions.cs @@ -6,7 +6,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -namespace CTA.Rules.Actions +namespace CTA.Rules.Actions.Csharp { /// /// List of actions that can run on Interface Declarations diff --git a/src/CTA.Rules.Actions/InvocationExpressionActions.cs b/src/CTA.Rules.Actions/Csharp/InvocationExpressionActions.cs similarity index 99% rename from src/CTA.Rules.Actions/InvocationExpressionActions.cs rename to src/CTA.Rules.Actions/Csharp/InvocationExpressionActions.cs index 7d38395f..19d19689 100644 --- a/src/CTA.Rules.Actions/InvocationExpressionActions.cs +++ b/src/CTA.Rules.Actions/Csharp/InvocationExpressionActions.cs @@ -6,7 +6,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -namespace CTA.Rules.Actions +namespace CTA.Rules.Actions.Csharp { /// /// List of actions that can run on Invocation Expressions diff --git a/src/CTA.Rules.Actions/MethodDeclarationActions.cs b/src/CTA.Rules.Actions/Csharp/MethodDeclarationActions.cs similarity index 99% rename from src/CTA.Rules.Actions/MethodDeclarationActions.cs rename to src/CTA.Rules.Actions/Csharp/MethodDeclarationActions.cs index 55119797..b2919555 100644 --- a/src/CTA.Rules.Actions/MethodDeclarationActions.cs +++ b/src/CTA.Rules.Actions/Csharp/MethodDeclarationActions.cs @@ -6,7 +6,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -namespace CTA.Rules.Actions +namespace CTA.Rules.Actions.Csharp { /// /// List of actions that can run on Method Declarations diff --git a/src/CTA.Rules.Actions/NamespaceActions.cs b/src/CTA.Rules.Actions/Csharp/NamespaceActions.cs similarity index 98% rename from src/CTA.Rules.Actions/NamespaceActions.cs rename to src/CTA.Rules.Actions/Csharp/NamespaceActions.cs index e132f6a8..cd3247d1 100644 --- a/src/CTA.Rules.Actions/NamespaceActions.cs +++ b/src/CTA.Rules.Actions/Csharp/NamespaceActions.cs @@ -1,11 +1,11 @@ using System; -using System.Linq; -using Microsoft.CodeAnalysis; +using System.Linq; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -namespace CTA.Rules.Actions +namespace CTA.Rules.Actions.Csharp { public class NamespaceActions { @@ -19,11 +19,11 @@ NamespaceDeclarationSyntax RenameNamespace(SyntaxGenerator syntaxGenerator, Name return RenameNamespace; } - /// - /// Only support remove using directive actions inside Namespace block. - /// The add using directive actions will be happening in CompiliationUnit. - /// - /// + /// + /// Only support remove using directive actions inside Namespace block. + /// The add using directive actions will be happening in CompiliationUnit. + /// + /// /// public Func GetRemoveDirectiveAction(string @namespace) { @@ -37,8 +37,8 @@ NamespaceDeclarationSyntax RemoveDirective(SyntaxGenerator syntaxGenerator, Name if (removeItem == null) return node; - allUsings = allUsings.Remove(removeItem); - + allUsings = allUsings.Remove(removeItem); + node = node.WithUsings(allUsings).NormalizeWhitespace(); return RemoveDirective(syntaxGenerator, node); } diff --git a/src/CTA.Rules.Actions/ObjectCreationExpressionActions.cs b/src/CTA.Rules.Actions/Csharp/ObjectCreationExpressionActions.cs similarity index 99% rename from src/CTA.Rules.Actions/ObjectCreationExpressionActions.cs rename to src/CTA.Rules.Actions/Csharp/ObjectCreationExpressionActions.cs index b0b58b6c..438ba1b0 100644 --- a/src/CTA.Rules.Actions/ObjectCreationExpressionActions.cs +++ b/src/CTA.Rules.Actions/Csharp/ObjectCreationExpressionActions.cs @@ -6,7 +6,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -namespace CTA.Rules.Actions +namespace CTA.Rules.Actions.Csharp { public class ObjectCreationExpressionActions { diff --git a/src/CTA.Rules.Actions/MemberAccessActions.cs b/src/CTA.Rules.Actions/MemberAccessActions.cs index fd321c0c..5a81e24c 100644 --- a/src/CTA.Rules.Actions/MemberAccessActions.cs +++ b/src/CTA.Rules.Actions/MemberAccessActions.cs @@ -4,7 +4,6 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -using System.Linq; namespace CTA.Rules.Update { @@ -18,7 +17,19 @@ public Func GetAddCommentAction(string SyntaxNode AddComment(SyntaxGenerator syntaxGenerator, SyntaxNode node) { SyntaxTriviaList currentTrivia = node.GetLeadingTrivia(); - currentTrivia = currentTrivia.Insert(0, SyntaxFactory.SyntaxTrivia(SyntaxKind.MultiLineCommentTrivia, string.Format(Constants.CommentFormat, comment))); + if (node is Microsoft.CodeAnalysis.VisualBasic.Syntax.MemberAccessExpressionSyntax) + { + currentTrivia = currentTrivia.Insert(0, + Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory.SyntaxTrivia( + Microsoft.CodeAnalysis.VisualBasic.SyntaxKind.CommentTrivia, + string.Format(Constants.VbCommentFormat, comment))); + } + else + { + currentTrivia = currentTrivia.Insert(0, + SyntaxFactory.SyntaxTrivia(SyntaxKind.MultiLineCommentTrivia, + string.Format(Constants.CommentFormat, comment))); + } node = node.WithLeadingTrivia(currentTrivia).NormalizeWhitespace(); return node; } @@ -33,6 +44,10 @@ static SyntaxNode RemoveMemberAccess(SyntaxGenerator syntaxGenerator, SyntaxNode { return (node as MemberAccessExpressionSyntax).Expression; } + if (node is Microsoft.CodeAnalysis.VisualBasic.Syntax.MemberAccessExpressionSyntax) + { + return (node as Microsoft.CodeAnalysis.VisualBasic.Syntax.MemberAccessExpressionSyntax).Expression; + } return node; } return RemoveMemberAccess; diff --git a/src/CTA.Rules.Actions/VisualBasic/AttributeActions.cs b/src/CTA.Rules.Actions/VisualBasic/AttributeActions.cs new file mode 100644 index 00000000..255e4630 --- /dev/null +++ b/src/CTA.Rules.Actions/VisualBasic/AttributeActions.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; + +namespace CTA.Rules.Actions.VisualBasic +{ + /// + /// List of actions that can run on attributes + /// + public class AttributeActions + { + public Func GetChangeAttributeAction(string attributeName) + { + AttributeSyntax ChangeAttribute(SyntaxGenerator syntaxGenerator, AttributeSyntax node) + { + node = node.WithName(SyntaxFactory.ParseName(attributeName)).NormalizeWhitespace(); + return node; + } + return ChangeAttribute; + } + } +} diff --git a/src/CTA.Rules.Actions/VisualBasic/AttributeListActions.cs b/src/CTA.Rules.Actions/VisualBasic/AttributeListActions.cs new file mode 100644 index 00000000..1a1f45d5 --- /dev/null +++ b/src/CTA.Rules.Actions/VisualBasic/AttributeListActions.cs @@ -0,0 +1,30 @@ +using System; +using CTA.Rules.Config; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; + +namespace CTA.Rules.Actions.VisualBasic +{ + /// + /// List of actions that can run on attribute lists + /// + public class AttributeListActions + { + public Func GetAddCommentAction(string comment) + { + AttributeListSyntax AddComment(SyntaxGenerator syntaxGenerator, AttributeListSyntax node) + { + //TODO IS there possibility of NPE , if there are no Trivia or it always returns a node... + var currentTrivia = node.GetLeadingTrivia(); + currentTrivia = currentTrivia.Insert(0, + SyntaxFactory.SyntaxTrivia(SyntaxKind.CommentTrivia, + string.Format(Constants.CommentFormat, comment))); + node = node.WithLeadingTrivia(currentTrivia).NormalizeWhitespace(); + return node; + } + return AddComment; + } + } +} diff --git a/src/CTA.Rules.Actions/VisualBasic/CompilationUnitActions.cs b/src/CTA.Rules.Actions/VisualBasic/CompilationUnitActions.cs new file mode 100644 index 00000000..2eb799b6 --- /dev/null +++ b/src/CTA.Rules.Actions/VisualBasic/CompilationUnitActions.cs @@ -0,0 +1,81 @@ +using System; +using System.Linq; +using CTA.Rules.Config; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; + +namespace CTA.Rules.Actions.VisualBasic +{ + /// + /// List of actions that can run on Compilation Units + /// + public class CompilationUnitActions + { + public Func GetAddStatementAction(string @namespace) + { + CompilationUnitSyntax AddStatement(SyntaxGenerator syntaxGenerator, CompilationUnitSyntax node) + { + var allImports = node.Imports; + var importStatement = SyntaxFactory.ImportsStatement( + SyntaxFactory.SeparatedList() + .Add(SyntaxFactory.SimpleImportsClause( + SyntaxFactory.ParseName(@namespace))) + ) + .NormalizeWhitespace(); + allImports = allImports.Add(importStatement); + + node = node.WithImports(allImports).NormalizeWhitespace(); + return node; + } + return AddStatement; + } + + public Func GetRemoveStatementAction(string @namespace) + { + CompilationUnitSyntax RemoveStatement(SyntaxGenerator syntaxGenerator, CompilationUnitSyntax node) + { + // remove duplicate directive references, don't use List based approach because + // since we will be replacing the node after each loop, it update text span which will not remove duplicate namespaces + var allImports = node.Imports; + + // difference in visual basic is that a single import statement can have multiple import clauses (namespaces) + var removeItem = allImports.FirstOrDefault(i => i.ImportsClauses.Any(c => c.ToString() == @namespace)); + if (removeItem == null) + { + return node; + } + allImports = allImports.Remove(removeItem); + // re-add import if it had multiple clauses + if (removeItem.ImportsClauses.Count > 1) + { + var removeClause = removeItem.ImportsClauses.FirstOrDefault(c => c.ToString() == @namespace); + if (removeClause != null) + { + var newClauses = removeItem.ImportsClauses.Remove(removeClause); + var newImportStatement = removeItem.WithImportsClauses(newClauses); + allImports = allImports.Add(newImportStatement); + } + } + node = node.WithImports(allImports).NormalizeWhitespace(); + return RemoveStatement(syntaxGenerator, node); + } + + return RemoveStatement; + } + + public Func GetAddCommentAction(string comment) + { + CompilationUnitSyntax AddComment(SyntaxGenerator syntaxGenerator, CompilationUnitSyntax node) + { + SyntaxTriviaList currentTrivia = node.GetLeadingTrivia(); + //TODO see if this will lead NPE + currentTrivia = currentTrivia.Add(SyntaxFactory.SyntaxTrivia(SyntaxKind.CommentTrivia, string.Format(Constants.VbCommentFormat, comment))); + node = node.WithLeadingTrivia(currentTrivia).NormalizeWhitespace(); + return node; + } + return AddComment; + } + } +} diff --git a/src/CTA.Rules.Actions/VisualBasic/ElementAccessActions.cs b/src/CTA.Rules.Actions/VisualBasic/ElementAccessActions.cs new file mode 100644 index 00000000..aed80102 --- /dev/null +++ b/src/CTA.Rules.Actions/VisualBasic/ElementAccessActions.cs @@ -0,0 +1,36 @@ +using System; +using CTA.Rules.Config; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; + +namespace CTA.Rules.Update.VisualBasic +{ + /// + /// List of actions that can run on Element Accesses + /// + public class ElementAccessActions + { + public Func GetAddCommentAction(string comment) + { + MemberAccessExpressionSyntax AddComment(SyntaxGenerator syntaxGenerator, MemberAccessExpressionSyntax node) + { + SyntaxTriviaList currentTrivia = node.GetLeadingTrivia(); + currentTrivia = currentTrivia.Insert(0, SyntaxFactory.SyntaxTrivia(SyntaxKind.CommentTrivia, string.Format(Constants.VbCommentFormat, comment))); + node = node.WithLeadingTrivia(currentTrivia).NormalizeWhitespace(); + return node; + } + return AddComment; + } + public Func GetReplaceElementAccessAction(string newExpression) + { + MemberAccessExpressionSyntax ReplaceElement(SyntaxGenerator syntaxGenerator, MemberAccessExpressionSyntax node) + { + var addCommentFunc = GetAddCommentAction($"Replace with {newExpression}"); + return addCommentFunc(syntaxGenerator, node); + } + return ReplaceElement; + } + } +} diff --git a/src/CTA.Rules.Actions/VisualBasic/ExpressionActions.cs b/src/CTA.Rules.Actions/VisualBasic/ExpressionActions.cs new file mode 100644 index 00000000..b4902bdf --- /dev/null +++ b/src/CTA.Rules.Actions/VisualBasic/ExpressionActions.cs @@ -0,0 +1,41 @@ +using System; +using System.Linq; +using CTA.Rules.Config; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.Editing; + +namespace CTA.Rules.Actions.VisualBasic +{ + public class ExpressionActions + { + /// + /// This Method adds an await operator to the invocation expression. For example, Math.Round(5.5) invocation expression would return await Math.Abs(5.5). + /// + /// + public Func GetAddAwaitOperatorAction(string _) + { + SyntaxNode AddAwaitOperator(SyntaxGenerator syntaxGenerator, SyntaxNode node) + { + var newNode = SyntaxFactory.AwaitExpression( + SyntaxFactory.ParseExpression(node.WithoutTrivia().NormalizeWhitespace().ToFullString())); + newNode = newNode.WithTriviaFrom(node).NormalizeWhitespace(); + return SyntaxFactory.ExpressionStatement(newNode); + } + return AddAwaitOperator; + } + + public Func GetAddCommentAction(string comment) + { + SyntaxNode AddComment(SyntaxGenerator syntaxGenerator, SyntaxNode node) + { + var currentTrivia = node.GetLeadingTrivia(); + currentTrivia = currentTrivia.Add(SyntaxFactory.SyntaxTrivia(SyntaxKind.CommentTrivia, + string.Format(Constants.VbCommentFormat, comment))); + node = node.WithLeadingTrivia(currentTrivia).NormalizeWhitespace(); + return node; + } + return AddComment; + } + } +} diff --git a/src/CTA.Rules.Actions/VisualBasic/IdentifierNameActions.cs b/src/CTA.Rules.Actions/VisualBasic/IdentifierNameActions.cs new file mode 100644 index 00000000..d030b9cc --- /dev/null +++ b/src/CTA.Rules.Actions/VisualBasic/IdentifierNameActions.cs @@ -0,0 +1,66 @@ +using System; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; + +namespace CTA.Rules.Actions.VisualBasic +{ + /// + /// List of actions that can run on Identifier Names + /// + public class IdentifierNameActions + { + public Func GetReplaceIdentifierAction(string identifierName) + { + IdentifierNameSyntax ReplaceIdentifier(SyntaxGenerator syntaxGenerator, IdentifierNameSyntax node) + { + var leadingTrivia = node.GetLeadingTrivia(); + var trailingTrivia = node.GetTrailingTrivia(); + node = node.WithIdentifier(SyntaxFactory.Identifier(identifierName)).NormalizeWhitespace(); + node = node.WithLeadingTrivia(leadingTrivia); + node = node.WithTrailingTrivia(trailingTrivia); + return node; + } + return ReplaceIdentifier; + } + + public Func GetReplaceIdentifierInsideClassAction(string identifier, string classFullKey) + { + IdentifierNameSyntax ReplaceIdentifier2(SyntaxGenerator syntaxGenerator, IdentifierNameSyntax node) + { + var currentNode = node.Parent; + while (currentNode != null && currentNode.GetType() != typeof(ClassBlockSyntax)) + { + currentNode = currentNode.Parent; + } + var classNode = currentNode as ClassBlockSyntax; + + while (currentNode != null && currentNode.GetType() != typeof(NamespaceBlockSyntax)) + { + currentNode = currentNode.Parent; + } + + if (classNode == null || !(currentNode is NamespaceBlockSyntax namespaceNode)) { return node; } + + var fullName = string.Concat(namespaceNode.NamespaceStatement.Name, ".", classNode.ClassStatement.Identifier.Text); + + if (fullName == classFullKey) + { + var leadingTrivia = node.GetLeadingTrivia(); + var trailingTrivia = node.GetTrailingTrivia(); + node = node.WithIdentifier(SyntaxFactory.Identifier(identifier)).NormalizeWhitespace(); + node = node.WithLeadingTrivia(leadingTrivia); + node = node.WithTrailingTrivia(trailingTrivia); + return node; + } + else + { + return node; + } + + } + return ReplaceIdentifier2; + } + } +} diff --git a/src/CTA.Rules.Actions/VisualBasic/InterfaceActions.cs b/src/CTA.Rules.Actions/VisualBasic/InterfaceActions.cs new file mode 100644 index 00000000..406d0d6a --- /dev/null +++ b/src/CTA.Rules.Actions/VisualBasic/InterfaceActions.cs @@ -0,0 +1,120 @@ +using System; +using System.Linq; +using CTA.Rules.Config; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; + +namespace CTA.Rules.Actions.VisualBasic +{ + /// + /// List of actions that can run on Interface Declarations + /// + public class InterfaceActions + { + public Func GetChangeNameAction(string newName) + { + InterfaceBlockSyntax ChangeName(SyntaxGenerator syntaxGenerator, InterfaceBlockSyntax node) + { + node = node.WithInterfaceStatement( + node.InterfaceStatement.WithIdentifier(SyntaxFactory.Identifier(newName))); + return node.NormalizeWhitespace(); + } + return ChangeName; + } + public Func GetRemoveAttributeAction(string attributeName) + { + InterfaceBlockSyntax RemoveAttribute(SyntaxGenerator syntaxGenerator, InterfaceBlockSyntax node) + { + var attributeLists = node.InterfaceStatement.AttributeLists; + AttributeListSyntax attributeToRemove = null; + + foreach (var attributeList in attributeLists) + { + foreach (var attribute in attributeList.Attributes) + { + if (attribute.Name.ToString() == attributeName) + { + attributeToRemove = attributeList; + break; + } + } + } + + if (attributeToRemove != null) + { + attributeLists = attributeLists.Remove(attributeToRemove); + } + + var newStatement = node.InterfaceStatement.WithAttributeLists(attributeLists); + return node.WithInterfaceStatement(newStatement).NormalizeWhitespace(); + } + + return RemoveAttribute; + } + public Func GetAddAttributeAction(string attribute) + { + InterfaceBlockSyntax AddAttribute(SyntaxGenerator syntaxGenerator, InterfaceBlockSyntax node) + { + var attributeLists = node.InterfaceStatement.AttributeLists; + attributeLists = attributeLists.Add( + SyntaxFactory.AttributeList( + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.Attribute(SyntaxFactory.ParseName(attribute))))); + + var newStatement = node.InterfaceStatement.WithAttributeLists(attributeLists).NormalizeWhitespace(); + return node.WithInterfaceStatement(newStatement).NormalizeWhitespace(); + } + return AddAttribute; + } + public Func GetAddCommentAction(string comment) + { + InterfaceBlockSyntax AddComment(SyntaxGenerator syntaxGenerator, InterfaceBlockSyntax node) + { + var currentTrivia = node.GetLeadingTrivia(); + //TODO see if this will lead NPE + currentTrivia = currentTrivia.Add(SyntaxFactory.SyntaxTrivia(SyntaxKind.CommentTrivia, + string.Format(Constants.VbCommentFormat, comment))); + node = node.WithLeadingTrivia(currentTrivia).NormalizeWhitespace(); + return node; + } + return AddComment; + } + public Func GetAddMethodAction(string expression) + { + InterfaceBlockSyntax AddMethod(SyntaxGenerator syntaxGenerator, InterfaceBlockSyntax node) + { + var allMembers = node.Members; + var methodStatement = ParseMethodStatement(expression); + allMembers = allMembers.Add(methodStatement); + node = node.WithMembers(allMembers).NormalizeWhitespace(); + return node; + } + return AddMethod; + } + public Func GetRemoveMethodAction(string methodName) + { + //TODO what if there is operator overloading + InterfaceBlockSyntax AddMethod(SyntaxGenerator syntaxGenerator, InterfaceBlockSyntax node) + { + var allMethods = node.Members.OfType().ToList(); + var removeMethod = + allMethods.FirstOrDefault(m => m.Identifier.ToString() == methodName); + if (removeMethod != null) + { + node = node.RemoveNode(removeMethod, SyntaxRemoveOptions.KeepNoTrivia).NormalizeWhitespace(); + } + + return node; + } + return AddMethod; + } + + private MethodStatementSyntax ParseMethodStatement(string expression) + { + var tree = SyntaxFactory.ParseSyntaxTree(expression); + return tree.GetRoot().DescendantNodes().OfType().FirstOrDefault(); + } + } +} diff --git a/src/CTA.Rules.Actions/VisualBasic/InvocationExpressionActions.cs b/src/CTA.Rules.Actions/VisualBasic/InvocationExpressionActions.cs new file mode 100644 index 00000000..8abfad3e --- /dev/null +++ b/src/CTA.Rules.Actions/VisualBasic/InvocationExpressionActions.cs @@ -0,0 +1,171 @@ +using System; +using System.Linq; +using CTA.Rules.Config; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; + +namespace CTA.Rules.Actions.VisualBasic +{ + /// + /// List of actions that can run on Invocation Expressions + /// + public class InvocationExpressionActions + { + /// + /// This Method replaces the entire expression including the parameters. For example, Math.Round(5.5) invocation expression matching on Round with a newMethod parameter of Abs(9.5) would return Abs(9.5) not Math.Abs(9.5). + /// Please ensure to include the prefix part of the matching invocation expression since this method will replace it and the parameters as well. + /// + /// The new invocation expression including the method to replace with and the parameters. + /// The new invocation expression parameters to replace the old parameters with. + /// + public Func GetReplaceMethodWithObjectAndParametersAction(string newMethod, string newParameters) + { + //TODO what's the outcome if newMethod doesn't have a valid signature.. are there any options we could provide to parse expression ? + InvocationExpressionSyntax ReplaceMethod(SyntaxGenerator syntaxGenerator, InvocationExpressionSyntax node) + { + node = SyntaxFactory.InvocationExpression( + SyntaxFactory.IdentifierName(newMethod), + SyntaxFactory.ParseArgumentList(newParameters)) + .WithLeadingTrivia(node.GetLeadingTrivia()) + .NormalizeWhitespace(); + return node; + } + return ReplaceMethod; + } + + /// + /// This Method replaces the entire expression up to the matching invocation expression. For example, Math.Round(5.5) invocation expression matching on Round with a newMethod parameter of Abs would return Abs(5.5) not Math.Abs(5.5). + /// Please ensure to include the prefix part of the matching invocation expression since this method will replace it. + /// + /// The new invocation expression including the method to replace with. + /// + public Func GetReplaceMethodWithObjectAction(string newMethod) + { + //TODO what's the outcome if newMethod doesn't have a valid signature.. are there any options we could provide to parseexpression ? + InvocationExpressionSyntax ReplaceMethod(SyntaxGenerator syntaxGenerator, InvocationExpressionSyntax node) + { + node = node.WithExpression(SyntaxFactory.ParseExpression(newMethod)) + .WithLeadingTrivia(node.GetLeadingTrivia()) + .NormalizeWhitespace(); + return node; + } + return ReplaceMethod; + } + + /// + /// This Method replaces the entire expression up to the matching invocation expression. It also adds the type from the typed argument list to the list of arguments. + /// Example: DependencyResolver.Current.GetService() becomes DependencyResolver.Current.GetService(typeof(object)) + /// + /// The new invocation expression including the method to replace with. + /// + public Func GetReplaceMethodWithObjectAddTypeAction(string newMethod) + { + InvocationExpressionSyntax ReplaceMethod(SyntaxGenerator syntaxGenerator, InvocationExpressionSyntax node) + { + var typeToAdd = node.Expression.DescendantNodes()?.OfType()?.FirstOrDefault()?.TypeArgumentList; + + if (typeToAdd != null) + { + var argumentList = SyntaxFactory.ParseArgumentList($"(TypeOf {typeToAdd.Arguments.ToString()})"); + node = node.WithArgumentList(argumentList).WithLeadingTrivia(node.GetLeadingTrivia()); + } + + // in the test, the NormalizeWhitespace() was added weird white spaces .GetService(TypeOf Object )" + node = node.WithExpression(SyntaxFactory.ParseExpression(newMethod)).WithLeadingTrivia(node.GetLeadingTrivia()); + return node; + } + return ReplaceMethod; + } + + /// + /// This Method replaces only matching method in the invocation expression and its parameters. + /// For example, Math.Round(5.5) invocation expression matching on Round with a newMethod parameter of Abs and an oldMethod parameter of Round with parameters of 8.5 would return Math.Abs(8.5). + /// + /// The matching method in the invocation expression to be replaced. + /// The new method to replace the old method with in the invocation expression. + /// The new invocation expression parameters to replace the old parameters with. + /// + public Func GetReplaceMethodAndParametersAction(string oldMethod, string newMethod, string newParameters) + { + //TODO what's the outcome if newMethod doesn't have a valid signature.. are there any options we could provide to parseexpression ? + InvocationExpressionSyntax ReplaceOnlyMethod(SyntaxGenerator syntaxGenerator, InvocationExpressionSyntax node) + { + node = node.WithExpression(SyntaxFactory.ParseExpression(node.Expression.ToString().Replace(oldMethod, newMethod))) + .WithArgumentList(SyntaxFactory.ParseArgumentList(newParameters)) + .WithLeadingTrivia(node.GetLeadingTrivia()) + .NormalizeWhitespace(); + return node; + } + return ReplaceOnlyMethod; + } + + /// + /// This Method replaces only matching method in the invocation expression. For example, Math.Round(5.5) invocation expression matching on Round with a newMethod parameter of Abs and an oldMethod parameter of Round would return Math.Abs(5.5). + /// + /// The matching method in the invocation expression to be replaced. + /// The new method to replace the old method with in the invocation expression. + /// + public Func GetReplaceMethodOnlyAction(string oldMethod, string newMethod) + { + //TODO what's the outcome if newMethod doesn't have a valid signature.. are there any options we could provide to parseexpression ? + InvocationExpressionSyntax ReplaceOnlyMethod(SyntaxGenerator syntaxGenerator, InvocationExpressionSyntax node) + { + node = node.WithExpression(SyntaxFactory.ParseExpression(node.Expression.ToString().Replace(oldMethod, newMethod))) + .WithLeadingTrivia(node.GetLeadingTrivia()) + .NormalizeWhitespace(); + return node; + } + return ReplaceOnlyMethod; + } + + /// + /// This Method replaces only the parameters in the invocation expression. For example, Math.Round(5.5) invocation expression matching on Round with a parameter of (8) would return Math.Abs(8). + /// + /// The new invocation expression parameters to replace the old parameters with. + /// + public Func GetReplaceParametersOnlyAction(string newParameters) + { + //TODO what's the outcome if newMethod doesn't have a valid signature.. are there any options we could provide to parseexpression ? + InvocationExpressionSyntax ReplaceOnlyMethod(SyntaxGenerator syntaxGenerator, InvocationExpressionSyntax node) + { + node = node.WithArgumentList(SyntaxFactory.ParseArgumentList(newParameters)).NormalizeWhitespace(); + return node; + } + return ReplaceOnlyMethod; + } + + public Func GetAppendMethodAction(string appendMethod) + { + InvocationExpressionSyntax ReplaceMethod(SyntaxGenerator syntaxGenerator, InvocationExpressionSyntax node) + { + var operatorToken = node.DescendantTokens().FirstOrDefault(t => t.IsKind(SyntaxKind.DotToken)); + node = SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.InvocationExpression(node), + operatorToken, + SyntaxFactory.IdentifierName(SyntaxFactory.ParseName(appendMethod).ToString()) + ), + SyntaxFactory.ArgumentList() + ).NormalizeWhitespace(); + return node; + } + return ReplaceMethod; + } + + public Func GetAddCommentAction(string comment) + { + InvocationExpressionSyntax AddComment(SyntaxGenerator syntaxGenerator, InvocationExpressionSyntax node) + { + SyntaxTriviaList currentTrivia = node.GetLeadingTrivia(); + currentTrivia = currentTrivia.Add(SyntaxFactory.SyntaxTrivia(SyntaxKind.CommentTrivia, string.Format(Constants.VbCommentFormat, comment))); + node = node.WithLeadingTrivia(currentTrivia).NormalizeWhitespace(); + return node; + } + return AddComment; + } + } +} + diff --git a/src/CTA.Rules.Actions/VisualBasic/MethodBlockActions.cs b/src/CTA.Rules.Actions/VisualBasic/MethodBlockActions.cs new file mode 100644 index 00000000..5cc50b31 --- /dev/null +++ b/src/CTA.Rules.Actions/VisualBasic/MethodBlockActions.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using CTA.Rules.Config; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; + +namespace CTA.Rules.Actions.VisualBasic +{ + /// + /// List of actions that can run on Method Declarations + /// + public class MethodBlockActions + { + public Func GetAddCommentAction(string comment, string dontUseCTAPrefix = null) + { + MethodBlockSyntax AddComment(SyntaxGenerator syntaxGenerator, MethodBlockSyntax node) + { + SyntaxTriviaList currentTrivia = node.GetLeadingTrivia(); + var commentFormat = !string.IsNullOrEmpty(dontUseCTAPrefix) ? Constants.VbCommentFormatBlank : Constants.VbCommentFormat; + currentTrivia = currentTrivia.Insert(0, SyntaxFactory.SyntaxTrivia(SyntaxKind.CommentTrivia, string.Format(commentFormat, comment))); + node = node.WithLeadingTrivia(currentTrivia).NormalizeWhitespace(); + return node; + } + return AddComment; + } + + public Func GetAppendExpressionAction(string expression) + { + // TODO: This will add an expression at the bottom of a method body, in the future we should add granularity for where to add the expression within a method body + Func appendExpression = (SyntaxGenerator syntaxGenerator, MethodBlockSyntax node) => + { + StatementSyntax statementExpression = expression.Contains("Await") + ? ParseAwaitExpression(expression) + : SyntaxFactory.ParseExecutableStatement(expression); + if (!statementExpression.FullSpan.IsEmpty) + { + node = node.AddStatements(statementExpression).NormalizeWhitespace(); + } + + return node; + }; + return appendExpression; + } + + public Func GetChangeMethodNameAction(string newMethodName) + { + MethodBlockSyntax ChangeMethodName(SyntaxGenerator syntaxGenerator, MethodBlockSyntax node) + { + var newMethodStatement = node.SubOrFunctionStatement.WithIdentifier(SyntaxFactory.Identifier(newMethodName)); + var newMethodNode = node.WithSubOrFunctionStatement(newMethodStatement).NormalizeWhitespace(); + return newMethodNode; + } + return ChangeMethodName; + } + + public Func GetChangeMethodToReturnTaskTypeAction() + { + MethodBlockSyntax ChangeMethodToReturnTaskType(SyntaxGenerator syntaxGenerator, MethodBlockSyntax node) + { + var methodStatement = node.SubOrFunctionStatement; + var oldAsClause = methodStatement.AsClause; + if (methodStatement.IsKind(SyntaxKind.SubStatement)) + { + // if sub, convert to function and return task + var functionStatement = SyntaxFactory.FunctionStatement(methodStatement.Identifier); + functionStatement = + functionStatement.WithAsClause( + SyntaxFactory.SimpleAsClause(SyntaxFactory.IdentifierName("Task"))) + .WithModifiers(methodStatement.Modifiers.Add(SyntaxFactory.Token(SyntaxKind.AsyncKeyword))); + + var newNode = SyntaxFactory.MethodBlock(SyntaxKind.FunctionBlock, + functionStatement, node.Statements, SyntaxFactory.EndFunctionStatement()); + return newNode.NormalizeWhitespace(); + } + + // already function, need to wrap return in task + var genericName = + SyntaxFactory.GenericName("Task", SyntaxFactory.TypeArgumentList(oldAsClause.Type)); + methodStatement = methodStatement.WithAsClause(SyntaxFactory.SimpleAsClause(genericName)) + .WithModifiers(methodStatement.Modifiers.Add(SyntaxFactory.Token(SyntaxKind.AsyncKeyword))); + return node.WithSubOrFunctionStatement(methodStatement).NormalizeWhitespace(); + } + return ChangeMethodToReturnTaskType; + } + + public Func GetRemoveMethodParametersAction() + { + MethodBlockSyntax RemoveMethodParametersAction(SyntaxGenerator syntaxGenerator, MethodBlockSyntax node) + { + var parameters = new List(); + var newMethodStatement = + node.SubOrFunctionStatement + .WithParameterList(SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(parameters))) + .NormalizeWhitespace(); + return node.WithSubOrFunctionStatement(newMethodStatement).NormalizeWhitespace(); + } + return RemoveMethodParametersAction; + } + + public Func GetAddParametersToMethodAction(string types, string identifiers) + { + MethodBlockSyntax AddParametersToMethodAction(SyntaxGenerator syntaxGenerator, MethodBlockSyntax node) + { + var newMethodStatement = node.SubOrFunctionStatement; + if (!string.IsNullOrWhiteSpace(identifiers) && !string.IsNullOrWhiteSpace(types)) + { + var identifiersArray = identifiers.Split(',', StringSplitOptions.RemoveEmptyEntries); + var typesArray = types.Split(',', StringSplitOptions.RemoveEmptyEntries); + + if (identifiersArray.Length == typesArray.Length) + { + var parameters = new List(); + for (var i = 0; i < identifiersArray.Length; i++) + { + parameters.Add(SyntaxFactory + .Parameter(SyntaxFactory.ModifiedIdentifier(identifiersArray[i])) + .WithAsClause( + SyntaxFactory.SimpleAsClause(SyntaxFactory.ParseTypeName(typesArray[i]))) + .NormalizeWhitespace()); + } + + var separatedSyntaxList = SyntaxFactory.SeparatedList(parameters); + newMethodStatement = + newMethodStatement.WithParameterList(SyntaxFactory.ParameterList(separatedSyntaxList)) + .NormalizeWhitespace(); + } + } + + return node.WithSubOrFunctionStatement(newMethodStatement).NormalizeWhitespace(); + } + + return AddParametersToMethodAction; + } + + public Func GetCommentMethodAction( + string comment = null, + string dontUseCTAPrefix = null) + { + MethodBlockSyntax CommentMethodAction(SyntaxGenerator syntaxGenerator, MethodBlockSyntax node) + { + var bodyStatments = node.Statements; + var newBody = new SyntaxTriviaList(); + foreach (var statement in bodyStatments) + { + var newLine = SyntaxFactory.SyntaxTrivia(SyntaxKind.CommentTrivia, @$"' {statement.ToFullString()}"); + newBody = newBody.Add(newLine).Add(SyntaxFactory.SyntaxTrivia(SyntaxKind.EndOfLineTrivia, "")); + } + + var endStatement = node.EndSubOrFunctionStatement.WithLeadingTrivia(newBody); + var newMethodNode = node.WithEndSubOrFunctionStatement(endStatement) + .WithStatements(new SyntaxList()); + + if (!string.IsNullOrWhiteSpace(comment)) + { + var addCommentsToMethodFunc = GetAddCommentAction(comment, dontUseCTAPrefix); + return addCommentsToMethodFunc(syntaxGenerator, newMethodNode); + } + + return newMethodNode.NormalizeWhitespace(); + } + return CommentMethodAction; + } + + public Func GetAddExpressionToMethodAction(string expression) + { + MethodBlockSyntax AddExpressionToMethodAction(SyntaxGenerator syntaxGenerator, MethodBlockSyntax node) + { + var newMethodNode = node; + var parsedExpression = expression.Contains("Await") + ? ParseAwaitExpression(expression) + : SyntaxFactory.ParseExecutableStatement(expression); + if (!parsedExpression.FullSpan.IsEmpty) + { + var body = node.Statements; + int returnIndex = body.IndexOf(s => s.IsKind(SyntaxKind.ReturnStatement)); + if (returnIndex >= 0) + { + // insert new statement before return + body = body.Insert(returnIndex, parsedExpression); + newMethodNode = node.WithStatements(body); + } + else + { + newMethodNode = node.AddStatements(parsedExpression); + } + } + + return newMethodNode.NormalizeWhitespace(); + } + + return AddExpressionToMethodAction; + } + + private StatementSyntax ParseAwaitExpression(string expression) + { + expression = expression.Replace("Await", "", StringComparison.OrdinalIgnoreCase); + var parsedExpression = SyntaxFactory.ParseExpression(expression); + var awaitExpression = SyntaxFactory.AwaitExpression(parsedExpression); + return SyntaxFactory.ExpressionStatement(awaitExpression); + } + } +} diff --git a/src/CTA.Rules.Actions/VisualBasic/NamespaceActions.cs b/src/CTA.Rules.Actions/VisualBasic/NamespaceActions.cs new file mode 100644 index 00000000..c988448c --- /dev/null +++ b/src/CTA.Rules.Actions/VisualBasic/NamespaceActions.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; + +namespace CTA.Rules.Actions.VisualBasic +{ + public class NamespaceActions + { + public Func GetRenameNamespaceAction(string newName) + { + NamespaceBlockSyntax RenameNamespace(SyntaxGenerator syntaxGenerator, NamespaceBlockSyntax node) + { + node = node.WithNamespaceStatement(SyntaxFactory.NamespaceStatement(SyntaxFactory.ParseName(newName))); + return node; + } + return RenameNamespace; + } + + /// + /// Only support remove using directive actions inside Namespace block. + /// The add using directive actions will be happening in CompiliationUnit. + /// + /// + /// + public Func GetRemoveDirectiveAction(string @namespace) + { + // Imports are not allowed within a namespace block in visual basic + throw new NotImplementedException(); + } + } +} diff --git a/src/CTA.Rules.Actions/VisualBasic/ObjectCreationExpressionActions.cs b/src/CTA.Rules.Actions/VisualBasic/ObjectCreationExpressionActions.cs new file mode 100644 index 00000000..46560162 --- /dev/null +++ b/src/CTA.Rules.Actions/VisualBasic/ObjectCreationExpressionActions.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis; +using CTA.Rules.Config; + + +namespace CTA.Rules.Actions.VisualBasic +{ + public class ObjectCreationExpressionActions + { + public Func GetReplaceObjectinitializationAction(string newStatement) + { + ExpressionSyntax Action(SyntaxGenerator syntaxGenerator, ObjectCreationExpressionSyntax node) + { + var newNode = SyntaxFactory.ParseExpression(newStatement).NormalizeWhitespace(); + return newNode; + } + return Action; + } + + public Func GetReplaceObjectWithInvocationAction(string newExpression, string useParameters) + { + ExpressionSyntax Action(SyntaxGenerator syntaxGenerator, ObjectCreationExpressionSyntax node) + { + bool.TryParse(useParameters, out var useParam); + + var newNode = SyntaxFactory.ParseExpression(newExpression) as InvocationExpressionSyntax; + if (useParam) + { + newNode = newNode.WithArgumentList(node.ArgumentList); + } + return newNode.NormalizeWhitespace(); + } + return Action; + } + + public Func GetReplaceOrAddObjectPropertyIdentifierAction(string oldMember, string newMember, string newValueIfAdding = null) + { + ExpressionSyntax Action(SyntaxGenerator syntaxGenerator, ObjectCreationExpressionSyntax node) + { + if (node.Initializer != null && node.Initializer.IsKind(SyntaxKind.ObjectMemberInitializer)) + { + var initializer = (ObjectMemberInitializerSyntax)node.Initializer; + var memberList = initializer.Initializers.Where(n => n.Kind() == SyntaxKind.NamedFieldInitializer).ToList(); + if (memberList.Any()) + { + var assignMemberList = memberList.Select(n => (NamedFieldInitializerSyntax)n); + var member = assignMemberList.FirstOrDefault(n => n.Name.ToFullString().Contains(oldMember)); + SeparatedSyntaxList newInitializers; + if (member != null) + { + var newExpression = SyntaxFactory.NamedFieldInitializer((IdentifierNameSyntax)syntaxGenerator.IdentifierName(newMember), member.Expression); + newInitializers = initializer.Initializers.Replace(member, newExpression); + } + else + { + var newExpression = SyntaxFactory.NamedFieldInitializer((IdentifierNameSyntax)syntaxGenerator.IdentifierName(newMember), (IdentifierNameSyntax)syntaxGenerator.IdentifierName(newValueIfAdding)); + newInitializers = initializer.Initializers.Add(newExpression); + } + var newInitializer = initializer.WithInitializers(newInitializers); + node = node.WithInitializer(newInitializer).NormalizeWhitespace(); + } + } + return node; + } + return Action; + } + + public Func GetReplaceObjectPropertyValueAction(string oldMember, string newMember) + { + ExpressionSyntax Action(SyntaxGenerator syntaxGenerator, ObjectCreationExpressionSyntax node) + { + if (node.Initializer != null && node.Initializer.IsKind(SyntaxKind.ObjectMemberInitializer)) + { + var initializer = (ObjectMemberInitializerSyntax)node.Initializer; + var memberList = initializer.Initializers.Where(n => n.Kind() == SyntaxKind.NamedFieldInitializer).ToList(); + if (memberList.Any()) + { + var assignMemberList = memberList.Select(n => (NamedFieldInitializerSyntax)n); + var member = assignMemberList.FirstOrDefault(n => n.Expression.ToFullString().Contains(oldMember)); + if (member != null) + { + var right = SyntaxFactory.ParseExpression(member.Expression.ToFullString().Replace(oldMember, newMember)); + var newExpression = SyntaxFactory.NamedFieldInitializer(member.Name, right); + var newInitializers = initializer.Initializers.Replace(member, newExpression); + var newInitializer = initializer.WithInitializers(newInitializers); + node = node.WithInitializer(newInitializer).NormalizeWhitespace(); + } + } + } + return node; + } + return Action; + } + + public Func GetAddCommentAction(string comment) + { + ObjectCreationExpressionSyntax AddComment(SyntaxGenerator syntaxGenerator, ObjectCreationExpressionSyntax node) + { + SyntaxTriviaList currentTrivia = node.GetLeadingTrivia(); + currentTrivia = currentTrivia.Add(SyntaxFactory.SyntaxTrivia(SyntaxKind.CommentTrivia, string.Format(Constants.CommentFormat, comment))); + node = node.WithLeadingTrivia(currentTrivia).NormalizeWhitespace(); + return node; + } + return AddComment; + } + + } +} diff --git a/src/CTA.Rules.Actions/VisualBasic/TypeBlockActions.cs b/src/CTA.Rules.Actions/VisualBasic/TypeBlockActions.cs new file mode 100644 index 00000000..57d6f8ee --- /dev/null +++ b/src/CTA.Rules.Actions/VisualBasic/TypeBlockActions.cs @@ -0,0 +1,533 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using CTA.Rules.Config; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; + +namespace CTA.Rules.Actions.VisualBasic +{ + /// + /// List of actions that can run on Class Blocks + /// + public class TypeBlockActions + { + public Func GetRemoveBaseClassAction(string baseClass) + { + TypeBlockSyntax RemoveBaseClass(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + var currentBaseTypes = node.Inherits.FirstOrDefault()?.Types ?? new SeparatedSyntaxList(); + SeparatedSyntaxList newBaseTypes = new SeparatedSyntaxList(); + + foreach (var baseTypeSyntax in currentBaseTypes) + { + if (!baseTypeSyntax.GetText().ToString().Trim().Equals(baseClass)) + { + newBaseTypes.Add(baseTypeSyntax); + } + } + + node = node.WithInherits(new SyntaxList + { + SyntaxFactory.InheritsStatement().WithTypes(newBaseTypes) + }); + + return node; + } + + return RemoveBaseClass; + } + + public Func GetAddBaseClassAction(string baseClass) + { + TypeBlockSyntax AddBaseClass(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + if (syntaxGenerator != null) + { + node = (TypeBlockSyntax)syntaxGenerator.AddBaseType(node, SyntaxFactory.ParseName(baseClass)); + } + else + { + var baseType = SyntaxFactory.InheritsStatement(SyntaxFactory.ParseTypeName(baseClass)); + node = node.WithInherits(new SyntaxList(baseType)); + } + return node; + } + return AddBaseClass; + } + + public Func GetChangeNameAction(string className) + { + TypeBlockSyntax ChangeName(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + node = node.WithBlockStatement(node.BlockStatement.WithIdentifier(SyntaxFactory.Identifier(className))) + .NormalizeWhitespace(); + return node; + } + return ChangeName; + } + + public Func GetRemoveAttributeAction(string attributeName) + { + TypeBlockSyntax RemoveAttribute(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + var attributeLists = node.BlockStatement.AttributeLists; + AttributeListSyntax attributeToRemove = null; + + foreach (var attributeList in attributeLists) + { + foreach (var attribute in attributeList.Attributes) + { + if (attribute.Name.ToString() == attributeName) + { + attributeToRemove = attributeList; + break; + } + } + } + + if (attributeToRemove != null) + { + attributeLists = attributeLists.Remove(attributeToRemove); + } + + node = node.WithBlockStatement(node.BlockStatement.WithAttributeLists(attributeLists)) + .NormalizeWhitespace(); + return node; + } + + return RemoveAttribute; + } + public Func GetAddAttributeAction(string attribute) + { + TypeBlockSyntax AddAttribute(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + var attributeLists = node.BlockStatement.AttributeLists; + attributeLists = attributeLists.Add( + SyntaxFactory.AttributeList( + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.Attribute(SyntaxFactory.ParseName(attribute))))); + + node = node.WithBlockStatement(node.BlockStatement.WithAttributeLists(attributeLists)) + .NormalizeWhitespace(); + return node; + } + return AddAttribute; + } + public Func GetAddCommentAction(string comment, string dontUseCTAPrefix = null) + { + TypeBlockSyntax AddComment(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + SyntaxTriviaList currentTrivia = node.GetLeadingTrivia(); + var commentFormat = dontUseCTAPrefix != null ? Constants.VbCommentFormatBlank : Constants.VbCommentFormat; + currentTrivia = currentTrivia.Add(SyntaxFactory.SyntaxTrivia(SyntaxKind.CommentTrivia, string.Format(commentFormat, comment))); + node = node.WithLeadingTrivia(currentTrivia).NormalizeWhitespace(); + return node; + } + return AddComment; + } + public Func GetAddMethodAction(string expression) + { + TypeBlockSyntax AddMethod(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + var methodBlockSyntax = SyntaxFactory.ParseSyntaxTree(expression).GetRoot().DescendantNodes() + .OfType().FirstOrDefault(); + if (methodBlockSyntax != null) + { + node = node.AddMembers(methodBlockSyntax); + } + return node.NormalizeWhitespace(); + } + return AddMethod; + } + public Func GetRemoveMethodAction(string methodName) + { + //TODO what if there is operator overloading + TypeBlockSyntax RemoveMethod(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + var allMembers = node.Members.ToList(); + var allMethods = allMembers.OfType(); + if (allMethods.Any()) + { + var removeMethod = allMethods.FirstOrDefault(m => m.Identifier.ToString() == methodName); + if (removeMethod != null) + { + node = node.RemoveNode(removeMethod, SyntaxRemoveOptions.KeepNoTrivia).NormalizeWhitespace(); + } + } + + return node; + } + return RemoveMethod; + } + public Func GetRenameClassAction(string newClassName) + { + TypeBlockSyntax RenameClass(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + node = node.WithBlockStatement(node.BlockStatement.WithIdentifier(SyntaxFactory.Identifier(newClassName))).NormalizeWhitespace(); + return node; + } + return RenameClass; + } + public Func GetReplaceMethodModifiersAction(string methodName, string modifiers) + { + TypeBlockSyntax ReplaceMethodModifiers(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + var allMethods = node.Members.OfType(); + if (allMethods.Any()) + { + var replaceMethod = allMethods.FirstOrDefault(m => m.Identifier.ToString() == methodName); + if (replaceMethod != null) + { + var allModifiers = modifiers.Split(new char[] { ' ', ',' }); + if (allModifiers.All(m => Constants.SupportedVbMethodModifiers.Contains(m))) + { + SyntaxTokenList tokenList = new SyntaxTokenList(); + foreach (string m in allModifiers) + { + if (m == "Async") + { + // for some reason syntax factory can't parse that async is a keyword + tokenList = tokenList.Add(SyntaxFactory.Token(SyntaxKind.AsyncKeyword)); + } + else + { + tokenList = tokenList.Add(SyntaxFactory.ParseToken(m)); + } + } + var newMethod = replaceMethod.WithModifiers(tokenList); + + node = node.WithMembers(node.Members.Replace(replaceMethod, newMethod)).NormalizeWhitespace(); + } + } + } + + return node; + } + return ReplaceMethodModifiers; + } + public Func GetAddExpressionAction(string expression) + { + TypeBlockSyntax AddExpression(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + var parsedExpression = SyntaxFactory.ParseExecutableStatement(expression); + if (!parsedExpression.FullSpan.IsEmpty) + { + var nodeDeclarations = node.Members; + nodeDeclarations = nodeDeclarations.Insert(0, parsedExpression); + node = node.WithMembers(nodeDeclarations).NormalizeWhitespace(); + } + return node; + } + return AddExpression; + } + public Func GetRemoveConstructorInitializerAction(string initializerArgument) + { + TypeBlockSyntax RemoveConstructorInitializer(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + var constructor = node.ChildNodes().FirstOrDefault(c => c.IsKind(SyntaxKind.ConstructorBlock)); + if (constructor != null) + { + var constructorNode = (ConstructorBlockSyntax)constructor; + var newArguments = new SeparatedSyntaxList(); + // base initializers should be the first statement + var firstStatement = constructorNode.Statements.FirstOrDefault(); + if (firstStatement != null && + firstStatement.DescendantNodes().Any(s => s.IsKind(SyntaxKind.MyBaseExpression))) + { + var arguments = firstStatement.DescendantNodes().OfType() + .FirstOrDefault(); + if (arguments != null) + { + foreach (var arg in arguments.Arguments) + { + if (!arg.GetText().ToString().Trim().Equals(initializerArgument)) + { + newArguments = newArguments.Add(arg); + } + } + + if (newArguments.Any()) + { + node = node.ReplaceNode(arguments, SyntaxFactory.ArgumentList(newArguments)) + .NormalizeWhitespace(); + } + } + } + } + return node; + } + + return RemoveConstructorInitializer; + } + public Func GetAppendConstructorExpressionAction(string expression) + { + TypeBlockSyntax AppendConstructorExpression(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + var constructor = node.Members.FirstOrDefault(c => c.IsKind(SyntaxKind.ConstructorBlock)); + if (constructor != null) + { + ConstructorBlockSyntax constructorNode = (ConstructorBlockSyntax)constructor; + StatementSyntax statementExpression = SyntaxFactory.ParseExecutableStatement(expression); + if (!statementExpression.FullSpan.IsEmpty) + { + constructorNode = constructorNode.AddStatements(statementExpression); + node = node.ReplaceNode(constructor, constructorNode).NormalizeWhitespace(); + } + } + return node; + } + return AppendConstructorExpression; + } + + public Func GetCreateConstructorAction(string types, string identifiers) + { + TypeBlockSyntax CreateConstructor(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + // constructors in vb are just named new + var constructorStatementNode = SyntaxFactory.SubNewStatement() + .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); + + // Add constructor parameters if provided + if (!string.IsNullOrWhiteSpace(identifiers) && !string.IsNullOrWhiteSpace(types)) + { + var identifiersArray = identifiers.Split(',', StringSplitOptions.RemoveEmptyEntries); + var typesArray = types.Split(',', StringSplitOptions.RemoveEmptyEntries); + + if (identifiersArray.Length == typesArray.Length) + { + List parameters = new List(); + for (var i = 0; i < identifiersArray.Length; i++) + { + parameters.Add(SyntaxFactory + .Parameter(SyntaxFactory.ModifiedIdentifier(identifiersArray[i])) + .WithAsClause(SyntaxFactory.SimpleAsClause(SyntaxFactory.ParseTypeName(typesArray[i])))); + } + + constructorStatementNode = constructorStatementNode.AddParameterListParameters(parameters.ToArray()); + } + } + + var constructorBlock = SyntaxFactory.ConstructorBlock(constructorStatementNode); + node = node.AddMembers(constructorBlock).NormalizeWhitespace(); + + return node; + } + return CreateConstructor; + } + + public Func GetChangeMethodNameAction(string existingMethodName, string newMethodName) + { + TypeBlockSyntax ChangeMethodName(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + // if we have more than one method with same name return without making changes + var methodNode = GetMethodNode(node, existingMethodName); + if (methodNode != null) + { + var methodActions = new MethodBlockActions(); + var changeMethodNameFunc = methodActions.GetChangeMethodNameAction(newMethodName); + var newMethodNode = changeMethodNameFunc(syntaxGenerator, methodNode); + node = node.ReplaceNode(methodNode, newMethodNode); + } + return node; + } + return ChangeMethodName; + } + + public Func GetChangeMethodToReturnTaskTypeAction(string methodName) + { + TypeBlockSyntax ChangeMethodToReturnTaskType(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + // if we have more than one method with same name return without making changes + var methodNode = GetMethodNode(node, methodName); + if (methodNode != null) + { + var methodActions = new MethodBlockActions(); + var changeMethodToReturnTaskTypeActionFunc = methodActions.GetChangeMethodToReturnTaskTypeAction(); + var newMethodNode = changeMethodToReturnTaskTypeActionFunc(syntaxGenerator, methodNode); + node = node.ReplaceNode(methodNode, newMethodNode); + } + return node; + } + return ChangeMethodToReturnTaskType; + } + + public Func GetRemoveMethodParametersAction(string methodName) + { + TypeBlockSyntax RemoveMethodParameters(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + // if we have more than one method with same name return without making changes + var methodNode = GetMethodNode(node, methodName); + if (methodNode != null) + { + var parameters = methodNode.SubOrFunctionStatement.ParameterList.Parameters; + MethodBlockActions methodActions = new MethodBlockActions(); + var removeMethodParametersActionFunc = methodActions.GetRemoveMethodParametersAction(); + var newMethodNode = removeMethodParametersActionFunc(syntaxGenerator, methodNode); + node = node.ReplaceNode(methodNode, newMethodNode); + } + return node.NormalizeWhitespace(); + } + return RemoveMethodParameters; + } + + public Func GetCommentMethodAction(string methodName, string comment = null, string dontUseCTAPrefix = null) + { + TypeBlockSyntax CommentMethod(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + // if we have more than one method with same name return without making changes + var methodNode = GetMethodNode(node, methodName); + + if (methodNode != null) + { + var methodActions = new MethodBlockActions(); + var commentMethodAction = methodActions.GetCommentMethodAction(comment, dontUseCTAPrefix); + var newMethodNode = commentMethodAction(syntaxGenerator, methodNode); + + var methodStatementComment = + SyntaxFactory.CommentTrivia($"' {newMethodNode.SubOrFunctionStatement.ToFullString()}"); + var methodBodyComment = newMethodNode.EndSubOrFunctionStatement.GetLeadingTrivia(); + var methodEndStatementComment = + SyntaxFactory.CommentTrivia($"' {newMethodNode.EndSubOrFunctionStatement.ToString()}"); + + var trivia = new SyntaxTriviaList(); + trivia = trivia.Add(methodStatementComment); + trivia = trivia.AddRange(methodBodyComment); + trivia = trivia.Add(methodEndStatementComment); + + node = node.RemoveNode(methodNode, SyntaxRemoveOptions.KeepNoTrivia); + node = node.WithEndBlockStatement(node.EndBlockStatement.WithLeadingTrivia(trivia)); + } + return node.NormalizeWhitespace(); + } + return CommentMethod; + } + + public Func GetAddCommentsToMethodAction(string methodName, string comment, string dontUseCTAPrefix = null) + { + TypeBlockSyntax AddCommentsToMethod(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + // if we have more than one method with same name return without making changes + var methodNode = GetMethodNode(node, methodName); + if (methodNode != null) + { + if (!string.IsNullOrWhiteSpace(comment)) + { + var methodActions = new MethodBlockActions(); + var addCommentActionFunc = methodActions.GetAddCommentAction(comment, dontUseCTAPrefix); + var newMethodNode = addCommentActionFunc(syntaxGenerator, methodNode); + node = node.ReplaceNode(methodNode, newMethodNode); + } + } + return node; + } + return AddCommentsToMethod; + } + + public Func GetAddExpressionToMethodAction(string methodName, string expression) + { + TypeBlockSyntax AddExpressionToMethod(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + // if we have more than one method with same name return without making changes + var methodNode = GetMethodNode(node, methodName); + if (methodNode != null) + { + var methodActions = new MethodBlockActions(); + var addExpressionToMethodAction = methodActions.GetAddExpressionToMethodAction(expression); + var newMethodNode = addExpressionToMethodAction(syntaxGenerator, methodNode); + node = node.ReplaceNode(methodNode, newMethodNode); + } + return node; + } + return AddExpressionToMethod; + } + + public Func GetAddParametersToMethodAction(string methodName, string types, string identifiers) + { + TypeBlockSyntax AddParametersToMethod(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + // if we have more than one method with same name return without making changes + var methodNode = GetMethodNode(node, methodName); + if (methodNode != null) + { + var methodActions = new MethodBlockActions(); + var addParametersToMethodAction = methodActions.GetAddParametersToMethodAction(types, identifiers); + var newMethodNode = addParametersToMethodAction(syntaxGenerator, methodNode); + node = node.ReplaceNode(methodNode, newMethodNode).NormalizeWhitespace(); + } + return node; + } + return AddParametersToMethod; + } + + public Func GetReplaceMvcControllerMethodsBodyAction(string expression) + { + TypeBlockSyntax ReplaceMvcControllerMethodsBodyFunc(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + return node; + } + + return ReplaceMvcControllerMethodsBodyFunc; + } + + public Func GetReplaceWebApiControllerMethodsBodyAction(string expression) + { + TypeBlockSyntax ReplaceMethodBodyFunc(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + return AddCommentToPublicMethods(node, expression); + } + return ReplaceMethodBodyFunc; + } + public Func GetReplaceCoreControllerMethodsBodyAction(string expression) + { + TypeBlockSyntax ReplaceCoreControllerMethodsBody(SyntaxGenerator syntaxGenerator, TypeBlockSyntax node) + { + return AddCommentToPublicMethods(node, expression); + } + return ReplaceCoreControllerMethodsBody; + } + + private TypeBlockSyntax AddCommentToPublicMethods(TypeBlockSyntax node, string expression) + { + var comment = string.Format(Constants.VbCommentFormat, $"Replace method body with {expression}"); + + var allMembers = node.Members.ToList(); + var allMethods = allMembers.OfType() + .Where(m => m.SubOrFunctionStatement.Modifiers.Any(mod => mod.IsKind(SyntaxKind.PublicKeyword))) + .Select(mb => GetMethodId(mb.SubOrFunctionStatement)).ToList(); + + foreach (var method in allMethods) + { + var currentMethodStatement = node.DescendantNodes().OfType() + .FirstOrDefault(m => GetMethodId(m) == method); + var originalMethod = currentMethodStatement; + if (currentMethodStatement != null) + { + var trivia = currentMethodStatement.GetLeadingTrivia(); + trivia = trivia.Add(SyntaxFactory.SyntaxTrivia(SyntaxKind.CommentTrivia, comment)); + currentMethodStatement = currentMethodStatement.WithLeadingTrivia(trivia).NormalizeWhitespace(); + node = node.ReplaceNode(originalMethod, currentMethodStatement); + } + } + return node; + } + + private string GetMethodId(MethodStatementSyntax method) + { + return $"{method.Identifier}{method.ParameterList}"; + } + + private MethodBlockSyntax GetMethodNode(TypeBlockSyntax node, string methodName) + { + var methodNodeList = node.DescendantNodes().OfType() + .Where(method => method.SubOrFunctionStatement.Identifier.Text == methodName); + if (methodNodeList != null && methodNodeList.Count() > 1) + { + return null; + } + return methodNodeList.FirstOrDefault(); + } + } +} diff --git a/src/CTA.Rules.Actions/VisualBasicActionsLoader.cs b/src/CTA.Rules.Actions/VisualBasicActionsLoader.cs new file mode 100644 index 00000000..d5022612 --- /dev/null +++ b/src/CTA.Rules.Actions/VisualBasicActionsLoader.cs @@ -0,0 +1,267 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Codelyzer.Analysis.Model; +using CTA.Rules.Config; +using CTA.Rules.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; + +namespace CTA.Rules.Actions; + +public class VisualBasicActionsLoader : ActionLoaderBase +{ + private readonly List _compilationUnitActions, + _invocationExpressionActions, + _namespaceActions, + _identifierNameActions, + _attributeActions, + _attributeListActions, + _typeBlockActions, + _elementAccessActions, + _expressionActions, + _interfaceActions, + _methodBlockActions, + _objectCreationExpressionActions; + + + private readonly object _compilationUnitObject, + _invocationExpressionObject, + _namespaceObject, + _identifierNameObject, + _attributeObject, + _attributeListObject, + _typeBlockObject, + _elementAccessObject, + _expressionObject, + _interfaceObject, + _methodBlockObject, + _objectExpressionObject; + + /// + /// Initializes a new ActionLoader that loads the default actions + /// + /// A directory containing additional actions to be used + public VisualBasicActionsLoader(List assemblyPaths) + { + _compilationUnitActions = new List(); + _invocationExpressionActions = new List(); + _namespaceActions = new List(); + _identifierNameActions = new List (); + _attributeActions = new List (); + _attributeListActions = new List (); + _typeBlockActions = new List (); + _elementAccessActions = new List (); + _expressionActions = new List (); + _interfaceActions = new List (); + _methodBlockActions = new List (); + _objectCreationExpressionActions = new List(); + memberAccessActions = new List(); + projectLevelActions = new List(); + projectFileActions = new List(); + projectTypeActions = new List(); + + var assemblies = GetAssemblies(assemblyPaths); + + foreach (var assembly in assemblies) + { + try + { + var types = assembly.GetTypes() + .Where(t => t.Name.EndsWith("Actions") && + (t.Namespace.EndsWith(ProjectLanguage.VisualBasic.ToString()) || + t.Name.StartsWith("Project"))).ToList(); + TryCreateInstance(Constants.CompilationUnitActions, types, out _compilationUnitObject); + TryCreateInstance(Constants.InvocationExpressionActions, types, out _invocationExpressionObject); + TryCreateInstance(Constants.NamespaceActions, types, out _namespaceObject); + TryCreateInstance(Constants.AttributeActions, types, out _attributeObject); + TryCreateInstance(Constants.AttributeListActions, types, out _attributeListObject); + TryCreateInstance(Constants.TypeBlockActions, types, out _typeBlockObject); + TryCreateInstance(Constants.ElementAccessActions, types, out _elementAccessObject); + TryCreateInstance(Constants.ExpressionActions, types, out _expressionObject); + TryCreateInstance(Constants.InterfaceActions, types, out _interfaceObject); + TryCreateInstance(Constants.MethodBlockActions, types, out _methodBlockObject); + TryCreateInstance(Constants.ProjectLevelActions, types, out projectLevelObject); + TryCreateInstance(Constants.ProjectFileActions, types, out projectFileObject); + TryCreateInstance(Constants.ProjectTypeActions, types, out projectTypeObject); + TryCreateInstance(Constants.IdentifierNameActions, types, out _identifierNameObject); + TryCreateInstance(Constants.ObjectCreationExpressionActions, types, out _objectExpressionObject); + TryCreateInstance(Constants.MemberAccessActions, types, out memberAccessObject); + + foreach (var t in types) + { + switch (t.Name) + { + case Constants.CompilationUnitActions: + { + _compilationUnitActions.AddRange(GetFuncMethods(t)); + break; + } + case Constants.InvocationExpressionActions: + { + _invocationExpressionActions.AddRange(GetFuncMethods(t)); + break; + } + case Constants.NamespaceActions: + { + _namespaceActions.AddRange(GetFuncMethods(t)); + break; + } + case Constants.ProjectLevelActions: + { + projectLevelActions.AddRange(GetFuncMethods(t)); + break; + } + case Constants.ProjectFileActions: + { + projectFileActions.AddRange(GetFuncMethods(t)); + break; + } + case Constants.ProjectTypeActions: + { + projectTypeActions.AddRange(GetFuncMethods(t)); + break; + } + case Constants.IdentifierNameActions: + { + _identifierNameActions.AddRange(GetFuncMethods(t)); + break; + } + case Constants.AttributeActions: + { + _attributeActions.AddRange(GetFuncMethods(t)); + break; + } + case Constants.AttributeListActions: + { + _attributeListActions.AddRange(GetFuncMethods(t)); + break; + } + case Constants.TypeBlockActions: + { + _typeBlockActions.AddRange(GetFuncMethods(t)); + break; + } + case Constants.InterfaceActions: + { + _interfaceActions.AddRange(GetFuncMethods(t)); + break; + } + case Constants.ExpressionActions: + { + _expressionActions.AddRange(GetFuncMethods(t)); + break; + } + case Constants.MethodBlockActions: + { + _methodBlockActions.AddRange(GetFuncMethods(t)); + break; + } + case Constants.ElementAccessActions: + { + _elementAccessActions.AddRange(GetFuncMethods(t)); + break; + } + case Constants.ObjectCreationExpressionActions: + { + _objectCreationExpressionActions.AddRange(GetFuncMethods(t)); + break; + } + case Constants.MemberAccessActions: + { + memberAccessActions.AddRange(GetFuncMethods(t)); + break; + } + default: + { + LogHelper.LogError($"Action type {t.Name} is not found"); + break; + } + } + } + } + catch (Exception ex) + { + LogHelper.LogError(ex, string.Format("Error loading actions from {0}", assembly.FullName, ex.Message)); + } + } + } + + public Func GetInvocationExpressionAction(string name, dynamic value) + { + return GetAction> + (_invocationExpressionActions, _invocationExpressionObject, name, value); + } + + public Func GetCompilationUnitAction(string name, + dynamic value) + { + return GetAction> + (_compilationUnitActions, _compilationUnitObject, name, value); + } + + public Func GetAttributeAction(string name, dynamic value) + { + return GetAction> + (_attributeActions, _attributeObject, name, value); + } + + public Func GetAttributeListAction(string name, dynamic value) + { + return GetAction> + (_attributeListActions, _attributeListObject, name, value); + } + + public Func GetClassAction(string name, dynamic value) + { + return GetAction> + (_typeBlockActions, _typeBlockObject, name, value); + } + + public Func GetInterfaceAction(string name, dynamic value) + { + return GetAction> + (_interfaceActions, _interfaceObject, name, value); + } + + public Func GetIdentifierNameAction(string name, dynamic value) + { + return GetAction> + (_identifierNameActions, _identifierNameObject, name, value); + } + + public Func GetExpressionAction(string name, dynamic value) + { + return GetAction> + (_expressionActions, _expressionObject, name, value); + } + + public Func GetMethodDeclarationAction(string name, dynamic value) + { + return GetAction> + (_methodBlockActions, _methodBlockObject, name, value); + } + + public Func GetNamespaceActions(string name, + dynamic value) + { + return GetAction> + (_namespaceActions, _namespaceObject, name, value); + } + + public Func GetObjectCreationExpressionActions( + string name, dynamic value) + { + return GetAction> + (_objectCreationExpressionActions, _objectExpressionObject, name, value); + } + + public Func + GetElementAccessExpressionActions(string name, dynamic value) + { + return GetAction> + (_elementAccessActions, _elementAccessObject, name, value); + } +} diff --git a/src/CTA.Rules.Analysis/IRulesAnalysis.cs b/src/CTA.Rules.Analysis/IRulesAnalysis.cs new file mode 100644 index 00000000..35bdede1 --- /dev/null +++ b/src/CTA.Rules.Analysis/IRulesAnalysis.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using CTA.Rules.Models; + +namespace CTA.Rules.Analyzer; + +public interface IRulesAnalysis +{ + /// + /// Runs the Rules Analysis + /// + /// + ProjectActions Analyze(); + + ProjectActions AnalyzeFiles(ProjectActions projectActions, List updatedFiles); +} diff --git a/src/CTA.Rules.Analysis/RulesAnalysis.cs b/src/CTA.Rules.Analysis/RulesAnalysis.cs index dacc2ff1..e985445a 100644 --- a/src/CTA.Rules.Analysis/RulesAnalysis.cs +++ b/src/CTA.Rules.Analysis/RulesAnalysis.cs @@ -8,13 +8,14 @@ using CTA.Rules.Config; using CTA.Rules.Models; using CTA.Rules.Models.Tokens; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace CTA.Rules.Analyzer { /// /// Object to use for creating an analysis based on a code analysis and a list of rules /// - public class RulesAnalysis + public class RulesAnalysis : IRulesAnalysis { private readonly RootNodes _rootNodes; private readonly List _sourceFileResults; @@ -438,7 +439,7 @@ private string GetFullKey(string containingNamespace, string containingClass, st /// /// The file to run actions on /// The token that matched the file - private void AddActions(FileActions fileAction, NodeToken token, TextSpan textSpan, string overrideKey = "") + private void AddActions(FileActions fileAction, CsharpNodeToken token, TextSpan textSpan, string overrideKey = "") { fileAction.AttributeActions.UnionWith(token.AttributeActions.Select(a => new AttributeAction() { @@ -466,7 +467,7 @@ private void AddActions(FileActions fileAction, NodeToken token, TextSpan textSp AttributeListActionFunc = a.AttributeListActionFunc }).ToList()); - fileAction.IdentifierNameActions.UnionWith(token.IdentifierNameActions.Select(a => new IdentifierNameAction() + fileAction.IdentifierNameActions.UnionWith(token.IdentifierNameActions.Select(a => new IdentifierNameAction() { Key = a.Key, Description = a.Description, @@ -478,7 +479,7 @@ private void AddActions(FileActions fileAction, NodeToken token, TextSpan textSp IdentifierNameActionFunc = a.IdentifierNameActionFunc, }).ToList()); - fileAction.InvocationExpressionActions.UnionWith(token.InvocationExpressionActions.Select(a => new InvocationExpressionAction() + fileAction.InvocationExpressionActions.UnionWith(token.InvocationExpressionActions.Select(a => new InvocationExpressionAction() { Key = !string.IsNullOrEmpty(overrideKey) ? overrideKey : a.Key, Description = a.Description, @@ -527,7 +528,7 @@ private void AddActions(FileActions fileAction, NodeToken token, TextSpan textSp NamespaceUsingActionFunc = a.NamespaceUsingActionFunc, }).ToList()); - fileAction.NamespaceActions.UnionWith(token.NamespaceActions.Select(a => new NamespaceAction() + fileAction.NamespaceActions.UnionWith(token.NamespaceActions.Select(a => new NamespaceAction() { Key = a.Key, Description = a.Description, @@ -607,7 +608,7 @@ private void AddPackages(List packageActions, TextSpan textSpan) /// /// /// - private void AddNamedActions(FileActions fileAction, NodeToken token, string identifier, TextSpan textSpan) + private void AddNamedActions(FileActions fileAction, CsharpNodeToken token, string identifier, TextSpan textSpan) { fileAction.ClassDeclarationActions.UnionWith(token.ClassDeclarationActions diff --git a/src/CTA.Rules.Analysis/VisualBasicRulesAnalysis.cs b/src/CTA.Rules.Analysis/VisualBasicRulesAnalysis.cs new file mode 100644 index 00000000..315a89a5 --- /dev/null +++ b/src/CTA.Rules.Analysis/VisualBasicRulesAnalysis.cs @@ -0,0 +1,798 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Codelyzer.Analysis.Model; +using CTA.Rules.Common.Extensions; +using CTA.Rules.Config; +using CTA.Rules.Models; +using CTA.Rules.Models.Actions.VisualBasic; +using CTA.Rules.Models.Tokens.VisualBasic; +using CTA.Rules.Models.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using AttributeAction = CTA.Rules.Models.Actions.VisualBasic.AttributeAction; +using ElementAccessAction = CTA.Rules.Models.Actions.VisualBasic.ElementAccessAction; +using ElementAccessToken = CTA.Rules.Models.Tokens.VisualBasic.ElementAccessToken; +using IdentifierNameToken = CTA.Rules.Models.VisualBasic.IdentifierNameToken; +using MemberAccessToken = CTA.Rules.Models.Tokens.VisualBasic.MemberAccessToken; +using ObjectCreationExpressionAction = CTA.Rules.Models.Actions.VisualBasic.ObjectCreationExpressionAction; + +namespace CTA.Rules.Analyzer; + +public class VisualBasicRulesAnalysis : IRulesAnalysis +{ + private readonly VisualBasicRootNodes _visualBasicRootNodes; + private readonly List _sourceFileResults; + private readonly ProjectActions _projectActions; + private readonly ProjectType _projectType; + + /// + /// Initializes a RulesAnalysis instance for Visual Basic projects + /// + /// List of analyzed code files + /// List of rules to be applied to the code files + /// Type of project + public VisualBasicRulesAnalysis(List sourceFileResults, VisualBasicRootNodes visualBasicRootNodes, + ProjectType projectType = ProjectType.ClassLibrary) + { + _projectActions = new ProjectActions(); + _sourceFileResults = sourceFileResults; + _visualBasicRootNodes = visualBasicRootNodes; + _projectType = projectType; + } + + public ProjectActions Analyze() + { + var options = new ParallelOptions { MaxDegreeOfParallelism = Constants.ThreadCount }; + Parallel.ForEach(_sourceFileResults, options, result => + { + var fileAction = new FileActions { FilePath = result.FileFullPath }; + if (AnalyzeChildren(fileAction, result.Children, 0)) + { + _projectActions.FileActions.Add(fileAction); + } + }); + + AddPackages( + _visualBasicRootNodes.ProjectTokens.Where(p => p.FullKey == _projectType.ToString()) + ?.SelectMany(p => p.PackageActions)?.Distinct()?.ToList(), null); + + return _projectActions; + } + + public ProjectActions AnalyzeFiles(ProjectActions projectActions, List updatedFiles) + { + var options = new ParallelOptions { MaxDegreeOfParallelism = Constants.ThreadCount }; + var selectedSourceFileResults = _sourceFileResults.Where(s => updatedFiles.Contains(s.FileFullPath)); + + Parallel.ForEach(selectedSourceFileResults, options, result => + { + var fileAction = new FileActions { FilePath = result.FileFullPath }; + + if (AnalyzeChildren(fileAction, result.Children, 0)) + { + var existingFileAction = + _projectActions.FileActions.FirstOrDefault(f => f.FilePath == fileAction.FilePath); + if (existingFileAction != null) + { + existingFileAction = fileAction; + } + else + { + _projectActions.FileActions.Add(fileAction); + } + } + }); + return _projectActions; + } + + /// + /// Analyzes children of nodes in a particular file + /// + /// The object containing the actions to run on the file + /// List of child nodes to check + /// Recursion level to avoid stack overflows + private bool AnalyzeChildren(FileActions fileAction, UstList children, int level, + string parentNamespace = "", string parentClass = "") + { + var containsActions = false; + + if (children == null || level > Constants.MaxRecursionDepth) + { + return false; + } + + foreach (var child in children) + { + try + { + switch (child.NodeType) + { + case IdConstants.AttributeListName: + { + var attributeList = (AttributeList)child; + var compareToken = new AttributeListToken + { + Key = attributeList.Identifier, + Namespace = attributeList.Reference.Namespace, + Type = attributeList.SemanticClassType + }; + _visualBasicRootNodes.AttributeListTokens.TryGetValue(compareToken, out var token); + if (token != null) + { + AddActions(fileAction, token, child.TextSpan); + containsActions = true; + } + + break; + } + case IdConstants.ImportsStatementName: + { + var overrideKey = string.Empty; + + var compareToken = new ImportStatementToken { Key = child.Identifier }; + _visualBasicRootNodes.ImportStatementTokens.TryGetValue(compareToken, out var token); + if (token != null) + { + AddActions(fileAction, token, child.TextSpan); + containsActions = true; + } + + //Attempt a wildcard search, if applicable. This is import specific because it might want to include all sub-namespaces + if (token == null) + { + var wildcardMatches = _visualBasicRootNodes.ImportStatementTokens + .Where(i => i.Key.Contains("*")).ToList(); + if (wildcardMatches.Any()) + { + token = wildcardMatches.FirstOrDefault(i => compareToken.Key.WildcardEquals(i.Key)); + + if (token != null) + { + //We set the key so that we don't do another wildcard search during replacement, we just use the name as it was declared in the code + overrideKey = compareToken.Key; + } + } + } + + if (token != null) + { + AddActions(fileAction, token, child.TextSpan, overrideKey); + containsActions = true; + } + + break; + } + case IdConstants.NamespaceBlockIdName: + { + var compareToken = new NamespaceToken { Key = child.Identifier }; + _visualBasicRootNodes.NamespaceTokens.TryGetValue(compareToken, out var token); + if (token != null) + { + AddActions(fileAction, token, child.TextSpan); + containsActions = true; + } + + if (AnalyzeChildren(fileAction, child.Children, ++level, child.Identifier)) + { + containsActions = true; + } + + break; + } + case IdConstants.ModuleBlockName: + { + var moduleType = (ModuleBlock)child; + var name = string.Concat( + moduleType.Reference != null + ? string.Concat(moduleType.Reference.Namespace, ".") + : string.Empty, moduleType.Identifier); + var nameToken = new TypeBlockToken { FullKey = name }; + if (_visualBasicRootNodes.TypeBlockTokens.TryGetValue(nameToken, out var token)) + { + AddNamedActions(fileAction, token, moduleType.Identifier, child.TextSpan); + AddActions(fileAction, token, child.TextSpan); + containsActions = true; + } + + break; + } + case IdConstants.ClassBlockName: + { + var classType = (ClassBlock)child; + var baseToken = new TypeBlockToken { FullKey = classType.BaseType }; + if (_visualBasicRootNodes.TypeBlockTokens.TryGetValue(baseToken, out var token)) + { + AddNamedActions(fileAction, token, classType.Identifier, child.TextSpan); + AddActions(fileAction, token, child.TextSpan); + containsActions = true; + } + + token = null; + var name = string.Concat( + classType.Reference != null + ? string.Concat(classType.Reference.Namespace, ".") + : string.Empty, classType.Identifier); + var nameToken = new TypeBlockToken { FullKey = name }; + if (_visualBasicRootNodes.TypeBlockTokens.TryGetValue(nameToken, out token)) + { + AddNamedActions(fileAction, token, classType.Identifier, child.TextSpan); + AddActions(fileAction, token, child.TextSpan); + containsActions = true; + } + + token = null; + foreach (var interfaceName in classType.Inherits) + { + var baseListToken = new TypeBlockToken { FullKey = interfaceName }; + if (_visualBasicRootNodes.TypeBlockTokens.TryGetValue(baseListToken, out token)) + { + AddNamedActions(fileAction, token, classType.Identifier, child.TextSpan); + AddActions(fileAction, token, child.TextSpan); + containsActions = true; + } + + token = null; + } + + if (AnalyzeChildren(fileAction, child.Children, ++level, parentNamespace, classType.Identifier)) + { + containsActions = true; + } + + break; + } + case IdConstants.InterfaceBlockIdName: + { + var interfaceType = (InterfaceBlock)child; + var baseToken = new InterfaceBlockToken { FullKey = interfaceType.BaseType }; + InterfaceBlockToken token = null; + + if (!string.IsNullOrEmpty(interfaceType.BaseType)) + { + _visualBasicRootNodes.InterfaceBlockTokens.TryGetValue(baseToken, out token); + } + + if (token != null) + { + //In case of interface blocks, add actions on the interface by name, instead of property + AddNamedActions(fileAction, token, interfaceType.Identifier, child.TextSpan); + AddActions(fileAction, token, child.TextSpan); + containsActions = true; + } + + token = null; + var name = string.Concat( + interfaceType.Reference != null + ? string.Concat(interfaceType.Reference.Namespace, ".") + : string.Empty, interfaceType.Identifier); + var nameToken = new InterfaceBlockToken { FullKey = name }; + _visualBasicRootNodes.InterfaceBlockTokens.TryGetValue(baseToken, out token); + + if (token != null) + { + //In case of interface blocks, add actions on the interface by name, instead of property + AddNamedActions(fileAction, token, interfaceType.Identifier, child.TextSpan); + AddActions(fileAction, token, child.TextSpan); + containsActions = true; + } + + if (AnalyzeChildren(fileAction, child.Children, ++level, parentNamespace)) + { + containsActions = true; + } + + break; + } + case IdConstants.AccessorBlockName: + { + var accessorType = (AccessorBlock)child; + var name = string.Concat( + accessorType.Reference != null + ? string.Concat(accessorType.Reference.Namespace, ".") + : string.Empty, accessorType.Identifier); + var nameToken = new AccessorBlockToken { FullKey = name }; + if (_visualBasicRootNodes.AccessorBlockTokens.TryGetValue(nameToken, out var token)) + { + AddNamedActions(fileAction, token, accessorType.Identifier, child.TextSpan); + AddActions(fileAction, token, child.TextSpan); + containsActions = true; + } + + break; + } + case IdConstants.SubBlockName: + { + var compareToken = new MethodBlockToken { FullKey = string.Concat(child.Identifier) }; + _visualBasicRootNodes.MethodBlockTokens.TryGetValue(compareToken, out var token); + if (token != null) + { + AddNamedActions(fileAction, token, child.Identifier, child.TextSpan); + AddActions(fileAction, token, child.TextSpan); + containsActions = true; + } + + if (AnalyzeChildren(fileAction, child.Children, ++level, parentNamespace, parentClass)) + { + containsActions = true; + } + + break; + } + case IdConstants.InvocationIdName: + { + var overrideKey = string.Empty; + + var invocationExpression = (InvocationExpression)child; + + ////If we don't have a semantic analysis, we dont want to replace invocation expressions, otherwise we'll be replacing expressions regardless of their class/namespace + if (string.IsNullOrEmpty(invocationExpression.SemanticOriginalDefinition)) + { + break; + } + + var compareToken = new InvocationExpressionToken + { + Key = invocationExpression.SemanticOriginalDefinition, + Namespace = invocationExpression.Reference.Namespace, + Type = invocationExpression.SemanticClassType + }; + _visualBasicRootNodes.InvocationExpressionTokens.TryGetValue(compareToken, out var token); + + //Attempt a wildcard search, if applicable. This is invocation expression specific because it has to look inside the invocation expressions only + if (token == null) + { + var wildcardMatches = + _visualBasicRootNodes.InvocationExpressionTokens.Where(i => i.Key.Contains("*")); + if (wildcardMatches.Any()) + { + token = wildcardMatches.FirstOrDefault(i => + compareToken.Key.WildcardEquals(i.Key) && compareToken.Namespace == i.Namespace && + compareToken.Type == i.Type); + + if (token != null) + { + //We set the key so that we don't do another wildcard search during replacement, we just use the name as it was declared in the code + overrideKey = compareToken.Key; + } + } + + //If the semanticClassType is too specific to apply to all TData types + if (token == null) + { + if (invocationExpression.SemanticClassType.Contains('<')) + { + var semanticClassType = invocationExpression.SemanticClassType.Substring(0, + invocationExpression.SemanticClassType.IndexOf('<')); + compareToken = new InvocationExpressionToken + { + Key = invocationExpression.SemanticOriginalDefinition, + Namespace = invocationExpression.Reference.Namespace, + Type = semanticClassType + }; + _visualBasicRootNodes.InvocationExpressionTokens.TryGetValue(compareToken, + out token); + } + } + } + + if (token != null) + { + AddActions(fileAction, token, child.TextSpan, overrideKey); + containsActions = true; + } + + if (AnalyzeChildren(fileAction, child.Children, ++level, parentNamespace, parentClass)) + { + containsActions = true; + } + + break; + } + case IdConstants.ElementAccessIdName: + { + ElementAccess elementAccess = (ElementAccess)child; + var compareToken = new ElementAccessToken() + { + Key = elementAccess.Expression, + FullKey = GetFullKey(elementAccess.Reference?.Namespace, elementAccess.SemanticClassType, elementAccess.Expression), + Type = elementAccess.SemanticClassType, + Namespace = elementAccess.Reference?.Namespace + }; + _visualBasicRootNodes.ElementAccessTokens.TryGetValue(compareToken, out var token); + if (token != null) + { + AddActions(fileAction, token, child.TextSpan); + containsActions = true; + } + if (AnalyzeChildren(fileAction, child.Children, ++level, parentNamespace, parentClass)) { containsActions = true; } + break; + } + + case IdConstants.MemberAccessIdName: + { + var memberAccess = (MemberAccess)child; + var compareToken = new MemberAccessToken + { + Key = memberAccess.Name, + FullKey = GetFullKey(memberAccess.Reference?.Namespace, memberAccess.SemanticClassType, + memberAccess.Name), + Type = memberAccess.SemanticClassType, + Namespace = memberAccess.Reference?.Namespace + }; + _visualBasicRootNodes.MemberAccessTokens.TryGetValue(compareToken, out var token); + if (token != null) + { + AddActions(fileAction, token, child.TextSpan); + containsActions = true; + } + + if (AnalyzeChildren(fileAction, child.Children, ++level, parentNamespace, parentClass)) + { + containsActions = true; + } + + break; + } + case IdConstants.DeclarationNodeIdName: + { + var declarationNode = (DeclarationNode)child; + var compareToken = new IdentifierNameToken + { + Key = string.Concat(declarationNode.Reference.Namespace, ".", + declarationNode.Identifier), + Namespace = declarationNode.Reference.Namespace + }; + _visualBasicRootNodes.IdentifierNameTokens.TryGetValue(compareToken, out var token); + if (token != null) + { + AddActions(fileAction, token, child.TextSpan); + containsActions = true; + } + + if (AnalyzeChildren(fileAction, child.Children, ++level, parentNamespace, parentClass)) + { + containsActions = true; + } + + break; + } + case IdConstants.ObjectCreationIdName: + { + var objectCreationNode = (ObjectCreationExpression)child; + //Rules based on Object Creation Parent Hierarchy + var compareToken = new ObjectCreationExpressionToken + { + Key = objectCreationNode.Identifier, + Namespace = objectCreationNode.Reference?.Namespace, + Type = objectCreationNode.SemanticClassType + }; + _visualBasicRootNodes.ObjectCreationExpressionTokens.TryGetValue(compareToken, out var token); + if (token != null) + { + AddActions(fileAction, token, child.TextSpan); + containsActions = true; + } + + //Rules based on Object Creation location within code + var compareTokenLocation = new ObjectCreationExpressionToken + { + Key = objectCreationNode.Identifier, Namespace = parentNamespace, Type = parentClass + }; + _visualBasicRootNodes.ObjectCreationExpressionTokens.TryGetValue(compareTokenLocation, + out var tokenLocation); + if (tokenLocation != null) + { + AddActions(fileAction, tokenLocation, child.TextSpan); + containsActions = true; + } + + token = null; + if (!string.IsNullOrEmpty(objectCreationNode.SemanticOriginalDefinition)) + { + var nameToken = new ObjectCreationExpressionToken + { + Key = objectCreationNode.SemanticOriginalDefinition, + Namespace = objectCreationNode.SemanticNamespace, + Type = objectCreationNode.SemanticClassType + }; + _visualBasicRootNodes.ObjectCreationExpressionTokens.TryGetValue(nameToken, out token); + if (token != null) + { + AddActions(fileAction, token, child.TextSpan); + containsActions = true; + } + } + + if (AnalyzeChildren(fileAction, child.Children, ++level, parentNamespace, parentClass)) + { + containsActions = true; + } + + break; + } + default: + { + if (AnalyzeChildren(fileAction, child.Children, ++level, parentNamespace, parentClass)) + { + containsActions = true; + } + + break; + } + } + } + catch (Exception ex) + { + LogHelper.LogError(ex, "Error loading actions for item {0} of type {1}", child.Identifier, + child.NodeType); + } + } + + return containsActions; + } + + private string GetFullKey(string containingNamespace, string containingClass, string key) + { + if (string.IsNullOrEmpty(containingNamespace)) + { + return key; + } + + if (!string.IsNullOrEmpty(containingClass)) + { + return $"{containingNamespace}.{containingClass}.{key}"; + } + + return $"{containingNamespace}.{key}"; + } + + /// + /// Add actions matching the token + /// + /// The file to run actions on + /// The token that matched the file + private void AddActions(FileActions fileAction, VisualBasicNodeToken token, TextSpan textSpan, + string overrideKey = "") + { + fileAction.VbInvocationExpressionActions.UnionWith(token.InvocationExpressionActions.Select(a => + new InvocationExpressionAction + { + Key = !string.IsNullOrEmpty(overrideKey) ? overrideKey : a.Key, + Description = a.Description, + Value = a.Value, + Name = a.Name, + Type = a.Type, + TextSpan = textSpan, + ActionValidation = a.ActionValidation, + InvocationExpressionActionFunc = a.InvocationExpressionActionFunc + }).ToList()); + + fileAction.VbImportActions.UnionWith(token.ImportActions.Select(a => new ImportAction + { + Key = a.Key, + Description = a.Description, + Value = a.Value, + Name = a.Name, + Type = a.Type, + TextSpan = textSpan, + ActionValidation = a.ActionValidation, + ImportActionFunc = a.ImportActionFunc, + ImportsClauseActionFunc = a.ImportsClauseActionFunc + }).ToList()); + + fileAction.VbNamespaceActions.UnionWith(token.NamespaceActions.Select(a => + new NamespaceAction + { + Key = a.Key, + Description = a.Description, + Value = a.Value, + Name = a.Name, + Type = a.Type, + TextSpan = textSpan, + ActionValidation = a.ActionValidation, + NamespaceActionFunc = a.NamespaceActionFunc + }).ToList()); + + fileAction.VbIdentifierNameActions.UnionWith(token.IdentifierNameActions.Select(a => new IdentifierNameAction() + { + Key = a.Key, + Description = a.Description, + Value = a.Value, + Name = a.Name, + Type = a.Type, + TextSpan = textSpan, + ActionValidation = a.ActionValidation, + IdentifierNameActionFunc = a.IdentifierNameActionFunc, + }).ToList()); + + fileAction.VbAttributeActions.UnionWith(token.AttributeActions.Select(a => new AttributeAction() + { + Key = a.Key, + Description = a.Description, + Value = a.Value, + Name = a.Name, + Type = a.Type, + TextSpan = textSpan, + ActionValidation = a.ActionValidation, + AttributeActionFunc = a.AttributeActionFunc, + AttributeListActionFunc = a.AttributeListActionFunc + }).ToList()); + + fileAction.VbAttributeListActions.UnionWith(token.VbAttributeListActions.Select(a => new AttributeListAction + { + Key = a.Key, + Description = a.Description, + Value = a.Value, + Name = a.Name, + Type = a.Type, + TextSpan = textSpan, + ActionValidation = a.ActionValidation, + AttributeListActionFunc = a.AttributeListActionFunc + }).ToList()); + + fileAction.VbElementAccessActions.UnionWith(token.ElementAccessActions.Select(a => new ElementAccessAction() + { + Key = (token is ElementAccessToken) ? token.FullKey : a.Key, + Description = a.Description, + Value = a.Value, + Name = a.Name, + Type = a.Type, + TextSpan = textSpan, + ActionValidation = a.ActionValidation, + ElementAccessExpressionActionFunc = a.ElementAccessExpressionActionFunc + }).ToList()); + + fileAction.MemberAccessActions.UnionWith(token.MemberAccessActions.Select(a => new MemberAccessAction + { + Key = token is MemberAccessToken ? token.FullKey : a.Key, + Description = a.Description, + Value = a.Value, + Name = a.Name, + Type = a.Type, + TextSpan = textSpan, + ActionValidation = a.ActionValidation, + MemberAccessActionFunc = a.MemberAccessActionFunc + }).ToList()); + + fileAction.VbObjectCreationExpressionActions.UnionWith(token.ObjectCreationExpressionActions.Select(a => + new ObjectCreationExpressionAction + { + Key = a.Key, + Description = a.Description, + Value = a.Value, + Name = a.Name, + Type = a.Type, + TextSpan = textSpan, + ActionValidation = a.ActionValidation, + ObjectCreationExpressionGenericActionFunc = a.ObjectCreationExpressionGenericActionFunc + }).ToList()); + + fileAction.ExpressionActions.UnionWith(token.ExpressionActions.Select(a => new ExpressionAction() + { + Key = !string.IsNullOrEmpty(overrideKey) ? overrideKey : a.Key, + Description = a.Description, + Value = a.Value, + Name = a.Name, + Type = a.Type, + TextSpan = textSpan, + ActionValidation = a.ActionValidation, + ExpressionActionFunc = a.ExpressionActionFunc + }).ToList()); + + if (fileAction.InvocationExpressionActions.Any() + || fileAction.VbImportActions.Any() + || fileAction.VbNamespaceActions.Any() + || fileAction.VbIdentifierNameActions.Any() + || fileAction.VbAttributeListActions.Any() + || fileAction.MemberAccessActions.Any() + || fileAction.VbNamespaceActions.Any() + || fileAction.VbIdentifierNameActions.Any() + || fileAction.VbObjectCreationExpressionActions.Any() + || fileAction.VbElementAccessActions.Any() + || fileAction.ExpressionActions.Any()) + { + var nodeToken = token.Clone(); + nodeToken.TextSpan = textSpan; + fileAction.VbNodeTokens.Add(nodeToken); + } + + AddPackages(token.PackageActions, textSpan); + } + + /// + /// Adds a list of packages to the project actions + /// + /// List of package actions based on the rules + private void AddPackages(List packageActions, TextSpan textSpan) + { + if (packageActions != null && packageActions.Count > 0) + { + packageActions.ForEach(p => + { + if (!_projectActions.PackageActions.Contains(p)) + { + _projectActions.PackageActions.Add(new PackageAction + { + Name = p.Name, Version = p.Version, TextSpan = textSpan + }); + } + }); + } + } + + /// + /// Add actions using the identifier of the object matching the token + /// + /// + /// + /// + private void AddNamedActions(FileActions fileAction, VisualBasicNodeToken token, string identifier, + TextSpan textSpan) + { + fileAction.VbTypeBlockActions.UnionWith(token.TypeBlockActions + .Select(c => new TypeBlockAction + { + Key = identifier, + Value = c.Value, + Description = c.Description, + Name = c.Name, + Type = c.Type, + TextSpan = textSpan, + ActionValidation = c.ActionValidation, + TypeBlockActionFunc = c.TypeBlockActionFunc + })); + + fileAction.VbMethodBlockActions.UnionWith(token.MethodBlockActions + .Select(c => new MethodBlockAction + { + Key = identifier, + Value = c.Value, + Description = c.Description, + Name = c.Name, + Type = c.Type, + TextSpan = textSpan, + ActionValidation = c.ActionValidation, + MethodBlockActionFunc = c.MethodBlockActionFunc + })); + + fileAction.VbInterfaceBlockActions.UnionWith(token.InterfaceBlockActions + .Select(c => new InterfaceBlockAction + { + Key = identifier, + Value = c.Value, + Name = c.Name, + Type = c.Type, + Description = c.Description, + TextSpan = textSpan, + ActionValidation = c.ActionValidation, + InterfaceBlockActionFunc = c.InterfaceBlockActionFunc + })); + + fileAction.VbAccessorBlockActions.UnionWith(token.AccessorBlockActions + .Select(c => new AccessorBlockAction + { + Key = identifier, + Value = c.Value, + Name = c.Name, + Type = c.Type, + Description = c.Description, + TextSpan = textSpan, + ActionValidation = c.ActionValidation, + AccessorBlockActionFunc = c.AccessorBlockActionFunc + })); + + if (fileAction.VbTypeBlockActions.Any() || fileAction.VbMethodBlockActions.Any() + || fileAction.VbInterfaceBlockActions.Any() || + fileAction.VbAccessorBlockActions.Any()) + { + var nodeToken = token.Clone(); + nodeToken.TextSpan = textSpan; + nodeToken.AllActions.ForEach(action => + { + action.Key = identifier; + }); + fileAction.VbNodeTokens.Add(nodeToken); + } + } +} diff --git a/src/CTA.Rules.Common/Utils/VisualBasicUtils.cs b/src/CTA.Rules.Common/Utils/VisualBasicUtils.cs new file mode 100644 index 00000000..135b361d --- /dev/null +++ b/src/CTA.Rules.Common/Utils/VisualBasicUtils.cs @@ -0,0 +1,17 @@ +using System; + +namespace CTA.Rules.Common.Helpers +{ + public class VisualBasicUtils + { + /// + /// Checks project file for visual basic project extension + /// + /// projectFilePath + /// True if the file path contains the visual basic project extension + public static bool IsVisualBasicProject(string projectFilePath) + { + return projectFilePath.EndsWith(".vbproj", StringComparison.InvariantCultureIgnoreCase); + } + } +} diff --git a/src/CTA.Rules.Config/CTA.Rules.Config.csproj b/src/CTA.Rules.Config/CTA.Rules.Config.csproj index cae46c35..6e8548a4 100644 --- a/src/CTA.Rules.Config/CTA.Rules.Config.csproj +++ b/src/CTA.Rules.Config/CTA.Rules.Config.csproj @@ -5,8 +5,8 @@ - - + + diff --git a/src/CTA.Rules.Config/Constants.cs b/src/CTA.Rules.Config/Constants.cs index 1cdeffcc..eb85ecad 100644 --- a/src/CTA.Rules.Config/Constants.cs +++ b/src/CTA.Rules.Config/Constants.cs @@ -50,7 +50,9 @@ public class Constants public const string Templates = "Templates"; public const string CommentFormat = @"/* Added by CTA: {0} */"; + public const string VbCommentFormat = @"' Added by CTA: {0}"; public const string CommentFormatBlank = @"/* {0} */"; + public const string VbCommentFormatBlank = @"' {0}"; public const string WebSdkName = "Microsoft.NET.Sdk.Web"; public const string ClassLibrarySdkName = "Microsoft.NET.Sdk"; @@ -82,6 +84,8 @@ public class Constants public const string InterfaceActions = "InterfaceActions"; public const string ProjectFileActions = "ProjectFileActions"; public const string ProjectTypeActions = "ProjectTypeActions"; + public const string TypeBlockActions = "TypeBlockActions"; + public const string MethodBlockActions = "MethodBlockActions"; public const string ProjectRecommendationFile = "project.all"; @@ -119,6 +123,19 @@ public class Constants public static readonly List SupportedMethodModifiers = new List() { "public", "internal", "protected", "private", "abstract", "extern", "override", "static", "unsafe", "virtual", "async" }; + public static readonly List SupportedVbMethodModifiers = new() + { + "Public", + "Internal", + "Protected", + "Private", + "Friend", + "Overrides", + "Shared", + "Overridable", + "Async" + }; + //Monolith code constants public const string Await = "await"; public const string TaskActionResult = "Task"; @@ -155,7 +172,9 @@ public class Constants new List {"wcfcodebasedservice","Startup.cs"}, new List {"wcfconfigbasedservice","Program.cs"}, new List {"wcfconfigbasedservice","Startup.cs"}, - new List {"webforms","appsettings.json"} + new List {"webforms","appsettings.json"}, + new List {"webapi", "Program.vb"}, + new List {"webapi", "Startup.vb"} }; public const string WCFErrorTag = "WCF Porting Error: "; diff --git a/src/CTA.Rules.Metrics/Models/ActionPackageMetric.cs b/src/CTA.Rules.Metrics/Models/ActionPackageMetric.cs index e7bea90d..f5474215 100644 --- a/src/CTA.Rules.Metrics/Models/ActionPackageMetric.cs +++ b/src/CTA.Rules.Metrics/Models/ActionPackageMetric.cs @@ -27,6 +27,7 @@ public ActionPackageMetric(MetricsContext context, PackageAction packageAction, PackageVersion = packageAction.Version; SolutionPathHash = context.SolutionPathHash; ProjectGuid = context.ProjectGuidMap.GetValueOrDefault(projectPath, "N/A"); + Language = GetLanguage(projectPath); } } } diff --git a/src/CTA.Rules.Metrics/Models/BuildErrorMetric.cs b/src/CTA.Rules.Metrics/Models/BuildErrorMetric.cs index 941e74db..c7bb4aab 100644 --- a/src/CTA.Rules.Metrics/Models/BuildErrorMetric.cs +++ b/src/CTA.Rules.Metrics/Models/BuildErrorMetric.cs @@ -37,6 +37,7 @@ public BuildErrorMetric(MetricsContext context, string buildError, int count, st Count = count; SolutionPathHash = context.SolutionPathHash; ProjectGuid = context.ProjectGuidMap.GetValueOrDefault(projectPath, "N/A"); + Language = GetLanguage(projectPath); } private string ExtractBuildErrorCode(string buildError) diff --git a/src/CTA.Rules.Metrics/Models/CTAMetric.cs b/src/CTA.Rules.Metrics/Models/CTAMetric.cs index 4f3b28ca..a813c034 100644 --- a/src/CTA.Rules.Metrics/Models/CTAMetric.cs +++ b/src/CTA.Rules.Metrics/Models/CTAMetric.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System; +using Newtonsoft.Json; namespace CTA.Rules.Metrics { @@ -6,5 +7,19 @@ public abstract class CTAMetric { [JsonProperty("metricsType", Order = 1)] public string MetricsType => "CTA"; + + [JsonProperty("language", Order = 2)] + public string Language { get; set; } + + protected static string GetLanguage(string projectPath) + { + return projectPath.EndsWith(".csproj", + StringComparison.InvariantCultureIgnoreCase) + ? "csharp" + : projectPath.EndsWith(".vbproj", + StringComparison.InvariantCultureIgnoreCase) + ? "visualbasic" + : "unknown"; + } } } diff --git a/src/CTA.Rules.Metrics/Models/FeatureDetectionMetric.cs b/src/CTA.Rules.Metrics/Models/FeatureDetectionMetric.cs index a1d9fcec..bdc4b338 100644 --- a/src/CTA.Rules.Metrics/Models/FeatureDetectionMetric.cs +++ b/src/CTA.Rules.Metrics/Models/FeatureDetectionMetric.cs @@ -22,6 +22,7 @@ public FeatureDetectionMetric(MetricsContext context, string featureName, string FeatureName = featureName; SolutionPath = context.SolutionPathHash; ProjectGuid = context.ProjectGuidMap.GetValueOrDefault(projectPath, "N/A"); + Language = GetLanguage(projectPath); } } } diff --git a/src/CTA.Rules.Metrics/Models/GenericActionExecutionMetric.cs b/src/CTA.Rules.Metrics/Models/GenericActionExecutionMetric.cs index b1f11c4f..0399fc16 100644 --- a/src/CTA.Rules.Metrics/Models/GenericActionExecutionMetric.cs +++ b/src/CTA.Rules.Metrics/Models/GenericActionExecutionMetric.cs @@ -52,6 +52,7 @@ public GenericActionExecutionMetric(MetricsContext context, GenericActionExecuti { FilePath = string.IsNullOrEmpty(action.FilePath) ? "N/A" : EncryptionHelper.ConvertToSHA256Hex(action.FilePath); } + Language = GetLanguage(projectPath); } } } diff --git a/src/CTA.Rules.Metrics/Models/GenericActionMetric.cs b/src/CTA.Rules.Metrics/Models/GenericActionMetric.cs index 008f8991..dc346b26 100644 --- a/src/CTA.Rules.Metrics/Models/GenericActionMetric.cs +++ b/src/CTA.Rules.Metrics/Models/GenericActionMetric.cs @@ -44,6 +44,9 @@ public GenericActionMetric(MetricsContext context, GenericAction action, string { FilePath = string.IsNullOrEmpty(filePath) ? "N/A" : EncryptionHelper.ConvertToSHA256Hex(filePath); } + Language = GetLanguage(projectPath); } + + } } diff --git a/src/CTA.Rules.Metrics/Models/MissingMetaReferenceMetric.cs b/src/CTA.Rules.Metrics/Models/MissingMetaReferenceMetric.cs index 166716f4..9ded1784 100644 --- a/src/CTA.Rules.Metrics/Models/MissingMetaReferenceMetric.cs +++ b/src/CTA.Rules.Metrics/Models/MissingMetaReferenceMetric.cs @@ -24,6 +24,7 @@ public MissingMetaReferenceMetric(MetricsContext context, string metaReference, MetaReference = metaReference; SolutionPathHash = context.SolutionPathHash; ProjectGuid = context.ProjectGuidMap.GetValueOrDefault(projectPath, "N/A"); + Language = GetLanguage(projectPath); } } } diff --git a/src/CTA.Rules.Metrics/Models/TargetVersionMetric.cs b/src/CTA.Rules.Metrics/Models/TargetVersionMetric.cs index 5e1c064c..14cd53ba 100644 --- a/src/CTA.Rules.Metrics/Models/TargetVersionMetric.cs +++ b/src/CTA.Rules.Metrics/Models/TargetVersionMetric.cs @@ -26,6 +26,7 @@ public TargetVersionMetric(MetricsContext context, string targetVersion, string SourceVersion = sourceVersion; SolutionPathHash = context.SolutionPathHash; ProjectGuid = context.ProjectGuidMap.GetValueOrDefault(projectPath, "N/A"); + Language = GetLanguage(projectPath); } } } diff --git a/src/CTA.Rules.Metrics/Models/UpgradePackageMetric.cs b/src/CTA.Rules.Metrics/Models/UpgradePackageMetric.cs index 55aecc5e..973a9064 100644 --- a/src/CTA.Rules.Metrics/Models/UpgradePackageMetric.cs +++ b/src/CTA.Rules.Metrics/Models/UpgradePackageMetric.cs @@ -31,6 +31,7 @@ public UpgradePackageMetric(MetricsContext context, PackageAction packageAction, PackageOriginalVersion = packageAction.OriginalVersion; SolutionPathHash = context.SolutionPathHash; ProjectGuid = context.ProjectGuidMap.GetValueOrDefault(projectPath, "N/A"); + Language = GetLanguage(projectPath); } } } diff --git a/src/CTA.Rules.Metrics/Models/WebForms/WebFormsActionMetric.cs b/src/CTA.Rules.Metrics/Models/WebForms/WebFormsActionMetric.cs index 575d6232..611003d5 100644 --- a/src/CTA.Rules.Metrics/Models/WebForms/WebFormsActionMetric.cs +++ b/src/CTA.Rules.Metrics/Models/WebForms/WebFormsActionMetric.cs @@ -28,6 +28,7 @@ public WebFormsActionMetric(MetricsContext context, string childActionName, stri ChildActionName = childActionName; SolutionPathHash = context.SolutionPathHash; ProjectGuid = context.ProjectGuidMap.GetValueOrDefault(projectPath, "N/A"); + Language = GetLanguage(projectPath); } } } diff --git a/src/CTA.Rules.Models/Actions/Attributeaction.cs b/src/CTA.Rules.Models/Actions/Csharp/Attributeaction.cs similarity index 100% rename from src/CTA.Rules.Models/Actions/Attributeaction.cs rename to src/CTA.Rules.Models/Actions/Csharp/Attributeaction.cs diff --git a/src/CTA.Rules.Models/Actions/Classdeclarationaction.cs b/src/CTA.Rules.Models/Actions/Csharp/Classdeclarationaction.cs similarity index 100% rename from src/CTA.Rules.Models/Actions/Classdeclarationaction.cs rename to src/CTA.Rules.Models/Actions/Csharp/Classdeclarationaction.cs diff --git a/src/CTA.Rules.Models/Actions/Elementaccessaction.cs b/src/CTA.Rules.Models/Actions/Csharp/Elementaccessaction.cs similarity index 100% rename from src/CTA.Rules.Models/Actions/Elementaccessaction.cs rename to src/CTA.Rules.Models/Actions/Csharp/Elementaccessaction.cs diff --git a/src/CTA.Rules.Models/Actions/Identifiernameaction.cs b/src/CTA.Rules.Models/Actions/Csharp/Identifiernameaction.cs similarity index 53% rename from src/CTA.Rules.Models/Actions/Identifiernameaction.cs rename to src/CTA.Rules.Models/Actions/Csharp/Identifiernameaction.cs index ea361bdf..d962cd37 100644 --- a/src/CTA.Rules.Models/Actions/Identifiernameaction.cs +++ b/src/CTA.Rules.Models/Actions/Csharp/Identifiernameaction.cs @@ -1,17 +1,16 @@  using System; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; namespace CTA.Rules.Models { - public class IdentifierNameAction : GenericAction + public class IdentifierNameAction : GenericAction { - public Func IdentifierNameActionFunc { get; set; } + public Func IdentifierNameActionFunc { get; set; } public override bool Equals(object obj) { - var action = (IdentifierNameAction)obj; + var action = (IdentifierNameAction)obj; return action?.Key == this.Key && action?.Value == this.Value && action?.IdentifierNameActionFunc.Method.Name == this.IdentifierNameActionFunc.Method.Name; @@ -21,5 +20,12 @@ public override int GetHashCode() { return HashCode.Combine(Key, Value, IdentifierNameActionFunc?.Method.Name); } + + public override IdentifierNameAction Copy() + { + IdentifierNameAction copy = (IdentifierNameAction)base.Copy(); + copy.IdentifierNameActionFunc = this.IdentifierNameActionFunc; + return copy; + } } } diff --git a/src/CTA.Rules.Models/Actions/InterfaceDeclarationAction.cs b/src/CTA.Rules.Models/Actions/Csharp/InterfaceDeclarationAction.cs similarity index 100% rename from src/CTA.Rules.Models/Actions/InterfaceDeclarationAction.cs rename to src/CTA.Rules.Models/Actions/Csharp/InterfaceDeclarationAction.cs diff --git a/src/CTA.Rules.Models/Actions/MethodDeclarationAction.cs b/src/CTA.Rules.Models/Actions/Csharp/MethodDeclarationAction.cs similarity index 100% rename from src/CTA.Rules.Models/Actions/MethodDeclarationAction.cs rename to src/CTA.Rules.Models/Actions/Csharp/MethodDeclarationAction.cs diff --git a/src/CTA.Rules.Models/Actions/ObjectCreationExpressionAction.cs b/src/CTA.Rules.Models/Actions/Csharp/ObjectCreationExpressionAction.cs similarity index 100% rename from src/CTA.Rules.Models/Actions/ObjectCreationExpressionAction.cs rename to src/CTA.Rules.Models/Actions/Csharp/ObjectCreationExpressionAction.cs diff --git a/src/CTA.Rules.Models/Actions/Usingaction.cs b/src/CTA.Rules.Models/Actions/Csharp/Usingaction.cs similarity index 100% rename from src/CTA.Rules.Models/Actions/Usingaction.cs rename to src/CTA.Rules.Models/Actions/Csharp/Usingaction.cs diff --git a/src/CTA.Rules.Models/Actions/ExpressionAction.cs b/src/CTA.Rules.Models/Actions/ExpressionAction.cs index 04bf8647..b939e590 100644 --- a/src/CTA.Rules.Models/Actions/ExpressionAction.cs +++ b/src/CTA.Rules.Models/Actions/ExpressionAction.cs @@ -1,6 +1,5 @@ using System; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; namespace CTA.Rules.Models diff --git a/src/CTA.Rules.Models/Actions/GenericAction.cs b/src/CTA.Rules.Models/Actions/GenericAction.cs index 2acdeea8..b38bfc97 100644 --- a/src/CTA.Rules.Models/Actions/GenericAction.cs +++ b/src/CTA.Rules.Models/Actions/GenericAction.cs @@ -18,5 +18,18 @@ public GenericAction() public ActionValidation ActionValidation { get; set; } public T Clone() => (T)this.MemberwiseClone(); + + public virtual GenericAction Copy() + { + GenericAction copy = new GenericAction(); + copy.Name = this.Name; + copy.Type = this.Type; + copy.Key = this.Key; + copy.Value = this.Value; + copy.Description = this.Description; + copy.TextSpan = this.TextSpan; + copy.ActionValidation = this.ActionValidation; + return copy; + } } } diff --git a/src/CTA.Rules.Models/Actions/Invocationexpressionaction.cs b/src/CTA.Rules.Models/Actions/Invocationexpressionaction.cs index 8eef5821..1ad464e9 100644 --- a/src/CTA.Rules.Models/Actions/Invocationexpressionaction.cs +++ b/src/CTA.Rules.Models/Actions/Invocationexpressionaction.cs @@ -5,13 +5,13 @@ namespace CTA.Rules.Models { - public class InvocationExpressionAction : GenericAction + public class InvocationExpressionAction : GenericAction { - public Func InvocationExpressionActionFunc { get; set; } + public Func InvocationExpressionActionFunc { get; set; } public override bool Equals(object obj) { - var action = (InvocationExpressionAction)obj; + var action = (InvocationExpressionAction)obj; return action?.Key == this.Key && action?.Value == this.Value && action?.InvocationExpressionActionFunc.Method.Name == this.InvocationExpressionActionFunc.Method.Name; diff --git a/src/CTA.Rules.Models/Actions/NamespaceAction.cs b/src/CTA.Rules.Models/Actions/NamespaceAction.cs index 4c7dfb31..21e790be 100644 --- a/src/CTA.Rules.Models/Actions/NamespaceAction.cs +++ b/src/CTA.Rules.Models/Actions/NamespaceAction.cs @@ -5,13 +5,13 @@ namespace CTA.Rules.Models { - public class NamespaceAction : GenericAction + public class NamespaceAction : GenericAction { - public Func NamespaceActionFunc { get; set; } + public Func NamespaceActionFunc { get; set; } public override bool Equals(object obj) { - var action = (NamespaceAction)obj; + var action = (NamespaceAction)obj; return action?.Value == this.Value && action?.NamespaceActionFunc.Method.Name == this.NamespaceActionFunc.Method.Name; } diff --git a/src/CTA.Rules.Models/Actions/VisualBasic/AccessorBlockAction.cs b/src/CTA.Rules.Models/Actions/VisualBasic/AccessorBlockAction.cs new file mode 100644 index 00000000..30fe7449 --- /dev/null +++ b/src/CTA.Rules.Models/Actions/VisualBasic/AccessorBlockAction.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; + +namespace CTA.Rules.Models.Actions.VisualBasic +{ + public class AccessorBlockAction : GenericAction + { + public Func AccessorBlockActionFunc { get; set; } + + public override bool Equals(object obj) + { + var action = (AccessorBlockAction)obj; + return action?.Key == this.Key + && action?.Value == this.Value + && action?.AccessorBlockActionFunc.Method.Name == this.AccessorBlockActionFunc.Method.Name; + } + + public override int GetHashCode() + { + return HashCode.Combine(Key, Value, AccessorBlockActionFunc?.Method.Name); + } + } +} diff --git a/src/CTA.Rules.Models/Actions/VisualBasic/AttributeAction.cs b/src/CTA.Rules.Models/Actions/VisualBasic/AttributeAction.cs new file mode 100644 index 00000000..f1b17efb --- /dev/null +++ b/src/CTA.Rules.Models/Actions/VisualBasic/AttributeAction.cs @@ -0,0 +1,32 @@ +using System; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; + +namespace CTA.Rules.Models.Actions.VisualBasic +{ + public class AttributeAction : GenericAction + { + public Func AttributeActionFunc { get; set; } + public Func AttributeListActionFunc { get; set; } + + public override bool Equals(object obj) + { + var action = (AttributeAction)obj; + return action?.Key == Key + && action?.Value == Value + && + ( + (action.AttributeActionFunc != null && AttributeActionFunc != null && + action.AttributeActionFunc.Method.Name == AttributeActionFunc.Method.Name) + || + (action.AttributeListActionFunc != null && AttributeListActionFunc != null && + action.AttributeListActionFunc.Method.Name == AttributeListActionFunc.Method.Name) + ); + } + + public override int GetHashCode() + { + return HashCode.Combine(Key, Value, AttributeActionFunc?.Method.Name, AttributeListActionFunc?.Method.Name); + } + } +} diff --git a/src/CTA.Rules.Models/Actions/VisualBasic/AttributeListAction.cs b/src/CTA.Rules.Models/Actions/VisualBasic/AttributeListAction.cs new file mode 100644 index 00000000..8d2d7df8 --- /dev/null +++ b/src/CTA.Rules.Models/Actions/VisualBasic/AttributeListAction.cs @@ -0,0 +1,25 @@ +using System; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; + +namespace CTA.Rules.Models.Actions.VisualBasic +{ + public class AttributeListAction : GenericAction + { + public Func AttributeListActionFunc { get; set; } + + public override bool Equals(object obj) + { + var action = (Models.AttributeAction)obj; + return action?.Key == this.Key + && action?.Value == this.Value + && action.AttributeListActionFunc != null && this.AttributeListActionFunc != null + && action.AttributeListActionFunc.Method.Name == this.AttributeListActionFunc.Method.Name; + } + + public override int GetHashCode() + { + return HashCode.Combine(Key, Value, AttributeListActionFunc?.Method.Name); + } + } +} diff --git a/src/CTA.Rules.Models/Actions/VisualBasic/ElementAccessAction.cs b/src/CTA.Rules.Models/Actions/VisualBasic/ElementAccessAction.cs new file mode 100644 index 00000000..b5926265 --- /dev/null +++ b/src/CTA.Rules.Models/Actions/VisualBasic/ElementAccessAction.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; + +namespace CTA.Rules.Models.Actions.VisualBasic +{ + public class ElementAccessAction : GenericAction + { + public Func ElementAccessExpressionActionFunc { get; set; } + + public override bool Equals(object obj) + { + var action = (ElementAccessAction)obj; + return action?.Key == this.Key + && action?.Value == this.Value + && action?.ElementAccessExpressionActionFunc.Method.Name == this.ElementAccessExpressionActionFunc.Method.Name; + } + + public override int GetHashCode() + { + return HashCode.Combine(Key, Value, ElementAccessExpressionActionFunc?.Method.Name); + } + } +} diff --git a/src/CTA.Rules.Models/Actions/VisualBasic/ImportAction.cs b/src/CTA.Rules.Models/Actions/VisualBasic/ImportAction.cs new file mode 100644 index 00000000..452e8f59 --- /dev/null +++ b/src/CTA.Rules.Models/Actions/VisualBasic/ImportAction.cs @@ -0,0 +1,25 @@ +using System; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; + +namespace CTA.Rules.Models.Actions.VisualBasic { + + public class ImportAction : GenericAction + { + public Func ImportActionFunc { get; set; } + public Func ImportsClauseActionFunc { get; set; } + + public override bool Equals(object obj) + { + var action = (ImportAction)obj; + return action?.Value == Value && + (action?.ImportActionFunc.Method.Name == ImportActionFunc.Method.Name || + action?.ImportsClauseActionFunc.Method.Name == ImportsClauseActionFunc.Method.Name); + } + + public override int GetHashCode() + { + return HashCode.Combine(Value, ImportActionFunc?.Method.Name); + } + } +} diff --git a/src/CTA.Rules.Models/Actions/VisualBasic/InterfaceBlockAction.cs b/src/CTA.Rules.Models/Actions/VisualBasic/InterfaceBlockAction.cs new file mode 100644 index 00000000..f32cc85b --- /dev/null +++ b/src/CTA.Rules.Models/Actions/VisualBasic/InterfaceBlockAction.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; + +namespace CTA.Rules.Models.Actions.VisualBasic +{ + public class InterfaceBlockAction : GenericAction + { + public Func InterfaceBlockActionFunc { get; set; } + + public override bool Equals(object obj) + { + var action = (InterfaceBlockAction)obj; + return action?.Key == this.Key + && action?.Value == this.Value + && action?.InterfaceBlockActionFunc.Method.Name == this.InterfaceBlockActionFunc.Method.Name; + } + + public override int GetHashCode() + { + return HashCode.Combine(Key, Value, InterfaceBlockActionFunc?.Method.Name); + } + } +} diff --git a/src/CTA.Rules.Models/Actions/VisualBasic/MethodBlockAction.cs b/src/CTA.Rules.Models/Actions/VisualBasic/MethodBlockAction.cs new file mode 100644 index 00000000..7703b3ee --- /dev/null +++ b/src/CTA.Rules.Models/Actions/VisualBasic/MethodBlockAction.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; + +namespace CTA.Rules.Models.Actions.VisualBasic +{ + public class MethodBlockAction : GenericAction + { + public Func MethodBlockActionFunc { get; set; } + + public override bool Equals(object obj) + { + var action = (MethodBlockAction)obj; + return action?.Key == this.Key + && action?.Value == this.Value + && action?.MethodBlockActionFunc.Method.Name == this.MethodBlockActionFunc.Method.Name; + } + + public override int GetHashCode() + { + return HashCode.Combine(Key, Value, MethodBlockActionFunc?.Method.Name); + } + } +} diff --git a/src/CTA.Rules.Models/Actions/VisualBasic/ObjectCreationExpressionAction.cs b/src/CTA.Rules.Models/Actions/VisualBasic/ObjectCreationExpressionAction.cs new file mode 100644 index 00000000..c4f1b669 --- /dev/null +++ b/src/CTA.Rules.Models/Actions/VisualBasic/ObjectCreationExpressionAction.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; + +namespace CTA.Rules.Models.Actions.VisualBasic +{ + public class ObjectCreationExpressionAction : GenericAction + { + public Func ObjectCreationExpressionGenericActionFunc { get; set; } + + public override bool Equals(object obj) + { + var action = (ObjectCreationExpressionAction)obj; + return action?.Key == this.Key + && action?.Value == this.Value + && action?.ObjectCreationExpressionGenericActionFunc.Method.Name == this.ObjectCreationExpressionGenericActionFunc.Method.Name; + } + + public override int GetHashCode() + { + return HashCode.Combine(Value, ObjectCreationExpressionGenericActionFunc?.Method.Name); + } + } +} diff --git a/src/CTA.Rules.Models/Actions/VisualBasic/TypeBlockAction.cs b/src/CTA.Rules.Models/Actions/VisualBasic/TypeBlockAction.cs new file mode 100644 index 00000000..8cffb914 --- /dev/null +++ b/src/CTA.Rules.Models/Actions/VisualBasic/TypeBlockAction.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; + +namespace CTA.Rules.Models.Actions.VisualBasic +{ + public class TypeBlockAction : GenericAction + { + public Func TypeBlockActionFunc { get; set; } + + public override bool Equals(object obj) + { + var action = (TypeBlockAction)obj; + return action?.Key == this.Key + && action?.Value == this.Value + && action?.TypeBlockActionFunc.Method.Name == this.TypeBlockActionFunc.Method.Name; + } + + public override int GetHashCode() + { + return HashCode.Combine(Key, Value, TypeBlockActionFunc?.Method.Name); + } + } +} diff --git a/src/CTA.Rules.Models/Enums.cs b/src/CTA.Rules.Models/Enums.cs index d3baad19..3fbee4e1 100644 --- a/src/CTA.Rules.Models/Enums.cs +++ b/src/CTA.Rules.Models/Enums.cs @@ -72,4 +72,17 @@ public enum WebFormsActionType ClassConversion, FileConversion } + + public enum ProjectLanguage + { + VisualBasic, + Csharp, + } + + public static class FileExtension + { + public static readonly string VisualBasic = ".vb"; + public static readonly string CSharp = ".cs"; + public static readonly string Backup = ".bak"; + } } diff --git a/src/CTA.Rules.Models/FileActions/FileActions.cs b/src/CTA.Rules.Models/FileActions/FileActions.cs index 03012f9b..d438f6e9 100644 --- a/src/CTA.Rules.Models/FileActions/FileActions.cs +++ b/src/CTA.Rules.Models/FileActions/FileActions.cs @@ -1,5 +1,9 @@ using System.Collections.Generic; +using CTA.Rules.Models.Actions.VisualBasic; using CTA.Rules.Models.Tokens; +using CTA.Rules.Models.Tokens.VisualBasic; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; namespace CTA.Rules.Models { @@ -11,32 +15,70 @@ public FileActions() ClassDeclarationActions = new HashSet(); MethodDeclarationActions = new HashSet(); ElementAccessActions = new HashSet(); - IdentifierNameActions = new HashSet(); - InvocationExpressionActions = new HashSet(); + IdentifierNameActions = + new HashSet>(); + InvocationExpressionActions = + new HashSet>(); + // Member and Expression Actions do not need separate Vb models because they act on shared SyntaxNode object ExpressionActions = new HashSet(); MemberAccessActions = new HashSet(); Usingactions = new HashSet(); - NamespaceActions = new HashSet(); + NamespaceActions = new HashSet>(); ObjectCreationExpressionActions = new HashSet(); PackageActions = new HashSet(); InterfaceDeclarationActions = new HashSet(); - NodeTokens = new List(); + NodeTokens = new List(); + + VbNodeTokens = new List(); + VbInvocationExpressionActions = + new HashSet>(); + VbImportActions = new HashSet(); + VbNamespaceActions = + new HashSet>(); + VbTypeBlockActions = new HashSet(); + VbMethodBlockActions = new HashSet(); + VbInterfaceBlockActions = new HashSet(); + VbAttributeListActions = new HashSet(); + VbIdentifierNameActions = new HashSet>(); + VbAccessorBlockActions = new HashSet(); + VbElementAccessActions = new HashSet(); + VbObjectCreationExpressionActions = new HashSet(); + VbAttributeActions = new HashSet(); } - public List NodeTokens { get; set; } + public HashSet> VbNamespaceActions { get; set; } + public HashSet VbImportActions { get; set; } + public HashSet> + VbInvocationExpressionActions { get; set; } + public HashSet> + VbIdentifierNameActions { get; set; } + public HashSet VbTypeBlockActions { get; set; } + public HashSet VbMethodBlockActions { get; set; } + public HashSet VbInterfaceBlockActions { get; set; } + public HashSet VbAttributeListActions { get; set; } + public HashSet VbAccessorBlockActions { get; set; } + public HashSet VbObjectCreationExpressionActions + { get; set; } + public HashSet VbElementAccessActions { get; set; } + public HashSet VbAttributeActions { get; set; } + public List VbNodeTokens { get; set; } + + public List NodeTokens { get; set; } public string FilePath { get; set; } public HashSet AttributeActions { get; set; } public HashSet MethodDeclarationActions { get; set; } public HashSet ClassDeclarationActions { get; set; } public HashSet InterfaceDeclarationActions { get; set; } public HashSet ElementAccessActions { get; set; } - public HashSet IdentifierNameActions { get; set; } - public HashSet InvocationExpressionActions { get; set; } + public HashSet> IdentifierNameActions { get; set; } + public HashSet> InvocationExpressionActions { get; set; } public HashSet ExpressionActions { get; set; } public HashSet MemberAccessActions { get; set; } public HashSet ObjectCreationExpressionActions { get; set; } public HashSet Usingactions { get; set; } - public HashSet NamespaceActions { get; set; } + public HashSet> NamespaceActions { get; set; } public HashSet PackageActions { get; set; } public List AllActions @@ -53,10 +95,23 @@ public List AllActions allActions.AddRange(IdentifierNameActions); allActions.AddRange(InvocationExpressionActions); allActions.AddRange(ExpressionActions); - allActions.AddRange(MemberAccessActions); allActions.AddRange(Usingactions); allActions.AddRange(ObjectCreationExpressionActions); allActions.AddRange(NamespaceActions); + + // visual basic actions + allActions.AddRange(VbImportActions); + allActions.AddRange(VbNamespaceActions); + allActions.AddRange(VbInvocationExpressionActions); + allActions.AddRange(VbTypeBlockActions); + allActions.AddRange(VbMethodBlockActions); + allActions.AddRange(VbInterfaceBlockActions); + allActions.AddRange(VbAttributeListActions); + allActions.AddRange(VbIdentifierNameActions); + allActions.AddRange(VbAccessorBlockActions); + allActions.AddRange(VbObjectCreationExpressionActions); + allActions.AddRange(VbElementAccessActions); + allActions.AddRange(VbAttributeActions); return allActions; } } diff --git a/src/CTA.Rules.Models/ProjectActions.cs b/src/CTA.Rules.Models/ProjectActions.cs index 398b0df3..961694d2 100644 --- a/src/CTA.Rules.Models/ProjectActions.cs +++ b/src/CTA.Rules.Models/ProjectActions.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using CTA.Rules.Config; +using CTA.Rules.Models.VisualBasic; namespace CTA.Rules.Models { @@ -21,6 +22,8 @@ public ProjectActions() public BlockingCollection ProjectReferenceActions { get; set; } public List ProjectLevelActions { get; set; } public RootNodes ProjectRules { get; set; } + public RootNodes CsharpProjectRules { get; set; } + public VisualBasicRootNodes VbProjectRules { get; set; } public override string ToString() { @@ -33,39 +36,7 @@ public override string ToString() var actions = new List(); StringBuilder fileChanges = new StringBuilder(); - foreach (var action in fileAction.AttributeActions) - { - actions.Add(action.Description); - } - foreach (var action in fileAction.ClassDeclarationActions) - { - actions.Add(action.Description); - } - foreach (var action in fileAction.ElementAccessActions) - { - actions.Add(action.Description); - } - foreach (var action in fileAction.IdentifierNameActions) - { - actions.Add(action.Description); - } - foreach (var action in fileAction.InvocationExpressionActions) - { - actions.Add(action.Description); - } - foreach (var action in fileAction.ExpressionActions) - { - actions.Add(action.Description); - } - foreach (var action in fileAction.MemberAccessActions) - { - actions.Add(action.Description); - } - foreach (var action in fileAction.MethodDeclarationActions) - { - actions.Add(action.Description); - } - foreach (var action in fileAction.Usingactions) + foreach (var action in fileAction.AllActions) { actions.Add(action.Description); } @@ -99,40 +70,7 @@ public string ToSummaryString() { var actions = new List(); - StringBuilder fileChanges = new StringBuilder(); - foreach (var action in fileAction.AttributeActions) - { - actions.Add(string.Concat(action.Type, ":", action.Name, ":", action.Key)); - } - foreach (var action in fileAction.ClassDeclarationActions) - { - actions.Add(string.Concat(action.Type, ":", action.Name, ":", action.Key)); - } - foreach (var action in fileAction.ElementAccessActions) - { - actions.Add(string.Concat(action.Type, ":", action.Name, ":", action.Key)); - } - foreach (var action in fileAction.IdentifierNameActions) - { - actions.Add(string.Concat(action.Type, ":", action.Name, ":", action.Key)); - } - foreach (var action in fileAction.InvocationExpressionActions) - { - actions.Add(string.Concat(action.Type, ":", action.Name, ":", action.Key)); - } - foreach (var action in fileAction.ExpressionActions) - { - actions.Add(string.Concat(action.Type, ":", action.Name, ":", action.Key)); - } - foreach (var action in fileAction.MemberAccessActions) - { - actions.Add(string.Concat(action.Type, ":", action.Name, ":", action.Key)); - } - foreach (var action in fileAction.MethodDeclarationActions) - { - actions.Add(string.Concat(action.Type, ":", action.Name, ":", action.Key)); - } - foreach (var action in fileAction.Usingactions) + foreach (var action in fileAction.AllActions) { actions.Add(string.Concat(action.Type, ":", action.Name, ":", action.Key)); } diff --git a/src/CTA.Rules.Models/RootNodes.cs b/src/CTA.Rules.Models/RootNodes.cs index 1255cff2..cd355eea 100644 --- a/src/CTA.Rules.Models/RootNodes.cs +++ b/src/CTA.Rules.Models/RootNodes.cs @@ -19,7 +19,7 @@ public RootNodes() ObjectCreationExpressionTokens = new HashSet(); InterfaceDeclarationTokens = new HashSet(); NamespaceTokens = new HashSet(); - ProjectTokens = new HashSet(); + ProjectTokens = new HashSet(); } @@ -35,6 +35,6 @@ public RootNodes() public HashSet MethodDeclarationTokens { get; set; } public HashSet NamespaceTokens { get; set; } public HashSet ObjectCreationExpressionTokens { get; set; } - public HashSet ProjectTokens { get; set; } + public HashSet ProjectTokens { get; set; } } } diff --git a/src/CTA.Rules.Models/RulesFiles/RulesFileLoaderResponse.cs b/src/CTA.Rules.Models/RulesFiles/RulesFileLoaderResponse.cs new file mode 100644 index 00000000..2bc130cb --- /dev/null +++ b/src/CTA.Rules.Models/RulesFiles/RulesFileLoaderResponse.cs @@ -0,0 +1,10 @@ +using CTA.Rules.Models.VisualBasic; + +namespace CTA.Rules.Models.RulesFiles +{ + public class RulesFileLoaderResponse + { + public RootNodes CsharpRootNodes { get; set; } + public VisualBasicRootNodes VisualBasicRootNodes { get; set; } + } +} diff --git a/src/CTA.Rules.Models/RulesInput.cs b/src/CTA.Rules.Models/RulesInput.cs index de82cde1..e10557f3 100644 --- a/src/CTA.Rules.Models/RulesInput.cs +++ b/src/CTA.Rules.Models/RulesInput.cs @@ -78,8 +78,10 @@ public class Action public string Name { get; set; } public string Type { get; set; } public dynamic Value { get; set; } + public dynamic VbValue { get; set; } public string Description { get; set; } public ActionValidation ActionValidation { get; set; } + public ActionValidation VbActionValidation { get; set; } } public class Attribute diff --git a/src/CTA.Rules.Models/Tokens/Attributetoken.cs b/src/CTA.Rules.Models/Tokens/Csharp/Attributetoken.cs similarity index 90% rename from src/CTA.Rules.Models/Tokens/Attributetoken.cs rename to src/CTA.Rules.Models/Tokens/Csharp/Attributetoken.cs index b60a776b..0ad3413c 100644 --- a/src/CTA.Rules.Models/Tokens/Attributetoken.cs +++ b/src/CTA.Rules.Models/Tokens/Csharp/Attributetoken.cs @@ -5,7 +5,7 @@ namespace CTA.Rules.Models { - public class AttributeToken : NodeToken + public class AttributeToken : CsharpNodeToken { public override bool Equals(object obj) { diff --git a/src/CTA.Rules.Models/Tokens/Classdeclarationtoken.cs b/src/CTA.Rules.Models/Tokens/Csharp/Classdeclarationtoken.cs similarity index 86% rename from src/CTA.Rules.Models/Tokens/Classdeclarationtoken.cs rename to src/CTA.Rules.Models/Tokens/Csharp/Classdeclarationtoken.cs index fd2edbef..5a660a98 100644 --- a/src/CTA.Rules.Models/Tokens/Classdeclarationtoken.cs +++ b/src/CTA.Rules.Models/Tokens/Csharp/Classdeclarationtoken.cs @@ -4,7 +4,7 @@ namespace CTA.Rules.Models { - public class ClassDeclarationToken : NodeToken + public class ClassDeclarationToken : CsharpNodeToken { public override bool Equals(object obj) { diff --git a/src/CTA.Rules.Models/Tokens/Csharp/CsharpNodeToken.cs b/src/CTA.Rules.Models/Tokens/Csharp/CsharpNodeToken.cs new file mode 100644 index 00000000..69dc1161 --- /dev/null +++ b/src/CTA.Rules.Models/Tokens/Csharp/CsharpNodeToken.cs @@ -0,0 +1,76 @@ +using System.Linq; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CTA.Rules.Models.Tokens +{ + public class CsharpNodeToken : NodeToken + { + public CsharpNodeToken() + { + UsingActions = new List(); + InvocationExpressionActions = new List>(); + NamespaceActions = new List>(); + IdentifierNameActions = new List>(); + ObjectCreationExpressionActions = new List(); + ElementAccessActions = new List(); + AttributeActions = new List(); + AttributeListActions = new List(); + ClassDeclarationActions = new List(); + MethodDeclarationActions = new List(); + InterfaceDeclarationActions = new List(); + } + + public List UsingActions { get; set; } + public List> InvocationExpressionActions { get; set; } + public List> NamespaceActions { get; set; } + public List> IdentifierNameActions { get; set; } + public List ObjectCreationExpressionActions{ get; set; } + public List ElementAccessActions { get; set; } + public List AttributeActions { get; set; } + public List AttributeListActions { get; set; } + public List ClassDeclarationActions { get; set; } + public List InterfaceDeclarationActions { get; set; } + public List MethodDeclarationActions { get; set; } + + public override CsharpNodeToken Clone() + { + CsharpNodeToken cloned = (CsharpNodeToken)base.Clone(); + cloned.UsingActions = cloned.UsingActions.Select(action => action.Clone()).ToList(); + cloned.IdentifierNameActions = cloned.IdentifierNameActions.Select(action => action.Clone>()).ToList(); + cloned.InvocationExpressionActions = cloned.InvocationExpressionActions.Select(action => action.Clone>()).ToList(); + cloned.NamespaceActions = cloned.NamespaceActions.Select(action => action.Clone>()).ToList(); + cloned.ObjectCreationExpressionActions = cloned.ObjectCreationExpressionActions.Select(action => action.Clone()).ToList(); + cloned.ElementAccessActions = cloned.ElementAccessActions.Select(action => action.Clone()).ToList(); + cloned.AttributeActions = cloned.AttributeActions?.Select(action => action.Clone())?.ToList(); + cloned.AttributeListActions = cloned.AttributeListActions?.Select(action => action.Clone())?.ToList(); + cloned.ClassDeclarationActions = cloned.ClassDeclarationActions?.Select(action => action.Clone())?.ToList(); + cloned.InterfaceDeclarationActions = cloned.InterfaceDeclarationActions.Select(action => action.Clone()).ToList(); + cloned.MethodDeclarationActions = cloned.MethodDeclarationActions.Select(action => action.Clone()).ToList(); + + return cloned; + } + + public override List AllActions + { + get + { + var allActions = new List(); + allActions.AddRange(AttributeActions); + allActions.AddRange(AttributeListActions); + allActions.AddRange(InvocationExpressionActions); + allActions.AddRange(UsingActions); + allActions.AddRange(NamespaceActions); + allActions.AddRange(IdentifierNameActions); + allActions.AddRange(ObjectCreationExpressionActions); + allActions.AddRange(ElementAccessActions); + allActions.AddRange(MethodDeclarationActions); + allActions.AddRange(ClassDeclarationActions); + allActions.AddRange(InterfaceDeclarationActions); + allActions.AddRange(base.AllActions); + return allActions; + } + } + + } +} diff --git a/src/CTA.Rules.Models/Tokens/Elementaccesstoken.cs b/src/CTA.Rules.Models/Tokens/Csharp/Elementaccesstoken.cs similarity index 87% rename from src/CTA.Rules.Models/Tokens/Elementaccesstoken.cs rename to src/CTA.Rules.Models/Tokens/Csharp/Elementaccesstoken.cs index 36786c27..bb92bf40 100644 --- a/src/CTA.Rules.Models/Tokens/Elementaccesstoken.cs +++ b/src/CTA.Rules.Models/Tokens/Csharp/Elementaccesstoken.cs @@ -4,7 +4,7 @@ namespace CTA.Rules.Models { - public class ElementAccessToken : NodeToken + public class ElementAccessToken : CsharpNodeToken { public override bool Equals(object obj) { diff --git a/src/CTA.Rules.Models/Tokens/ExpressionToken.cs b/src/CTA.Rules.Models/Tokens/Csharp/ExpressionToken.cs similarity index 89% rename from src/CTA.Rules.Models/Tokens/ExpressionToken.cs rename to src/CTA.Rules.Models/Tokens/Csharp/ExpressionToken.cs index eb3eab9e..ab46e1cb 100644 --- a/src/CTA.Rules.Models/Tokens/ExpressionToken.cs +++ b/src/CTA.Rules.Models/Tokens/Csharp/ExpressionToken.cs @@ -3,7 +3,7 @@ namespace CTA.Rules.Models.Tokens { - public class ExpressionToken : NodeToken + public class ExpressionToken : CsharpNodeToken { public override bool Equals(object obj) { diff --git a/src/CTA.Rules.Models/Tokens/Identifiernametoken.cs b/src/CTA.Rules.Models/Tokens/Csharp/Identifiernametoken.cs similarity index 88% rename from src/CTA.Rules.Models/Tokens/Identifiernametoken.cs rename to src/CTA.Rules.Models/Tokens/Csharp/Identifiernametoken.cs index e1abab25..8ca31f72 100644 --- a/src/CTA.Rules.Models/Tokens/Identifiernametoken.cs +++ b/src/CTA.Rules.Models/Tokens/Csharp/Identifiernametoken.cs @@ -5,7 +5,7 @@ namespace CTA.Rules.Models { - public class IdentifierNameToken : NodeToken + public class IdentifierNameToken : CsharpNodeToken { public override bool Equals(object obj) { diff --git a/src/CTA.Rules.Models/Tokens/InterfaceDeclarationToken.cs b/src/CTA.Rules.Models/Tokens/Csharp/InterfaceDeclarationToken.cs similarity index 86% rename from src/CTA.Rules.Models/Tokens/InterfaceDeclarationToken.cs rename to src/CTA.Rules.Models/Tokens/Csharp/InterfaceDeclarationToken.cs index d39e34cf..2766066d 100644 --- a/src/CTA.Rules.Models/Tokens/InterfaceDeclarationToken.cs +++ b/src/CTA.Rules.Models/Tokens/Csharp/InterfaceDeclarationToken.cs @@ -4,7 +4,7 @@ namespace CTA.Rules.Models { - public class InterfaceDeclarationToken : NodeToken + public class InterfaceDeclarationToken : CsharpNodeToken { public override bool Equals(object obj) { diff --git a/src/CTA.Rules.Models/Tokens/Invocationexpressiontoken.cs b/src/CTA.Rules.Models/Tokens/Csharp/Invocationexpressiontoken.cs similarity index 87% rename from src/CTA.Rules.Models/Tokens/Invocationexpressiontoken.cs rename to src/CTA.Rules.Models/Tokens/Csharp/Invocationexpressiontoken.cs index b0e01826..28cf80fd 100644 --- a/src/CTA.Rules.Models/Tokens/Invocationexpressiontoken.cs +++ b/src/CTA.Rules.Models/Tokens/Csharp/Invocationexpressiontoken.cs @@ -3,7 +3,7 @@ namespace CTA.Rules.Models.Tokens { - public class InvocationExpressionToken : NodeToken + public class InvocationExpressionToken : CsharpNodeToken { public override bool Equals(object obj) { diff --git a/src/CTA.Rules.Models/Tokens/Memberaccesstoken.cs b/src/CTA.Rules.Models/Tokens/Csharp/Memberaccesstoken.cs similarity index 87% rename from src/CTA.Rules.Models/Tokens/Memberaccesstoken.cs rename to src/CTA.Rules.Models/Tokens/Csharp/Memberaccesstoken.cs index f42dd2c7..83767ca2 100644 --- a/src/CTA.Rules.Models/Tokens/Memberaccesstoken.cs +++ b/src/CTA.Rules.Models/Tokens/Csharp/Memberaccesstoken.cs @@ -5,7 +5,7 @@ namespace CTA.Rules.Models { - public class MemberAccessToken : NodeToken + public class MemberAccessToken : CsharpNodeToken { public override bool Equals(object obj) { diff --git a/src/CTA.Rules.Models/Tokens/MethodDeclarationToken.cs b/src/CTA.Rules.Models/Tokens/Csharp/MethodDeclarationToken.cs similarity index 85% rename from src/CTA.Rules.Models/Tokens/MethodDeclarationToken.cs rename to src/CTA.Rules.Models/Tokens/Csharp/MethodDeclarationToken.cs index 13d26610..c2486b96 100644 --- a/src/CTA.Rules.Models/Tokens/MethodDeclarationToken.cs +++ b/src/CTA.Rules.Models/Tokens/Csharp/MethodDeclarationToken.cs @@ -2,7 +2,7 @@ namespace CTA.Rules.Models.Tokens { - public class MethodDeclarationToken : NodeToken + public class MethodDeclarationToken : CsharpNodeToken { public override bool Equals(object obj) { diff --git a/src/CTA.Rules.Models/Tokens/NamespaceToken.cs b/src/CTA.Rules.Models/Tokens/Csharp/NamespaceToken.cs similarity index 86% rename from src/CTA.Rules.Models/Tokens/NamespaceToken.cs rename to src/CTA.Rules.Models/Tokens/Csharp/NamespaceToken.cs index 5a62df35..45711213 100644 --- a/src/CTA.Rules.Models/Tokens/NamespaceToken.cs +++ b/src/CTA.Rules.Models/Tokens/Csharp/NamespaceToken.cs @@ -3,7 +3,7 @@ namespace CTA.Rules.Models.Tokens { - public class NamespaceToken : NodeToken + public class NamespaceToken : CsharpNodeToken { public override bool Equals(object obj) { diff --git a/src/CTA.Rules.Models/Tokens/ObjectCreationExpressionToken.cs b/src/CTA.Rules.Models/Tokens/Csharp/ObjectCreationExpressionToken.cs similarity index 85% rename from src/CTA.Rules.Models/Tokens/ObjectCreationExpressionToken.cs rename to src/CTA.Rules.Models/Tokens/Csharp/ObjectCreationExpressionToken.cs index 8371e590..7fc08e4f 100644 --- a/src/CTA.Rules.Models/Tokens/ObjectCreationExpressionToken.cs +++ b/src/CTA.Rules.Models/Tokens/Csharp/ObjectCreationExpressionToken.cs @@ -4,7 +4,7 @@ namespace CTA.Rules.Models.Tokens { - public class ObjectCreationExpressionToken : NodeToken + public class ObjectCreationExpressionToken : CsharpNodeToken { public override bool Equals(object obj) { diff --git a/src/CTA.Rules.Models/Tokens/ProjectToken.cs b/src/CTA.Rules.Models/Tokens/Csharp/ProjectToken.cs similarity index 52% rename from src/CTA.Rules.Models/Tokens/ProjectToken.cs rename to src/CTA.Rules.Models/Tokens/Csharp/ProjectToken.cs index 0f66da6c..fe4b6857 100644 --- a/src/CTA.Rules.Models/Tokens/ProjectToken.cs +++ b/src/CTA.Rules.Models/Tokens/Csharp/ProjectToken.cs @@ -1,6 +1,6 @@ namespace CTA.Rules.Models.Tokens { - public class ProjectToken : NodeToken + public class ProjectToken : CsharpNodeToken { } } diff --git a/src/CTA.Rules.Models/Tokens/Usingdirectivetoken.cs b/src/CTA.Rules.Models/Tokens/Csharp/Usingdirectivetoken.cs similarity index 85% rename from src/CTA.Rules.Models/Tokens/Usingdirectivetoken.cs rename to src/CTA.Rules.Models/Tokens/Csharp/Usingdirectivetoken.cs index 1b5a1efe..cb9c4a53 100644 --- a/src/CTA.Rules.Models/Tokens/Usingdirectivetoken.cs +++ b/src/CTA.Rules.Models/Tokens/Csharp/Usingdirectivetoken.cs @@ -3,7 +3,7 @@ namespace CTA.Rules.Models.Tokens { - public class UsingDirectiveToken : NodeToken + public class UsingDirectiveToken : CsharpNodeToken { public override bool Equals(object obj) { diff --git a/src/CTA.Rules.Models/Tokens/NodeToken.cs b/src/CTA.Rules.Models/Tokens/NodeToken.cs index ffa0a9e7..6b5d5d9b 100644 --- a/src/CTA.Rules.Models/Tokens/NodeToken.cs +++ b/src/CTA.Rules.Models/Tokens/NodeToken.cs @@ -9,20 +9,9 @@ public class NodeToken { public NodeToken() { - AttributeActions = new List(); - AttributeListActions = new List(); - ClassDeclarationActions = new List(); - UsingActions = new List(); - IdentifierNameActions = new List(); - InvocationExpressionActions = new List(); ExpressionActions = new List(); - MethodDeclarationActions = new List(); - ElementAccessActions = new List(); MemberAccessActions = new List(); - NamespaceActions = new List(); - ObjectCreationExpressionActions = new List(); PackageActions = new List(); - InterfaceDeclarationActions = new List(); ProjectLevelActions = new List(); ProjectFileActions = new List(); ProjectTypeActions = new List(); @@ -38,42 +27,21 @@ public NodeToken() public string Description { get; set; } public IList TextChanges { get; set; } public List TargetCPU { get; set; } - public List AttributeActions { get; set; } - public List AttributeListActions { get; set; } - public List ClassDeclarationActions { get; set; } - public List InterfaceDeclarationActions { get; set; } - public List MethodDeclarationActions { get; set; } - public List ElementAccessActions { get; set; } + public List MemberAccessActions { get; set; } - public List UsingActions { get; set; } - public List IdentifierNameActions { get; set; } - public List InvocationExpressionActions { get; set; } public List ExpressionActions { get; set; } - public List NamespaceActions { get; set; } - public List ObjectCreationExpressionActions { get; set; } public List PackageActions { get; set; } public List ProjectLevelActions { get; set; } public List ProjectFileActions { get; set; } public List ProjectTypeActions { get; set; } - public NodeToken Clone() + public virtual NodeToken Clone() { NodeToken cloned = (NodeToken)this.MemberwiseClone(); cloned.TextChanges = cloned.TextChanges?.Select(textChange => textChange.Clone()).ToList(); - cloned.TargetCPU = cloned.TargetCPU?.ToList(); - cloned.AttributeActions = cloned.AttributeActions?.Select(action => action.Clone())?.ToList(); - cloned.AttributeListActions = cloned.AttributeListActions?.Select(action => action.Clone())?.ToList(); - cloned.ClassDeclarationActions = cloned.ClassDeclarationActions?.Select(action => action.Clone())?.ToList(); - cloned.InterfaceDeclarationActions = cloned.InterfaceDeclarationActions.Select(action => action.Clone()).ToList(); - cloned.MethodDeclarationActions = cloned.MethodDeclarationActions.Select(action => action.Clone()).ToList(); - cloned.ElementAccessActions = cloned.ElementAccessActions.Select(action => action.Clone()).ToList(); + cloned.TargetCPU = cloned.TargetCPU?.ToList(); cloned.MemberAccessActions = cloned.MemberAccessActions.Select(action => action.Clone()).ToList(); - cloned.UsingActions = cloned.UsingActions.Select(action => action.Clone()).ToList(); - cloned.IdentifierNameActions = cloned.IdentifierNameActions.Select(action => action.Clone()).ToList(); - cloned.InvocationExpressionActions = cloned.InvocationExpressionActions.Select(action => action.Clone()).ToList(); cloned.ExpressionActions = cloned.ExpressionActions.Select(action => action.Clone()).ToList(); - cloned.NamespaceActions = cloned.NamespaceActions.Select(action => action.Clone()).ToList(); - cloned.ObjectCreationExpressionActions = cloned.ObjectCreationExpressionActions.Select(action => action.Clone()).ToList(); cloned.PackageActions = cloned.PackageActions.Select(action => action.Clone()).ToList(); cloned.ProjectLevelActions = cloned.ProjectLevelActions.Select(action => action.Clone()).ToList(); cloned.ProjectFileActions = cloned.ProjectFileActions.Select(action => action.Clone()).ToList(); @@ -81,25 +49,14 @@ public NodeToken Clone() return cloned; } - public List AllActions + public virtual List AllActions { get { var allActions = new List(); - allActions.AddRange(AttributeActions); - allActions.AddRange(AttributeListActions); - allActions.AddRange(MethodDeclarationActions); - allActions.AddRange(ClassDeclarationActions); - allActions.AddRange(InterfaceDeclarationActions); - allActions.AddRange(ElementAccessActions); allActions.AddRange(MemberAccessActions); - allActions.AddRange(IdentifierNameActions); - allActions.AddRange(InvocationExpressionActions); allActions.AddRange(ExpressionActions); allActions.AddRange(MemberAccessActions); - allActions.AddRange(UsingActions); - allActions.AddRange(ObjectCreationExpressionActions); - allActions.AddRange(NamespaceActions); return allActions; } } diff --git a/src/CTA.Rules.Models/Tokens/VisualBasic/AccessorBlockToken.cs b/src/CTA.Rules.Models/Tokens/VisualBasic/AccessorBlockToken.cs new file mode 100644 index 00000000..28057aed --- /dev/null +++ b/src/CTA.Rules.Models/Tokens/VisualBasic/AccessorBlockToken.cs @@ -0,0 +1,18 @@ +using System; + +namespace CTA.Rules.Models.Tokens.VisualBasic +{ + public class AccessorBlockToken : VisualBasicNodeToken + { + public override bool Equals(object obj) + { + var token = (AccessorBlockToken)obj; + return token?.Type == this.Type && token?.Namespace == this.Namespace && token?.Key.Trim() == this.Key.Trim(); + } + + public override int GetHashCode() + { + return HashCode.Combine(Key, Namespace, Type); + } + } +} diff --git a/src/CTA.Rules.Models/Tokens/VisualBasic/AttributeListToken.cs b/src/CTA.Rules.Models/Tokens/VisualBasic/AttributeListToken.cs new file mode 100644 index 00000000..fd1c209f --- /dev/null +++ b/src/CTA.Rules.Models/Tokens/VisualBasic/AttributeListToken.cs @@ -0,0 +1,18 @@ +using System; + +namespace CTA.Rules.Models.Tokens.VisualBasic +{ + public class AttributeListToken : VisualBasicNodeToken + { + public override bool Equals(object obj) + { + var token = (AttributeListToken)obj; + return token?.Type == this.Type && token?.Namespace == this.Namespace && token?.Key.Trim() == this.Key.Trim(); + } + + public override int GetHashCode() + { + return HashCode.Combine(Key, Namespace, Type); + } + } +} diff --git a/src/CTA.Rules.Models/Tokens/VisualBasic/AttributeToken.cs b/src/CTA.Rules.Models/Tokens/VisualBasic/AttributeToken.cs new file mode 100644 index 00000000..98d10de3 --- /dev/null +++ b/src/CTA.Rules.Models/Tokens/VisualBasic/AttributeToken.cs @@ -0,0 +1,18 @@ +using System; + +namespace CTA.Rules.Models.Tokens.VisualBasic +{ + public class AttributeToken : VisualBasicNodeToken + { + public override bool Equals(object obj) + { + var token = (AttributeToken)obj; + return token?.Type == this.Type && token?.Namespace == this.Namespace && token?.Key.Trim() == this.Key.Trim(); + } + + public override int GetHashCode() + { + return HashCode.Combine(Key, Namespace, Type); + } + } +} diff --git a/src/CTA.Rules.Models/Tokens/VisualBasic/ElementAccessToken.cs b/src/CTA.Rules.Models/Tokens/VisualBasic/ElementAccessToken.cs new file mode 100644 index 00000000..9b4c1ce7 --- /dev/null +++ b/src/CTA.Rules.Models/Tokens/VisualBasic/ElementAccessToken.cs @@ -0,0 +1,17 @@ +using System; + +namespace CTA.Rules.Models.Tokens.VisualBasic +{ + public class ElementAccessToken : VisualBasicNodeToken + { + public override bool Equals(object obj) + { + var token = (ElementAccessToken)obj; + return token?.FullKey == this.FullKey; + } + public override int GetHashCode() + { + return HashCode.Combine(FullKey); + } + } +} diff --git a/src/CTA.Rules.Models/Tokens/VisualBasic/ExpressionToken.cs b/src/CTA.Rules.Models/Tokens/VisualBasic/ExpressionToken.cs new file mode 100644 index 00000000..02135f46 --- /dev/null +++ b/src/CTA.Rules.Models/Tokens/VisualBasic/ExpressionToken.cs @@ -0,0 +1,17 @@ +using System; + +namespace CTA.Rules.Models.Tokens.VisualBasic +{ + public class ExpressionToken : VisualBasicNodeToken + { + public override bool Equals(object obj) + { + var token = (ExpressionToken)obj; + return token?.Key == this.Key && token?.Namespace == this.Namespace && token?.Type == this.Type; + } + public override int GetHashCode() + { + return HashCode.Combine(Key, Namespace, Type); + } + } +} diff --git a/src/CTA.Rules.Models/Tokens/VisualBasic/IdentifierNameToken.cs b/src/CTA.Rules.Models/Tokens/VisualBasic/IdentifierNameToken.cs new file mode 100644 index 00000000..d9701fd5 --- /dev/null +++ b/src/CTA.Rules.Models/Tokens/VisualBasic/IdentifierNameToken.cs @@ -0,0 +1,19 @@ + +using System; +using CTA.Rules.Models.Tokens.VisualBasic; + +namespace CTA.Rules.Models.VisualBasic +{ + public class IdentifierNameToken : VisualBasicNodeToken + { + public override bool Equals(object obj) + { + var token = (IdentifierNameToken)obj; + return token?.Key == this.Key && token?.Namespace == this.Namespace; + } + public override int GetHashCode() + { + return HashCode.Combine(Key, Namespace); + } + } +} diff --git a/src/CTA.Rules.Models/Tokens/VisualBasic/ImportStatementToken.cs b/src/CTA.Rules.Models/Tokens/VisualBasic/ImportStatementToken.cs new file mode 100644 index 00000000..3d05eafb --- /dev/null +++ b/src/CTA.Rules.Models/Tokens/VisualBasic/ImportStatementToken.cs @@ -0,0 +1,17 @@ +using System; + +namespace CTA.Rules.Models.Tokens.VisualBasic +{ + public class ImportStatementToken : VisualBasicNodeToken + { + public override bool Equals(object obj) + { + var token = (ImportStatementToken)obj; + return token?.Key == this.Key; + } + public override int GetHashCode() + { + return HashCode.Combine(Key); + } + } +} diff --git a/src/CTA.Rules.Models/Tokens/VisualBasic/InterfaceBlockToken.cs b/src/CTA.Rules.Models/Tokens/VisualBasic/InterfaceBlockToken.cs new file mode 100644 index 00000000..3dbc518e --- /dev/null +++ b/src/CTA.Rules.Models/Tokens/VisualBasic/InterfaceBlockToken.cs @@ -0,0 +1,18 @@ +using System; +using CTA.Rules.Models.Tokens; + +namespace CTA.Rules.Models.Tokens.VisualBasic +{ + public class InterfaceBlockToken : VisualBasicNodeToken + { + public override bool Equals(object obj) + { + var token = (InterfaceBlockToken)obj; + return token?.FullKey == this.FullKey; + } + public override int GetHashCode() + { + return HashCode.Combine(FullKey); + } + } +} diff --git a/src/CTA.Rules.Models/Tokens/VisualBasic/InvocationExpressionToken.cs b/src/CTA.Rules.Models/Tokens/VisualBasic/InvocationExpressionToken.cs new file mode 100644 index 00000000..85b466a8 --- /dev/null +++ b/src/CTA.Rules.Models/Tokens/VisualBasic/InvocationExpressionToken.cs @@ -0,0 +1,18 @@ +using System; +using CTA.Rules.Config; + +namespace CTA.Rules.Models.Tokens.VisualBasic +{ + public class InvocationExpressionToken : VisualBasicNodeToken + { + public override bool Equals(object obj) + { + var token = (InvocationExpressionToken)obj; + return token?.Key == this.Key && token?.Namespace == this.Namespace && token?.Type == this.Type; + } + public override int GetHashCode() + { + return HashCode.Combine(Key, Namespace, Type); + } + } +} diff --git a/src/CTA.Rules.Models/Tokens/VisualBasic/MemberAccessToken.cs b/src/CTA.Rules.Models/Tokens/VisualBasic/MemberAccessToken.cs new file mode 100644 index 00000000..e8e5ce82 --- /dev/null +++ b/src/CTA.Rules.Models/Tokens/VisualBasic/MemberAccessToken.cs @@ -0,0 +1,18 @@ +using System; + + +namespace CTA.Rules.Models.Tokens.VisualBasic +{ + public class MemberAccessToken : VisualBasicNodeToken + { + public override bool Equals(object obj) + { + var token = (MemberAccessToken)obj; + return token?.FullKey == this.FullKey; + } + public override int GetHashCode() + { + return HashCode.Combine(FullKey); + } + } +} diff --git a/src/CTA.Rules.Models/Tokens/VisualBasic/MethodBlockToken.cs b/src/CTA.Rules.Models/Tokens/VisualBasic/MethodBlockToken.cs new file mode 100644 index 00000000..bead566e --- /dev/null +++ b/src/CTA.Rules.Models/Tokens/VisualBasic/MethodBlockToken.cs @@ -0,0 +1,17 @@ +using System; + +namespace CTA.Rules.Models.Tokens.VisualBasic +{ + public class MethodBlockToken : VisualBasicNodeToken + { + public override bool Equals(object obj) + { + var token = (MethodBlockToken)obj; + return token?.FullKey == this.FullKey; + } + public override int GetHashCode() + { + return HashCode.Combine(FullKey); + } + } +} diff --git a/src/CTA.Rules.Models/Tokens/VisualBasic/NamespaceToken.cs b/src/CTA.Rules.Models/Tokens/VisualBasic/NamespaceToken.cs new file mode 100644 index 00000000..c8c7c206 --- /dev/null +++ b/src/CTA.Rules.Models/Tokens/VisualBasic/NamespaceToken.cs @@ -0,0 +1,17 @@ +using System; + +namespace CTA.Rules.Models.Tokens.VisualBasic +{ + public class NamespaceToken : VisualBasicNodeToken + { + public override bool Equals(object obj) + { + var token = (NamespaceToken)obj; + return token?.Key == this.Key; + } + public override int GetHashCode() + { + return HashCode.Combine(Key); + } + } +} diff --git a/src/CTA.Rules.Models/Tokens/VisualBasic/ObjectCreationExpressionToken.cs b/src/CTA.Rules.Models/Tokens/VisualBasic/ObjectCreationExpressionToken.cs new file mode 100644 index 00000000..7a180334 --- /dev/null +++ b/src/CTA.Rules.Models/Tokens/VisualBasic/ObjectCreationExpressionToken.cs @@ -0,0 +1,16 @@ +using System; + +namespace CTA.Rules.Models.Tokens.VisualBasic; + +public class ObjectCreationExpressionToken : VisualBasicNodeToken +{ + public override bool Equals(object obj) + { + var token = (ObjectCreationExpressionToken)obj; + return token?.Key == this.Key; + } + public override int GetHashCode() + { + return HashCode.Combine(Key, Namespace, Type); + } +} diff --git a/src/CTA.Rules.Models/Tokens/VisualBasic/ProjectToken.cs b/src/CTA.Rules.Models/Tokens/VisualBasic/ProjectToken.cs new file mode 100644 index 00000000..b8940d59 --- /dev/null +++ b/src/CTA.Rules.Models/Tokens/VisualBasic/ProjectToken.cs @@ -0,0 +1,6 @@ +namespace CTA.Rules.Models.Tokens.VisualBasic +{ + public class ProjectToken : VisualBasicNodeToken + { + } +} diff --git a/src/CTA.Rules.Models/Tokens/VisualBasic/TypeBlockToken.cs b/src/CTA.Rules.Models/Tokens/VisualBasic/TypeBlockToken.cs new file mode 100644 index 00000000..3cc78d53 --- /dev/null +++ b/src/CTA.Rules.Models/Tokens/VisualBasic/TypeBlockToken.cs @@ -0,0 +1,19 @@ + +using System; +using CTA.Rules.Models.Tokens.VisualBasic; + +namespace CTA.Rules.Models.VisualBasic +{ + public class TypeBlockToken : VisualBasicNodeToken + { + public override bool Equals(object obj) + { + var token = (ClassDeclarationToken)obj; + return token?.FullKey == this.FullKey; + } + public override int GetHashCode() + { + return HashCode.Combine(FullKey); + } + } +} diff --git a/src/CTA.Rules.Models/Tokens/VisualBasic/VisualBasicNodeToken.cs b/src/CTA.Rules.Models/Tokens/VisualBasic/VisualBasicNodeToken.cs new file mode 100644 index 00000000..2e80213a --- /dev/null +++ b/src/CTA.Rules.Models/Tokens/VisualBasic/VisualBasicNodeToken.cs @@ -0,0 +1,94 @@ +using System.Linq; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using CTA.Rules.Models.Actions.VisualBasic; + +namespace CTA.Rules.Models.Tokens.VisualBasic +{ + public class VisualBasicNodeToken : NodeToken + { + public VisualBasicNodeToken() + { + InvocationExpressionActions = new List>(); + ImportActions = new List(); + NamespaceActions = new List>(); + IdentifierNameActions = new List>(); + TypeBlockActions = new List(); + MethodBlockActions = new List(); + InterfaceBlockActions = new List(); + VbAttributeListActions = new List(); + AccessorBlockActions = new List(); + ObjectCreationExpressionActions = new List(); + ElementAccessActions = new List(); + AttributeActions = new List(); + + } + + public List> InvocationExpressionActions { get; set; } + public List ImportActions { get; set; } + public List> NamespaceActions { get; set; } + public List> IdentifierNameActions { get; set; } + public List TypeBlockActions { get; set; } + public List MethodBlockActions { get; set; } + public List InterfaceBlockActions { get; set; } + public List VbAttributeListActions { get; set; } + public List AccessorBlockActions { get; set; } + public List ObjectCreationExpressionActions{ get; set; } + public List ElementAccessActions { get; set; } + public List AttributeActions { get; set; } + + + public override VisualBasicNodeToken Clone() + { + VisualBasicNodeToken cloned = (VisualBasicNodeToken)base.Clone(); + cloned.InvocationExpressionActions = cloned.InvocationExpressionActions + .Select(action => action.Clone>()).ToList(); + cloned.ImportActions = cloned.ImportActions + .Select(action => action.Clone()).ToList(); + cloned.NamespaceActions = cloned.NamespaceActions + .Select(action => action.Clone>()).ToList(); + cloned.IdentifierNameActions = cloned.IdentifierNameActions + .Select(action => action.Clone>()).ToList(); + cloned.TypeBlockActions = cloned.TypeBlockActions + .Select(action => action.Clone()).ToList(); + cloned.MethodBlockActions = cloned.MethodBlockActions + .Select(action => action.Clone()).ToList(); + cloned.InterfaceBlockActions = cloned.InterfaceBlockActions + .Select(action => action.Clone()).ToList(); + cloned.VbAttributeListActions = cloned.VbAttributeListActions + .Select(action => action.Clone()).ToList(); + cloned.AccessorBlockActions = cloned.AccessorBlockActions + .Select(action => action.Clone()).ToList(); + cloned.ObjectCreationExpressionActions = cloned.ObjectCreationExpressionActions + .Select(action => action.Clone()).ToList(); + cloned.ElementAccessActions = cloned.ElementAccessActions + .Select(action => action.Clone()).ToList(); + cloned.AttributeActions = cloned.AttributeActions + .Select(action => action.Clone()).ToList(); + return cloned; + } + + public override List AllActions + { + get + { + var allActions = new List(); + allActions.AddRange(InvocationExpressionActions); + allActions.AddRange(NamespaceActions); + allActions.AddRange(ImportActions); + allActions.AddRange(IdentifierNameActions); + allActions.AddRange(TypeBlockActions); + allActions.AddRange(MethodBlockActions); + allActions.AddRange(InterfaceBlockActions); + allActions.AddRange(VbAttributeListActions); + allActions.AddRange(AccessorBlockActions); + allActions.AddRange(ObjectCreationExpressionActions); + allActions.AddRange(ElementAccessActions); + allActions.AddRange(AttributeActions); + allActions.AddRange(base.AllActions); + return allActions; + } + } + + } +} diff --git a/src/CTA.Rules.Models/VisualBasic/VisualBasicRootNodes.cs b/src/CTA.Rules.Models/VisualBasic/VisualBasicRootNodes.cs new file mode 100644 index 00000000..03111bba --- /dev/null +++ b/src/CTA.Rules.Models/VisualBasic/VisualBasicRootNodes.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using CTA.Rules.Models.Tokens.VisualBasic; +using NodeToken = CTA.Rules.Models.Tokens.NodeToken; + +namespace CTA.Rules.Models.VisualBasic +{ + public class VisualBasicRootNodes + { + public VisualBasicRootNodes() + { + InvocationExpressionTokens = new HashSet(); + ImportStatementTokens = new HashSet(); + InterfaceBlockTokens = new HashSet(); + MethodBlockTokens = new HashSet(); + AttributeTokens = new HashSet(); + AttributeListTokens = new HashSet(); + AccessorBlockTokens = new HashSet(); + MemberAccessTokens = new HashSet(); + NamespaceTokens = new HashSet(); + ExpressionTokens = new HashSet(); + ElementAccessTokens = new HashSet(); + ProjectTokens = new HashSet(); + IdentifierNameTokens = new HashSet(); + TypeBlockTokens = new HashSet(); + ObjectCreationExpressionTokens = new HashSet(); + } + + public HashSet InvocationExpressionTokens { get; set; } + public HashSet ImportStatementTokens { get; set; } + public HashSet InterfaceBlockTokens { get; set; } + public HashSet MethodBlockTokens { get; set; } + public HashSet AttributeTokens { get; set; } + public HashSet AttributeListTokens { get; set; } + public HashSet AccessorBlockTokens { get; set; } + public HashSet MemberAccessTokens { get; set; } + public HashSet NamespaceTokens { get; set; } + public HashSet ExpressionTokens { get; set; } + public HashSet ElementAccessTokens { get; set; } + public HashSet ProjectTokens { get; set; } + public HashSet IdentifierNameTokens { get; set; } + public HashSet TypeBlockTokens { get; set; } + public HashSet ObjectCreationExpressionTokens { get; set; } + } +} diff --git a/src/CTA.Rules.Models/packages.lock.json b/src/CTA.Rules.Models/packages.lock.json index a4185289..aaf1f2e4 100644 --- a/src/CTA.Rules.Models/packages.lock.json +++ b/src/CTA.Rules.Models/packages.lock.json @@ -60,12 +60,12 @@ }, "Codelyzer.Analysis": { "type": "Transitive", - "resolved": "2.4.56-alpha-g89b570063c", - "contentHash": "3u40R9jXqCZ3pLyY3cEFB2WbZIjvF/I+xQcT83oFbJXFAE5n7g1txDjzrv4Vyb9YHQJtlkAIUMkkiFn8dUNt7Q==", + "resolved": "2.4.65-alpha-gd06dae876d", + "contentHash": "8ocAQHFpL7NIVSQ8i518EQWUFujXcHqS/AuQfxHbX64PA6wNAkrBWTsjhKTvI05dLWZ6iHUZm3wQ5uxXU2gPlA==", "dependencies": { - "Codelyzer.Analysis.Build": "2.4.56-alpha-g89b570063c", - "Codelyzer.Analysis.CSharp": "2.4.56-alpha-g89b570063c", - "Codelyzer.Analysis.VisualBasic": "2.4.56-alpha-g89b570063c", + "Codelyzer.Analysis.Build": "2.4.65-alpha-gd06dae876d", + "Codelyzer.Analysis.CSharp": "2.4.65-alpha-gd06dae876d", + "Codelyzer.Analysis.VisualBasic": "2.4.65-alpha-gd06dae876d", "CommandLineParser": "2.8.0", "Microsoft.Build.Utilities.Core": "17.1.0", "Microsoft.Extensions.Logging.Console": "6.0.0", @@ -75,23 +75,23 @@ }, "Codelyzer.Analysis.Build": { "type": "Transitive", - "resolved": "2.4.56-alpha-g89b570063c", - "contentHash": "hvxLNqVU9Yh/hdTNsmT+qtLgy7J+0Nm1diRjHGKH/FI6ymZkvgAkYB2UWge1JkRAWyRm4WNF4ukgwUlB8WboPA==", + "resolved": "2.4.65-alpha-gd06dae876d", + "contentHash": "1EqRE2hIKMtkzzKpTyzDZrqCDcSjr9iJlM8WYQ7evQ1gT9YOGaUB/iO+/DdW4CsKNbFqhq6RvCeyhu32x272hw==", "dependencies": { "Buildalyzer": "4.1.4", "Buildalyzer.Logger": "4.1.4", "Buildalyzer.Workspaces": "4.1.4", - "Codelyzer.Analysis.Common": "2.4.56-alpha-g89b570063c", + "Codelyzer.Analysis.Common": "2.4.65-alpha-gd06dae876d", "Microsoft.Extensions.Logging": "6.0.0", "NuGet.Packaging": "6.0.0" } }, "Codelyzer.Analysis.Common": { "type": "Transitive", - "resolved": "2.4.56-alpha-g89b570063c", - "contentHash": "ZflwAG3tOHPfqHmfnkUTl5RKuCzN7HqhVF7aw6fwUD1uE93HTdB4VBvkm0n3Nj6Ucv6Mv8UK3bYoYGr0+EYYIQ==", + "resolved": "2.4.65-alpha-gd06dae876d", + "contentHash": "igze76koUw5Ao9XrpjjdyLPobF5CCi9uSb26zLcnrnQsiS1k8m58CQJZKCfsP2mAsGb7HhC5IHmkzxUqGIu29g==", "dependencies": { - "Codelyzer.Analysis.Model": "2.4.56-alpha-g89b570063c", + "Codelyzer.Analysis.Model": "2.4.65-alpha-gd06dae876d", "Microsoft.Build": "17.0.0", "Microsoft.VisualStudio.Setup.Configuration.Interop": "3.1.2196", "Newtonsoft.Json": "13.0.1" @@ -99,17 +99,17 @@ }, "Codelyzer.Analysis.CSharp": { "type": "Transitive", - "resolved": "2.4.56-alpha-g89b570063c", - "contentHash": "68Z3EFQpRG7OJk3ImRVXjKxRcqZl9nk7E8rrD5fdfy64qd/I9KD8/eJ0xyEPwhwTzv+OsnZQyZ5YidOd9WLIEg==", + "resolved": "2.4.65-alpha-gd06dae876d", + "contentHash": "HlxgpiPQUw53w1c25F3WpO2NMEm+9ZekHmhxNcspy+P2pIHmhRLvmAhkQGYerk+J6teS7oJTHG31w6NgrxgdqA==", "dependencies": { - "Codelyzer.Analysis.Common": "2.4.56-alpha-g89b570063c", - "Codelyzer.Analysis.Model": "2.4.56-alpha-g89b570063c" + "Codelyzer.Analysis.Common": "2.4.65-alpha-gd06dae876d", + "Codelyzer.Analysis.Model": "2.4.65-alpha-gd06dae876d" } }, "Codelyzer.Analysis.Model": { "type": "Transitive", - "resolved": "2.4.56-alpha-g89b570063c", - "contentHash": "CJCJsGouBGuFe0MCJD3zinuaKQQXfLxT81oqpz2Z+yaTEi420fiHNLvlRzHILnaT3Uu1pBnbkCix5hxHRDWF0A==", + "resolved": "2.4.65-alpha-gd06dae876d", + "contentHash": "E0pzEAkmo1znKadLMIsrINDAffA+7wHaRhSV+IGjayvYx77XAjb54j2U61ysATOz2YXHqU3wQfWU2KhIgGHdZw==", "dependencies": { "Microsoft.CodeAnalysis": "4.1.0", "Microsoft.CodeAnalysis.CSharp": "4.1.0", @@ -120,11 +120,11 @@ }, "Codelyzer.Analysis.VisualBasic": { "type": "Transitive", - "resolved": "2.4.56-alpha-g89b570063c", - "contentHash": "oZ2tGbAMHPjegveNz2hXLEJYxJvOOMjb27W7KNMKpynwLDm+Ck9GloGiGZi/ADpSvrxelYl6JQtYIeACJBu+Iw==", + "resolved": "2.4.65-alpha-gd06dae876d", + "contentHash": "92Y5YpPXOXvlNe8g3ipZ1MQ54yZwl8Ah91OGJEZSTnnb6CwFkgqiIp/mIlFtor/AyUCO/+MEKdqJ7Q/fOgogVQ==", "dependencies": { - "Codelyzer.Analysis.Common": "2.4.56-alpha-g89b570063c", - "Codelyzer.Analysis.Model": "2.4.56-alpha-g89b570063c" + "Codelyzer.Analysis.Common": "2.4.65-alpha-gd06dae876d", + "Codelyzer.Analysis.Model": "2.4.65-alpha-gd06dae876d" } }, "CommandLineParser": { @@ -1717,8 +1717,8 @@ "cta.rules.config": { "type": "Project", "dependencies": { - "Codelyzer.Analysis": "2.4.56-alpha-g89b570063c", - "Codelyzer.Analysis.Model": "2.4.56-alpha-g89b570063c", + "Codelyzer.Analysis": "2.4.65-alpha-gd06dae876d", + "Codelyzer.Analysis.Model": "2.4.65-alpha-gd06dae876d", "Microsoft.Extensions.Logging": "6.0.0", "Microsoft.Extensions.Logging.Abstractions": "6.0.0", "Microsoft.Extensions.Logging.Console": "6.0.0", diff --git a/src/CTA.Rules.PortCore/ProjectSpecificPort/ProjectRewriters/PortCoreProjectRewriterFactory.cs b/src/CTA.Rules.PortCore/ProjectSpecificPort/ProjectRewriters/PortCoreProjectRewriterFactory.cs index 00985b69..60ea8366 100644 --- a/src/CTA.Rules.PortCore/ProjectSpecificPort/ProjectRewriters/PortCoreProjectRewriterFactory.cs +++ b/src/CTA.Rules.PortCore/ProjectSpecificPort/ProjectRewriters/PortCoreProjectRewriterFactory.cs @@ -14,6 +14,10 @@ public ProjectRewriter GetInstance(AnalyzerResult analyzerResult, ProjectConfigu { ProjectType.WCFCodeBasedService => new WCFProjectRewriter(analyzerResult, projectConfiguration), ProjectType.WCFConfigBasedService => new WCFProjectRewriter(analyzerResult, projectConfiguration), + ProjectType.VBClassLibrary => new VisualBasicProjectRewriter(analyzerResult, projectConfiguration), + ProjectType.VBWebApi => new VisualBasicProjectRewriter(analyzerResult, projectConfiguration), + ProjectType.VBWebForms => new VisualBasicProjectRewriter(analyzerResult, projectConfiguration), + ProjectType.VBNetMvc => new VisualBasicProjectRewriter(analyzerResult, projectConfiguration), _ => new ProjectRewriter(analyzerResult, projectConfiguration) }; return projectRewriter; @@ -26,6 +30,10 @@ public ProjectRewriter GetInstance(IDEProjectResult ideProjectResult, ProjectCon { ProjectType.WCFCodeBasedService => new WCFProjectRewriter(ideProjectResult, projectConfiguration), ProjectType.WCFConfigBasedService => new WCFProjectRewriter(ideProjectResult, projectConfiguration), + ProjectType.VBClassLibrary => new VisualBasicProjectRewriter(ideProjectResult, projectConfiguration), + ProjectType.VBWebApi => new VisualBasicProjectRewriter(ideProjectResult, projectConfiguration), + ProjectType.VBWebForms => new VisualBasicProjectRewriter(ideProjectResult, projectConfiguration), + ProjectType.VBNetMvc => new VisualBasicProjectRewriter(ideProjectResult, projectConfiguration), _ => new ProjectRewriter(ideProjectResult, projectConfiguration) }; return projectRewriter; diff --git a/src/CTA.Rules.PortCore/ProjectSpecificPort/ProjectRewriters/VisualBasicProjectRewriter.cs b/src/CTA.Rules.PortCore/ProjectSpecificPort/ProjectRewriters/VisualBasicProjectRewriter.cs new file mode 100644 index 00000000..f59c4b0d --- /dev/null +++ b/src/CTA.Rules.PortCore/ProjectSpecificPort/ProjectRewriters/VisualBasicProjectRewriter.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using Codelyzer.Analysis; +using Codelyzer.Analysis.Build; +using CTA.Rules.Models; +using CTA.Rules.Update; + +namespace CTA.Rules.PortCore +{ + public class VisualBasicProjectRewriter : ProjectRewriter + { + public readonly ProjectType _projectType; + + /// + /// Initializes a new instance of ProjectRewriter using an existing analysis + /// + /// The analysis results of the project + /// ProjectConfiguration for this project + public VisualBasicProjectRewriter(AnalyzerResult analyzerResult, ProjectConfiguration projectConfiguration) + : base(analyzerResult, projectConfiguration) + { + _projectType = projectConfiguration.ProjectType; + } + + /// + /// Initializes a new instance of ProjectRewriter using an existing analysis + /// + /// The analysis results of the project + /// ProjectConfiguration for this project + public VisualBasicProjectRewriter(IDEProjectResult projectResult, ProjectConfiguration projectConfiguration) + : base(projectResult, projectConfiguration) + { + _projectType = projectConfiguration.ProjectType; + } + + /// + /// Initializes the ProjectRewriter then runs it + /// + public override ProjectResult Run() + { + if (isExludedFromPorting(_projectType)) + return _projectResult; + + var projectResult = Initialize(); + return Run(projectResult.ProjectActions); + } + + /// + /// Runs the project rewriter using a previously initialized analysis + /// + /// + public override ProjectResult Run(ProjectActions projectActions) + { + if (isExludedFromPorting(_projectType)) + return _projectResult; + + base.Run(projectActions); + return _projectResult; + } + + /// + /// Runs the project rewriter using an incremental analysis + /// + /// + /// + public override List RunIncremental(List updatedFiles, RootNodes projectRules) + { + List ideFileActions = new List(); + if(!isExludedFromPorting(_projectType)) + ideFileActions = base.RunIncremental(updatedFiles, projectRules); + return ideFileActions; + } + + + private bool isExludedFromPorting(ProjectType projectType) + { + return projectType == ProjectType.VBWebForms || projectType == ProjectType.VBNetMvc; + } + } +} diff --git a/src/CTA.Rules.PortCore/ProjectSpecificPort/ProjectRewriters/WCFProjectRewriter.cs b/src/CTA.Rules.PortCore/ProjectSpecificPort/ProjectRewriters/WCFProjectRewriter.cs index a7f06690..4a5f1fe6 100644 --- a/src/CTA.Rules.PortCore/ProjectSpecificPort/ProjectRewriters/WCFProjectRewriter.cs +++ b/src/CTA.Rules.PortCore/ProjectSpecificPort/ProjectRewriters/WCFProjectRewriter.cs @@ -5,10 +5,10 @@ using Codelyzer.Analysis.Build; using CTA.Rules.Config; using CTA.Rules.Models; -using CTA.Rules.PortCore; +using CTA.Rules.Update; using Microsoft.CodeAnalysis.CSharp; -namespace CTA.Rules.Update +namespace CTA.Rules.PortCore { /// /// Runs rule updates on a Project diff --git a/src/CTA.Rules.RuleFiles/RulesFileLoader.cs b/src/CTA.Rules.RuleFiles/RulesFileLoader.cs index e8d09199..96ceb64e 100644 --- a/src/CTA.Rules.RuleFiles/RulesFileLoader.cs +++ b/src/CTA.Rules.RuleFiles/RulesFileLoader.cs @@ -6,6 +6,7 @@ using Codelyzer.Analysis.Model; using CTA.Rules.Config; using CTA.Rules.Models; +using CTA.Rules.Models.RulesFiles; using Newtonsoft.Json; namespace CTA.Rules.RuleFiles @@ -20,6 +21,7 @@ public class RulesFileLoader private readonly string _overrideFile; private readonly string _assembliesDir; private readonly IEnumerable _projectReferences; + private readonly ProjectLanguage _projectLanguage; /// /// Initializes a new RulesFileLoader @@ -29,7 +31,8 @@ public class RulesFileLoader /// Target framework to port to /// Path to rules file containing override rules. The override rules will be added to the built in rules, overriding any matching existing rules /// Directory containing assemblies containing additional actions - public RulesFileLoader(IEnumerable projectReferences, string rulesFilesDir, List targetFramework, string overrideFile = "", string assembliesDir = "") + /// The language the project is written in, C# or VB + public RulesFileLoader(IEnumerable projectReferences, string rulesFilesDir, List targetFramework, ProjectLanguage projectLanguage, string overrideFile = "", string assembliesDir = "") { _rulesFilesDir = rulesFilesDir; try @@ -48,13 +51,14 @@ public RulesFileLoader(IEnumerable projectReferences, string rulesFil _overrideFile = overrideFile; _assembliesDir = assembliesDir; _projectReferences = projectReferences; + _projectLanguage = projectLanguage; } /// /// Loads rules from the main rules file and override file /// /// A RootNodes object containing all the rules after being merged - public RootNodes Load() + public RulesFileLoaderResponse Load() { var mainNamespaceFileTasks = new Task(() => { @@ -110,20 +114,36 @@ public RootNodes Load() Task.WaitAll(mainNamespaceFileTasks, overrideNamespaceFileTasks, mainFileTask, overrideTask); - RulesFileParser rulesFileParser = new RulesFileParser(mainNamespaceFileTasks.Result, - overrideNamespaceFileTasks.Result, - mainFileTask.Result, - overrideTask.Result, - _assembliesDir, - _targetFramework - ); - var rootNodes = rulesFileParser.Process(); + var response = new RulesFileLoaderResponse(); + + if (_projectLanguage == ProjectLanguage.VisualBasic) + { + var rulesFileParser = new VisualBasicRulesFileParser(mainNamespaceFileTasks.Result, + overrideNamespaceFileTasks.Result, + mainFileTask.Result, + overrideTask.Result, + _assembliesDir, + _targetFramework); + var rootNodes = rulesFileParser.Process(); + response.VisualBasicRootNodes = rootNodes; + } + else + { + RulesFileParser rulesFileParser = new RulesFileParser(mainNamespaceFileTasks.Result, + overrideNamespaceFileTasks.Result, + mainFileTask.Result, + overrideTask.Result, + _assembliesDir, + _targetFramework); + var rootNodes = rulesFileParser.Process(); + response.CsharpRootNodes = rootNodes; + } - return rootNodes; + return response; } - private NamespaceRecommendations LoadNamespaceFile(string pathToLoad) + public NamespaceRecommendations LoadNamespaceFile(string pathToLoad) { NamespaceRecommendations nr = new NamespaceRecommendations(); diff --git a/src/CTA.Rules.RuleFiles/RulesFileParser.cs b/src/CTA.Rules.RuleFiles/RulesFileParser.cs index a83d34d7..77a2a79d 100644 --- a/src/CTA.Rules.RuleFiles/RulesFileParser.cs +++ b/src/CTA.Rules.RuleFiles/RulesFileParser.cs @@ -6,6 +6,7 @@ using CTA.Rules.Config; using CTA.Rules.Models; using CTA.Rules.Models.Tokens; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Newtonsoft.Json; namespace CTA.Rules.RuleFiles @@ -29,11 +30,20 @@ public class RulesFileParser /// /// Runs the rules parser /// + /// Override namespace recommendations /// Object containing built in rules /// Object containing override rules /// Directory containing additional actions assemblies - public RulesFileParser(NamespaceRecommendations namespaceRecommendations, NamespaceRecommendations overrideNamespaceRecommendations, - Rootobject rulesObject, Rootobject overrideObject, string assembliesDir, string targetFramework) + /// Namespace recommendations + /// Framework version being targeted for porting + /// + public RulesFileParser( + NamespaceRecommendations namespaceRecommendations, + NamespaceRecommendations overrideNamespaceRecommendations, + Rootobject rulesObject, + Rootobject overrideObject, + string assembliesDir, + string targetFramework) { _rootNodes = new RootNodes(); _rootNodes.ProjectTokens.Add(new ProjectToken() { Key = "Project" }); @@ -84,7 +94,7 @@ public RootNodes Process() /// /// Loads actions from the actions project and additional assemblies /// - private void LoadActions() + public void LoadActions() { List assemblies = new List(); if (!string.IsNullOrEmpty(_assembliesDir)) @@ -98,7 +108,7 @@ private void LoadActions() /// Processes each rule object by creating tokens and associated actions /// /// An object containing tokens and actions to run on these tokens - private void ProcessObject(Rootobject rootobject) + public void ProcessObject(Rootobject rootobject) { var namespaces = rootobject.NameSpaces; @@ -110,7 +120,7 @@ private void ProcessObject(Rootobject rootobject) if (@namespace.@namespace == Constants.Project && @namespace.Assembly == Constants.Project) { var projectToken = _rootNodes.ProjectTokens.FirstOrDefault(); - ParseActions(projectToken, @namespace.Actions); + ParseActions((ProjectToken)projectToken, @namespace.Actions); } //Namespace specific actions: else @@ -218,7 +228,7 @@ private void ProcessObject(Rootobject rootobject) /// Processes each rule object by creating tokens and associated actions /// /// An object containing tokens and actions to run on these tokens - private void ProcessObject(NamespaceRecommendations namespaceRecommendations) + public void ProcessObject(NamespaceRecommendations namespaceRecommendations) { var namespaces = namespaceRecommendations.NameSpaces; @@ -371,9 +381,9 @@ private void ProcessObject(NamespaceRecommendations namespaceRecommendations) /// /// Add actions to each node type /// - /// The token to add the action to + /// The token to add the action to /// The list of actions associated with this token - private void ParseActions(NodeToken nodeToken, List actions) + public void ParseActions(CsharpNodeToken csharpNodeToken, List actions) { foreach (var action in actions) { @@ -387,9 +397,9 @@ private void ParseActions(NodeToken nodeToken, List actions) var actionFunc = actionsLoader.GetInvocationExpressionAction(action.Name, action.Value); if (actionFunc != null) { - nodeToken.InvocationExpressionActions.Add(new InvocationExpressionAction() + csharpNodeToken.InvocationExpressionActions.Add(new InvocationExpressionAction() { - Key = nodeToken.Key, + Key = csharpNodeToken.Key, Value = GetActionValue(action.Value), Description = action.Description, ActionValidation = action.ActionValidation, @@ -405,9 +415,9 @@ private void ParseActions(NodeToken nodeToken, List actions) var actionFunc = actionsLoader.GetExpressionAction(action.Name, action.Value); if (actionFunc != null) { - nodeToken.ExpressionActions.Add(new ExpressionAction() + csharpNodeToken.ExpressionActions.Add(new ExpressionAction() { - Key = nodeToken.Key, + Key = csharpNodeToken.Key, Value = GetActionValue(action.Value), Description = action.Description, ActionValidation = action.ActionValidation, @@ -423,9 +433,9 @@ private void ParseActions(NodeToken nodeToken, List actions) var actionFunc = actionsLoader.GetClassAction(action.Name, action.Value); if (actionFunc != null) { - nodeToken.ClassDeclarationActions.Add(new ClassDeclarationAction() + csharpNodeToken.ClassDeclarationActions.Add(new ClassDeclarationAction() { - Key = nodeToken.Key, + Key = csharpNodeToken.Key, Value = GetActionValue(action.Value), Description = action.Description, ActionValidation = action.ActionValidation, @@ -442,9 +452,9 @@ private void ParseActions(NodeToken nodeToken, List actions) var actionFunc = actionsLoader.GetInterfaceAction(action.Name, action.Value); if (actionFunc != null) { - nodeToken.InterfaceDeclarationActions.Add(new InterfaceDeclarationAction() + csharpNodeToken.InterfaceDeclarationActions.Add(new InterfaceDeclarationAction() { - Key = nodeToken.Key, + Key = csharpNodeToken.Key, Value = GetActionValue(action.Value), Description = action.Description, ActionValidation = action.ActionValidation, @@ -464,9 +474,9 @@ private void ParseActions(NodeToken nodeToken, List actions) var namespaceActionFunc = actionsLoader.GetNamespaceActions(action.Name, action.Value); if (actionFunc != null) { - nodeToken.UsingActions.Add(new UsingAction() + csharpNodeToken.UsingActions.Add(new UsingAction() { - Key = nodeToken.Key, + Key = csharpNodeToken.Key, Value = GetActionValue(action.Value), Description = action.Description, ActionValidation = action.ActionValidation, @@ -483,9 +493,9 @@ private void ParseActions(NodeToken nodeToken, List actions) var actionFunc = actionsLoader.GetNamespaceActions(action.Name, action.Value); if (actionFunc != null) { - nodeToken.NamespaceActions.Add(new NamespaceAction() + csharpNodeToken.NamespaceActions.Add(new NamespaceAction() { - Key = nodeToken.Key, + Key = csharpNodeToken.Key, Value = GetActionValue(action.Value), Description = action.Description, ActionValidation = action.ActionValidation, @@ -501,9 +511,9 @@ private void ParseActions(NodeToken nodeToken, List actions) var actionFunc = actionsLoader.GetIdentifierNameAction(action.Name, action.Value); if (actionFunc != null) { - nodeToken.IdentifierNameActions.Add(new IdentifierNameAction() + csharpNodeToken.IdentifierNameActions.Add(new IdentifierNameAction() { - Key = nodeToken.Key, + Key = csharpNodeToken.Key, Value = GetActionValue(action.Value), Description = action.Description, ActionValidation = action.ActionValidation, @@ -519,9 +529,9 @@ private void ParseActions(NodeToken nodeToken, List actions) var actionFunc = actionsLoader.GetAttributeAction(action.Name, action.Value); if (actionFunc != null) { - nodeToken.AttributeActions.Add(new AttributeAction() + csharpNodeToken.AttributeActions.Add(new AttributeAction() { - Key = nodeToken.Key, + Key = csharpNodeToken.Key, Value = GetActionValue(action.Value), Description = action.Description, ActionValidation = action.ActionValidation, @@ -537,9 +547,9 @@ private void ParseActions(NodeToken nodeToken, List actions) var actionFunc = actionsLoader.GetAttributeListAction(action.Name, action.Value); if (actionFunc != null) { - nodeToken.AttributeListActions.Add(new AttributeAction() + csharpNodeToken.AttributeListActions.Add(new AttributeAction() { - Key = nodeToken.Key, + Key = csharpNodeToken.Key, Value = GetActionValue(action.Value), Description = action.Description, ActionValidation = action.ActionValidation, @@ -555,9 +565,9 @@ private void ParseActions(NodeToken nodeToken, List actions) var actionFunc = actionsLoader.GetObjectCreationExpressionActions(action.Name, action.Value); if (actionFunc != null) { - nodeToken.ObjectCreationExpressionActions.Add(new ObjectCreationExpressionAction() + csharpNodeToken.ObjectCreationExpressionActions.Add(new ObjectCreationExpressionAction() { - Key = nodeToken.Key, + Key = csharpNodeToken.Key, Value = GetActionValue(action.Value), Description = action.Description, ActionValidation = action.ActionValidation, @@ -573,9 +583,9 @@ private void ParseActions(NodeToken nodeToken, List actions) var actionFunc = actionsLoader.GetMethodDeclarationAction(action.Name, action.Value); if (actionFunc != null) { - nodeToken.MethodDeclarationActions.Add(new MethodDeclarationAction() + csharpNodeToken.MethodDeclarationActions.Add(new MethodDeclarationAction() { - Key = nodeToken.Key, + Key = csharpNodeToken.Key, Value = GetActionValue(action.Value), Description = action.Description, ActionValidation = action.ActionValidation, @@ -591,9 +601,9 @@ private void ParseActions(NodeToken nodeToken, List actions) var actionFunc = actionsLoader.GetElementAccessExpressionActions(action.Name, action.Value); if (actionFunc != null) { - nodeToken.ElementAccessActions.Add(new ElementAccessAction() + csharpNodeToken.ElementAccessActions.Add(new ElementAccessAction() { - Key = nodeToken.Key, + Key = csharpNodeToken.Key, Value = GetActionValue(action.Value), Description = action.Description, ActionValidation = action.ActionValidation, @@ -609,9 +619,9 @@ private void ParseActions(NodeToken nodeToken, List actions) var actionFunc = actionsLoader.GetMemberAccessExpressionActions(action.Name, action.Value); if (actionFunc != null) { - nodeToken.MemberAccessActions.Add(new MemberAccessAction() + csharpNodeToken.MemberAccessActions.Add(new MemberAccessAction() { - Key = nodeToken.Key, + Key = csharpNodeToken.Key, Value = GetActionValue(action.Value), Description = action.Description, ActionValidation = action.ActionValidation, @@ -627,9 +637,9 @@ private void ParseActions(NodeToken nodeToken, List actions) var actionFunc = actionsLoader.GetProjectLevelActions(action.Name, action.Value); if (actionFunc != null) { - nodeToken.ProjectLevelActions.Add(new ProjectLevelAction() + csharpNodeToken.ProjectLevelActions.Add(new ProjectLevelAction() { - Key = nodeToken.Key, + Key = csharpNodeToken.Key, Value = GetActionValue(action.Value), Description = action.Description, ActionValidation = action.ActionValidation, @@ -645,9 +655,9 @@ private void ParseActions(NodeToken nodeToken, List actions) var actionFunc = actionsLoader.GetProjectFileActions(action.Name, action.Value); if (actionFunc != null) { - nodeToken.ProjectFileActions.Add(new ProjectLevelAction() + csharpNodeToken.ProjectFileActions.Add(new ProjectLevelAction() { - Key = nodeToken.Key, + Key = csharpNodeToken.Key, Value = GetActionValue(action.Value), Description = action.Description, ActionValidation = action.ActionValidation, @@ -663,9 +673,9 @@ private void ParseActions(NodeToken nodeToken, List actions) var actionFunc = actionsLoader.GetProjectTypeActions(action.Name, action.Value); if (actionFunc != null) { - nodeToken.ProjectTypeActions.Add(new ProjectLevelAction() + csharpNodeToken.ProjectTypeActions.Add(new ProjectLevelAction() { - Key = nodeToken.Key, + Key = csharpNodeToken.Key, Value = GetActionValue(action.Value), Description = action.Description, ActionValidation = action.ActionValidation, @@ -704,7 +714,7 @@ private void ParseActions(NodeToken nodeToken, List actions) packageAction.Version = jsonParameters[CTA.Rules.Config.Constants.PackageVersion]; } } - nodeToken.PackageActions.Add(packageAction); + csharpNodeToken.PackageActions.Add(packageAction); break; } } @@ -716,7 +726,7 @@ private void ParseActions(NodeToken nodeToken, List actions) } } - private string GetActionValue(dynamic value) + public string GetActionValue(dynamic value) { if (value is string) { diff --git a/src/CTA.Rules.RuleFiles/VisualBasicRulesFileParser.cs b/src/CTA.Rules.RuleFiles/VisualBasicRulesFileParser.cs new file mode 100644 index 00000000..498a4bc2 --- /dev/null +++ b/src/CTA.Rules.RuleFiles/VisualBasicRulesFileParser.cs @@ -0,0 +1,904 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using CTA.Rules.Actions; +using CTA.Rules.Config; +using CTA.Rules.Models; +using CTA.Rules.Models.Actions.VisualBasic; +using CTA.Rules.Models.VisualBasic; +using CTA.Rules.Models.Tokens.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Newtonsoft.Json; +using AttributeToken = CTA.Rules.Models.Tokens.VisualBasic.AttributeToken; +using ElementAccessAction = CTA.Rules.Models.Actions.VisualBasic.ElementAccessAction; +using ElementAccessToken = CTA.Rules.Models.Tokens.VisualBasic.ElementAccessToken; +using IdentifierNameToken = CTA.Rules.Models.VisualBasic.IdentifierNameToken; +using MemberAccessToken = CTA.Rules.Models.Tokens.VisualBasic.MemberAccessToken; +using ObjectCreationExpressionAction = CTA.Rules.Models.Actions.VisualBasic.ObjectCreationExpressionAction; + + +namespace CTA.Rules.RuleFiles +{ + /// + /// Parser to load rules in form usable by the rules engine + /// + public class VisualBasicRulesFileParser + { + private readonly VisualBasicRootNodes _visualBasicRootNodes; + private readonly string _assembliesDir; + private readonly string _targetFramework; + + private VisualBasicActionsLoader _actionsLoader; + private readonly Rootobject _rulesObject; + private readonly Rootobject _overrideObject; + + private readonly NamespaceRecommendations _namespaceRecommendations; + private readonly NamespaceRecommendations _overrideNamespaceRecommendations; + + /// + /// Runs the rules parser + /// + /// Override namespace recommendations + /// Object containing built in rules + /// Object containing override rules + /// Directory containing additional actions assemblies + /// Namespace recommendations + /// Framework version being targeted for porting + /// + public VisualBasicRulesFileParser( + NamespaceRecommendations namespaceRecommendations, + NamespaceRecommendations overrideNamespaceRecommendations, + Rootobject rulesObject, + Rootobject overrideObject, + string assembliesDir, + string targetFramework) + { + _visualBasicRootNodes = new VisualBasicRootNodes(); + _visualBasicRootNodes.ProjectTokens.Add(new ProjectToken() { Key = "Project" }); + _rulesObject = rulesObject; + _overrideObject = overrideObject; + _assembliesDir = assembliesDir; + _namespaceRecommendations = namespaceRecommendations; + _overrideNamespaceRecommendations = overrideNamespaceRecommendations; + _targetFramework = targetFramework; + + LoadActions(); + } + + /// + /// Runs the parser to merge the rules + /// + /// RootNodes object that contains the tokens and their associated actions + public VisualBasicRootNodes Process() + { + //Process overrides first: + if (_overrideObject.NameSpaces != null) + { + ProcessObject(_overrideObject); + } + + //Add remaining objects, if not available: + if (_overrideNamespaceRecommendations.NameSpaces != null) + { + ProcessObject(_overrideNamespaceRecommendations); + } + + //Add remaining objects, if not available: + if (_rulesObject.NameSpaces != null) + { + ProcessObject(_rulesObject); + } + + + //Add remaining objects, if not available: + if (_namespaceRecommendations.NameSpaces != null) + { + ProcessObject(_namespaceRecommendations); + } + + return _visualBasicRootNodes; + } + + /// + /// Loads actions from the actions project and additional assemblies + /// + public void LoadActions() + { + List assemblies = new List(); + if (!string.IsNullOrEmpty(_assembliesDir)) + { + assemblies = Directory.EnumerateFiles(_assembliesDir, "*.dll").ToList(); + } + _actionsLoader = new VisualBasicActionsLoader(assemblies); + } + + /// + /// Processes each rule object by creating tokens and associated actions + /// + /// An object containing tokens and actions to run on these tokens + public void ProcessObject(Rootobject rootobject) + { + var namespaces = rootobject.NameSpaces; + + foreach (var @namespace in namespaces) + { + if (@namespace.Actions != null && @namespace.Actions.Count > 0) + { + //Global Actions: + if (@namespace.@namespace == Constants.Project && @namespace.Assembly == Constants.Project) + { + var projectToken = _visualBasicRootNodes.ProjectTokens.FirstOrDefault(); + ParseActions((VisualBasicNodeToken)projectToken, @namespace.Actions); + } + //Namespace specific actions: + else + { + var usingToken = new ImportStatementToken() { Key = @namespace.@namespace }; + var namespaceToken = new NamespaceToken() { Key = @namespace.@namespace }; + + if (!_visualBasicRootNodes.ImportStatementTokens.Contains(usingToken)) + { + _visualBasicRootNodes.ImportStatementTokens.Add(usingToken); + } + + if (!_visualBasicRootNodes.NamespaceTokens.Contains(namespaceToken)) + { + _visualBasicRootNodes.NamespaceTokens.Add(namespaceToken); + } + + ParseActions(usingToken, @namespace.Actions); + ParseActions(namespaceToken, @namespace.Actions); + } + } + foreach (var @class in @namespace.Classes) + { + if (@class.Actions != null && @class.Actions.Count > 0) + { + if (@class.KeyType is Constants.BaseClass or Constants.ClassName) + { + var token = new TypeBlockToken() { Key = @class.FullKey, FullKey = @class.FullKey, Namespace = @namespace.@namespace }; + if (!_visualBasicRootNodes.TypeBlockTokens.Contains(token)) { _visualBasicRootNodes.TypeBlockTokens.Add(token); } + ParseActions(token, @class.Actions); + } + if (@class.KeyType == CTA.Rules.Config.Constants.Identifier) + { + var token = new IdentifierNameToken + { + Key = @class.FullKey, FullKey = @class.FullKey, Namespace = @namespace.@namespace + }; + if (!_visualBasicRootNodes.IdentifierNameTokens.Contains(token)) + { + _visualBasicRootNodes.IdentifierNameTokens.Add(token); + } + + ParseActions(token, @class.Actions); + } + } + + foreach (var attribute in @class.Attributes) + { + if (attribute.Actions != null && attribute.Actions.Count > 0) + { + var token = new AttributeToken + { + Key = attribute.Key, + Namespace = @namespace.@namespace, + FullKey = attribute.FullKey, + Type = @class.Key + }; + if (!_visualBasicRootNodes.AttributeTokens.Contains(token)) + { + _visualBasicRootNodes.AttributeTokens.Add(token); + } + + ParseActions(token, attribute.Actions); + } + } + + foreach (var objectCreation in @class.ObjectCreations) + { + if (objectCreation.Actions != null && objectCreation.Actions.Count > 0) + { + var token = new ObjectCreationExpressionToken + { + Key = objectCreation.Key, + Namespace = @namespace.@namespace, + FullKey = objectCreation.FullKey, + Type = @class.Key + }; + if (!_visualBasicRootNodes.ObjectCreationExpressionTokens.Contains(token)) + { + _visualBasicRootNodes.ObjectCreationExpressionTokens.Add(token); + } + + ParseActions(token, objectCreation.Actions); + } + } + + foreach (var method in @class.Methods) + { + if (method.Actions != null && method.Actions.Count > 0) + { + var token = new InvocationExpressionToken() { Key = method.Key, Namespace = @namespace.@namespace, FullKey = method.FullKey, Type = @class.Key }; + if (!_visualBasicRootNodes.InvocationExpressionTokens.Contains(token)) { _visualBasicRootNodes.InvocationExpressionTokens.Add(token); } + ParseActions(token, method.Actions); + } + } + + } + + foreach (var @interface in @namespace.Interfaces) + { + if (@interface.Actions != null && @interface.Actions.Count > 0) + { + if (@interface.KeyType == Constants.BaseClass || @interface.KeyType == CTA.Rules.Config.Constants.InterfaceName) + { + } + else if (@interface.KeyType == Constants.Identifier) + { + var token = new IdentifierNameToken + { + Key = @interface.FullKey, + FullKey = @interface.FullKey, + Namespace = @namespace.@namespace + }; + if (!_visualBasicRootNodes.IdentifierNameTokens.Contains(token)) + { + _visualBasicRootNodes.IdentifierNameTokens.Add(token); + } + + ParseActions(token, @interface.Actions); + } + } + foreach (var attribute in @interface.Attributes) + { + if (attribute.Actions != null && attribute.Actions.Count > 0) + { + var token = new AttributeToken + { + Key = attribute.Key, + Namespace = @namespace.@namespace, + FullKey = attribute.FullKey, + Type = @interface.Key + }; + if (!_visualBasicRootNodes.AttributeTokens.Contains(token)) + { + _visualBasicRootNodes.AttributeTokens.Add(token); + } + + ParseActions(token, attribute.Actions); + } + } + + foreach (var method in @interface.Methods) + { + if (method.Actions != null && method.Actions.Count > 0) + { + var token = new InvocationExpressionToken() { Key = method.Key, Namespace = @namespace.@namespace, FullKey = method.FullKey, Type = @interface.Key }; + if (!_visualBasicRootNodes.InvocationExpressionTokens.Contains(token)) { _visualBasicRootNodes.InvocationExpressionTokens.Add(token); } + ParseActions(token, method.Actions); + } + } + } + } + } + + + /// + /// Processes each rule object by creating tokens and associated actions + /// + /// An object containing tokens and actions to run on these tokens + private void ProcessObject(NamespaceRecommendations namespaceRecommendations) + { + var namespaces = namespaceRecommendations.NameSpaces; + + foreach (var @namespace in namespaces) + { + foreach (var recommendation in @namespace.Recommendations) + { + var recommendedActions = recommendation.RecommendedActions.FirstOrDefault(ra => + ra.Preferred == "Yes" && ra.TargetFrameworks.Any(t => t.Name.Equals(_targetFramework))); + + //There are recommendations, but none of them are preferred + if (recommendedActions == null && recommendation.RecommendedActions.Count > 0) + { + LogHelper.LogError( + "No preferred recommendation set for recommendation {0} with target framework {1}", + recommendation.Value, _targetFramework); + continue; + } + + if (recommendedActions != null) + { + if (recommendedActions.Actions != null && recommendedActions.Actions.Count > 0) + { + var targetCPUs = new List { "x86", "x64", "ARM64" }; + try + { + targetCPUs = recommendedActions.TargetFrameworks + .FirstOrDefault(t => t.Name == _targetFramework)?.TargetCPU; + } + catch + { + LogHelper.LogError("Error parsing CPUs for target framework"); + } + + var recommendationType = Enum.Parse(typeof(ActionTypes), recommendation.Type); + switch (recommendationType) + { + case ActionTypes.Namespace: + { + var importToken = new ImportStatementToken() { Key = recommendation.Value, Description = recommendedActions.Description, TargetCPU = targetCPUs }; + var namespaceToken = new NamespaceToken() { Key = recommendation.Value, Description = recommendedActions.Description, TargetCPU = targetCPUs }; + + if (!_visualBasicRootNodes.ImportStatementTokens.Contains(importToken)) { _visualBasicRootNodes.ImportStatementTokens.Add(importToken); } + if (!_visualBasicRootNodes.NamespaceTokens.Contains(namespaceToken)) { _visualBasicRootNodes.NamespaceTokens.Add(namespaceToken); } + + ParseActions(importToken, recommendedActions.Actions); + ParseActions(namespaceToken, recommendedActions.Actions); + break; + } + case ActionTypes.Class: + { + if (recommendation.KeyType == CTA.Rules.Config.Constants.BaseClass || recommendation.KeyType == CTA.Rules.Config.Constants.ClassName) + { + } + else if (recommendation.KeyType == CTA.Rules.Config.Constants.Identifier) + { + var token = new IdentifierNameToken + { + Key = recommendation.Value, + Description = recommendedActions.Description, + TargetCPU = targetCPUs, + FullKey = recommendation.Value, + Namespace = @namespace.Name + }; + if (!_visualBasicRootNodes.IdentifierNameTokens.Contains(token)) + { + _visualBasicRootNodes.IdentifierNameTokens.Add(token); + } + + ParseActions(token, recommendedActions.Actions); + } + break; + } + + case ActionTypes.Interface: + { + if (recommendation.KeyType == CTA.Rules.Config.Constants.BaseClass || recommendation.KeyType == CTA.Rules.Config.Constants.ClassName) + { + } + else if (recommendation.KeyType == CTA.Rules.Config.Constants.Identifier) + { + var token = new IdentifierNameToken + { + Key = recommendation.Value, + Description = recommendedActions.Description, + TargetCPU = targetCPUs, + FullKey = recommendation.Value, + Namespace = @namespace.Name + }; + if (!_visualBasicRootNodes.IdentifierNameTokens.Contains(token)) + { + _visualBasicRootNodes.IdentifierNameTokens.Add(token); + } + + ParseActions(token, recommendedActions.Actions); + } + break; + } + + case ActionTypes.Method: + { + var token = new InvocationExpressionToken + { + Key = recommendation.Name, + Description = recommendedActions.Description, + TargetCPU = targetCPUs, + Namespace = @namespace.Name, + FullKey = recommendation.Value, + Type = recommendation.ContainingType + }; + if (!_visualBasicRootNodes.InvocationExpressionTokens.Contains(token)) + { + _visualBasicRootNodes.InvocationExpressionTokens.Add(token); + } + + ParseActions(token, recommendedActions.Actions); + break; + } + case ActionTypes.Expression: + { + var token = new ExpressionToken + { + Key = recommendation.Name, + Description = recommendedActions.Description, + TargetCPU = targetCPUs, + Namespace = @namespace.Name, + FullKey = recommendation.Value, + Type = recommendation.ContainingType + }; + if (!_visualBasicRootNodes.ExpressionTokens.Contains(token)) + { + _visualBasicRootNodes.ExpressionTokens.Add(token); + } + + ParseActions(token, recommendedActions.Actions); + break; + } + case ActionTypes.Attribute: + { + var token = new AttributeToken() + { + Key = recommendation.Name, + Description = recommendedActions.Description, + TargetCPU = targetCPUs, + Namespace = @namespace.Name, + FullKey = recommendation.Value, + Type = recommendation.ContainingType + }; + if (!_visualBasicRootNodes.AttributeTokens.Contains(token)) + { + _visualBasicRootNodes.AttributeTokens.Add(token); + } + + ParseActions(token, recommendedActions.Actions); + break; + } + + case ActionTypes.ObjectCreation: + { + var token = new ObjectCreationExpressionToken + { + Key = recommendation.Name, + Description = recommendedActions.Description, + TargetCPU = targetCPUs, + Namespace = @namespace.Name, + FullKey = recommendation.Value, + Type = recommendation.ContainingType + }; + if (!_visualBasicRootNodes.ObjectCreationExpressionTokens.Contains(token)) + { + _visualBasicRootNodes.ObjectCreationExpressionTokens.Add(token); + } + ParseActions(token, recommendedActions.Actions); + break; + } + + case ActionTypes.MethodDeclaration: + { + var token = new MethodBlockToken + { + Key = recommendation.Name, + Description = recommendedActions.Description, + TargetCPU = targetCPUs, + Namespace = @namespace.Name, + FullKey = recommendation.Value, + Type = recommendation.ContainingType + }; + if (!_visualBasicRootNodes.MethodBlockTokens.Contains(token)) + { + _visualBasicRootNodes.MethodBlockTokens.Add(token); + } + + ParseActions(token, recommendedActions.Actions); + break; + } + + case ActionTypes.ElementAccess: + { + var token = new ElementAccessToken + { + Key = recommendation.Name, + Description = recommendedActions.Description, + TargetCPU = targetCPUs, + Namespace = @namespace.Name, + FullKey = recommendation.Value, + Type = recommendation.ContainingType + }; + if (!_visualBasicRootNodes.ElementAccessTokens.Contains(token)) + { + _visualBasicRootNodes.ElementAccessTokens.Add(token); + } + + ParseActions(token, recommendedActions.Actions); + break; + } + + case ActionTypes.MemberAccess: + { + var token = new MemberAccessToken + { + Key = recommendation.Name, + Description = recommendedActions.Description, + TargetCPU = targetCPUs, + Namespace = @namespace.Name, + FullKey = recommendation.Value, + Type = recommendation.ContainingType + }; + if (!_visualBasicRootNodes.MemberAccessTokens.Contains(token)) + { + _visualBasicRootNodes.MemberAccessTokens.Add(token); + } + + ParseActions(token, recommendedActions.Actions); + break; + } + + case ActionTypes.Project: + { + var token = new ProjectToken() { Key = recommendation.Name, Description = recommendedActions.Description, TargetCPU = targetCPUs, Namespace = @namespace.Name, FullKey = recommendation.Value }; + if (!_visualBasicRootNodes.ProjectTokens.Contains(token)) { _visualBasicRootNodes.ProjectTokens.Add(token); } + ParseActions(token, recommendedActions.Actions); + break; + } + } + } + } + } + } + } + + /// + /// Add actions to each node type + /// + /// The token to add the action to + /// The list of actions associated with this token + public void ParseActions(VisualBasicNodeToken visualBasicNodeToken, List actions) + { + foreach (var action in actions) + { + try + { + var actionType = Enum.Parse(typeof(ActionTypes), action.Type); + + dynamic value = action.VbValue ?? action.Value; + ActionValidation validation = action.VbActionValidation ?? action.ActionValidation; + string vbActionName = action.Name; + + switch (actionType) + { + case ActionTypes.Method: + { + var actionFunc = _actionsLoader.GetInvocationExpressionAction(action.Name, value); + if (actionFunc != null) + { + visualBasicNodeToken.InvocationExpressionActions.Add( + new InvocationExpressionAction + { + Key = visualBasicNodeToken.Key, + Value = GetActionValue(value), + Description = action.Description, + ActionValidation = validation, + Name = vbActionName, + Type = action.Type, + InvocationExpressionActionFunc = actionFunc + }); + } + + break; + } + case ActionTypes.Expression: + { + var actionFunc = _actionsLoader.GetExpressionAction(action.Name, value); + if (actionFunc != null) + { + visualBasicNodeToken.ExpressionActions.Add(new ExpressionAction() + { + Key = visualBasicNodeToken.Key, + Value = GetActionValue(value), + Description = action.Description, + ActionValidation = action.ActionValidation, + Name = vbActionName, + Type = action.Type, + ExpressionActionFunc = actionFunc + }); + } + break; + } + case ActionTypes.Class: + { + var actionFunc = _actionsLoader.GetClassAction(action.Name, value); + if (actionFunc != null) + { + visualBasicNodeToken.TypeBlockActions.Add(new TypeBlockAction() + { + Key = visualBasicNodeToken.Key, + Value = GetActionValue(value), + Description = action.Description, + ActionValidation = validation, + Name = vbActionName, + Type = action.Type, + TypeBlockActionFunc = actionFunc + }); + } + break; + } + case ActionTypes.Interface: + { + var actionFunc = _actionsLoader.GetInterfaceAction(action.Name, value); + if (actionFunc != null) + { + visualBasicNodeToken.InterfaceBlockActions.Add(new InterfaceBlockAction() + { + Key = visualBasicNodeToken.Key, + Value = GetActionValue(value), + Description = action.Description, + ActionValidation = action.ActionValidation, + Name = vbActionName, + Type = action.Type, + InterfaceBlockActionFunc = actionFunc + }); + } + break; + } + case ActionTypes.Using: + { + var actionName = action.Name.Replace("Directive", "Statement"); + var actionFunc = _actionsLoader.GetCompilationUnitAction(actionName, value); + if (actionFunc != null) + { + visualBasicNodeToken.ImportActions.Add(new ImportAction() + { + Key = visualBasicNodeToken.Key, + Value = GetActionValue(value), + Description = action.Description, + ActionValidation = validation, + Name = vbActionName, + Type = action.Type, + ImportActionFunc = actionFunc + }); + } + break; + } + case ActionTypes.Namespace: + { + var actionFunc = _actionsLoader.GetNamespaceActions(action.Name, value); + if (actionFunc != null) + { + visualBasicNodeToken.NamespaceActions.Add(new NamespaceAction() + { + Key = visualBasicNodeToken.Key, + Value = GetActionValue(value), + Description = action.Description, + ActionValidation = validation, + Name = vbActionName, + Type = action.Type, + NamespaceActionFunc = actionFunc + }); + } + break; + } + case ActionTypes.Identifier: + { + var actionFunc = _actionsLoader.GetIdentifierNameAction(action.Name, value); + if (actionFunc != null) + { + visualBasicNodeToken.IdentifierNameActions.Add(new IdentifierNameAction() + { + Key = visualBasicNodeToken.Key, + Value = GetActionValue(value), + Description = action.Description, + ActionValidation = validation, + Name = vbActionName, + Type = action.Type, + IdentifierNameActionFunc = actionFunc + }); + } + break; + } + case ActionTypes.Attribute: + { + var actionFunc = _actionsLoader.GetAttributeAction(action.Name, value); + if (actionFunc != null) + { + visualBasicNodeToken.AttributeActions.Add(new Models.Actions.VisualBasic.AttributeAction() + { + Key = visualBasicNodeToken.Key, + Value = GetActionValue(value), + Description = action.Description, + ActionValidation = action.ActionValidation, + Name = vbActionName, + Type = action.Type, + AttributeActionFunc = actionFunc + }); + } + break; + } + case ActionTypes.AttributeList: + { + var actionFunc = _actionsLoader.GetAttributeListAction(action.Name, value); + if (actionFunc != null) + { + visualBasicNodeToken.VbAttributeListActions.Add(new Models.Actions.VisualBasic.AttributeListAction() + { + Key = visualBasicNodeToken.Key, + Value = GetActionValue(value), + Description = action.Description, + ActionValidation = action.ActionValidation, + Name = vbActionName, + Type = action.Type, + AttributeListActionFunc = actionFunc + }); + } + break; + } + case ActionTypes.ObjectCreation: + { + var actionFunc = _actionsLoader.GetObjectCreationExpressionActions(action.Name, value); + if (actionFunc != null) + { + visualBasicNodeToken.ObjectCreationExpressionActions.Add(new ObjectCreationExpressionAction() + { + Key = visualBasicNodeToken.Key, + Value = GetActionValue(value), + Description = action.Description, + ActionValidation = validation, + Name = vbActionName, + Type = action.Type, + ObjectCreationExpressionGenericActionFunc = actionFunc + }); + } + break; + } + case ActionTypes.MethodDeclaration: + { + var actionFunc = _actionsLoader.GetMethodDeclarationAction(action.Name, value); + if (actionFunc != null) + { + visualBasicNodeToken.MethodBlockActions.Add(new MethodBlockAction() + { + Key = visualBasicNodeToken.Key, + Value = GetActionValue(value), + Description = action.Description, + ActionValidation = action.ActionValidation, + Name = vbActionName, + Type = action.Type, + MethodBlockActionFunc = actionFunc + }); + } + break; + } + case ActionTypes.ElementAccess: + { + var actionFunc = _actionsLoader.GetElementAccessExpressionActions(action.Name, value); + if (actionFunc != null) + { + visualBasicNodeToken.ElementAccessActions.Add(new ElementAccessAction() + { + Key = visualBasicNodeToken.Key, + Value = GetActionValue(value), + Description = action.Description, + ActionValidation = action.ActionValidation, + Name = vbActionName, + Type = action.Type, + ElementAccessExpressionActionFunc = actionFunc + }); + } + break; + } + case ActionTypes.MemberAccess: + { + var actionFunc = _actionsLoader.GetMemberAccessExpressionActions(action.Name, value); + if (actionFunc != null) + { + visualBasicNodeToken.MemberAccessActions.Add(new MemberAccessAction() + { + Key = visualBasicNodeToken.Key, + Value = GetActionValue(value), + Description = action.Description, + ActionValidation = action.ActionValidation, + Name = vbActionName, + Type = action.Type, + MemberAccessActionFunc = actionFunc + }); + } + break; + } + case ActionTypes.Project: + { + var actionFunc = _actionsLoader.GetProjectLevelActions(action.Name, value); + if (actionFunc != null) + { + visualBasicNodeToken.ProjectLevelActions.Add(new ProjectLevelAction() + { + Key = visualBasicNodeToken.Key, + Value = GetActionValue(value), + Description = action.Description, + ActionValidation = validation, + Name = vbActionName, + Type = action.Type, + ProjectLevelActionFunc = actionFunc + }); + } + break; + } + case ActionTypes.ProjectFile: + { + var actionFunc = _actionsLoader.GetProjectFileActions(action.Name, value); + if (actionFunc != null) + { + visualBasicNodeToken.ProjectFileActions.Add(new ProjectLevelAction() + { + Key = visualBasicNodeToken.Key, + Value = GetActionValue(value), + Description = action.Description, + ActionValidation = action.ActionValidation, + Name = vbActionName, + Type = action.Type, + ProjectFileActionFunc = actionFunc + }); + } + break; + } + case ActionTypes.ProjectType: + { + var actionFunc = _actionsLoader.GetProjectTypeActions(action.Name, value); + if (actionFunc != null) + { + visualBasicNodeToken.ProjectTypeActions.Add(new ProjectLevelAction() + { + Key = visualBasicNodeToken.Key, + Value = GetActionValue(value), + Description = action.Description, + ActionValidation = action.ActionValidation, + Name = vbActionName, + Type = action.Type, + ProjectTypeActionFunc = actionFunc + }); + } + break; + } + case ActionTypes.Package: + { + PackageAction packageAction = new PackageAction(); + + if (action.Value is string) + { + packageAction.Name = action.Value; + } + else + { + Dictionary jsonParameters = JsonConvert.DeserializeObject>(action.Value.ToString()); + if (jsonParameters.ContainsKey(CTA.Rules.Config.Constants.PackageName)) + { + packageAction.Name = jsonParameters[CTA.Rules.Config.Constants.PackageName]; + } + else + { + LogHelper.LogDebug( + $"Parameter {Config.Constants.PackageName} is not available for action {action.Name}"); + continue; + } + + if (jsonParameters.ContainsKey(CTA.Rules.Config.Constants.PackageVersion)) + { + packageAction.Version = jsonParameters[CTA.Rules.Config.Constants.PackageVersion]; + } + } + visualBasicNodeToken.PackageActions.Add(packageAction); + break; + } + } + } + catch (Exception ex) + { + LogHelper.LogError(ex, $"Error parsing action type {action.Type}"); + } + } + } + + private string GetActionValue(dynamic value) + { + if (value is string) + { + return value; + } + else + { + return value + string.Empty; + } + } + } +} diff --git a/src/CTA.Rules.Update/ActionsRewriter.cs b/src/CTA.Rules.Update/ActionsRewriter.cs index 30393b2c..2999293f 100644 --- a/src/CTA.Rules.Update/ActionsRewriter.cs +++ b/src/CTA.Rules.Update/ActionsRewriter.cs @@ -11,7 +11,7 @@ namespace CTA.Rules.Update.Rewriters { - public class ActionsRewriter : CSharpSyntaxRewriter + public class ActionsRewriter : CSharpSyntaxRewriter, ISyntaxRewriter { private readonly SemanticModel _semanticModel; private readonly SemanticModel _preportSemanticModel; @@ -20,7 +20,7 @@ public class ActionsRewriter : CSharpSyntaxRewriter private readonly string _filePath; private readonly List _allActions; - public List allExecutedActions { get; private set; } + public List AllExecutedActions { get; set; } private static readonly Type[] identifierNameTypes = new Type[] { typeof(MethodDeclarationSyntax), @@ -42,7 +42,7 @@ public ActionsRewriter(SemanticModel semanticModel, SemanticModel preportSemanti _syntaxGenerator = syntaxGenerator; _filePath = filePath; _allActions = allActions; - allExecutedActions = new List(); + AllExecutedActions = new List(); } public ActionsRewriter(SemanticModel semanticModel, SemanticModel preportSemanticModel, SyntaxGenerator syntaxGenerator, string filePath, GenericAction runningAction) @@ -52,7 +52,7 @@ public ActionsRewriter(SemanticModel semanticModel, SemanticModel preportSemanti _syntaxGenerator = syntaxGenerator; _filePath = filePath; _allActions = new List() { runningAction }; - allExecutedActions = new List(); + AllExecutedActions = new List(); } public override SyntaxNode VisitAttributeList(AttributeListSyntax node) @@ -82,7 +82,7 @@ public override SyntaxNode VisitAttributeList(AttributeListSyntax node) actionExecution.InvalidExecutions = 1; LogHelper.LogError(actionExecutionException); } - allExecutedActions.Add(actionExecution); + AllExecutedActions.Add(actionExecution); } } } @@ -115,7 +115,7 @@ public override SyntaxNode VisitAttribute(AttributeSyntax node) actionExecution.InvalidExecutions = 1; LogHelper.LogError(actionExecutionException); } - allExecutedActions.Add(actionExecution); + AllExecutedActions.Add(actionExecution); } } } @@ -146,7 +146,7 @@ public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) actionExecution.InvalidExecutions = 1; LogHelper.LogError(actionExecutionException); } - allExecutedActions.Add(actionExecution); + AllExecutedActions.Add(actionExecution); } } return newNode; @@ -175,7 +175,7 @@ public override SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax actionExecution.InvalidExecutions = 1; LogHelper.LogError(actionExecutionException); } - allExecutedActions.Add(actionExecution); + AllExecutedActions.Add(actionExecution); } } return newNode; @@ -188,7 +188,7 @@ public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) if (symbol != null) { var nodeKey = symbol.OriginalDefinition != null ? symbol.OriginalDefinition.ToString() : symbol.ToString(); - foreach (var action in _allActions.OfType()) + foreach (var action in _allActions.OfType>()) { if (nodeKey == action.Key && identifierNameTypes.Contains(identifierNameSyntax.Parent?.GetType())) { @@ -207,7 +207,7 @@ public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) actionExecution.InvalidExecutions = 1; LogHelper.LogError(actionExecutionException); } - allExecutedActions.Add(actionExecution); + AllExecutedActions.Add(actionExecution); } } } @@ -252,7 +252,7 @@ public override SyntaxNode VisitExpressionStatement(ExpressionStatementSyntax no actionExecution.InvalidExecutions = 1; LogHelper.LogError(actionExecutionException); } - allExecutedActions.Add(actionExecution); + AllExecutedActions.Add(actionExecution); } } return modifiedNode; @@ -271,7 +271,7 @@ public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax var nodeKey = symbol.OriginalDefinition.ToString(); - foreach (var action in _allActions.OfType()) + foreach (var action in _allActions.OfType>()) { if (nodeKey == action.Key) { @@ -290,7 +290,7 @@ public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax actionExecution.InvalidExecutions = 1; LogHelper.LogError(actionExecutionException); } - allExecutedActions.Add(actionExecution); + AllExecutedActions.Add(actionExecution); } } return newNode; @@ -325,7 +325,7 @@ public override SyntaxNode VisitElementAccessExpression(ElementAccessExpressionS actionExecution.InvalidExecutions = 1; LogHelper.LogError(actionExecutionException); } - allExecutedActions.Add(actionExecution); + AllExecutedActions.Add(actionExecution); } } } @@ -360,7 +360,7 @@ public override SyntaxNode VisitMemberAccessExpression(MemberAccessExpressionSyn actionExecution.InvalidExecutions = 1; LogHelper.LogError(actionExecutionException); } - allExecutedActions.Add(actionExecution); + AllExecutedActions.Add(actionExecution); } } } @@ -388,7 +388,7 @@ public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node) actionExecution.InvalidExecutions = 1; LogHelper.LogError(actionExecutionException); } - allExecutedActions.Add(actionExecution); + AllExecutedActions.Add(actionExecution); } return newNode; @@ -410,7 +410,7 @@ public override SyntaxNode VisitObjectCreationExpression(ObjectCreationExpressio { skipChildren = true; newNode = action.ObjectCreationExpressionGenericActionFunc(_syntaxGenerator, (ObjectCreationExpressionSyntax)newNode); - allExecutedActions.Add(actionExecution); + AllExecutedActions.Add(actionExecution); LogHelper.LogInformation(string.Format("{0}", action.Description)); } catch (Exception ex) @@ -431,7 +431,7 @@ public override SyntaxNode VisitNamespaceDeclaration(NamespaceDeclarationSyntax { NamespaceDeclarationSyntax newNode = (NamespaceDeclarationSyntax)base.VisitNamespaceDeclaration(node); // Handle namespace renaming actions etc. - foreach (var action in _allActions.OfType()) + foreach (var action in _allActions.OfType>()) { if (action.Key == newNode.Name.ToString()) { @@ -450,24 +450,24 @@ public override SyntaxNode VisitNamespaceDeclaration(NamespaceDeclarationSyntax actionExecution.InvalidExecutions = 1; LogHelper.LogError(actionExecutionException); } - allExecutedActions.Add(actionExecution); + AllExecutedActions.Add(actionExecution); } } // Handle namespace remove using actions. foreach (var action in _allActions.OfType()) { - if (action.NamespaceUsingActionFunc == null) - { - continue; - } - + if (action.NamespaceUsingActionFunc == null) + { + continue; + } + var actionExecution = new GenericActionExecution(action, _filePath) { TimesRun = 1 }; try { - newNode = action.NamespaceUsingActionFunc(_syntaxGenerator, newNode); + newNode = action.NamespaceUsingActionFunc(_syntaxGenerator, newNode); LogHelper.LogInformation(string.Format("{0} in Namespace block.", action.Description)); } catch (Exception ex) @@ -476,7 +476,7 @@ public override SyntaxNode VisitNamespaceDeclaration(NamespaceDeclarationSyntax actionExecution.InvalidExecutions = 1; LogHelper.LogError(actionExecutionException); } - allExecutedActions.Add(actionExecution); + AllExecutedActions.Add(actionExecution); } return newNode; diff --git a/src/CTA.Rules.Update/CodeReplacer.cs b/src/CTA.Rules.Update/CodeReplacer.cs index cda0879f..2687aa3f 100644 --- a/src/CTA.Rules.Update/CodeReplacer.cs +++ b/src/CTA.Rules.Update/CodeReplacer.cs @@ -11,6 +11,7 @@ using CTA.Rules.Models; using CTA.Rules.Update.Rewriters; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editing; using TextChange = CTA.Rules.Models.TextChange; namespace CTA.Rules.Update @@ -22,9 +23,15 @@ public class CodeReplacer protected readonly List _metadataReferences; protected readonly AnalyzerResult _analyzerResult; protected readonly ProjectResult _projectResult; - - public CodeReplacer(List sourceFileBuildResults, ProjectConfiguration projectConfiguration, List metadataReferences, AnalyzerResult analyzerResult, - List updatedFiles = null, ProjectResult projectResult = null) + protected readonly ProjectLanguage _projectLanguage; + + public CodeReplacer(List sourceFileBuildResults, + ProjectConfiguration projectConfiguration, + List metadataReferences, + AnalyzerResult analyzerResult, + ProjectLanguage projectLanguage, + List updatedFiles = null, + ProjectResult projectResult = null) { _sourceFileBuildResults = sourceFileBuildResults; if (updatedFiles != null) @@ -35,6 +42,7 @@ public CodeReplacer(List sourceFileBuildResults, ProjectC _projectConfiguration = projectConfiguration; _metadataReferences = metadataReferences; _projectResult = projectResult ?? new ProjectResult(); + _projectLanguage = projectLanguage; } public Dictionary> Run(ProjectActions projectActions, ProjectType projectType) @@ -92,8 +100,8 @@ public Dictionary> Run(ProjectActions proje private void RunCodeChanges(SyntaxNode root, SourceFileBuildResult sourceFileBuildResult, FileActions currentFileActions, ConcurrentDictionary> actionsPerProject) { - ActionsRewriter oneRewriter = new ActionsRewriter(sourceFileBuildResult.SemanticModel, sourceFileBuildResult.PrePortSemanticModel, sourceFileBuildResult.SyntaxGenerator, currentFileActions.FilePath, currentFileActions.AllActions); - root = oneRewriter.Visit(root); + var syntaxRewriter = GetSyntaxRewriter(sourceFileBuildResult.SemanticModel, sourceFileBuildResult.PrePortSemanticModel, sourceFileBuildResult.SyntaxGenerator, currentFileActions.FilePath, currentFileActions.AllActions); + root = syntaxRewriter.Visit(root); var result = root.NormalizeWhitespace().ToFullString(); if (!_projectConfiguration.IsMockRun) @@ -101,8 +109,8 @@ private void RunCodeChanges(SyntaxNode root, SourceFileBuildResult sourceFileBui File.WriteAllText(sourceFileBuildResult.SourceFileFullPath, result); } - var processedActions = ValidateActions(oneRewriter.allExecutedActions, root); - processedActions = AddActionsWithoutExecutions(currentFileActions, oneRewriter.allExecutedActions); + var processedActions = ValidateActions(syntaxRewriter.AllExecutedActions, root); + processedActions = AddActionsWithoutExecutions(currentFileActions, syntaxRewriter.AllExecutedActions); if (!actionsPerProject.TryAdd(sourceFileBuildResult.SourceFileFullPath, processedActions)) { @@ -125,9 +133,9 @@ private void GenerateCodeChanges(SyntaxNode root, SourceFileBuildResult sourceFi currentFileActions.NodeTokens.ForEach(nodetoken => { nodetoken.AllActions.ForEach(nodeAction => { - ActionsRewriter oneRewriter = new ActionsRewriter(sourceFileBuildResult.SemanticModel, sourceFileBuildResult.PrePortSemanticModel, sourceFileBuildResult.SyntaxGenerator, currentFileActions.FilePath, nodeAction); + var syntaxRewriter = GetSyntaxRewriter(sourceFileBuildResult.SemanticModel, sourceFileBuildResult.PrePortSemanticModel, sourceFileBuildResult.SyntaxGenerator, currentFileActions.FilePath, nodeAction); - var newRoot = oneRewriter.Visit(root); + var newRoot = syntaxRewriter.Visit(root); var allChanges = newRoot.SyntaxTree.GetChanges(root.SyntaxTree); foreach (var textChange in allChanges) @@ -144,6 +152,24 @@ private void GenerateCodeChanges(SyntaxNode root, SourceFileBuildResult sourceFi } } + private ISyntaxRewriter GetSyntaxRewriter(SemanticModel semanticModel, SemanticModel preportSemanticModel, + SyntaxGenerator syntaxGenerator, string filePath, List allActions) + { + return _projectLanguage == ProjectLanguage.VisualBasic + ? new VisualBasicActionsRewriter(semanticModel, preportSemanticModel, syntaxGenerator, filePath, + allActions) + : new ActionsRewriter(semanticModel, preportSemanticModel, syntaxGenerator, filePath, allActions); + } + + private ISyntaxRewriter GetSyntaxRewriter(SemanticModel semanticModel, SemanticModel preportSemanticModel, + SyntaxGenerator syntaxGenerator, string filePath, GenericAction runningAction) + { + return _projectLanguage == ProjectLanguage.VisualBasic + ? new VisualBasicActionsRewriter(semanticModel, preportSemanticModel, syntaxGenerator, filePath, + runningAction) + : new ActionsRewriter(semanticModel, preportSemanticModel, syntaxGenerator, filePath, runningAction); + } + protected virtual List ApplyProjectActions(ProjectActions projectActions, ProjectType projectType) { var projectRunActions = new List(); diff --git a/src/CTA.Rules.Update/ISyntaxRewriter.cs b/src/CTA.Rules.Update/ISyntaxRewriter.cs new file mode 100644 index 00000000..65c57365 --- /dev/null +++ b/src/CTA.Rules.Update/ISyntaxRewriter.cs @@ -0,0 +1,12 @@ +#nullable enable +using System.Collections.Generic; +using CTA.Rules.Models; +using Microsoft.CodeAnalysis; + +namespace CTA.Rules.Update; + +public interface ISyntaxRewriter +{ + public SyntaxNode? Visit(SyntaxNode? node); + public List AllExecutedActions { get; set; } +} diff --git a/src/CTA.Rules.Update/ProjectRewriters/ProjectRewriter.cs b/src/CTA.Rules.Update/ProjectRewriters/ProjectRewriter.cs index fceaa3f1..5e074137 100644 --- a/src/CTA.Rules.Update/ProjectRewriters/ProjectRewriter.cs +++ b/src/CTA.Rules.Update/ProjectRewriters/ProjectRewriter.cs @@ -6,8 +6,10 @@ using Codelyzer.Analysis.Build; using Codelyzer.Analysis.Model; using CTA.Rules.Analyzer; +using CTA.Rules.Common.Helpers; using CTA.Rules.Config; using CTA.Rules.Models; +using CTA.Rules.Models.Tokens; using CTA.Rules.RuleFiles; namespace CTA.Rules.Update @@ -24,6 +26,8 @@ public class ProjectRewriter protected readonly ProjectResult _projectResult; protected readonly List _metaReferences; protected readonly AnalyzerResult _analyzerResult; + protected readonly ProjectLanguage _projectLanguage; + private IRulesAnalysis _rulesAnalyzer; /// /// Initializes a new instance of ProjectRewriter using an existing analysis @@ -53,6 +57,7 @@ public ProjectRewriter(AnalyzerResult analyzerResult, ProjectConfiguration proje _metaReferences = analyzerResult?.ProjectBuildResult?.Project?.MetadataReferences?.Select(m => m.Display).ToList() ?? projectConfiguration.MetaReferences; ProjectConfiguration = projectConfiguration; + _projectLanguage = VisualBasicUtils.IsVisualBasicProject(ProjectConfiguration.ProjectPath) ? ProjectLanguage.VisualBasic : ProjectLanguage.Csharp; } public ProjectRewriter(IDEProjectResult projectResult, ProjectConfiguration projectConfiguration) @@ -86,13 +91,26 @@ public ProjectResult Initialize() { var allReferences = _sourceFileResults?.SelectMany(s => s.References) .Union(_sourceFileResults.SelectMany(s => s.Children.OfType())?.Select(u => new Reference() { Namespace = u.Identifier, Assembly = u.Identifier }).Distinct()) + .Union(_sourceFileResults.SelectMany(s => s.Children.OfType())?.Select(u => new Reference() {Namespace = u.Identifier, Assembly = u.Identifier }).Distinct()) .Union(ProjectConfiguration.AdditionalReferences.Select(r => new Reference { Assembly = r, Namespace = r })); - RulesFileLoader rulesFileLoader = new RulesFileLoader(allReferences, ProjectConfiguration.RulesDir, ProjectConfiguration.TargetVersions, string.Empty, ProjectConfiguration.AssemblyDir); - + RulesFileLoader rulesFileLoader = new RulesFileLoader(allReferences, ProjectConfiguration.RulesDir, ProjectConfiguration.TargetVersions, _projectLanguage, string.Empty, ProjectConfiguration.AssemblyDir); + var projectRules = rulesFileLoader.Load(); - RulesAnalysis walker = new RulesAnalysis(_sourceFileResults, projectRules, ProjectConfiguration.ProjectType); - projectActions = walker.Analyze(); + HashSet projectTokens; + if (_projectLanguage == ProjectLanguage.VisualBasic) + { + _rulesAnalyzer = new VisualBasicRulesAnalysis(_sourceFileResults, projectRules.VisualBasicRootNodes, + ProjectConfiguration.ProjectType); + projectTokens = projectRules.VisualBasicRootNodes.ProjectTokens; + } + else + { + _rulesAnalyzer = new RulesAnalysis(_sourceFileResults, projectRules.CsharpRootNodes, ProjectConfiguration.ProjectType); + projectTokens = projectRules.CsharpRootNodes.ProjectTokens; + } + + projectActions = _rulesAnalyzer.Analyze(); _projectReferences.ForEach(p => { projectActions.ProjectReferenceActions.Add(Config.Utils.GetRelativePath(ProjectConfiguration.ProjectPath, p)); @@ -107,11 +125,12 @@ public ProjectResult Initialize() } MergePackages(projectActions.PackageActions); - projectActions.ProjectLevelActions = projectRules.ProjectTokens.SelectMany(p => p.ProjectTypeActions).Distinct().ToList(); - projectActions.ProjectLevelActions.AddRange(projectRules.ProjectTokens.SelectMany(p => p.ProjectLevelActions).Distinct()); - projectActions.ProjectLevelActions.AddRange(projectRules.ProjectTokens.SelectMany(p => p.ProjectFileActions).Distinct()); + projectActions.ProjectLevelActions = projectTokens.SelectMany(p => p.ProjectTypeActions).Distinct().ToList(); + projectActions.ProjectLevelActions.AddRange(projectTokens.SelectMany(p => p.ProjectLevelActions).Distinct()); + projectActions.ProjectLevelActions.AddRange(projectTokens.SelectMany(p => p.ProjectFileActions).Distinct()); - projectActions.ProjectRules = projectRules; + projectActions.CsharpProjectRules = projectRules.CsharpRootNodes; + projectActions.VbProjectRules = projectRules.VisualBasicRootNodes; _projectResult.ProjectActions = projectActions; _projectResult.FeatureType = ProjectConfiguration.ProjectType; @@ -140,26 +159,37 @@ public virtual ProjectResult Run() public virtual ProjectResult Run(ProjectActions projectActions) { _projectResult.ProjectActions = projectActions; - CodeReplacer baseReplacer = new CodeReplacer(_sourceFileBuildResults, ProjectConfiguration, _metaReferences, _analyzerResult, projectResult: _projectResult); + CodeReplacer baseReplacer = new CodeReplacer(_sourceFileBuildResults, ProjectConfiguration, _metaReferences, _analyzerResult, _projectLanguage, projectResult: _projectResult); _projectResult.ExecutedActions = baseReplacer.Run(projectActions, ProjectConfiguration.ProjectType); return _projectResult; } public virtual List RunIncremental(List updatedFiles, RootNodes projectRules) { - var ideFileActions = new List(); - var allReferences = _sourceFileResults?.SelectMany(s => s.References).Distinct(); - RulesFileLoader rulesFileLoader = new RulesFileLoader(allReferences, Constants.RulesDefaultPath, ProjectConfiguration.TargetVersions, string.Empty, ProjectConfiguration.AssemblyDir); - projectRules = rulesFileLoader.Load(); + RulesFileLoader rulesFileLoader = new RulesFileLoader(allReferences, Constants.RulesDefaultPath, ProjectConfiguration.TargetVersions, _projectLanguage, string.Empty, ProjectConfiguration.AssemblyDir); + + var rules = rulesFileLoader.Load(); + + HashSet projectTokens; + if (_projectLanguage == ProjectLanguage.VisualBasic) + { + _rulesAnalyzer = new VisualBasicRulesAnalysis(_sourceFileResults, rules.VisualBasicRootNodes, + ProjectConfiguration.ProjectType); + projectTokens = rules.VisualBasicRootNodes.ProjectTokens; + } + else + { + _rulesAnalyzer = new RulesAnalysis(_sourceFileResults, rules.CsharpRootNodes, ProjectConfiguration.ProjectType); + projectTokens = rules.CsharpRootNodes.ProjectTokens; + } - RulesAnalysis walker = new RulesAnalysis(_sourceFileResults, projectRules, ProjectConfiguration.ProjectType); - var projectActions = walker.Analyze(); + var projectActions = _rulesAnalyzer.Analyze(); - CodeReplacer baseReplacer = new CodeReplacer(_sourceFileBuildResults, ProjectConfiguration, _metaReferences, _analyzerResult, updatedFiles, projectResult: _projectResult); + CodeReplacer baseReplacer = new CodeReplacer(_sourceFileBuildResults, ProjectConfiguration, _metaReferences, _analyzerResult, _projectLanguage, updatedFiles, projectResult: _projectResult); _projectResult.ExecutedActions = baseReplacer.Run(projectActions, ProjectConfiguration.ProjectType); - ideFileActions = projectActions + List ideFileActions = projectActions .FileActions .SelectMany(f => f.NodeTokens.Select(n => new IDEFileActions() { TextSpan = n.TextSpan, Description = n.Description, FilePath = f.FilePath, TextChanges = n.TextChanges })) .ToList(); diff --git a/src/CTA.Rules.Update/VisualBasicActionsRewriter.cs b/src/CTA.Rules.Update/VisualBasicActionsRewriter.cs new file mode 100644 index 00000000..340e2fc3 --- /dev/null +++ b/src/CTA.Rules.Update/VisualBasicActionsRewriter.cs @@ -0,0 +1,458 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Codelyzer.Analysis.VisualBasic; +using CTA.Rules.Config; +using CTA.Rules.Models; +using CTA.Rules.Models.Actions.VisualBasic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using GenericAction = CTA.Rules.Models.GenericAction; +using GenericActionExecution = CTA.Rules.Models.GenericActionExecution; +using ActionExecutionException = CTA.Rules.Models.ActionExecutionException; +using AttributeAction = CTA.Rules.Models.Actions.VisualBasic.AttributeAction; + +namespace CTA.Rules.Update; + +public class VisualBasicActionsRewriter : VisualBasicSyntaxRewriter, ISyntaxRewriter +{ + private readonly SemanticModel _semanticModel; + private readonly SemanticModel _preportSemanticModel; + private readonly SyntaxGenerator _syntaxGenerator; + + private readonly string _filePath; + private readonly List _allActions; + + public List AllExecutedActions { get; set; } + + private static readonly Type[] identifierNameTypes = new Type[] + { + typeof(MethodBlockSyntax), + typeof(ConstructorBlockSyntax), + typeof(TypeBlockSyntax), + typeof(VariableDeclaratorSyntax), + typeof(TypeArgumentListSyntax), + typeof(TypeParameterListSyntax), + typeof(ParameterSyntax), + typeof(ObjectCreationExpressionSyntax), + typeof(QualifiedNameSyntax), + typeof(CastExpressionSyntax), + typeof(SimpleAsClauseSyntax) + }; + + public VisualBasicActionsRewriter(SemanticModel semanticModel, SemanticModel preportSemanticModel, + SyntaxGenerator syntaxGenerator, string filePath, List allActions) + { + _semanticModel = semanticModel; + _preportSemanticModel = preportSemanticModel; + _syntaxGenerator = syntaxGenerator; + _filePath = filePath; + _allActions = allActions; + AllExecutedActions = new List(); + } + + public VisualBasicActionsRewriter(SemanticModel semanticModel, SemanticModel preportSemanticModel, + SyntaxGenerator syntaxGenerator, string filePath, GenericAction runningAction) + { + _semanticModel = semanticModel; + _preportSemanticModel = preportSemanticModel; + _syntaxGenerator = syntaxGenerator; + _filePath = filePath; + _allActions = new List() { runningAction }; + AllExecutedActions = new List(); + } + + public override SyntaxNode VisitAttributeList(AttributeListSyntax node) + { + AttributeListSyntax attributeListSyntax = (AttributeListSyntax)base.VisitAttributeList(node); + + foreach (var attributeSyntax in attributeListSyntax.Attributes) + { + foreach (var action in _allActions.OfType()) + { + if (action.Key == attributeSyntax.Name.ToString()) + { + if (action.AttributeListActionFunc != null) + { + var actionExecution = new GenericActionExecution(action, _filePath) { TimesRun = 1 }; + try + { + attributeListSyntax = action.AttributeListActionFunc(_syntaxGenerator, attributeListSyntax); + LogHelper.LogInformation(string.Format("{0}: {1}", node.SpanStart, action.Description)); + } + catch (Exception ex) + { + var actionExecutionException = new ActionExecutionException(action.Name, action.Key, ex); + actionExecution.InvalidExecutions = 1; + LogHelper.LogError(actionExecutionException); + } + + AllExecutedActions.Add(actionExecution); + } + } + } + } + + return attributeListSyntax; + } + + public override SyntaxNode VisitAttribute(AttributeSyntax node) + { + AttributeSyntax attributeSyntax = (AttributeSyntax)base.VisitAttribute(node); + + foreach (var action in _allActions.OfType()) + { + if (action.Key == node.Name.ToString()) + { + if (action.AttributeActionFunc != null) + { + var actionExecution = new GenericActionExecution(action, _filePath) { TimesRun = 1 }; + try + { + attributeSyntax = action.AttributeActionFunc(_syntaxGenerator, attributeSyntax); + LogHelper.LogInformation(string.Format("{0}: {1}", node.SpanStart, action.Description)); + } + catch (Exception ex) + { + var actionExecutionException = new ActionExecutionException(action.Name, action.Key, ex); + actionExecution.InvalidExecutions = 1; + LogHelper.LogError(actionExecutionException); + } + + AllExecutedActions.Add(actionExecution); + } + } + } + + return attributeSyntax; + } + + public override SyntaxNode VisitModuleBlock(ModuleBlockSyntax node) + { + var moduleSymbol = SemanticHelper.GetDeclaredSymbol(node, _semanticModel, _preportSemanticModel); + var newNode = (ModuleBlockSyntax)base.VisitModuleBlock(node); + + foreach (var action in _allActions.OfType()) + { + if (action.Key == node.ModuleStatement.Identifier.Text.Trim()) + { + var actionExecution = new GenericActionExecution(action, _filePath) { TimesRun = 1 }; + try + { + newNode = (ModuleBlockSyntax)action.TypeBlockActionFunc(_syntaxGenerator, newNode); + LogHelper.LogInformation(string.Format("{0}: {1}", node.SpanStart, action.Description)); + } + catch (Exception ex) + { + var actionExecutionException = new ActionExecutionException(action.Name, action.Key, ex); + actionExecution.InvalidExecutions = 1; + LogHelper.LogError(actionExecutionException); + } + + AllExecutedActions.Add(actionExecution); + } + } + + return newNode; + } + + public override SyntaxNode VisitClassBlock(ClassBlockSyntax node) + { + var classSymbol = SemanticHelper.GetDeclaredSymbol(node, _semanticModel, _preportSemanticModel); + ClassBlockSyntax newNode = (ClassBlockSyntax)base.VisitClassBlock(node); + + foreach (var action in _allActions.OfType()) + { + if (action.Key == node.ClassStatement.Identifier.Text.Trim()) + { + var actionExecution = new GenericActionExecution(action, _filePath) { TimesRun = 1 }; + try + { + newNode = (ClassBlockSyntax)action.TypeBlockActionFunc(_syntaxGenerator, newNode); + LogHelper.LogInformation(string.Format("{0}: {1}", node.SpanStart, action.Description)); + } + catch (Exception ex) + { + var actionExecutionException = new ActionExecutionException(action.Name, action.Key, ex); + actionExecution.InvalidExecutions = 1; + LogHelper.LogError(actionExecutionException); + } + + AllExecutedActions.Add(actionExecution); + } + } + + return newNode; + } + + public override SyntaxNode VisitInterfaceBlock(InterfaceBlockSyntax node) + { + var classSymbol = SemanticHelper.GetDeclaredSymbol(node, _semanticModel, _preportSemanticModel); + InterfaceBlockSyntax newNode = (InterfaceBlockSyntax)base.VisitInterfaceBlock(node); + + foreach (var action in _allActions.OfType()) + { + if (action.Key == node.InterfaceStatement.Identifier.Text.Trim()) + { + var actionExecution = new GenericActionExecution(action, _filePath) { TimesRun = 1 }; + try + { + newNode = action.InterfaceBlockActionFunc(_syntaxGenerator, newNode); + LogHelper.LogInformation(string.Format("{0}: {1}", node.SpanStart, action.Description)); + } + catch (Exception ex) + { + var actionExecutionException = new ActionExecutionException(action.Name, action.Key, ex); + actionExecution.InvalidExecutions = 1; + LogHelper.LogError(actionExecutionException); + } + + AllExecutedActions.Add(actionExecution); + } + } + + return newNode; + } + + public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) + { + var symbol = SemanticHelper.GetSemanticSymbol(node, _semanticModel, _preportSemanticModel); + + var identifierNameSyntax = (IdentifierNameSyntax)base.VisitIdentifierName(node); + if (symbol != null) + { + var nodeKey = symbol.OriginalDefinition != null ? symbol.OriginalDefinition.ToString() : symbol.ToString(); + foreach (var action in _allActions.OfType>()) + { + if (nodeKey == action.Key && identifierNameTypes.Contains(identifierNameSyntax.Parent?.GetType())) + { + var actionExecution = new GenericActionExecution(action, _filePath) { TimesRun = 1 }; + try + { + identifierNameSyntax = action.IdentifierNameActionFunc(_syntaxGenerator, identifierNameSyntax); + LogHelper.LogInformation(string.Format("{0}: {1}", node.SpanStart, action.Description)); + } + catch (Exception ex) + { + var actionExecutionException = new ActionExecutionException(action.Name, action.Key, ex); + actionExecution.InvalidExecutions = 1; + LogHelper.LogError(actionExecutionException); + } + + AllExecutedActions.Add(actionExecution); + } + } + } + + return identifierNameSyntax; + } + + public override SyntaxNode VisitExpressionStatement(ExpressionStatementSyntax node) + { + var newNode = base.VisitExpressionStatement(node); + SyntaxNode modifiedNode = newNode; + + var invocationExpressionNodes = node.DescendantNodes().OfType().ToList(); + if (invocationExpressionNodes.Count <= 0) + { + return newNode; + } + + var invocationExpressionNode = invocationExpressionNodes.First(); + + var symbol = SemanticHelper.GetSemanticSymbol(invocationExpressionNode, _semanticModel, _preportSemanticModel); + if (symbol == null) + { + return newNode; + } + + var nodeKey = symbol.OriginalDefinition.ToString(); + + foreach (var action in _allActions.OfType()) + { + if (nodeKey == action.Key) + { + var actionExecution = new GenericActionExecution(action, _filePath) { TimesRun = 1 }; + try + { + modifiedNode = action.ExpressionActionFunc(_syntaxGenerator, newNode); + LogHelper.LogInformation(string.Format("{0}: {1}", node.SpanStart, action.Description)); + } + catch (Exception ex) + { + var actionExecutionException = new ActionExecutionException(action.Name, action.Key, ex); + actionExecution.InvalidExecutions = 1; + LogHelper.LogError(actionExecutionException); + } + + AllExecutedActions.Add(actionExecution); + } + } + + return modifiedNode; + } + + public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node) + { + var symbol = (IMethodSymbol)SemanticHelper.GetSemanticSymbol(node, _semanticModel, _preportSemanticModel); + var newNode = base.VisitInvocationExpression(node); + + if (symbol == null) + { + return node; + } + + var prefix = symbol.IsExtensionMethod ? symbol.ReceiverType?.ToString() ?? "" : symbol.ContainingType?.ToString() ?? ""; + var nodeKey = $"{prefix}.{symbol.Name}({string.Join(", ", symbol.Parameters.Select(p => p.Type))})"; + + foreach (var action in _allActions.OfType>()) + { + if (nodeKey == action.Key) + { + var actionExecution = new GenericActionExecution(action, _filePath) { TimesRun = 1 }; + try + { + newNode = action.InvocationExpressionActionFunc(_syntaxGenerator, + (InvocationExpressionSyntax)newNode); + LogHelper.LogInformation($"{node.SpanStart}: {action.Description}"); + } + catch (Exception ex) + { + var actionExecutionException = new ActionExecutionException(action.Name, action.Key, ex); + actionExecution.InvalidExecutions = 1; + LogHelper.LogError(actionExecutionException); + } + + AllExecutedActions.Add(actionExecution); + } + } + + return newNode; + } + + public override SyntaxNode VisitMemberAccessExpression(MemberAccessExpressionSyntax node) + { + var symbol = SemanticHelper.GetSemanticSymbol(node, _semanticModel, _preportSemanticModel); + var newNode = base.VisitMemberAccessExpression(node); + + if (symbol != null) + { + var nodeKey = $"{symbol.ContainingType}.{node.Name}"; + + foreach (var action in _allActions.OfType()) + { + if (nodeKey == action.Key) + { + var actionExecution = new GenericActionExecution(action, _filePath) { TimesRun = 1 }; + try + { + newNode = action.MemberAccessActionFunc(_syntaxGenerator, newNode); + LogHelper.LogInformation(string.Format("{0}: {1}", node.SpanStart, action.Description)); + } + catch (Exception ex) + { + var actionExecutionException = new ActionExecutionException(action.Name, action.Key, ex); + actionExecution.InvalidExecutions = 1; + LogHelper.LogError(actionExecutionException); + } + + AllExecutedActions.Add(actionExecution); + } + } + } + + return newNode; + } + + public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node) + { + CompilationUnitSyntax newNode = (CompilationUnitSyntax)base.VisitCompilationUnit(node); + //Applying using actions + foreach (var action in _allActions.OfType()) + { + var actionExecution = new GenericActionExecution(action, _filePath) { TimesRun = 1 }; + try + { + newNode = action.ImportActionFunc(_syntaxGenerator, newNode); + LogHelper.LogInformation(string.Format("{0} in CompilationUnit.", action.Description)); + } + catch (Exception ex) + { + var actionExecutionException = new ActionExecutionException(action.Name, action.Key, ex); + actionExecution.InvalidExecutions = 1; + LogHelper.LogError(actionExecutionException); + } + + AllExecutedActions.Add(actionExecution); + } + + return newNode; + } + + public override SyntaxNode VisitObjectCreationExpression(ObjectCreationExpressionSyntax node) + { + var symbol = SemanticHelper.GetSemanticSymbol(node, _semanticModel, _preportSemanticModel); + ExpressionSyntax newNode = node; + var + skipChildren = + false; // This is here to skip actions on children node when the main identifier was changed. Just use new expression for the subsequent children actions. + foreach (var action in _allActions.OfType()) + { + if (newNode.ToString() == action.Key || symbol?.OriginalDefinition.ToDisplayString() == action.Key) + { + var actionExecution = new GenericActionExecution(action, _filePath) { TimesRun = 1 }; + try + { + skipChildren = true; + newNode = action.ObjectCreationExpressionGenericActionFunc(_syntaxGenerator, + (ObjectCreationExpressionSyntax)newNode); + AllExecutedActions.Add(actionExecution); + LogHelper.LogInformation(string.Format("{0}", action.Description)); + } + catch (Exception ex) + { + var actionExecutionException = new ActionExecutionException(action.Name, action.Key, ex); + actionExecution.InvalidExecutions = 1; + LogHelper.LogError(actionExecutionException); + } + } + } + + if (!skipChildren) + { + newNode = (ObjectCreationExpressionSyntax)base.VisitObjectCreationExpression(node); + } + + return newNode; + } + + public override SyntaxNode VisitNamespaceBlock(NamespaceBlockSyntax node) + { + NamespaceBlockSyntax newNode = (NamespaceBlockSyntax)base.VisitNamespaceBlock(node); + // Handle namespace renaming actions etc. + foreach (var action in _allActions.OfType>()) + { + if (action.Key == newNode.NamespaceStatement.Name.ToString()) + { + var actionExecution = new GenericActionExecution(action, _filePath) { TimesRun = 1 }; + try + { + newNode = action.NamespaceActionFunc(_syntaxGenerator, newNode); + LogHelper.LogInformation(string.Format("{0}", action.Description)); + } + catch (Exception ex) + { + var actionExecutionException = new ActionExecutionException(action.Name, action.Key, ex); + actionExecution.InvalidExecutions = 1; + LogHelper.LogError(actionExecutionException); + } + + AllExecutedActions.Add(actionExecution); + } + } + //VB doesn't allow for imports within a namespace. + return newNode; + } +} diff --git a/tst/CTA.Rules.Test/Actions/ActionsLoaderTests.cs b/tst/CTA.Rules.Test/Actions/ActionsLoaderTests.cs index ffb4617b..b5916607 100644 --- a/tst/CTA.Rules.Test/Actions/ActionsLoaderTests.cs +++ b/tst/CTA.Rules.Test/Actions/ActionsLoaderTests.cs @@ -1,10 +1,4 @@ using CTA.Rules.Actions; -using CTA.Rules.Models; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Editing; -using Newtonsoft.Json; using NUnit.Framework; using System.Collections.Generic; @@ -12,7 +6,7 @@ namespace CTA.Rules.Test.Actions { public class ActionLoaderTests { - ActionsLoader _actionLoader; + private ActionsLoader _actionLoader; [SetUp] public void SetUp() diff --git a/tst/CTA.Rules.Test/Actions/AttributeActionsTests.cs b/tst/CTA.Rules.Test/Actions/AttributeActionsTests.cs index 6dc8c464..2172072d 100644 --- a/tst/CTA.Rules.Test/Actions/AttributeActionsTests.cs +++ b/tst/CTA.Rules.Test/Actions/AttributeActionsTests.cs @@ -1,4 +1,4 @@ -using CTA.Rules.Actions; +using CTA.Rules.Actions.Csharp; using CTA.Rules.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; diff --git a/tst/CTA.Rules.Test/Actions/AttributeListActionsTests.cs b/tst/CTA.Rules.Test/Actions/AttributeListActionsTests.cs index 05b9f263..baada3a9 100644 --- a/tst/CTA.Rules.Test/Actions/AttributeListActionsTests.cs +++ b/tst/CTA.Rules.Test/Actions/AttributeListActionsTests.cs @@ -1,4 +1,4 @@ -using CTA.Rules.Actions; +using CTA.Rules.Actions.Csharp; using CTA.Rules.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; diff --git a/tst/CTA.Rules.Test/Actions/ClassActionsTests.cs b/tst/CTA.Rules.Test/Actions/ClassActionsTests.cs index 38576cf8..b1ac563f 100644 --- a/tst/CTA.Rules.Test/Actions/ClassActionsTests.cs +++ b/tst/CTA.Rules.Test/Actions/ClassActionsTests.cs @@ -1,4 +1,4 @@ -using CTA.Rules.Actions; +using CTA.Rules.Actions.Csharp; using CTA.Rules.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; diff --git a/tst/CTA.Rules.Test/Actions/CompilationUnitActionsTests.cs b/tst/CTA.Rules.Test/Actions/CompilationUnitActionsTests.cs index 8e8a6237..0b2f27bf 100644 --- a/tst/CTA.Rules.Test/Actions/CompilationUnitActionsTests.cs +++ b/tst/CTA.Rules.Test/Actions/CompilationUnitActionsTests.cs @@ -1,4 +1,4 @@ -using CTA.Rules.Actions; +using CTA.Rules.Actions.Csharp; using CTA.Rules.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; diff --git a/tst/CTA.Rules.Test/Actions/ElementAccessActionsTests.cs b/tst/CTA.Rules.Test/Actions/ElementAccessActionsTests.cs index 8e590d9a..80ba1dfd 100644 --- a/tst/CTA.Rules.Test/Actions/ElementAccessActionsTests.cs +++ b/tst/CTA.Rules.Test/Actions/ElementAccessActionsTests.cs @@ -1,5 +1,5 @@ using CTA.Rules.Models; -using CTA.Rules.Update; +using CTA.Rules.Update.Csharp; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; diff --git a/tst/CTA.Rules.Test/Actions/ExpressionActionsTests.cs b/tst/CTA.Rules.Test/Actions/ExpressionActionsTests.cs index e82e9979..d0d67c2e 100644 --- a/tst/CTA.Rules.Test/Actions/ExpressionActionsTests.cs +++ b/tst/CTA.Rules.Test/Actions/ExpressionActionsTests.cs @@ -1,4 +1,4 @@ -using CTA.Rules.Actions; +using CTA.Rules.Actions.Csharp; using CTA.Rules.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; diff --git a/tst/CTA.Rules.Test/Actions/IdentifierNameActionsTests.cs b/tst/CTA.Rules.Test/Actions/IdentifierNameActionsTests.cs index b2784886..8190918d 100644 --- a/tst/CTA.Rules.Test/Actions/IdentifierNameActionsTests.cs +++ b/tst/CTA.Rules.Test/Actions/IdentifierNameActionsTests.cs @@ -1,4 +1,4 @@ -using CTA.Rules.Actions; +using CTA.Rules.Actions.Csharp; using CTA.Rules.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; diff --git a/tst/CTA.Rules.Test/Actions/InterfaceActionsTests.cs b/tst/CTA.Rules.Test/Actions/InterfaceActionsTests.cs index 57d933ea..400dc069 100644 --- a/tst/CTA.Rules.Test/Actions/InterfaceActionsTests.cs +++ b/tst/CTA.Rules.Test/Actions/InterfaceActionsTests.cs @@ -1,4 +1,4 @@ -using CTA.Rules.Actions; +using CTA.Rules.Actions.Csharp; using CTA.Rules.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; diff --git a/tst/CTA.Rules.Test/Actions/InvocationExpressionActionsTests.cs b/tst/CTA.Rules.Test/Actions/InvocationExpressionActionsTests.cs index 06c0b405..7462d098 100644 --- a/tst/CTA.Rules.Test/Actions/InvocationExpressionActionsTests.cs +++ b/tst/CTA.Rules.Test/Actions/InvocationExpressionActionsTests.cs @@ -1,4 +1,4 @@ -using CTA.Rules.Actions; +using CTA.Rules.Actions.Csharp; using CTA.Rules.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -115,8 +115,8 @@ public void GetAppendMethodAction_Appends_A_Method_Invocation() [Test] public void InvocationExpressionEquals() { - var invocationExpressionAction = new InvocationExpressionAction() { Key = "Test", Value = "Test2", InvocationExpressionActionFunc = _invocationExpressionActions.GetAddCommentAction("Test") }; - var cloned = invocationExpressionAction.Clone(); + var invocationExpressionAction = new InvocationExpressionAction() { Key = "Test", Value = "Test2", InvocationExpressionActionFunc = _invocationExpressionActions.GetAddCommentAction("Test") }; + var cloned = invocationExpressionAction.Clone>(); Assert.True(invocationExpressionAction.Equals(cloned)); cloned.Value = "DifferentValue"; diff --git a/tst/CTA.Rules.Test/Actions/MemberAccessActionsTests.cs b/tst/CTA.Rules.Test/Actions/MemberAccessActionsTests.cs index 36125b97..a4eb1048 100644 --- a/tst/CTA.Rules.Test/Actions/MemberAccessActionsTests.cs +++ b/tst/CTA.Rules.Test/Actions/MemberAccessActionsTests.cs @@ -5,6 +5,9 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using NUnit.Framework; +using SimpleNameSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax.SimpleNameSyntax; +using VbMemberAccessExpressionSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax.MemberAccessExpressionSyntax; +using VbSyntaxFactory = Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory; namespace CTA.Rules.Test.Actions { @@ -14,6 +17,9 @@ public class MemberAccessActionsTests private SyntaxGenerator _syntaxGenerator; private MemberAccessExpressionSyntax _node; + private SyntaxGenerator _vbGenerator; + private VbMemberAccessExpressionSyntax _vbNode; + [SetUp] public void SetUp() { @@ -21,7 +27,14 @@ public void SetUp() var language = LanguageNames.CSharp; _syntaxGenerator = SyntaxGenerator.GetGenerator(workspace, language); _memberAccessActions = new MemberAccessActions(); - _node = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.ThisExpression(), SyntaxFactory.IdentifierName("value")); + _node = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.ThisExpression(), SyntaxFactory.IdentifierName("value")); + _vbGenerator = SyntaxGenerator.GetGenerator(workspace, LanguageNames.VisualBasic); + _vbNode = VbSyntaxFactory.MemberAccessExpression( + Microsoft.CodeAnalysis.VisualBasic.SyntaxKind.SimpleMemberAccessExpression, + VbSyntaxFactory.ParseExpression("Math.Abs(-1)"), + VbSyntaxFactory.Token(Microsoft.CodeAnalysis.VisualBasic.SyntaxKind.DotToken), + (SimpleNameSyntax)VbSyntaxFactory.ParseName("Value")); } [Test] @@ -50,5 +63,16 @@ public void MemberAccessActionComparison() cloned.Value = "DifferentValue"; Assert.False(memberAccessAction.Equals(cloned)); } + + [Test] + public void RemoveMemberAccess() + { + var removeMemberAccessFunc = _memberAccessActions.GetRemoveMemberAccessAction(""); + var newCsharpNode = removeMemberAccessFunc(_syntaxGenerator, _node); + var newVbNode = removeMemberAccessFunc(_vbGenerator, _vbNode); + + Assert.AreEqual("this", newCsharpNode.ToFullString()); + Assert.AreEqual("Math.Abs(-1)", newVbNode.ToFullString()); + } } } \ No newline at end of file diff --git a/tst/CTA.Rules.Test/Actions/MethodDeclarationActionsTests.cs b/tst/CTA.Rules.Test/Actions/MethodDeclarationActionsTests.cs index e7e86d5d..09b0f9f9 100644 --- a/tst/CTA.Rules.Test/Actions/MethodDeclarationActionsTests.cs +++ b/tst/CTA.Rules.Test/Actions/MethodDeclarationActionsTests.cs @@ -1,4 +1,4 @@ -using CTA.Rules.Actions; +using CTA.Rules.Actions.Csharp; using CTA.Rules.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; diff --git a/tst/CTA.Rules.Test/Actions/NamespaceActionsTests.cs b/tst/CTA.Rules.Test/Actions/NamespaceActionsTests.cs index 0a13c335..1d0aad84 100644 --- a/tst/CTA.Rules.Test/Actions/NamespaceActionsTests.cs +++ b/tst/CTA.Rules.Test/Actions/NamespaceActionsTests.cs @@ -1,79 +1,79 @@ -using CTA.FeatureDetection.Common.Extensions; -using CTA.Rules.Actions; -using CTA.Rules.Models; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Editing; -using NUnit.Framework; -using System.Linq; - -namespace CTA.Rules.Test.Actions -{ - internal class NamespaceActionsTests - { - private SyntaxGenerator _syntaxGenerator; - private NamespaceActions _namespaceActions; - private NamespaceDeclarationSyntax _node; - - [SetUp] - public void SetUp() - { - var workspace = new AdhocWorkspace(); - var language = LanguageNames.CSharp; - _syntaxGenerator = SyntaxGenerator.GetGenerator(workspace, language); - _namespaceActions = new NamespaceActions(); - - SyntaxTree tree = CSharpSyntaxTree.ParseText( - @$" - namespace DummyNamespace - {{ - using System.Web; - class MyClass - {{ - }}; - }}"); - _node = tree.GetRoot() - .DescendantNodes() - .OfType() - .FirstOrDefault(); - } - - [Test] - public void GetRenameNamespaceAction_Rename_Namespace() - { - const string newNamespaceName = "NewNamespace"; - var renameNamespaceFunc = _namespaceActions.GetRenameNamespaceAction(newNamespaceName); - var newNode = renameNamespaceFunc(_syntaxGenerator, _node); - - var expectedResult = CSharpSyntaxTree.ParseText(@$" - namespace NewNamespace - {{ - using System.Web; - class MyClass - {{ - }}; - }}").GetRoot(); - Assert.AreEqual(expectedResult.RemoveAllTrivia().ToFullString(), newNode.RemoveAllTrivia().ToFullString()); - } - - [Test] - public void GetRemoveDirectiveAction_Removes_Directive() - { - const string directive = "System.Web"; - var removeDirectiveFunc = _namespaceActions.GetRemoveDirectiveAction(directive); - var newNode = removeDirectiveFunc(_syntaxGenerator, _node); - - var expectedResult = CSharpSyntaxTree.ParseText(@$" - namespace DummyNamespace - {{ - class MyClass - {{ - }}; - }}").GetRoot(); - Assert.AreEqual(expectedResult.RemoveAllTrivia().ToFullString(), newNode.RemoveAllTrivia().ToFullString()); - } - - - } -} +using CTA.FeatureDetection.Common.Extensions; +using CTA.Rules.Actions.Csharp; +using CTA.Rules.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using NUnit.Framework; +using System.Linq; + +namespace CTA.Rules.Test.Actions +{ + internal class NamespaceActionsTests + { + private SyntaxGenerator _syntaxGenerator; + private NamespaceActions _namespaceActions; + private NamespaceDeclarationSyntax _node; + + [SetUp] + public void SetUp() + { + var workspace = new AdhocWorkspace(); + var language = LanguageNames.CSharp; + _syntaxGenerator = SyntaxGenerator.GetGenerator(workspace, language); + _namespaceActions = new NamespaceActions(); + + SyntaxTree tree = CSharpSyntaxTree.ParseText( + @$" + namespace DummyNamespace + {{ + using System.Web; + class MyClass + {{ + }}; + }}"); + _node = tree.GetRoot() + .DescendantNodes() + .OfType() + .FirstOrDefault(); + } + + [Test] + public void GetRenameNamespaceAction_Rename_Namespace() + { + const string newNamespaceName = "NewNamespace"; + var renameNamespaceFunc = _namespaceActions.GetRenameNamespaceAction(newNamespaceName); + var newNode = renameNamespaceFunc(_syntaxGenerator, _node); + + var expectedResult = CSharpSyntaxTree.ParseText(@$" + namespace NewNamespace + {{ + using System.Web; + class MyClass + {{ + }}; + }}").GetRoot(); + Assert.AreEqual(expectedResult.RemoveAllTrivia().ToFullString(), newNode.RemoveAllTrivia().ToFullString()); + } + + [Test] + public void GetRemoveDirectiveAction_Removes_Directive() + { + const string directive = "System.Web"; + var removeDirectiveFunc = _namespaceActions.GetRemoveDirectiveAction(directive); + var newNode = removeDirectiveFunc(_syntaxGenerator, _node); + + var expectedResult = CSharpSyntaxTree.ParseText(@$" + namespace DummyNamespace + {{ + class MyClass + {{ + }}; + }}").GetRoot(); + Assert.AreEqual(expectedResult.RemoveAllTrivia().ToFullString(), newNode.RemoveAllTrivia().ToFullString()); + } + + + } +} diff --git a/tst/CTA.Rules.Test/Actions/ObjectCreationExpressionActionsTests.cs b/tst/CTA.Rules.Test/Actions/ObjectCreationExpressionActionsTests.cs index a5cdb00b..48d835d9 100644 --- a/tst/CTA.Rules.Test/Actions/ObjectCreationExpressionActionsTests.cs +++ b/tst/CTA.Rules.Test/Actions/ObjectCreationExpressionActionsTests.cs @@ -1,4 +1,4 @@ -using CTA.Rules.Actions; +using CTA.Rules.Actions.Csharp; using CTA.Rules.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; diff --git a/tst/CTA.Rules.Test/Actions/VisualBasic/ActionsLoaderTests.cs b/tst/CTA.Rules.Test/Actions/VisualBasic/ActionsLoaderTests.cs new file mode 100644 index 00000000..9df676f8 --- /dev/null +++ b/tst/CTA.Rules.Test/Actions/VisualBasic/ActionsLoaderTests.cs @@ -0,0 +1,247 @@ +using CTA.Rules.Actions; +using NUnit.Framework; +using System.Collections.Generic; + +namespace CTA.Rules.Test.Actions.VisualBasic +{ + public class ActionLoaderTests + { + private VisualBasicActionsLoader _actionLoader; + + [SetUp] + public void SetUp() + { + _actionLoader = new VisualBasicActionsLoader(new List()); + } + + [Test] + public void CompilationUnitActionsTest() + { + var addStatement = _actionLoader.GetCompilationUnitAction("AddStatement", "namespace"); + var removeStatement = _actionLoader.GetCompilationUnitAction("RemoveStatement", "namespace"); + var addComment = _actionLoader.GetCompilationUnitAction("AddComment", "comment"); + + Assert.IsNotNull(addStatement); + Assert.IsNotNull(removeStatement); + Assert.IsNotNull(addComment); + } + + [Test] + public void InvocationExpressionActionsTest() + { + var replaceMethodWithObjectAndParameters = _actionLoader.GetInvocationExpressionAction("ReplaceMethodWithObjectAndParameters", "{newMethod: \"method\", newParameters: \"params\"}"); + var replaceMethodWithObject = _actionLoader.GetInvocationExpressionAction("ReplaceMethodWithObject", "newMethod"); + var replaceMethodWithObjectAddType = _actionLoader.GetInvocationExpressionAction("ReplaceMethodWithObjectAddType", "newMethod"); + var replaceMethodAndParameters = _actionLoader.GetInvocationExpressionAction("ReplaceMethodAndParameters", "{ oldMethod: \"method\", newMethod: \"method\", newParameters: \"params\"}"); + var replaceMethodOnly = _actionLoader.GetInvocationExpressionAction("ReplaceMethodOnly", "{ oldMethod: \"method\", newMethod: \"method\" }"); + var replaceParametersOnly = _actionLoader.GetInvocationExpressionAction("ReplaceParametersOnly", "newParameters"); + var appendMethod = _actionLoader.GetInvocationExpressionAction("AppendMethod", "appendMethod"); + var addComment = _actionLoader.GetInvocationExpressionAction("AddComment", "comment"); + + Assert.IsNotNull(replaceMethodWithObjectAndParameters); + Assert.IsNotNull(replaceMethodWithObject); + Assert.IsNotNull(replaceMethodWithObjectAddType); + Assert.IsNotNull(replaceMethodAndParameters); + Assert.IsNotNull(replaceMethodOnly); + Assert.IsNotNull(replaceParametersOnly); + Assert.IsNotNull(appendMethod); + Assert.IsNotNull(addComment); + } + + [Test] + public void NamespaceActionsTest() + { + var renameNamespace = _actionLoader.GetNamespaceActions("RenameNamespace", "newName"); + + Assert.IsNotNull(renameNamespace); + } + + [Test] + public void AttributeActionsTest() + { + var changeAttribute = _actionLoader.GetAttributeAction("ChangeAttribute", "attributeName"); + + Assert.IsNotNull(changeAttribute); + } + + [Test] + public void AttributeListActionsTest() + { + var addComment = _actionLoader.GetAttributeListAction("AddComment", "comment"); + + Assert.IsNotNull(addComment); + } + + [Test] + public void ClassActionsTest() + { + var removeBaseClass = _actionLoader.GetClassAction("RemoveBaseClass", "baseClass"); + var addBaseClass = _actionLoader.GetClassAction("AddBaseClass", "baseClass"); + var changeName = _actionLoader.GetClassAction("ChangeName", "className"); + var removeAttribute = _actionLoader.GetClassAction("RemoveAttribute", "attributeName"); + var addAttribute = _actionLoader.GetClassAction("AddAttribute", "attribute"); + var addComment = _actionLoader.GetClassAction("AddComment", "{comment: \"comment here\", dontUseCTAPrefix: \"true\"}"); + var addComment2 = _actionLoader.GetClassAction("AddComment", "comment"); + var addMethod = _actionLoader.GetClassAction("AddMethod", "expression"); + var removeMethod = _actionLoader.GetClassAction("RemoveMethod", "methodName"); + var renameClass = _actionLoader.GetClassAction("RenameClass", "newClassName"); + var replaceMethodModifiers = _actionLoader.GetClassAction("ReplaceMethodModifiers", "{ methodName: \"\", modifiers: \"\"}"); + var addExpression = _actionLoader.GetClassAction("AddExpression", "expression"); + var removeConstructorInitializer = _actionLoader.GetClassAction("RemoveConstructorInitializer", "baseClass"); + var appendConstructorExpression = _actionLoader.GetClassAction("AppendConstructorExpression", "expression"); + var createConstructor = _actionLoader.GetClassAction("CreateConstructor", "{ types: \"type\", identifiers: \"identify\"}"); + var changeMethodName = _actionLoader.GetClassAction("ChangeMethodName", "{ existingMethodName: \"method\", newMethodName: \"newMethod\"}"); + var changeMethodToReturnTaskType = _actionLoader.GetClassAction("ChangeMethodToReturnTaskType", "methodName"); + var removeMethodParameters = _actionLoader.GetClassAction("RemoveMethodParameters", "methodName"); + var commentMethod = _actionLoader.GetClassAction("CommentMethod", "{ methodName: \"SuperMethod\", comment: \"comment here\", dontUseCTAPrefix: \"true\"}"); + var addCommentsToMethod = _actionLoader.GetClassAction("AddCommentsToMethod", "{ methodName: \"SuperMethod\", comment: \"comments here\", dontUseCTAPrefix: \"false\"}"); + var addCommentsToMethod2 = _actionLoader.GetClassAction("AddCommentsToMethod", "{ methodName: \"SuperMethod\", comment: \"comments here\" }"); + var addExpressionToMethod = _actionLoader.GetClassAction("AddExpressionToMethod", "{ methodName: \"DuperMethod\", expression: \"var maths = 1+1;\"}"); + var addParametersToMethod = _actionLoader.GetClassAction("AddParametersToMethod", "{ methodName: \"MethodHere\", types: \"type\", identifiers: \"identifier\"}"); + var replaceWebApiControllerMethodsBody = _actionLoader.GetClassAction("ReplaceWebApiControllerMethodsBody", "expression"); + + Assert.IsNotNull(removeBaseClass); + Assert.IsNotNull(addBaseClass); + Assert.IsNotNull(changeName); + Assert.IsNotNull(removeAttribute); + Assert.IsNotNull(addAttribute); + Assert.IsNotNull(addComment); + Assert.IsNotNull(addComment2); + Assert.IsNotNull(addMethod); + Assert.IsNotNull(removeMethod); + Assert.IsNotNull(renameClass); + Assert.IsNotNull(replaceMethodModifiers); + Assert.IsNotNull(addExpression); + Assert.IsNotNull(removeConstructorInitializer); + Assert.IsNotNull(appendConstructorExpression); + Assert.IsNotNull(createConstructor); + Assert.IsNotNull(changeMethodName); + Assert.IsNotNull(changeMethodToReturnTaskType); + Assert.IsNotNull(removeMethodParameters); + Assert.IsNotNull(commentMethod); + Assert.IsNotNull(addCommentsToMethod); + Assert.IsNotNull(addCommentsToMethod2); + Assert.IsNotNull(addExpressionToMethod); + Assert.IsNotNull(addParametersToMethod); + Assert.IsNotNull(replaceWebApiControllerMethodsBody); + } + + [Test] + public void ElementAccessActionsTest() + { + var replaceElementAccess = _actionLoader.GetElementAccessExpressionActions("ReplaceElementAccess", "newExpression"); + var addComment = _actionLoader.GetElementAccessExpressionActions("AddComment", "comment"); + + Assert.IsNotNull(replaceElementAccess); + Assert.IsNotNull(addComment); + } + + [Test] + public void ExpressionActionsTest() + { + var addAwaitOperator = _actionLoader.GetExpressionAction("AddAwaitOperator", "string"); + var addComment = _actionLoader.GetExpressionAction("AddComment", "comment"); + + Assert.IsNotNull(addAwaitOperator); + Assert.IsNotNull(addComment); + } + + [Test] + public void IdentifierNameActionsTest() + { + var replaceIdentifier = _actionLoader.GetIdentifierNameAction("ReplaceIdentifier", "identifierName"); + var replaceIdentifierInsideClass = _actionLoader.GetIdentifierNameAction("ReplaceIdentifierInsideClass", "{ identifier: \"identifier\", classFullKey: \"classKey\" }"); + + Assert.IsNotNull(replaceIdentifier); + Assert.IsNotNull(replaceIdentifierInsideClass); + } + + [Test] + public void InterfaceActionsTest() + { + var changeName = _actionLoader.GetInterfaceAction("ChangeName", "newName"); + var removeAttribute = _actionLoader.GetInterfaceAction("RemoveAttribute", "attributeName"); + var addAttribute = _actionLoader.GetInterfaceAction("AddAttribute", "attribute"); + var addComment = _actionLoader.GetInterfaceAction("AddComment", "comment"); + var addMethod = _actionLoader.GetInterfaceAction("AddMethod", "expression"); + var removeMethod = _actionLoader.GetInterfaceAction("RemoveMethod", "methodName"); + + Assert.IsNotNull(changeName); + Assert.IsNotNull(addComment); + Assert.IsNotNull(removeAttribute); + Assert.IsNotNull(addAttribute); + Assert.IsNotNull(addMethod); + Assert.IsNotNull(removeMethod); + } + + [Test] + public void MethodBlockActionsTest() + { + var addComment = _actionLoader.GetClassAction("AddComment", "{comment: \"comment here\", dontUseCTAPrefix: \"true\"}"); + var addComment2 = _actionLoader.GetClassAction("AddComment", "comment"); + var appendExpression = _actionLoader.GetMethodDeclarationAction("AppendExpression", "expression"); + var changeMethodName = _actionLoader.GetMethodDeclarationAction("ChangeMethodName", "newMethodName"); + var changeMethodToReturnTaskType = _actionLoader.GetMethodDeclarationAction("ChangeMethodToReturnTaskType", "{}"); + var removeMethodParameters = _actionLoader.GetMethodDeclarationAction("RemoveMethodParameters", null); + var addParametersToMethod = _actionLoader.GetMethodDeclarationAction("AddParametersToMethod", "{ types: \"type\", identifiers: \"identifiers\" }"); + var commentMethod = _actionLoader.GetMethodDeclarationAction("CommentMethod", "{ comment: \"comment\", dontUseCTAPrefix: \"true\" }"); + var commentMethod2 = _actionLoader.GetMethodDeclarationAction("CommentMethod", "{ comment: \"comment\" }"); + var addExpressionToMethod = _actionLoader.GetMethodDeclarationAction("AddExpressionToMethod", "expression"); + + Assert.IsNotNull(addComment); + Assert.IsNotNull(addComment2); + Assert.IsNotNull(appendExpression); + Assert.IsNotNull(changeMethodName); + Assert.IsNotNull(changeMethodToReturnTaskType); + Assert.IsNotNull(removeMethodParameters); + Assert.IsNotNull(addParametersToMethod); + Assert.IsNotNull(commentMethod); + Assert.IsNotNull(commentMethod2); + Assert.IsNotNull(addExpressionToMethod); + } + + [Test] + public void ObjectCreationExpressionActionsTest() + { + var replaceObjectinitialization = _actionLoader.GetObjectCreationExpressionActions("ReplaceObjectinitialization", "newStatement"); + var replaceObjectWithInvocation = _actionLoader.GetObjectCreationExpressionActions("ReplaceObjectWithInvocation", "{ newExpression: \"expression\", useParameters: \"params\" }"); + var replaceOrAddObjectPropertyIdentifier = _actionLoader.GetObjectCreationExpressionActions("ReplaceOrAddObjectPropertyIdentifier", "{ oldMember: \"old\", newMember: \"new\", newValueIfAdding: \"value\" }"); + var replaceOrAddObjectPropertyIdentifier2 = _actionLoader.GetObjectCreationExpressionActions("ReplaceOrAddObjectPropertyIdentifier", "{ oldMember: \"old\", newMember: \"new\" }"); + var replaceObjectPropertyValue = _actionLoader.GetObjectCreationExpressionActions("ReplaceObjectPropertyValue", "{ oldMember: \"old\", newMember: \"new\" }"); + var addComment = _actionLoader.GetObjectCreationExpressionActions("AddComment", "comment"); + + Assert.IsNotNull(replaceObjectinitialization); + Assert.IsNotNull(replaceObjectWithInvocation); + Assert.IsNotNull(replaceOrAddObjectPropertyIdentifier); + Assert.IsNotNull(replaceOrAddObjectPropertyIdentifier2); + Assert.IsNotNull(replaceObjectPropertyValue); + Assert.IsNotNull(addComment); + } + + [Test] + public void ProjectFileActionsTest() + { + var migrateProjectFile = _actionLoader.GetProjectFileActions("MigrateProjectFile", "string"); + + Assert.IsNotNull(migrateProjectFile); + } + + [Test] + public void ProjectLevelActionsTest() + { + var archiveFiles = _actionLoader.GetProjectLevelActions("ArchiveFiles", "archiveFiles"); + var createNet3FolderHierarchy = _actionLoader.GetProjectLevelActions("CreateNet3FolderHierarchy", "string"); + var createNet5FolderHierarchy = _actionLoader.GetProjectLevelActions("CreateNet5FolderHierarchy", "string"); + var createNet6FolderHierarchy = _actionLoader.GetProjectLevelActions("CreateNet6FolderHierarchy", "string"); + var migrateConfig = _actionLoader.GetProjectLevelActions("MigrateConfig", "string"); + var createMonolithService = _actionLoader.GetProjectLevelActions("CreateMonolithService", "{ namespaceString: \"namespace\", projectName: \"project\" }"); + + Assert.IsNotNull(archiveFiles); + Assert.IsNotNull(createNet3FolderHierarchy); + Assert.IsNotNull(createNet5FolderHierarchy); + Assert.IsNotNull(createNet6FolderHierarchy); + Assert.IsNotNull(migrateConfig); + Assert.IsNotNull(createMonolithService); + } + } +} \ No newline at end of file diff --git a/tst/CTA.Rules.Test/Actions/VisualBasic/AttributeActionsTests.cs b/tst/CTA.Rules.Test/Actions/VisualBasic/AttributeActionsTests.cs new file mode 100644 index 00000000..6856d0b3 --- /dev/null +++ b/tst/CTA.Rules.Test/Actions/VisualBasic/AttributeActionsTests.cs @@ -0,0 +1,56 @@ +using CTA.Rules.Actions.VisualBasic; +using CTA.Rules.Models.Actions.VisualBasic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; +using NUnit.Framework; + +namespace CTA.Rules.Test.Actions.VisualBasic +{ + public class AttributeActionsTests + { + private const string OriginalAttribute = "Serializable"; + private AttributeActions _attributeActions; + private SyntaxGenerator _syntaxGenerator; + private AttributeSyntax _node; + + [SetUp] + public void SetUp() + { + var workspace = new AdhocWorkspace(); + var language = LanguageNames.VisualBasic; + _syntaxGenerator = SyntaxGenerator.GetGenerator(workspace, language); + _attributeActions = new AttributeActions(); + _node = SyntaxFactory.Attribute(SyntaxFactory.IdentifierName(SyntaxFactory.ParseToken(OriginalAttribute))); + } + + [Test] + public void GetChangeAttributeAction_Changes_Attribute_To_Specified_Value() + { + const string newAttributeName = "NewAttribute"; + var changeAttributeFunc = _attributeActions.GetChangeAttributeAction(newAttributeName); + var newNode = changeAttributeFunc(_syntaxGenerator, _node); + + Assert.AreEqual(OriginalAttribute, _node.ToString()); + Assert.AreEqual(newAttributeName, newNode.ToString()); + } + + [Test] + public void AttributeActionComparison() + { + var attributeAction = new AttributeAction() + { + Key = "Test", + Value = "Test2", + AttributeActionFunc = _attributeActions.GetChangeAttributeAction("NewAttribute") + }; + + var cloned = attributeAction.Clone(); + + Assert.True(attributeAction.Equals(cloned)); + cloned.Value = "DifferentValue"; + Assert.False(attributeAction.Equals(cloned)); + } + } +} \ No newline at end of file diff --git a/tst/CTA.Rules.Test/Actions/VisualBasic/AttributeListActionsTests.cs b/tst/CTA.Rules.Test/Actions/VisualBasic/AttributeListActionsTests.cs new file mode 100644 index 00000000..18782439 --- /dev/null +++ b/tst/CTA.Rules.Test/Actions/VisualBasic/AttributeListActionsTests.cs @@ -0,0 +1,56 @@ +using CTA.Rules.Actions.VisualBasic; +using CTA.Rules.Models.Actions.VisualBasic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; +using NUnit.Framework; + +namespace CTA.Rules.Test.Actions.VisualBasic +{ + public class AttributeListActionsTests + { + private AttributeListActions _attributeListActions; + private SyntaxGenerator _syntaxGenerator; + private AttributeListSyntax _node; + + [SetUp] + public void SetUp() + { + var workspace = new AdhocWorkspace(); + var language = LanguageNames.VisualBasic; + _syntaxGenerator = SyntaxGenerator.GetGenerator(workspace, language); + _attributeListActions = new AttributeListActions(); + var seperatedList = SyntaxFactory.SeparatedList(); + seperatedList.Add(SyntaxFactory.Attribute(SyntaxFactory.ParseName("Test"))); + _node = SyntaxFactory.AttributeList(seperatedList); + } + + [Test] + public void AttributeListAddComment() + { + const string comment = "This is a comment"; + var changeAttributeFunc = _attributeListActions.GetAddCommentAction(comment); + var newNode = changeAttributeFunc(_syntaxGenerator, _node); + + StringAssert.Contains(comment, newNode.ToFullString()); + } + + [Test] + public void AttributeListActionComparison() + { + var attributeAction = new AttributeAction() + { + Key = "Test", + Value = "Test2", + AttributeListActionFunc = _attributeListActions.GetAddCommentAction("NewAttribute") + }; + + var cloned = attributeAction.Clone(); + + Assert.True(attributeAction.Equals(cloned)); + cloned.Value = "DifferentValue"; + Assert.False(attributeAction.Equals(cloned)); + } + } +} \ No newline at end of file diff --git a/tst/CTA.Rules.Test/Actions/VisualBasic/ClassActionsTests.cs b/tst/CTA.Rules.Test/Actions/VisualBasic/ClassActionsTests.cs new file mode 100644 index 00000000..095108d3 --- /dev/null +++ b/tst/CTA.Rules.Test/Actions/VisualBasic/ClassActionsTests.cs @@ -0,0 +1,508 @@ +using System; +using CTA.Rules.Actions.VisualBasic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; +using CTA.Rules.Models.Actions.VisualBasic; + +namespace CTA.Rules.Test.Actions.VisualBasic +{ + public class ClassActionsTests + { + private SyntaxGenerator _syntaxGenerator; + private TypeBlockActions _typeBlockActions; + private ClassBlockSyntax _node; + private MethodBlockSyntax _subNode; + private MethodBlockSyntax _functionNode; + + private Dictionary _blockNodes; + + [SetUp] + public void SetUp() + { + + var workspace = new AdhocWorkspace(); + var language = LanguageNames.VisualBasic; + _syntaxGenerator = SyntaxGenerator.GetGenerator(workspace, language); + _typeBlockActions = new TypeBlockActions(); + SyntaxTree tree = VisualBasicSyntaxTree.ParseText(@$"Class MyClass +End Class + +Module MyModule +EndModule +"); + _node = tree.GetRoot() + .DescendantNodes() + .OfType() + .FirstOrDefault(); + + _blockNodes = new Dictionary(); + _blockNodes.Add("class", _node); + _blockNodes.Add("module", tree.GetRoot().DescendantNodes().OfType().FirstOrDefault()); + + _subNode = CreateMethodNode("Invoke", + new List() + { + SyntaxFactory.ParseExecutableStatement(@"' Nothing to see here") + }, + true, + new List() + { + SyntaxKind.PublicKeyword + }); + + _functionNode = CreateMethodNode("TestFunction", + new List() + { + SyntaxFactory.ParseExecutableStatement(@"' Nothing to see here"), + SyntaxFactory.ReturnStatement(SyntaxFactory.ParseExpression("\"hello world\"")) + }, false, + new List() + { + SyntaxKind.PublicKeyword + }, + "String"); + } + + [Test] + [TestCase("class")] + [TestCase("module")] + public void GetAddCommentAction_Adds_Leading_Comment_To_Class_Declaration(string blockType) + { + const string commentToAdd = "This is a comment"; + var addCommentFunc = _typeBlockActions.GetAddCommentAction(commentToAdd); + var newNode = addCommentFunc(_syntaxGenerator, _blockNodes[blockType]); + + var expectedResult = @$"' Added by CTA: {commentToAdd} +"; + Assert.AreEqual(expectedResult, newNode.GetLeadingTrivia().ToFullString()); + } + + [Test] + [TestCase("class")] + [TestCase("module")] + public void GetChangeNameAction_Changes_Class_Name_To_Specified_Value(string blockType) + { + const string newClassName = "NewClassName"; + var changeNameFunc = _typeBlockActions.GetChangeNameAction(newClassName); + var newNode = changeNameFunc(_syntaxGenerator, _blockNodes[blockType]); + Assert.AreEqual(newClassName, newNode.BlockStatement.Identifier.ToString()); + } + + [Test] + [TestCase("class")] + [TestCase("module")] + public void GetRenameClassAction_Changes_Class_Name_To_Specified_Value(string blockType) + { + const string newClassName = "NewClassName"; + var changeNameFunc = _typeBlockActions.GetRenameClassAction(newClassName); + var newNode = changeNameFunc(_syntaxGenerator, _blockNodes[blockType]); + Assert.AreEqual(newClassName, newNode.BlockStatement.Identifier.ToString()); + } + + [Test] + [TestCase("class")] + [TestCase("module")] + public void GetRemoveAttributeAction_Removes_Specified_Attribute(string blockType) + { + const string attributeToRemove = "Serializable"; + var node = _blockNodes[blockType]; + var addAttributeFunc1 = _typeBlockActions.GetAddAttributeAction("Serializable"); + var addAttributeFunc2 = _typeBlockActions.GetAddAttributeAction("SecurityCritical"); + + var nodeWithAttributes = addAttributeFunc1(_syntaxGenerator, node); + nodeWithAttributes = addAttributeFunc2(_syntaxGenerator, nodeWithAttributes); + + var removeAttributeFunc = _typeBlockActions.GetRemoveAttributeAction(attributeToRemove); + var newNode = removeAttributeFunc(_syntaxGenerator, nodeWithAttributes); + Assert.IsTrue(newNode.BlockStatement.AttributeLists.ToFullString().Contains("")); + Assert.IsTrue(!newNode.BlockStatement.AttributeLists.ToFullString().Contains("", newNode.BlockStatement.AttributeLists.ToString()); + } + + [Test] + [TestCase(@"Public Sub MySub(i as Integer) + Console.WriteLine(i) + End Sub", "class")] + [TestCase(@"Public Function MyFunction(i as Integer) As Integer + Console.WriteLine(i) + Return i + End Function", "class")] + [TestCase(@"Public Sub MySub(i as Integer) + Console.WriteLine(i) + End Sub", "module")] + [TestCase(@"Public Function MyFunction(i as Integer) As Integer + Console.WriteLine(i) + Return i + End Function", "module")] + public void GetAddMethodAction_Adds_Method(string expression, string blockType) + { + var addMethodFunc = _typeBlockActions.GetAddMethodAction(expression); + var newNode = addMethodFunc(_syntaxGenerator, _blockNodes[blockType]); + Assert.AreEqual(expression, newNode.Members.OfType().FirstOrDefault()?.ToString()); + } + + [Test] + [TestCase("class")] + [TestCase("module")] + public void GetRemoveMethodAction_Removes_Specified_Method(string blockType) + { + const string methodName = "MyMethod"; + var methodNode = SyntaxFactory.MethodStatement(SyntaxKind.SubStatement, + SyntaxFactory.Token(SyntaxKind.SubKeyword), methodName); + var nodeWithMethod = _blockNodes[blockType].AddMembers(methodNode); + + var removeMethodFunc = _typeBlockActions.GetRemoveMethodAction(methodName); + var newNode = removeMethodFunc(_syntaxGenerator, nodeWithMethod); + + var expectedResult = _blockNodes[blockType].NormalizeWhitespace().ToFullString(); + Assert.AreEqual(expectedResult, newNode.ToFullString()); + } + + [Test] + [TestCase("class")] + [TestCase("module")] + public void RemoveLastBaseClass(string blockType) + { + var baseClassname = "ControllerBase"; + var addBaseClass = _typeBlockActions.GetAddBaseClassAction(baseClassname); + var removeBaseClassMethod = _typeBlockActions.GetRemoveBaseClassAction(baseClassname); + + var nodeWithClass = addBaseClass(_syntaxGenerator, _blockNodes[blockType]); + nodeWithClass = removeBaseClassMethod(_syntaxGenerator, nodeWithClass); + + //Make sure the inheritance symbol is removed when last base class is removed: + StringAssert.DoesNotContain(":", nodeWithClass.ToFullString()); + } + + [Test] + [TestCase("class")] + [TestCase("module")] + public void ReplaceMethodModifiers(string blockType) + { + const string methodName = "MyMethod"; + var methodNode = SyntaxFactory.MethodStatement(SyntaxKind.SubStatement, + SyntaxFactory.Token(SyntaxKind.SubKeyword), methodName); + var nodeWithMethod = _blockNodes[blockType].AddMembers(methodNode); + + var modifier = "Private Async"; + var replaceModifier = _typeBlockActions.GetReplaceMethodModifiersAction(methodName, modifier); + + var node = replaceModifier(_syntaxGenerator, nodeWithMethod); + + StringAssert.Contains(modifier, node.ToFullString()); + } + + [Test] + [TestCase("class")] + [TestCase("module")] + public void AddExpression(string blockType) + { + string expression = "Dim _next As RequestDelegate"; + + var addBaseClass = _typeBlockActions.GetAddExpressionAction(expression); + + var nodeWithExpression = addBaseClass(_syntaxGenerator, _blockNodes[blockType]); + + StringAssert.Contains(expression, nodeWithExpression.ToFullString()); + } + + [Test] + [TestCase("class")] + [TestCase("module")] + public void AppendConstructorExpression(string blockType) + { + var constructorStatements = new SyntaxList(); + constructorStatements = constructorStatements.Add( + SyntaxFactory.ExpressionStatement(SyntaxFactory.ParseExpression("_breadcrumb = breadcrumb"))); + var methodNode = SyntaxFactory.ConstructorBlock( + SyntaxFactory.SubNewStatement(), + constructorStatements, + SyntaxFactory.EndSubStatement()); + var nodeWithMethod = _blockNodes[blockType].AddMembers(methodNode); + string expression = "_next = [next]"; + + var addBaseClass = _typeBlockActions.GetAppendConstructorExpressionAction(expression); + + var nodeWithExpression = addBaseClass(_syntaxGenerator, nodeWithMethod); + + StringAssert.Contains(expression, nodeWithExpression.ToFullString()); + } + + [Test] + [TestCase("class")] + [TestCase("module")] + public void RemoveConstructorBaseInitializer(string blockType) + { + var constructorStatements = new SyntaxList(); + constructorStatements = constructorStatements.Add( + SyntaxFactory.ExpressionStatement(SyntaxFactory.ParseExpression("MyBase.New(next, testing)"))); + constructorStatements = constructorStatements.Add( + SyntaxFactory.ExpressionStatement(SyntaxFactory.ParseExpression("_breadcrumb = breadcrumb"))); + + var methodNode = SyntaxFactory.ConstructorBlock( + SyntaxFactory.SubNewStatement(), + constructorStatements, + SyntaxFactory.EndSubStatement()); + + var nodeWithMethod = _blockNodes[blockType].AddMembers(methodNode); + string expression = "next"; + + var addBaseClass = _typeBlockActions.GetRemoveConstructorInitializerAction(expression); + + var nodeWithExpression = addBaseClass(_syntaxGenerator, nodeWithMethod); + + StringAssert.DoesNotContain(expression, nodeWithExpression.ToFullString()); + } + + [Test] + [TestCase("class")] + [TestCase("module")] + public void CreateConstructor(string blockType) + { + string types = "RequestDelegate, string"; + string identifiers = "[next], value"; + + var createConstructorFunc = _typeBlockActions.GetCreateConstructorAction(types: types, identifiers: identifiers); + var nodeWithExpression = createConstructorFunc(_syntaxGenerator, _blockNodes[blockType]); + + StringAssert.Contains(types.Split(',')[0], nodeWithExpression.ToFullString()); + StringAssert.Contains(identifiers.Split(',')[0], nodeWithExpression.ToFullString()); + } + + + [Test] + [TestCase(true, "class")] + [TestCase(false, "module")] + [TestCase(true, "module")] + [TestCase(false, "class")] + public void ChangeMethodName(bool isSubMethod, string blockType) + { + string existingMethodName = "ProcessRequest"; + string newMethodName = "Invoke"; + + MethodBlockSyntax methodNode; + if (isSubMethod) + { + methodNode = SyntaxFactory.MethodBlock(SyntaxKind.SubBlock, + SyntaxFactory.MethodStatement(SyntaxKind.SubStatement, SyntaxFactory.Token(SyntaxKind.SubKeyword), + existingMethodName), + SyntaxFactory.EndSubStatement()); + } + else + { + methodNode = SyntaxFactory.MethodBlock(SyntaxKind.FunctionBlock, + SyntaxFactory.MethodStatement(SyntaxKind.FunctionStatement, + SyntaxFactory.Token(SyntaxKind.FunctionKeyword), + existingMethodName), + SyntaxFactory.EndFunctionStatement()); + } + + var nodeWithMethod = _blockNodes[blockType].AddMembers(methodNode); + + var changeMethodNameFunc = _typeBlockActions.GetChangeMethodNameAction(existingMethodName, newMethodName); + var nodeWithExpression = changeMethodNameFunc(_syntaxGenerator, nodeWithMethod); + + StringAssert.Contains(newMethodName, nodeWithExpression.ToFullString()); + } + + [Test] + [TestCase("class")] + [TestCase("module")] + public void RemoveMethodParameters(string blockType) + { + var parameters = new List + { + SyntaxFactory.Parameter(SyntaxFactory.ModifiedIdentifier("context")) + .WithAsClause(SyntaxFactory.SimpleAsClause(SyntaxFactory.ParseTypeName("HttpContext"))), + SyntaxFactory.Parameter(SyntaxFactory.ModifiedIdentifier("value")) + .WithAsClause(SyntaxFactory.SimpleAsClause(SyntaxFactory.ParseTypeName("string"))) + }; + var methodName = "Invoke"; + var methodNode = _subNode.WithSubOrFunctionStatement( + _subNode.SubOrFunctionStatement.WithParameterList( + SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(parameters)))); + var nodeWithMethod = _blockNodes[blockType].AddMembers(methodNode); + var removeMethodParametersFunc = _typeBlockActions.GetRemoveMethodParametersAction(methodName); + var nodeWithExpression = removeMethodParametersFunc(_syntaxGenerator, nodeWithMethod); + StringAssert.DoesNotContain("HttpContext", nodeWithExpression.ToFullString()); + } + + [Test] + [TestCase("class")] + [TestCase("module")] + public void ChangeMethodReturnTaskTypeVoid(string blockType) + { + string methodName = "Invoke"; + var nodeWithMethod = _blockNodes[blockType].AddMembers(_subNode); + var changeMethodToReturnTaskTypeFunc = _typeBlockActions.GetChangeMethodToReturnTaskTypeAction(methodName); + var nodeWithExpression = changeMethodToReturnTaskTypeFunc(_syntaxGenerator, nodeWithMethod); + StringAssert.Contains("Task", nodeWithExpression.ToFullString()); + } + + [Test] + [TestCase("class")] + [TestCase("module")] + public void ChangeMethodReturnTaskTypeString(string blockType) + { + string methodName = "TestFunction"; + var nodeWithMethod = _blockNodes[blockType].AddMembers(_functionNode); + var changeMethodToReturnTaskTypeFunc = _typeBlockActions.GetChangeMethodToReturnTaskTypeAction(methodName); + var nodeWithExpression = changeMethodToReturnTaskTypeFunc(_syntaxGenerator, nodeWithMethod); + StringAssert.Contains("Task(Of String)", nodeWithExpression.ToFullString()); + } + + [Test] + [TestCase("class")] + [TestCase("module")] + public void CommentMethod(string blockType) + { + string methodName = "Invoke"; + var nodeWithMethod = _blockNodes[blockType].AddMembers(_subNode); + + var commentMethodeFunc = _typeBlockActions.GetCommentMethodAction(methodName); + var nodeWithExpression = commentMethodeFunc(_syntaxGenerator, nodeWithMethod); + + StringAssert.Contains("' Public Sub Invoke", nodeWithExpression.ToFullString()); + } + + [Test] + [TestCase("class")] + [TestCase("module")] + public void AddCommentsToMethod(string blockType) + { + string methodName = "Invoke"; + string comment = "This method is deprecated"; + var nodeWithMethod = _blockNodes[blockType].AddMembers(_subNode); + + var addCommentsToMethodFunc = _typeBlockActions.GetAddCommentsToMethodAction(methodName, comment); + var nodeWithExpression = addCommentsToMethodFunc(_syntaxGenerator, nodeWithMethod); + + StringAssert.Contains("' Added by CTA: This method is deprecated", nodeWithExpression.ToFullString()); + } + + [Test] + [TestCase("class")] + [TestCase("module")] + public void AddExpressionToMethod(string blockType) + { + string methodName = "TestFunction"; + string expression = "Await _next.Invoke(context)"; + var nodeWithMethod = _blockNodes[blockType].AddMembers(_functionNode); + var changeMethodToReturnTaskTypeFunc = _typeBlockActions.GetChangeMethodToReturnTaskTypeAction(methodName); + var nodeWithExpression = changeMethodToReturnTaskTypeFunc(_syntaxGenerator, nodeWithMethod); + + var addExpressionToMethodFunc = _typeBlockActions.GetAddExpressionToMethodAction(methodName, expression); + nodeWithExpression = addExpressionToMethodFunc(_syntaxGenerator, nodeWithExpression); + + StringAssert.Contains("Await _next.Invoke(context)", nodeWithExpression.ToFullString()); + } + + [Test] + [TestCase("class")] + [TestCase("module")] + public void AddParametersToMethod(string blockType) + { + string methodName = "Invoke"; + string types = "HttpContext,String"; + string identifiers = "context,value"; + + var nodeWithMethod = _blockNodes[blockType].AddMembers(_subNode); + + var addParametersToMethodFunc = + _typeBlockActions.GetAddParametersToMethodAction(methodName, types, identifiers); + var nodeWithExpression = addParametersToMethodFunc(_syntaxGenerator, nodeWithMethod); + + var expectedString = @"Public Sub Invoke(context As HttpContext, value As String)"; + StringAssert.Contains(expectedString, nodeWithExpression.ToFullString()); + } + + [Test] + [TestCase("WebApiController")] + [TestCase("CoreController")] + public void ReplacePublicMethodsBody(string controller) + { + string newBody = "Return Content(MonolithService.CreateRequest())"; + var nodeWithMethods = _node.AddMembers(CreateMethodNode("SuperStringAsyncMethod", + new List() + { + SyntaxFactory.ParseExecutableStatement(@"Dim hello = ""hello world""") + }, + false, new List() { SyntaxKind.PublicKeyword, SyntaxKind.AsyncKeyword }, + "Task(Of String)")); + nodeWithMethods = nodeWithMethods.AddMembers(CreateMethodNode("SuperStringMethod", + new List() + { + SyntaxFactory.ParseExecutableStatement(@"Dim hello = ""hello world again?!""") + }, + false, new List() { SyntaxKind.PublicKeyword }, + "String")); + nodeWithMethods = nodeWithMethods.AddMembers(CreateMethodNode("SuperStringAsyncMethod", + new List() + { + SyntaxFactory.ParseExecutableStatement(@"Dim hello = ""not hello world!"""), + }, + false, new List() { SyntaxKind.PrivateKeyword}, + "String")); + + var replacePublicMethodsBodyFunc = controller == "WebApiController" + ? _typeBlockActions.GetReplaceWebApiControllerMethodsBodyAction(newBody) + : _typeBlockActions.GetReplaceCoreControllerMethodsBodyAction(newBody); + var newNode = replacePublicMethodsBodyFunc(_syntaxGenerator, nodeWithMethods.NormalizeWhitespace()); + + var publicMembers = newNode.Members.OfType().Where(m => + m.Modifiers.Any(modifier => modifier.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.PublicKeyword))); + var privateMembers = newNode.Members.OfType().Where(m => + m.Modifiers.Any(modifier => modifier.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.PrivateKeyword))); + + Assert.IsTrue(publicMembers.All(m => m.ToFullString().Contains($"'Added by CTA: Replace method body with {newBody}"))); + Assert.IsTrue(privateMembers.All(m => !m.ToFullString().Contains($"'Added by CTA: Replace method body with {newBody}"))); + } + + [Test] + public void ClassDeclarationEquals() + { + var classAction = new TypeBlockAction() { Key = "Test", Value = "Test2", TypeBlockActionFunc = _typeBlockActions.GetAddAttributeAction("Test") }; + var cloned = classAction.Clone(); + Assert.True(classAction.Equals(cloned)); + + cloned.Value = "DifferentValue"; + Assert.False(classAction.Equals(cloned)); + } + + private MethodBlockSyntax CreateMethodNode(string identifier, + List bodyExpressions, + bool isSub, + List modifiers, + string returnType = "") + { + var body = new SyntaxList(bodyExpressions); + var modifiersSyntax = new SyntaxTokenList(modifiers.Select(m => SyntaxFactory.Token(m))); + var blockKind = isSub ? SyntaxKind.SubBlock: SyntaxKind.FunctionBlock; + var statementKind = isSub ? SyntaxKind.SubStatement : SyntaxKind.FunctionStatement; + var keyword = isSub? SyntaxKind.SubKeyword : SyntaxKind.FunctionKeyword; + var endStatement = isSub ? SyntaxFactory.EndSubStatement() : SyntaxFactory.EndFunctionStatement(); + + return SyntaxFactory.MethodBlock(blockKind, + SyntaxFactory.MethodStatement(statementKind, + SyntaxFactory.Token(keyword), identifier) + .WithModifiers(modifiersSyntax) + .WithAsClause(SyntaxFactory.SimpleAsClause(SyntaxFactory.ParseTypeName(returnType))), + endStatement).WithStatements(body).NormalizeWhitespace(); + } + } +} \ No newline at end of file diff --git a/tst/CTA.Rules.Test/Actions/VisualBasic/CompilationUnitActionsTests.cs b/tst/CTA.Rules.Test/Actions/VisualBasic/CompilationUnitActionsTests.cs new file mode 100644 index 00000000..8f847ee9 --- /dev/null +++ b/tst/CTA.Rules.Test/Actions/VisualBasic/CompilationUnitActionsTests.cs @@ -0,0 +1,77 @@ +using CTA.Rules.Actions.VisualBasic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; +using NUnit.Framework; +using VisualBasicSyntaxTree = Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxTree; + + +namespace CTA.Rules.Test.Actions.VisualBasic +{ + public class CompilationUnitActionsTests + { + private SyntaxGenerator _syntaxGenerator; + private CompilationUnitActions _compilationUnitActions; + private CompilationUnitSyntax _node; + + [SetUp] + public void SetUp() + { + var workspace = new AdhocWorkspace(); + var language = LanguageNames.VisualBasic; + _syntaxGenerator = SyntaxGenerator.GetGenerator(workspace, language); + _compilationUnitActions = new CompilationUnitActions(); + + SyntaxTree tree = VisualBasicSyntaxTree.ParseText(@$"Imports System.Web +Class MyClass +End Class"); + _node = tree.GetCompilationUnitRoot(); + } + + [Test] + public void GetAddDirectiveAction_Adds_Directive() + { + const string directive = "System.Collections.Generic"; + var addStatementAction = _compilationUnitActions.GetAddStatementAction(directive); + var newNode = addStatementAction(_syntaxGenerator, _node); + + var expectedResult = @$"Imports System.Web +Imports {directive} + +Class MyClass +End Class +"; + Assert.AreEqual(expectedResult, newNode.ToFullString()); + } + + [Test] + public void GetRemoveDirectiveAction_Removes_Directive() + { + const string directive = "System.Web"; + var removeStatementAction = _compilationUnitActions.GetRemoveStatementAction(directive); + var newNode = removeStatementAction(_syntaxGenerator, _node); + + var expectedResult = @$"Class MyClass +End Class +"; + Assert.AreEqual(expectedResult, newNode.ToFullString()); + } + + [Test] + public void GetAddCommentAction_Adds_Comment_Above_Directive() + { + const string commentToAdd = "This is a comment"; + var addCommentFunc = _compilationUnitActions.GetAddCommentAction(commentToAdd); + var newNode = addCommentFunc(_syntaxGenerator, _node); + + var expectedResult = @$"' Added by CTA: {commentToAdd} +Imports System.Web + +Class MyClass +End Class +"; + Assert.AreEqual(expectedResult, newNode.ToFullString()); + } + } +} diff --git a/tst/CTA.Rules.Test/Actions/VisualBasic/ElementAccessActionsTests.cs b/tst/CTA.Rules.Test/Actions/VisualBasic/ElementAccessActionsTests.cs new file mode 100644 index 00000000..6070793c --- /dev/null +++ b/tst/CTA.Rules.Test/Actions/VisualBasic/ElementAccessActionsTests.cs @@ -0,0 +1,65 @@ +using CTA.Rules.Models.Actions.VisualBasic; +using CTA.Rules.Update.VisualBasic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; +using NUnit.Framework; + +namespace CTA.Rules.Test.Actions.VisualBasic +{ + public class ElementAccessActionsTests + { + private ElementAccessActions _elementAccessActions; + private SyntaxGenerator _syntaxGenerator; + private MemberAccessExpressionSyntax _node; + + [SetUp] + public void SetUp() + { + var workspace = new AdhocWorkspace(); + var language = LanguageNames.VisualBasic; + _syntaxGenerator = SyntaxGenerator.GetGenerator(workspace, language); + _elementAccessActions = new ElementAccessActions(); + _node = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.ParseExpression("Expression"), SyntaxFactory.Token(SyntaxKind.DotToken), + (SimpleNameSyntax)SyntaxFactory.ParseName("ElementAccess")); + } + + [Test] + public void ElementAccessAddComment() + { + const string comment = "This is a comment"; + var addCommentFunc = _elementAccessActions.GetAddCommentAction(comment); + var newNode = addCommentFunc(_syntaxGenerator, _node); + StringAssert.Contains(comment, newNode.ToFullString()); + } + + [Test] + public void ReplaceElementAccess() + { + const string expression = "ConfigurationManager.Configuration.GetSection(\"ConnectionStrings\")"; + var replaceElementAccessFunc = _elementAccessActions.GetReplaceElementAccessAction(expression); + var newNode = replaceElementAccessFunc(_syntaxGenerator, _node); + StringAssert.Contains($"' Added by CTA: Replace with {expression}", newNode.ToFullString()); + } + + [Test] + public void ElementAccessActionComparison() + { + var elementAccessAction = new ElementAccessAction() + { + Key = "Test", + Value = "Test2", + ElementAccessExpressionActionFunc = _elementAccessActions.GetAddCommentAction("Test") + + }; + + var cloned = elementAccessAction.Clone(); + + Assert.True(elementAccessAction.Equals(cloned)); + cloned.Value = "DifferentValue"; + Assert.False(elementAccessAction.Equals(cloned)); + } + } +} \ No newline at end of file diff --git a/tst/CTA.Rules.Test/Actions/VisualBasic/ExpressionActionsTests.cs b/tst/CTA.Rules.Test/Actions/VisualBasic/ExpressionActionsTests.cs new file mode 100644 index 00000000..cb6abb2a --- /dev/null +++ b/tst/CTA.Rules.Test/Actions/VisualBasic/ExpressionActionsTests.cs @@ -0,0 +1,90 @@ +using CTA.Rules.Actions.VisualBasic; +using CTA.Rules.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; +using NUnit.Framework; + +namespace CTA.Rules.Test.Actions.VisualBasic +{ + public class ExpressionActionsTests + { + private SyntaxGenerator _syntaxGenerator; + private ExpressionActions _expressionActions; + private ExpressionStatementSyntax _node; + + [SetUp] + public void SetUp() + { + var workspace = new AdhocWorkspace(); + var language = LanguageNames.VisualBasic; + _syntaxGenerator = SyntaxGenerator.GetGenerator(workspace, language); + _expressionActions = new ExpressionActions(); + _node = SyntaxFactory.ExpressionStatement(SyntaxFactory.ParseExpression("Math.Abs(-1)") + .WithLeadingTrivia(SyntaxFactory.CommentTrivia("' Super Comment"))); + } + + [Test] + public void GetAddAwaitOperatorAction() + { + var addAwaitFunc = + _expressionActions.GetAddAwaitOperatorAction(""); + var newNode = addAwaitFunc(_syntaxGenerator, _node); + + var expectedResult = "' Super Comment\r\nAwait Math.Abs(-1)"; + Assert.AreEqual(expectedResult, newNode.ToFullString()); + } + + + [Test] + public void ExpressionActionAddComment() + { + var comment = "Super comment"; + var expressionAction = SyntaxFactory.ExpressionStatement(SyntaxFactory.ParseExpression("var t = 1+5")); + + var addCommentFunc = _expressionActions.GetAddCommentAction(comment); + var newNode = addCommentFunc(_syntaxGenerator, expressionAction); + + var expectedResult = @"' Added by CTA: Super comment +var t = 1 + 5"; + Assert.AreEqual(expectedResult, newNode.ToFullString()); + } + + [Test] + public void ObjectCreationAddComment() + { + var comment = "Super comment"; + var objectNode = _syntaxGenerator.ObjectCreationExpression(SyntaxFactory.ParseTypeName("StringBuilder")) + .NormalizeWhitespace() as ObjectCreationExpressionSyntax; + + objectNode = + objectNode.AddArgumentListArguments( + SyntaxFactory.SimpleArgument(SyntaxFactory.ParseExpression(@"""SomeText"""))); + + var addCommentFunc = _expressionActions.GetAddCommentAction(comment); + var newNode = addCommentFunc(_syntaxGenerator, objectNode); + + var expectedResult = @"' Added by CTA: Super comment +New StringBuilder(""SomeText"")"; + Assert.AreEqual(expectedResult, newNode.ToFullString()); + } + + [Test] + public void InvocationExpressionAddComment() + { + var comment = "Super comment"; + var invocationNode = + SyntaxFactory.ParseExpression("Math.Abs(-1)") + .WithLeadingTrivia(SyntaxFactory.CommentTrivia("' Comment")) as InvocationExpressionSyntax; + + var addCommentFunc = _expressionActions.GetAddCommentAction(comment); + var newNode = addCommentFunc(_syntaxGenerator, invocationNode); + + var expectedResult = @"' Comment +' Added by CTA: Super comment +Math.Abs(-1)"; + Assert.AreEqual(expectedResult, newNode.ToFullString()); + } + } +} \ No newline at end of file diff --git a/tst/CTA.Rules.Test/Actions/VisualBasic/IdentifierNameActionsTests.cs b/tst/CTA.Rules.Test/Actions/VisualBasic/IdentifierNameActionsTests.cs new file mode 100644 index 00000000..3d5b53b7 --- /dev/null +++ b/tst/CTA.Rules.Test/Actions/VisualBasic/IdentifierNameActionsTests.cs @@ -0,0 +1,71 @@ +using CTA.Rules.Actions.VisualBasic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; + +namespace CTA.Rules.Test.Actions.VisualBasic +{ + public class IdentifierNameActionsTests + { + private SyntaxGenerator _syntaxGenerator; + private IdentifierNameActions _identifierNameActions; + private IdentifierNameSyntax _node; + + [SetUp] + public void SetUp() + { + var workspace = new AdhocWorkspace(); + var language = LanguageNames.VisualBasic; + _syntaxGenerator = SyntaxGenerator.GetGenerator(workspace, language); + _identifierNameActions = new IdentifierNameActions(); + _node = (IdentifierNameSyntax)_syntaxGenerator.IdentifierName("MusicStoreEntities"); + } + + [Test] + public void ReplaceIdentifierInsideClassActionTest() + { + string newIdentifier = "SuperAwesomeClass.SomethingElse"; + string originalIdentifier = "SuperAwesomeClass.MusicStoreEntities"; + string namespaceName = "MvcMusicStore.Controllers"; + string className = "ShoppingCartController"; + SyntaxNode customNode = _syntaxGenerator.NamespaceDeclaration(namespaceName, + _syntaxGenerator.ClassDeclaration(className, null, Accessibility.NotApplicable, + DeclarationModifiers.None, _syntaxGenerator.IdentifierName("Controller"), null, + new List() + { + _syntaxGenerator.FieldDeclaration("storeDB", + _syntaxGenerator.IdentifierName(originalIdentifier), Accessibility.Public, + DeclarationModifiers.None, + _syntaxGenerator.ObjectCreationExpression(SyntaxFactory.ParseTypeName(originalIdentifier))) + })).NormalizeWhitespace(); + + var replaceIdentifierFunc = + _identifierNameActions.GetReplaceIdentifierInsideClassAction(newIdentifier, + namespaceName + "." + className); + var variableDeclaration = (FieldDeclarationSyntax)customNode.ChildNodes() + .FirstOrDefault(c => c.IsKind(SyntaxKind.ClassBlock)).ChildNodes() + .FirstOrDefault(f => f.IsKind(SyntaxKind.FieldDeclaration)); + + var newNode = replaceIdentifierFunc(_syntaxGenerator, + (IdentifierNameSyntax)variableDeclaration.Declarators.First().AsClause.ChildNodes() + .FirstOrDefault(c => c.IsKind(SyntaxKind.IdentifierName))); + + Assert.AreEqual(newIdentifier, newNode.ToFullString().Trim()); + } + + [Test] + public void ReplaceIdentifierActionTest() + { + string newIdentifier = "SomethingElse"; + var removeAttributeFunc = + _identifierNameActions.GetReplaceIdentifierAction(newIdentifier); + var newNode = removeAttributeFunc(_syntaxGenerator, _node); + + Assert.AreEqual(newIdentifier, newNode.ToFullString()); + } + } +} diff --git a/tst/CTA.Rules.Test/Actions/VisualBasic/InterfaceActionsTests.cs b/tst/CTA.Rules.Test/Actions/VisualBasic/InterfaceActionsTests.cs new file mode 100644 index 00000000..a9881bef --- /dev/null +++ b/tst/CTA.Rules.Test/Actions/VisualBasic/InterfaceActionsTests.cs @@ -0,0 +1,104 @@ +using CTA.Rules.Actions.VisualBasic; +using CTA.Rules.Models.Actions.VisualBasic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; +using NUnit.Framework; + +namespace CTA.Rules.Test.Actions.VisualBasic +{ + public class InterfaceActionsTests + { + private SyntaxGenerator _syntaxGenerator; + private InterfaceActions _interfaceActions; + private InterfaceBlockSyntax _node; + + [SetUp] + public void SetUp() + { + var workspace = new AdhocWorkspace(); + var language = LanguageNames.VisualBasic; + _syntaxGenerator = SyntaxGenerator.GetGenerator(workspace, language); + _interfaceActions = new InterfaceActions(); + _node = _syntaxGenerator.InterfaceDeclaration("ISomeInterface") + .NormalizeWhitespace() as InterfaceBlockSyntax; + } + + [Test] + public void ChangeName() + { + string newName = "INewInterface"; + var changeNameFunc = _interfaceActions.GetChangeNameAction(newName); + var newNode = changeNameFunc(_syntaxGenerator, _node); + StringAssert.Contains(newName, newNode.ToFullString()); + } + + [Test] + public void GetAddRemoveAttributeAction() + { + var addAttributeFunc = _interfaceActions.GetAddAttributeAction("SomeAttribute"); + var nodeWithAttribute = addAttributeFunc(_syntaxGenerator, _node); + + StringAssert.Contains("SomeAttribute", nodeWithAttribute.ToFullString()); + + var removeAttributeFunc = + _interfaceActions.GetRemoveAttributeAction("SomeAttribute"); + var newNode = removeAttributeFunc(_syntaxGenerator, nodeWithAttribute); + + var expectedResult = _node.ToFullString(); + Assert.AreEqual(expectedResult, newNode.ToFullString()); + } + + [Test] + public void GetAddCommentAction_Appends_Comment_To_Interface_Declaration() + { + const string commentToAdd = "This is a comment"; + var addCommentFunc = _interfaceActions.GetAddCommentAction(commentToAdd); + var newNode = addCommentFunc(_syntaxGenerator, _node); + + var expectedResult = @$"' Added by CTA: {commentToAdd} +Interface ISomeInterface +End Interface"; + Assert.AreEqual(expectedResult, newNode.ToFullString()); + } + + [Test] + public void AddAndRemoveMethod() + { + string newMethod = @"Public Function NewMethod As String"; + var addMethodFunc = _interfaceActions.GetAddMethodAction(newMethod); + var newNode = addMethodFunc(_syntaxGenerator, _node); + + var addSubFunc = _interfaceActions.GetAddMethodAction("Public Sub NewSub As Integer"); + + Assert.IsTrue(newNode.Members.Count == 1); + Assert.IsTrue(newNode.ToFullString().Contains("NewMethod")); + + newNode = addSubFunc(_syntaxGenerator, newNode); + Assert.IsTrue(newNode.Members.Count == 2); + + var removeMethodFunc = _interfaceActions.GetRemoveMethodAction("NewMethod"); + newNode = removeMethodFunc(_syntaxGenerator, newNode); + + Assert.IsTrue(newNode.Members.Count == 1); + Assert.IsTrue(!newNode.ToFullString().Contains("NewMethod")); + Assert.IsTrue(newNode.ToFullString().Contains("NewSub")); + } + + [Test] + public void InterfaceBlockEquals() + { + var interfaceAction = new InterfaceBlockAction() + { + Key = "Test", + Value = "Test2", + InterfaceBlockActionFunc = _interfaceActions.GetAddAttributeAction("Test") + }; + var cloned = interfaceAction.Clone(); + Assert.True(interfaceAction.Equals(cloned)); + + cloned.Value = "DifferentValue"; + Assert.False(interfaceAction.Equals(cloned)); + } + } +} \ No newline at end of file diff --git a/tst/CTA.Rules.Test/Actions/VisualBasic/InvocationExpressionActionsTests.cs b/tst/CTA.Rules.Test/Actions/VisualBasic/InvocationExpressionActionsTests.cs new file mode 100644 index 00000000..1f5f3efb --- /dev/null +++ b/tst/CTA.Rules.Test/Actions/VisualBasic/InvocationExpressionActionsTests.cs @@ -0,0 +1,126 @@ +using CTA.Rules.Actions.VisualBasic; +using CTA.Rules.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; +using NUnit.Framework; + +namespace CTA.Rules.Test.Actions.VisualBasic +{ + public class VisualBasicInvocationExpressionActionsTests + { + private SyntaxGenerator _syntaxGenerator; + private InvocationExpressionActions _invocationExpressionActions; + private InvocationExpressionSyntax _node; + + [SetUp] + public void SetUp() + { + var workspace = new AdhocWorkspace(); + var language = LanguageNames.VisualBasic; + _syntaxGenerator = SyntaxGenerator.GetGenerator(workspace, language); + _invocationExpressionActions = new InvocationExpressionActions(); + _node = SyntaxFactory.ParseExpression("' Comment \nMath.Abs(-1)") as InvocationExpressionSyntax; + } + + [Test] + public void GetReplaceMethodWithObjectAndParametersAction() + { + const string newMethod = "Math.Floor"; + const string newParameter = "(-2)"; + var replaceMethodFunc = + _invocationExpressionActions.GetReplaceMethodWithObjectAndParametersAction(newMethod, newParameter); + var newNode = replaceMethodFunc(_syntaxGenerator, _node); + + var expectedResult = "' Comment \r\nMath.Floor(-2)"; + Assert.AreEqual(expectedResult, newNode.ToFullString()); + } + + [Test] + public void GetReplaceMethodWithObjectAction() + { + const string newMethod = "Math.Floor"; + var replaceMethodFunc = + _invocationExpressionActions.GetReplaceMethodWithObjectAction(newMethod); + var newNode = replaceMethodFunc(_syntaxGenerator, _node); + + var expectedResult = "' Comment \r\nMath.Floor(-1)"; + Assert.AreEqual(expectedResult, newNode.ToFullString()); + } + + [Test] + public void GetReplaceMethodWithObjectAddTypeAction() + { + _node = SyntaxFactory.ParseExpression("' Comment \r\nDependencyResolver.Current.GetService(Of Object)()") as InvocationExpressionSyntax; + const string newMethod = "DependencyResolver.Current.GetService"; + var replaceMethodFunc = + _invocationExpressionActions.GetReplaceMethodWithObjectAddTypeAction(newMethod); + var newNode = replaceMethodFunc(_syntaxGenerator, _node); + + var expectedResult = "' Comment \r\nDependencyResolver.Current.GetService(TypeOf Object)"; + Assert.AreEqual(expectedResult, newNode.ToFullString()); + } + + [Test] + public void GetReplaceMethodAndParametersAction() + { + const string newMethod = "Floor"; + const string newParameter = "(-2)"; + var replaceMethodFunc = + _invocationExpressionActions.GetReplaceMethodAndParametersAction("Abs", newMethod, newParameter); + var newNode = replaceMethodFunc(_syntaxGenerator, _node); + + var expectedResult = "' Comment \r\nMath.Floor(-2)"; + Assert.AreEqual(expectedResult, newNode.ToFullString()); + } + + [Test] + public void GetReplaceMethodOnlyAction() + { + const string newMethod = "Floor"; + var replaceMethodFunc = + _invocationExpressionActions.GetReplaceMethodOnlyAction("Abs",newMethod); + var newNode = replaceMethodFunc(_syntaxGenerator, _node); + + var expectedResult = "' Comment \r\nMath.Floor(-1)"; + Assert.AreEqual(expectedResult, newNode.ToFullString()); + } + + [Test] + public void GetReplaceParameterOnlyAction() + { + const string newParam = "(8)"; + var replaceMethodFunc = + _invocationExpressionActions.GetReplaceParametersOnlyAction(newParam); + var newNode = replaceMethodFunc(_syntaxGenerator, _node); + + var expectedResult = "' Comment \r\nMath.Abs(8)"; + Assert.AreEqual(expectedResult, newNode.ToFullString()); + } + + [Test] + public void GetAppendMethodAction_Appends_A_Method_Invocation() + { + const string invocationToAppend = "ToString()"; + var appendMethodFunc = + _invocationExpressionActions.GetAppendMethodAction(invocationToAppend); + var newNode = appendMethodFunc(_syntaxGenerator, _node); + + var expectedResult = "' Comment \r\nMath.Abs(-1).ToString()"; + Assert.AreEqual(expectedResult, newNode.ToFullString()); + } + + + [Test] + public void InvocationExpressionEquals() + { + var invocationExpressionAction = new InvocationExpressionAction() { Key = "Test", Value = "Test2", InvocationExpressionActionFunc = _invocationExpressionActions.GetAddCommentAction("Test") }; + var cloned = invocationExpressionAction.Clone>(); + Assert.True(invocationExpressionAction.Equals(cloned)); + + cloned.Value = "DifferentValue"; + Assert.False(invocationExpressionAction.Equals(cloned)); + } + } +} \ No newline at end of file diff --git a/tst/CTA.Rules.Test/Actions/VisualBasic/MethodBlockActionsTests.cs b/tst/CTA.Rules.Test/Actions/VisualBasic/MethodBlockActionsTests.cs new file mode 100644 index 00000000..9f7ca5f4 --- /dev/null +++ b/tst/CTA.Rules.Test/Actions/VisualBasic/MethodBlockActionsTests.cs @@ -0,0 +1,169 @@ +using CTA.Rules.Actions.VisualBasic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; +using NUnit.Framework; +using System.Collections.Generic; +using CTA.Rules.Models.Actions.VisualBasic; + +namespace CTA.Rules.Test.Actions.VisualBasic +{ + public class MethodBlockActionsTests + { + private MethodBlockActions _methodBlockActions; + private SyntaxGenerator _syntaxGenerator; + private MethodBlockSyntax _subNode; + private MethodBlockSyntax _functionNode; + + [SetUp] + public void SetUp() + { + var workspace = new AdhocWorkspace(); + var language = LanguageNames.VisualBasic; + _syntaxGenerator = SyntaxGenerator.GetGenerator(workspace, language); + _methodBlockActions = new MethodBlockActions(); + + var body = new SyntaxList().Add( + SyntaxFactory.ParseExecutableStatement(@"' Nothing to see here")); + _subNode = SyntaxFactory.MethodBlock(SyntaxKind.SubBlock, + SyntaxFactory + .MethodStatement(SyntaxKind.SubStatement, SyntaxFactory.Token(SyntaxKind.SubKeyword), "Authorize") + .WithModifiers(SyntaxFactory.TokenList().Add(SyntaxFactory.Token(SyntaxKind.PublicKeyword))), + SyntaxFactory.EndSubStatement()).WithStatements(body); + + body = body.Add(SyntaxFactory.ReturnStatement(SyntaxFactory.ParseExpression("0"))); + _functionNode = SyntaxFactory.MethodBlock(SyntaxKind.FunctionBlock, + SyntaxFactory.MethodStatement(SyntaxKind.FunctionStatement, + SyntaxFactory.Token(SyntaxKind.FunctionKeyword), "TestFunction") + .WithModifiers(SyntaxFactory.TokenList().Add(SyntaxFactory.Token(SyntaxKind.PublicKeyword))) + .WithAsClause(SyntaxFactory.SimpleAsClause(SyntaxFactory.ParseTypeName("Integer"))), + SyntaxFactory.EndFunctionStatement()).WithStatements(body); + } + + [Test] + public void MethodDeclarationAddComment() + { + const string comment = "This is a comment"; + var addCommentFunction = _methodBlockActions.GetAddCommentAction(comment); + var newNode = addCommentFunction(_syntaxGenerator, _subNode); + + StringAssert.Contains(comment, newNode.ToFullString()); + } + + [Test] + public void MethodDeclarationAddExpression() + { + var nodeBody = _subNode.Statements; + nodeBody = nodeBody.Add(SyntaxFactory.ParseExecutableStatement("Dim testing as String = \"Testing\"")); + _subNode = _subNode.WithStatements(nodeBody); + + const string expression = "Dim i as Integer = 5"; + var addExpressionFunction = _methodBlockActions.GetAppendExpressionAction(expression); + var newNode = addExpressionFunction(_syntaxGenerator, _subNode); + + StringAssert.Contains(expression, newNode.ToFullString()); + } + + [Test] + public void MethodBlockActionComparison() + { + var methodDeclarationAction = new MethodBlockAction() + { + Key = "Test", + Value = "Test2", + MethodBlockActionFunc = _methodBlockActions.GetAddCommentAction("NewAttribute") + }; + + var cloned = methodDeclarationAction.Clone(); + + Assert.True(methodDeclarationAction.Equals(cloned)); + cloned.Value = "DifferentValue"; + Assert.False(methodDeclarationAction.Equals(cloned)); + } + + [Test] + public void CommentMethodAction() + { + var expressions = new List + { + "Dim testing as String = \"Testing\"", + "Dim testing2 as String = \"Testing2\"" + }; + var nodeBody = _subNode.Statements; + foreach (var e in expressions) + { + nodeBody = nodeBody.Add(SyntaxFactory.ParseExecutableStatement(e)); + } + _subNode = _subNode.WithStatements(nodeBody); + + var commentMethodFunc = _methodBlockActions.GetCommentMethodAction("this is a comment"); + var newNode = commentMethodFunc(_syntaxGenerator, _subNode); + + foreach (var e in expressions) + { + StringAssert.Contains($"' {e}", newNode.ToFullString()); + } + } + + [Test] + public void AddExpressionToMethod() + { + var nodeBody = _subNode.Statements; + nodeBody = nodeBody.Add(SyntaxFactory.ParseExecutableStatement("Dim testing as String = \"Testing\"")); + _subNode = _subNode.WithStatements(nodeBody); + + const string expression = "Dim i as Integer = 5"; + var addExpressionFunction = _methodBlockActions.GetAddExpressionToMethodAction(expression); + var newNode = addExpressionFunction(_syntaxGenerator, _subNode); + + StringAssert.Contains(expression, newNode.ToFullString()); + } + + [Test] + public void AddExpressionToFunction() + { + const string expression = "Await Task.Delay(1000)"; + var addExpressionFunc = _methodBlockActions.GetAddExpressionToMethodAction(expression); + var newNode = addExpressionFunc(_syntaxGenerator, _functionNode); + + StringAssert.Contains(expression, newNode.ToFullString()); + Assert.IsTrue(newNode.Statements.Last().IsKind(SyntaxKind.ReturnStatement)); + } + + [Test] + public void AddParametersToMethod() + { + var addParamsAction = _methodBlockActions.GetAddParametersToMethodAction("String,String", "param1,param2"); + var newNode = addParamsAction(_syntaxGenerator, _subNode); + StringAssert.Contains("param1 As String", newNode.ToFullString()); + } + + [Test] + public void ChangeMethodToReturnTask() + { + var changeReturnToTaskFunc = _methodBlockActions.GetChangeMethodToReturnTaskTypeAction(); + var newNode = changeReturnToTaskFunc(_syntaxGenerator, _subNode); + + StringAssert.Contains("Task", newNode.ToFullString()); + StringAssert.Contains("Async", newNode.ToFullString()); + Assert.True(newNode.SubOrFunctionStatement.IsKind(SyntaxKind.FunctionStatement)); + + var newFunction = changeReturnToTaskFunc(_syntaxGenerator, _functionNode); + StringAssert.Contains("Async", newFunction.ToFullString()); + StringAssert.Contains("Task(Of Integer)", newFunction.ToFullString()); + } + + [Test] + public void ChangeMethodName() + { + var changeMethodNameFunc = _methodBlockActions.GetChangeMethodNameAction("NewName"); + + var newNode = changeMethodNameFunc(_syntaxGenerator, _subNode); + var newFunction = changeMethodNameFunc(_syntaxGenerator, _functionNode); + + StringAssert.Contains("Sub NewName", newNode.ToFullString()); + StringAssert.Contains("Function NewName", newFunction.ToFullString()); + } + } +} \ No newline at end of file diff --git a/tst/CTA.Rules.Test/Actions/VisualBasic/NamespaceActionsTests.cs b/tst/CTA.Rules.Test/Actions/VisualBasic/NamespaceActionsTests.cs new file mode 100644 index 00000000..d52c9736 --- /dev/null +++ b/tst/CTA.Rules.Test/Actions/VisualBasic/NamespaceActionsTests.cs @@ -0,0 +1,52 @@ +using CTA.FeatureDetection.Common.Extensions; +using CTA.Rules.Actions.VisualBasic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; +using NUnit.Framework; +using System.Linq; + +namespace CTA.Rules.Test.Actions.VisualBasic +{ + internal class NamespaceActionsTests + { + private SyntaxGenerator _syntaxGenerator; + private NamespaceActions _namespaceActions; + private NamespaceBlockSyntax _node; + + [SetUp] + public void SetUp() + { + var workspace = new AdhocWorkspace(); + var language = LanguageNames.CSharp; + _syntaxGenerator = SyntaxGenerator.GetGenerator(workspace, language); + _namespaceActions = new NamespaceActions(); + + SyntaxTree tree = VisualBasicSyntaxTree.ParseText(@$" + Namespace DummyNamespace + Class MyClass + End Class + End Namespace"); + _node = tree.GetRoot() + .DescendantNodes() + .OfType() + .FirstOrDefault(); + } + + [Test] + public void GetRenameNamespaceAction_Rename_Namespace() + { + const string newNamespaceName = "NewNamespace"; + var renameNamespaceFunc = _namespaceActions.GetRenameNamespaceAction(newNamespaceName); + var newNode = renameNamespaceFunc(_syntaxGenerator, _node); + + var expectedResult = VisualBasicSyntaxTree.ParseText(@$" + Namespace NewNamespace + Class MyClass + End Class + End Namespace").GetRoot(); + Assert.AreEqual(expectedResult.RemoveAllTrivia().ToFullString(), newNode.RemoveAllTrivia().ToFullString()); + } + } +} diff --git a/tst/CTA.Rules.Test/Actions/VisualBasic/ObjectCreationExpressionActionsTests.cs b/tst/CTA.Rules.Test/Actions/VisualBasic/ObjectCreationExpressionActionsTests.cs new file mode 100644 index 00000000..e859e8c8 --- /dev/null +++ b/tst/CTA.Rules.Test/Actions/VisualBasic/ObjectCreationExpressionActionsTests.cs @@ -0,0 +1,148 @@ +using CTA.Rules.Actions.VisualBasic; +using CTA.Rules.Models.Actions.VisualBasic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; +using NUnit.Framework; + +namespace CTA.Rules.Test.Actions.VisualBasic +{ + public class ObjectCreationExpressionActionsTests + { + private SyntaxGenerator _syntaxGenerator; + private ObjectCreationExpressionActions _objectCreationExpressionActions; + private ObjectCreationExpressionSyntax _node; + private ObjectMemberInitializerSyntax _objectMemberNode; + + [SetUp] + public void SetUp() + { + var workspace = new AdhocWorkspace(); + var language = LanguageNames.VisualBasic; + _syntaxGenerator = SyntaxGenerator.GetGenerator(workspace, language); + _objectCreationExpressionActions = new ObjectCreationExpressionActions(); + _node = _syntaxGenerator.ObjectCreationExpression(SyntaxFactory.ParseTypeName("StringBuilder")) + .NormalizeWhitespace() as ObjectCreationExpressionSyntax; + + _node = _node.AddArgumentListArguments(SyntaxFactory.SimpleArgument( + SyntaxFactory.LiteralExpression( + SyntaxKind.StringLiteralExpression, + SyntaxFactory.Literal( + SyntaxFactory.TriviaList(), + "\"SomeText\"", + "\"SomeText\"", + SyntaxFactory.TriviaList())))); + } + + [Test] + public void GetReplaceObjectWithInvocationAction_Replaces_Constructor_With_New_Invocation_And_Preserves_Args() + { + const string newStatement = "Console.WriteLine()"; + var replaceObjectWithInvocationFunc = + _objectCreationExpressionActions.GetReplaceObjectWithInvocationAction(newStatement, "true"); + var newNode = replaceObjectWithInvocationFunc(_syntaxGenerator, _node); + + var expectedResult = "Console.WriteLine(\"SomeText\")"; + Assert.AreEqual(expectedResult, newNode.ToFullString()); + } + + + [Test] + public void ObjectCreationExpressionActionEquals() + { + var objectCreationExpressionAction = new ObjectCreationExpressionAction() {Key = "Test", Value = "Test2", ObjectCreationExpressionGenericActionFunc = _objectCreationExpressionActions.GetReplaceObjectinitializationAction("Test")}; + var cloned = objectCreationExpressionAction.Clone(); + Assert.True(objectCreationExpressionAction.Equals(cloned)); + + cloned.Value = "DifferentValue"; + Assert.False(objectCreationExpressionAction.Equals(cloned)); + } + + [Test] + public void GetReplaceObjectPropertyIdentifier() + { + string oldIdentifier = "FileSystem", newIdentifier = "FileProvider"; + _objectMemberNode = + SyntaxFactory.ObjectMemberInitializer( + SyntaxFactory.SeparatedList( + new SyntaxNodeOrToken[] { + SyntaxFactory.NamedFieldInitializer( + SyntaxFactory.IdentifierName("RequestPath"), + SyntaxFactory.ParseExpression("PathString.Empty")), + SyntaxFactory.Token(SyntaxKind.CommaToken), + SyntaxFactory.NamedFieldInitializer( + SyntaxFactory.IdentifierName("FileSystem"), + SyntaxFactory.ParseExpression("new PhysicalFileSystem(@\".\\defaults\")")), + SyntaxFactory.Token(SyntaxKind.CommaToken)})) + .NormalizeWhitespace(); + _node = _node.WithArgumentList(SyntaxFactory.ArgumentList()).WithInitializer(_objectMemberNode) + .NormalizeWhitespace(); + var replaceObjectWithInvocationFunc = + _objectCreationExpressionActions.GetReplaceOrAddObjectPropertyIdentifierAction(oldIdentifier, + newIdentifier, string.Empty); + var newNode = replaceObjectWithInvocationFunc(_syntaxGenerator, _node); + StringAssert.Contains(newIdentifier, newNode.ToFullString()); + } + + [Test] + public void GetAddObjectPropertyIdentifier() + { + string oldIdentifier = "FileSystem", newIdentifier = "FileProvider", newValue = @"new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), @""""))"; + + _objectMemberNode = SyntaxFactory.ObjectMemberInitializer( + SyntaxFactory.SeparatedList( + new SyntaxNodeOrToken[]{ + SyntaxFactory.NamedFieldInitializer( + SyntaxFactory.IdentifierName("RequestPath"), + SyntaxFactory.ParseExpression("PathString.Empty")), + SyntaxFactory.Token(SyntaxKind.CommaToken), + SyntaxFactory.NamedFieldInitializer( + SyntaxFactory.IdentifierName("EnableDirectoryBrowsing"), + SyntaxFactory.ParseExpression("true")), + SyntaxFactory.Token(SyntaxKind.CommaToken)})) + .NormalizeWhitespace(); + _node = _node.WithArgumentList(SyntaxFactory.ArgumentList()).WithInitializer(_objectMemberNode); + var replaceObjectWithInvocationFunc = _objectCreationExpressionActions.GetReplaceOrAddObjectPropertyIdentifierAction(oldIdentifier, newIdentifier, newValue); + var newNode = replaceObjectWithInvocationFunc(_syntaxGenerator, _node); + + StringAssert.Contains(newIdentifier, newNode.ToFullString()); + StringAssert.Contains(newValue, newNode.ToFullString()); + } + + [Test] + public void GetReplaceObjectPropertyValue() + { + string oldIdentifier = "PhysicalFileSystem", newIdentifier = "PhysicalFileProvider"; + + _objectMemberNode = SyntaxFactory.ObjectMemberInitializer( + SyntaxFactory.SeparatedList( + new SyntaxNodeOrToken[]{ + SyntaxFactory.NamedFieldInitializer( + SyntaxFactory.IdentifierName("RequestPath"), + SyntaxFactory.ParseExpression("PathString.Empty")), + SyntaxFactory.Token(SyntaxKind.CommaToken), + SyntaxFactory.NamedFieldInitializer( + SyntaxFactory.IdentifierName("FileSystem"), + SyntaxFactory.ParseExpression("new PhysicalFileSystem(@\".\\defaults\")")), + SyntaxFactory.Token(SyntaxKind.CommaToken)})) + .NormalizeWhitespace(); + _node = _node.WithArgumentList(SyntaxFactory.ArgumentList()).WithInitializer(_objectMemberNode); + var replaceObjectWithInvocationFunc = _objectCreationExpressionActions.GetReplaceObjectPropertyValueAction(oldIdentifier, newIdentifier); + var newNode = replaceObjectWithInvocationFunc(_syntaxGenerator, _node); + StringAssert.Contains(newIdentifier, newNode.ToFullString()); + StringAssert.DoesNotContain(oldIdentifier, newNode.ToFullString()); + } + + [Test] + public void ObjectCreationExpressionEquals() + { + var objectCreationExpressionAction = new ObjectCreationExpressionAction() { Key = "Test", Value = "Test2", ObjectCreationExpressionGenericActionFunc = _objectCreationExpressionActions.GetAddCommentAction("Test") }; + var cloned = objectCreationExpressionAction.Clone(); + Assert.True(objectCreationExpressionAction.Equals(cloned)); + + cloned.Value = "DifferentValue"; + Assert.False(objectCreationExpressionAction.Equals(cloned)); + } + } +} \ No newline at end of file diff --git a/tst/CTA.Rules.Test/Actions/VisualBasicActionsLoaderTest.cs b/tst/CTA.Rules.Test/Actions/VisualBasicActionsLoaderTest.cs new file mode 100644 index 00000000..1173a116 --- /dev/null +++ b/tst/CTA.Rules.Test/Actions/VisualBasicActionsLoaderTest.cs @@ -0,0 +1,67 @@ +using CTA.Rules.Actions; +using CTA.Rules.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Newtonsoft.Json; +using NUnit.Framework; +using System.Collections.Generic; + +namespace CTA.Rules.Test.Actions +{ + public class VisualBasicActionLoaderTests + { + private VisualBasicActionsLoader _actionLoader; + + [SetUp] + public void SetUp() + { + _actionLoader = new VisualBasicActionsLoader(new List()); + } + + [Test] + public void CompilationUnitActionsTest() + { + var addStatement = _actionLoader.GetCompilationUnitAction("AddStatement", "namespace"); + var removeStatement = _actionLoader.GetCompilationUnitAction("RemoveStatement", "namespace"); + var addComment = _actionLoader.GetCompilationUnitAction("AddComment", "comment"); + + Assert.IsNotNull(addStatement); + Assert.IsNotNull(removeStatement); + Assert.IsNotNull(addComment); + } + + [Test] + public void InvocationExpressionActionsTest() + { + var replaceMethodWithObjectAndParameters = _actionLoader.GetInvocationExpressionAction("ReplaceMethodWithObjectAndParameters", "{newMethod: \"method\", newParameters: \"params\"}"); + var replaceMethodWithObject = _actionLoader.GetInvocationExpressionAction("ReplaceMethodWithObject", "newMethod"); + var replaceMethodWithObjectAddType = _actionLoader.GetInvocationExpressionAction("ReplaceMethodWithObjectAddType", "newMethod"); + var replaceMethodAndParameters = _actionLoader.GetInvocationExpressionAction("ReplaceMethodAndParameters", "{ oldMethod: \"method\", newMethod: \"method\", newParameters: \"params\"}"); + var replaceMethodOnly = _actionLoader.GetInvocationExpressionAction("ReplaceMethodOnly", "{ oldMethod: \"method\", newMethod: \"method\" }"); + var replaceParametersOnly = _actionLoader.GetInvocationExpressionAction("ReplaceParametersOnly", "newParameters"); + var appendMethod = _actionLoader.GetInvocationExpressionAction("AppendMethod", "appendMethod"); + var addComment = _actionLoader.GetInvocationExpressionAction("AddComment", "comment"); + + Assert.IsNotNull(replaceMethodWithObjectAndParameters); + Assert.IsNotNull(replaceMethodWithObject); + Assert.IsNotNull(replaceMethodWithObjectAddType); + Assert.IsNotNull(replaceMethodAndParameters); + Assert.IsNotNull(replaceMethodOnly); + Assert.IsNotNull(replaceParametersOnly); + Assert.IsNotNull(appendMethod); + Assert.IsNotNull(addComment); + } + + [Test] + public void IdentifierNameActionsTest() + { + var replaceIdentifier = _actionLoader.GetIdentifierNameAction("ReplaceIdentifier", "identifierName"); + var replaceIdentifierInsideClass = _actionLoader.GetIdentifierNameAction("ReplaceIdentifierInsideClass", "{ identifier: \"identifier\", classFullKey: \"classKey\" }"); + + Assert.IsNotNull(replaceIdentifier); + Assert.IsNotNull(replaceIdentifierInsideClass); + } + } +} \ No newline at end of file diff --git a/tst/CTA.Rules.Test/AwsRulesBaseTest.cs b/tst/CTA.Rules.Test/AwsRulesBaseTest.cs index 51e903c7..4fae0c7c 100644 --- a/tst/CTA.Rules.Test/AwsRulesBaseTest.cs +++ b/tst/CTA.Rules.Test/AwsRulesBaseTest.cs @@ -203,7 +203,7 @@ internal TestSolutionAnalysis GenerateSolutionResult(string solutionPath, Soluti return result; } - private void CopyTestRules() + protected void CopyTestRules() { // Project configured to copy TempRules folder to output directory // so no extra action necessary here diff --git a/tst/CTA.Rules.Test/CTA.Rules.Test.csproj b/tst/CTA.Rules.Test/CTA.Rules.Test.csproj index 3d043628..d716884f 100644 --- a/tst/CTA.Rules.Test/CTA.Rules.Test.csproj +++ b/tst/CTA.Rules.Test/CTA.Rules.Test.csproj @@ -31,6 +31,9 @@ + + PreserveNewest + diff --git a/tst/CTA.Rules.Test/CTAFiles/vb.rules.test.json b/tst/CTA.Rules.Test/CTAFiles/vb.rules.test.json new file mode 100644 index 00000000..75fc5589 --- /dev/null +++ b/tst/CTA.Rules.Test/CTAFiles/vb.rules.test.json @@ -0,0 +1,167 @@ +{ + "Name": "VbWebApi", + "Version": "1.0.0", + "Packages": [ + { + "Name": "VbWebApi", + "Type": "Project" + } + ], + "Recommendations": [ + { + "Type": "ElementAccess", + "Name": "MigrateShoppingCart", + "Value": "MvcMusicStore.Controllers.AccountController.MigrateShoppingCart", + "RecommendedActions": [ + { + "Source": "Amazon", + "Preferred": "Yes", + "TargetFrameworks": [ + { + "Name": "netcoreapp3.1", + "TargetCPU": [ "x86", "x64", "ARM64" ] + }, + { + "Name": "net5.0", + "TargetCPU": [ "x86", "x64", "ARM64" ] + } + ], + "Description": "Add a comment", + "Actions": [ + { + "Name": "AddComment", + "Type": "ElementAccess", + "Value": "The text of the comment", + "Description": "Add a comment to an element access statement" + } + ] + } + ] + }, + { + "Type": "Namespace", + "Name": "MvcMusicStore.ViewModels", + "Value": "MvcMusicStore.ViewModels", + "KeyType": "Name", + "ContainingType": "", + "RecommendedActions": [ + { + "Source": "Amazon", + "Preferred": "Yes", + "TargetFrameworks": [ + { + "Name": "netcoreapp3.1", + "TargetCPU": [ "x86", "x64", "ARM64" ] + }, + { + "Name": "net5.0", + "TargetCPU": [ "x86", "x64", "ARM64" ] + } + ], + "Description": "Replace namespacee with a new namespace", + "Actions": [ + { + "Name": "RenameNamespace", + "Type": "Namespace", + "Value": "MvcMusicStore.ChangedViewModels", + "Description": "Replace namespace with a new namespace", + "ActionValidation": { + "Contains": "notvalid;", + "NotContains": "notvalidtoo" + } + } + ] + } + ] + }, + { + "Type": "MethodDeclaration", + "Name": "MigrateShoppingCart", + "Value": "MvcMusicStore.Controllers.AccountController.MigrateShoppingCart", + "RecommendedActions": [ + { + "Source": "Amazon", + "Preferred": "Yes", + "TargetFrameworks": [ + { + "Name": "netcoreapp3.1", + "TargetCPU": [ "x86", "x64", "ARM64" ] + }, + { + "Name": "net5.0", + "TargetCPU": [ "x86", "x64", "ARM64" ] + } + ], + "Description": "Add a comment", + "Actions": [ + { + "Name": "AddComment", + "Type": "MethodDeclaration", + "Value": "The text of the comment", + "Description": "Add a comment to a method declaration" + } + ] + } + ] + }, + { + "Type": "Attribute", + "Name": "RoutePrefix", + "Value": "System.Web.Http.RoutePrefixAttribute.RoutePrefixAttribute(string)", + "KeyType": "Name", + "ContainingType": "RoutePrefixAttribute", + "RecommendedActions": [ + { + "Source": "Amazon", + "Preferred": "Yes", + "TargetFrameworks": [ + { + "Name": "netcoreapp3.1", + "TargetCPU": [ + "x86", + "x64", + "ARM32", + "ARM64" + ] + }, + { + "Name": "net5.0", + "TargetCPU": [ + "x86", + "x64", + "ARM32", + "ARM64" + ] + }, + { + "Name": "net6.0", + "TargetCPU": [ + "x86", + "x64", + "ARM32", + "ARM64" + ] + } + ], + "Description": "Add a comment: RoutePrefix attribute is no longer supported.", + "Actions": [ + { + "Name": "AddComment", + "Type": "AttributeList", + "Value": { + "comment": "RoutePrefix attribute is no longer supported", + "dontUseCTAPrefix": "" + }, + "Description": "RoutePrefix attribute is no longer supported", + "ActionValidation": { + "Contains": "RoutePrefix attribute is no longer supported", + "NotContains": "", + "CheckComments": "True" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/tst/CTA.Rules.Test/CTATests.cs b/tst/CTA.Rules.Test/CTATests.cs index aeb4956b..9f10c300 100644 --- a/tst/CTA.Rules.Test/CTATests.cs +++ b/tst/CTA.Rules.Test/CTATests.cs @@ -241,6 +241,49 @@ public void ConvertHierarchicalToNamespaceFile() FileAssert.Exists(Path.Combine(dir, "system.web.mvc.json")); } + [Test] + public void VBTestWebApiSolution() + { + var results = runCTAFile("VBWebApi.sln").ProjectResults.FirstOrDefault(); + Assert.IsTrue(results != null); + StringAssert.Contains("Create service class.", results.ProjectAnalysisResult); + + var homeControllerText = File.ReadAllText(Path.Combine(results.ProjectDirectory, "Controllers", "HomeController.vb")); + var valuesControllerText = File.ReadAllText(Path.Combine(results.ProjectDirectory, "Controllers", "ValuesController.vb")); + + //Check that attribute has been added to class and inherits has been added: + StringAssert.Contains(@"Public Class HomeController", homeControllerText); + StringAssert.Contains(@"Inherits", homeControllerText); + StringAssert.Contains(@"End Class", homeControllerText); + + //Check that function has been added to class: + StringAssert.Contains(@"Function", homeControllerText); + StringAssert.Contains(@"End Function", homeControllerText); + + //Check that identifier as replaced: + StringAssert.Contains(@"As ActionResult", homeControllerText); + + //Check that import statement has been added: + StringAssert.Contains(@"Imports System.Net", valuesControllerText); + + //Check that attribute has been added to class and inherits has been added: + StringAssert.Contains(@"Public Class ValuesController", valuesControllerText); + StringAssert.Contains(@"ByVal id As Integer", valuesControllerText); + StringAssert.Contains(@"Inherits", valuesControllerText); + StringAssert.Contains(@"End Class", valuesControllerText); + + //Check that function has been added to class: + StringAssert.Contains(@"Function", valuesControllerText); + StringAssert.Contains(@"End Function", valuesControllerText); + + //Check that sub statement has been added to class: + StringAssert.Contains(@"Public Sub", valuesControllerText); + StringAssert.Contains(@"End Sub", valuesControllerText); + + //Check that identifier as replaced: + StringAssert.Contains(@"As String", valuesControllerText); + } + [Test] public void LoggerTest() { diff --git a/tst/CTA.Rules.Test/FolderUpdateTests.cs b/tst/CTA.Rules.Test/FolderUpdateTests.cs index b7813570..49760246 100644 --- a/tst/CTA.Rules.Test/FolderUpdateTests.cs +++ b/tst/CTA.Rules.Test/FolderUpdateTests.cs @@ -13,6 +13,8 @@ public class FolderUpdateTests Directory.GetCurrentDirectory(), "TestProject"); private string _testProjectPath = Path.Combine( Directory.GetCurrentDirectory(), "TestProject", "TestProject.csproj"); + private string _vbTestProjectPath = Path.Combine( + Directory.GetCurrentDirectory(), "TestProject", "TestProject.vbproj"); [SetUp] public void Setup() @@ -196,5 +198,23 @@ public void Folder_Update_for_WebApi_Project() Cleanup(_testProjectDir); } + + [Test] + public void Folder_Update_for_WebApi_Project_Vb() + { + // Run FolderUpdate on the test project + ProjectType projectType = ProjectType.WebApi; + FolderUpdate folderUpdate = new FolderUpdate( + _vbTestProjectPath, projectType); + folderUpdate.Run(); + + // Validate Program.cs and Startup.cs files are created + Assert.True(File.Exists(Path.Combine( + _testProjectDir, FileTypeCreation.Program.ToString() + FileExtension.VisualBasic))); + Assert.True(File.Exists(Path.Combine( + _testProjectDir, FileTypeCreation.Startup.ToString() + FileExtension.VisualBasic))); + + Cleanup(_testProjectDir); + } } } diff --git a/tst/CTA.Rules.Test/Metrics/PortSolutionResultReportGeneratorTests.cs b/tst/CTA.Rules.Test/Metrics/PortSolutionResultReportGeneratorTests.cs index fe443c4a..2c6c4dc6 100644 --- a/tst/CTA.Rules.Test/Metrics/PortSolutionResultReportGeneratorTests.cs +++ b/tst/CTA.Rules.Test/Metrics/PortSolutionResultReportGeneratorTests.cs @@ -187,6 +187,7 @@ public void GenerateAnalysisReport() var expectedAnalysisReport = @"[ { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""UpgradePackage"", ""packageName"": ""Newtonsoft.Json"", ""packageVersion"": ""12.0.0"", @@ -196,6 +197,7 @@ public void GenerateAnalysisReport() }, { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""GenericAction"", ""actionName"": ""GA1 Name"", ""actionType"": ""GA1 Type"", @@ -206,6 +208,7 @@ public void GenerateAnalysisReport() }, { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""GenericAction"", ""actionName"": ""GA2 Name"", ""actionType"": ""GA2 Type"", @@ -216,6 +219,7 @@ public void GenerateAnalysisReport() }, { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""MissingMetaReference"", ""metaReference"": ""C://reference1.dll"", ""solutionPath"": ""5fa9de0cb5af2d468dfb1702b1e342f47de2df9a195dabb3be2d04f9c2767482"", @@ -223,6 +227,7 @@ public void GenerateAnalysisReport() }, { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""MissingMetaReference"", ""metaReference"": ""C://reference2.dll"", ""solutionPath"": ""5fa9de0cb5af2d468dfb1702b1e342f47de2df9a195dabb3be2d04f9c2767482"", @@ -239,6 +244,7 @@ public void GenerateAnalysisReportWithFeatureDetection() var expectedAnalysisReport = @"[ { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""UpgradePackage"", ""packageName"": ""Newtonsoft.Json"", ""packageVersion"": ""12.0.0"", @@ -248,6 +254,7 @@ public void GenerateAnalysisReportWithFeatureDetection() }, { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""GenericAction"", ""actionName"": ""GA1 Name"", ""actionType"": ""GA1 Type"", @@ -258,6 +265,7 @@ public void GenerateAnalysisReportWithFeatureDetection() }, { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""GenericAction"", ""actionName"": ""GA2 Name"", ""actionType"": ""GA2 Type"", @@ -268,6 +276,7 @@ public void GenerateAnalysisReportWithFeatureDetection() }, { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""DetectedFeature"", ""featureName"": ""Feature 1"", ""solutionPath"": ""5fa9de0cb5af2d468dfb1702b1e342f47de2df9a195dabb3be2d04f9c2767482"", @@ -275,6 +284,7 @@ public void GenerateAnalysisReportWithFeatureDetection() }, { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""DetectedFeature"", ""featureName"": ""Feature 1a"", ""solutionPath"": ""5fa9de0cb5af2d468dfb1702b1e342f47de2df9a195dabb3be2d04f9c2767482"", @@ -282,6 +292,7 @@ public void GenerateAnalysisReportWithFeatureDetection() }, { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""MissingMetaReference"", ""metaReference"": ""C://reference1.dll"", ""solutionPath"": ""5fa9de0cb5af2d468dfb1702b1e342f47de2df9a195dabb3be2d04f9c2767482"", @@ -289,6 +300,7 @@ public void GenerateAnalysisReportWithFeatureDetection() }, { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""MissingMetaReference"", ""metaReference"": ""C://reference2.dll"", ""solutionPath"": ""5fa9de0cb5af2d468dfb1702b1e342f47de2df9a195dabb3be2d04f9c2767482"", @@ -385,24 +397,28 @@ public void GenerateAndExportReports_Creates_Expected_Json_Report() var expectedJsonReport = @"[ { ""metricsType"": ""CTA"", + ""language"": null, ""metricName"": ""Namespace"", ""reference"": ""System.Web"", ""solutionPath"": ""5fa9de0cb5af2d468dfb1702b1e342f47de2df9a195dabb3be2d04f9c2767482"" }, { ""metricsType"": ""CTA"", + ""language"": null, ""metricName"": ""Namespace"", ""reference"": ""System.Web.Mvc"", ""solutionPath"": ""5fa9de0cb5af2d468dfb1702b1e342f47de2df9a195dabb3be2d04f9c2767482"" }, { ""metricsType"": ""CTA"", + ""language"": null, ""metricName"": ""RulesFile"", ""downloadedFile"": ""project.all.json"", ""solutionPath"": ""5fa9de0cb5af2d468dfb1702b1e342f47de2df9a195dabb3be2d04f9c2767482"" }, { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""TargetVersion"", ""targetVersion"": ""netcoreapp3.1"", ""sourceVersion"": ""net48"", @@ -411,6 +427,7 @@ public void GenerateAndExportReports_Creates_Expected_Json_Report() }, { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""UpgradePackage"", ""packageName"": ""Newtonsoft.Json"", ""packageVersion"": ""12.0.0"", @@ -420,6 +437,7 @@ public void GenerateAndExportReports_Creates_Expected_Json_Report() }, { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""ActionExecution"", ""actionName"": ""GA1 Name"", ""actionType"": ""GA1 Type"", @@ -432,6 +450,7 @@ public void GenerateAndExportReports_Creates_Expected_Json_Report() }, { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""ActionExecution"", ""actionName"": ""GA2 Name"", ""actionType"": ""GA2 Type"", @@ -444,6 +463,7 @@ public void GenerateAndExportReports_Creates_Expected_Json_Report() }, { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""BuildError"", ""buildErrorCode"": ""CS0000"", ""buildError"": ""CS0000: BuildError1"", @@ -453,6 +473,7 @@ public void GenerateAndExportReports_Creates_Expected_Json_Report() }, { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""BuildError"", ""buildErrorCode"": ""OTHER"", ""buildError"": ""BuildError2"", @@ -462,6 +483,7 @@ public void GenerateAndExportReports_Creates_Expected_Json_Report() }, { ""metricsType"": ""CTA"", + ""language"": ""unknown"", ""metricName"": ""BuildError"", ""buildErrorCode"": ""OTHER"", ""buildError"": ""BuildError3"", @@ -471,6 +493,7 @@ public void GenerateAndExportReports_Creates_Expected_Json_Report() }, { ""metricsType"": ""CTA"", + ""language"": ""unknown"", ""metricName"": ""BuildError"", ""buildErrorCode"": ""OTHER"", ""buildError"": ""BuildError4"", @@ -480,6 +503,7 @@ public void GenerateAndExportReports_Creates_Expected_Json_Report() }, { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""MissingMetaReference"", ""metaReference"": ""C://reference1.dll"", ""solutionPath"": ""5fa9de0cb5af2d468dfb1702b1e342f47de2df9a195dabb3be2d04f9c2767482"", @@ -487,6 +511,7 @@ public void GenerateAndExportReports_Creates_Expected_Json_Report() }, { ""metricsType"": ""CTA"", + ""language"": ""csharp"", ""metricName"": ""MissingMetaReference"", ""metaReference"": ""C://reference2.dll"", ""solutionPath"": ""5fa9de0cb5af2d468dfb1702b1e342f47de2df9a195dabb3be2d04f9c2767482"", diff --git a/tst/CTA.Rules.Test/UtilsTest.cs b/tst/CTA.Rules.Test/UtilsTest.cs index 01a48f2e..7f0a5750 100644 --- a/tst/CTA.Rules.Test/UtilsTest.cs +++ b/tst/CTA.Rules.Test/UtilsTest.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using CTA.Rules.Common.Helpers; namespace CTA.Rules.Test { @@ -112,5 +113,13 @@ public void ThreadSafeExportStringToFile_Permits_Concurrent_Processes() // Confirm that text was written to file Assert.AreEqual(content, File.ReadAllText(filePath)); } + + [Test] + public void Test_Is_VisualBasic_Project() + { + Assert.IsFalse(VisualBasicUtils.IsVisualBasicProject("test.csproj")); + Assert.IsTrue(VisualBasicUtils.IsVisualBasicProject("C://user/john/repos/test.vbproj")); + Assert.IsFalse(VisualBasicUtils.IsVisualBasicProject("vbprojproject.cs")); + } } } \ No newline at end of file diff --git a/tst/CTA.Rules.Test/VisualBasicRulesLoaderTest.cs b/tst/CTA.Rules.Test/VisualBasicRulesLoaderTest.cs new file mode 100644 index 00000000..4b3f96e2 --- /dev/null +++ b/tst/CTA.Rules.Test/VisualBasicRulesLoaderTest.cs @@ -0,0 +1,39 @@ +using System.IO; +using System.Reflection; +using CTA.Rules.Config; +using CTA.Rules.RuleFiles; +using NUnit.Framework; +using Newtonsoft.Json; + +namespace CTA.Rules.Test; + +public class VisualBasicRulesLoaderTest : AwsRulesBaseTest +{ + [Test] + public void TestRulesFileParser() + { + var ctaFilesDir = Path.GetFullPath(Path.Combine(Path.GetDirectoryName( + Assembly.GetExecutingAssembly().Location), + "CTAFiles")); + var rootObject = JsonConvert.DeserializeObject( + File.ReadAllText(Path.Combine(ctaFilesDir, + "consolidated.json"))); + + var overrideObject = JsonConvert.DeserializeObject( + File.ReadAllText(Path.Combine(ctaFilesDir, "project.specific.json"))); + + var namespaceRecommendations = JsonConvert.DeserializeObject( + File.ReadAllText(Path.Combine(ctaFilesDir, + "vb.rules.test.json"))); + + var fileParser = new VisualBasicRulesFileParser( + namespaceRecommendations, + new NamespaceRecommendations(), + rootObject, + overrideObject, + "", + "netcoreapp3.1"); + var nodes = fileParser.Process(); + Assert.IsNotNull(nodes); + } +} \ No newline at end of file diff --git a/tst/CTA.Rules.Test/VisualBasicTests.cs b/tst/CTA.Rules.Test/VisualBasicTests.cs new file mode 100644 index 00000000..7fc10fc5 --- /dev/null +++ b/tst/CTA.Rules.Test/VisualBasicTests.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Codelyzer.Analysis; +using CTA.Rules.Config; +using CTA.Rules.Models; +using CTA.Rules.Update; +using NUnit.Framework; + +namespace CTA.Rules.Test +{ + internal class VisualBasicTests : AwsRulesBaseTest + { + private string _tempDir; + private string _downloadLocation; + private List _ctaFiles; + private readonly string _version = "net5.0"; + //We don't care about version for CTA-only rules: + + [SetUp] + public void Setup() + { + _tempDir = SetupTests.TempDir; + _downloadLocation = SetupTests.DownloadLocation; + _ctaFiles = Directory.EnumerateFiles(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "CTAFiles")), "*.json") + .Select(s => Path.GetFileNameWithoutExtension(s)) + .ToList(); + } + + private TestSolutionAnalysis runCTAFile(string solutionName, string projectName = null) + { + var solutionPath = CopySolutionFolderToTemp(solutionName, _downloadLocation); + var solutionDir = Directory.GetParent(solutionPath).FullName; + + FileAssert.Exists(solutionPath); + + //Sample Web API has only one project: + string projectFile = (projectName == null ? Utils.GetProjectPaths(Path.Combine(solutionDir, solutionName)).FirstOrDefault() : Directory.EnumerateFiles(solutionDir, projectName, SearchOption.AllDirectories).FirstOrDefault()); + FileAssert.Exists(projectFile); + + ProjectConfiguration projectConfiguration = new ProjectConfiguration() + { + SolutionPath = solutionPath, + ProjectPath = projectFile, + TargetVersions = new List { _version }, + RulesDir = Constants.RulesDefaultPath, + AdditionalReferences = _ctaFiles + }; + + List solutionConfiguration = new List + { + projectConfiguration + }; + CopyTestRules(); + + SolutionRewriter solutionRewriter = new SolutionRewriter(solutionPath, solutionConfiguration); + var analysisRunResult = solutionRewriter.AnalysisRun(); + StringBuilder str = new StringBuilder(); + foreach (var projectResult in analysisRunResult.ProjectResults) + { + str.AppendLine(projectResult.ProjectFile); + str.AppendLine(projectResult.ProjectActions.ToString()); + } + var analysisResult = str.ToString(); + solutionRewriter.Run(analysisRunResult.ProjectResults.ToDictionary(p => p.ProjectFile, p => p.ProjectActions)); + + TestSolutionAnalysis result = new TestSolutionAnalysis() + { + SolutionAnalysisResult = analysisResult, + ProjectResults = new List() + { + new ProjectResult() + { + ProjectAnalysisResult = analysisResult, + CsProjectPath = projectFile, + ProjectDirectory = Directory.GetParent(projectFile).FullName, + CsProjectContent = File.ReadAllText(projectFile) + } + } + }; + + return result; + } + + [Test] + public void TestOwinParadiseVb() + { + var slnResults = runCTAFile("OwinParadiseVb.sln"); + var projresults = slnResults.ProjectResults.FirstOrDefault(); + Assert.IsTrue(projresults != null); + + StringAssert.Contains("Microsoft.AspNetCore.Hosting", projresults.ProjectAnalysisResult); + + var signalR = File.ReadAllText(Path.Combine(projresults.ProjectDirectory, "SignalR.vb")); + var startUp = File.ReadAllText(Path.Combine(projresults.ProjectDirectory, "Startup.vb")); + + //Check that namespace has been added + StringAssert.Contains(@"Microsoft.AspNetCore.Owin", startUp); + StringAssert.DoesNotContain("Imports Owin", startUp); + StringAssert.Contains("Imports Microsoft.AspNetCore.Hosting", signalR); + StringAssert.DoesNotContain("Imports Microsoft.Owin.Hosting", signalR); + + //Check identifier actions + StringAssert.Contains("IApplicationBuilder", startUp); + StringAssert.DoesNotContain("IAppBuilder", startUp); + StringAssert.Contains("IApplicationBuilder", signalR); + StringAssert.DoesNotContain("IAppBuilder", signalR); + + //Check method actions + StringAssert.Contains("UseEndpoints", signalR); + } + + [Test] + public void TestVbNetMvc() + { + var results = AnalyzeSolution("VBNetMvc.sln", + _tempDir, + _downloadLocation, + _version) + .ProjectResults.FirstOrDefault(); + // Check that nothing is ported. + + // uncomment once template in datastore is merged. + // StringAssert.Contains( + // "v4.7.2", + // results.CsProjectContent); + } + } +}