diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..a89c382552 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,39 @@ +--- +name: Bug report +about: Rubberduck does not work as expected +title: '' +labels: bug +assignees: '' + +--- +**Rubberduck version information** +The info below can be copy-paste-completed from the first lines of Rubberduck's log or the About box: + + Rubberduck version [...] + Operating System: [...] + Host Product: [...] + Host Version: [...] + Host Executable: [...] + + +**Description** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Logfile** +Rubberduck generates extensive logging in TRACE-Level. If no log was created at `%APPDATA%\Rubberduck\Logs`, check your settings. Include this log for bug reports about the behavior of Rubberduck. + +**Additional context** +Add any other context about the problem here. diff --git a/.gitignore b/.gitignore index 16a67f78c5..535cccc51e 100644 --- a/.gitignore +++ b/.gitignore @@ -183,3 +183,6 @@ CodeGraphData/ /Rubberduck.Deployment/Properties/launchSettings.json /Rubberduck.Deployment/Rubberduck.API.idl /Rubberduck.Deployment/Rubberduck.idl + +#Gradle +/.gradle/ diff --git a/README.md b/README.md index fbe1103039..ccd4c710b6 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ If you like this project and would like to thank its contributors, you are welco [masterBuildStatus]:https://ci.appveyor.com/api/projects/status/we3pdnkeebo4nlck/branch/master?svg=true [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/Rubberduck-vba/rubberduck.svg)](http://isitmaintained.com/project/Rubberduck-vba/rubberduck "Average time to resolve an issue") [![Percentage of issues still open](http://isitmaintained.com/badge/open/Rubberduck-vba/rubberduck.svg)](http://isitmaintained.com/project/Rubberduck-vba/rubberduck "Percentage of issues still open") +[![Chat on stackexchange](https://img.shields.io/badge/chat-on%20stackexchange-blue.svg)](https://chat.stackexchange.com/rooms/14929/vba-rubberducking) +[![License](https://img.shields.io/github/license/rubberduck-vba/Rubberduck.svg)](https://github.com/rubberduck-vba/Rubberduck/blob/next/LICENSE) > **[rubberduckvba.com](http://rubberduckvba.com)** [Wiki](https://github.com/rubberduck-vba/Rubberduck/wiki) [Rubberduck News](https://rubberduckvba.wordpress.com/) > devs@rubberduckvba.com @@ -36,7 +38,7 @@ If you like this project and would like to thank its contributors, you are welco Rubberduck is a COM add-in for the VBA IDE (VBE). -Copyright (C) 2014-2018 Rubberduck project contributors +Copyright (C) 2014-2019 Rubberduck project contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Rubberduck.CodeAnalysis/CodePathAnalysis/Walker.cs b/Rubberduck.CodeAnalysis/CodePathAnalysis/Walker.cs index a9c2ba6522..f2e956df41 100644 --- a/Rubberduck.CodeAnalysis/CodePathAnalysis/Walker.cs +++ b/Rubberduck.CodeAnalysis/CodePathAnalysis/Walker.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using Antlr4.Runtime; namespace Rubberduck.Inspections.CodePathAnalysis { diff --git a/Rubberduck.CodeAnalysis/Inspections/Abstract/InspectionBase.cs b/Rubberduck.CodeAnalysis/Inspections/Abstract/InspectionBase.cs index 56129ade2c..9e3b513de2 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Abstract/InspectionBase.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Abstract/InspectionBase.cs @@ -166,5 +166,10 @@ public IEnumerable GetInspectionResults(CancellationToken tok _logger.Trace("Intercepted invocation of '{0}.{1}' ran for {2}ms", GetType().Name, nameof(DoGetInspectionResults), _stopwatch.ElapsedMilliseconds); return result; } + + public virtual bool ChangesInvalidateResult(IInspectionResult result, ICollection modifiedModules) + { + return true; + } } } \ No newline at end of file diff --git a/Rubberduck.CodeAnalysis/Inspections/Abstract/InspectionResultBase.cs b/Rubberduck.CodeAnalysis/Inspections/Abstract/InspectionResultBase.cs index 9d7a861b7a..232f203276 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Abstract/InspectionResultBase.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Abstract/InspectionResultBase.cs @@ -1,4 +1,5 @@ -using System.IO; +using System.Collections.Generic; +using System.IO; using Antlr4.Runtime; using Rubberduck.Common; using Rubberduck.Parsing.Inspections; @@ -39,6 +40,12 @@ public abstract class InspectionResultBase : IInspectionResult, INavigateSource, public Declaration Target { get; } public dynamic Properties { get; } + public virtual bool ChangesInvalidateResult(ICollection modifiedModules) + { + return modifiedModules.Contains(QualifiedName) + || Inspection.ChangesInvalidateResult(this, modifiedModules); + } + /// /// Gets the information needed to select the target instruction in the VBE. /// diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/AssignmentNotUsedInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/AssignmentNotUsedInspection.cs index 88e3bc0f8f..9e293c8694 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Concrete/AssignmentNotUsedInspection.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/AssignmentNotUsedInspection.cs @@ -7,7 +7,6 @@ using Rubberduck.Inspections.CodePathAnalysis.Extensions; using System.Linq; using Rubberduck.Inspections.Results; -using Rubberduck.Parsing; using Rubberduck.Parsing.Grammar; namespace Rubberduck.Inspections.Concrete @@ -30,9 +29,20 @@ protected override IEnumerable DoGetInspectionResults() var nodes = new List(); foreach (var variable in variables) { - var tree = _walker.GenerateTree(variable.ParentScopeDeclaration.Context, variable); + var parentScopeDeclaration = variable.ParentScopeDeclaration; - nodes.AddRange(tree.GetIdentifierReferences()); + if (parentScopeDeclaration.DeclarationType.HasFlag(DeclarationType.Module)) + { + continue; + } + + var tree = _walker.GenerateTree(parentScopeDeclaration.Context, variable); + + var references = tree.GetIdentifierReferences(); + // ignore set-assignments to 'Nothing' + nodes.AddRange(references.Where(r => + !(r.Context.Parent is VBAParser.SetStmtContext setStmtContext && + setStmtContext.expression().GetText().Equals(Tokens.Nothing)))); } return nodes diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/FunctionReturnValueNotUsedInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/FunctionReturnValueNotUsedInspection.cs index 5981f4ec5d..2dcdd0555e 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Concrete/FunctionReturnValueNotUsedInspection.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/FunctionReturnValueNotUsedInspection.cs @@ -75,13 +75,14 @@ private bool IsRecursive(Declaration function) private bool IsReturnValueUsed(Declaration function) { + // TODO: This is O(MG) at work here. Need to refactor the whole shebang. return (from usage in function.References - where !IsAddressOfCall(usage) - where !IsTypeOfExpression(usage) - where !IsCallStmt(usage) - where !IsLet(usage) - where !IsSet(usage) - select usage).Any(usage => !IsReturnStatement(function, usage)); + where !IsLet(usage) + where !IsSet(usage) + where !IsCallStmt(usage) + where !IsTypeOfExpression(usage) + where !IsAddressOfCall(usage) + select usage).Any(usage => !IsReturnStatement(function, usage)); } private bool IsAddressOfCall(IdentifierReference usage) @@ -93,7 +94,7 @@ private bool IsTypeOfExpression(IdentifierReference usage) { return usage.Context.IsDescendentOf(); } - + private bool IsReturnStatement(Declaration function, IdentifierReference assignment) { return assignment.ParentScoping.Equals(function) && assignment.Declaration.Equals(function); @@ -111,6 +112,19 @@ private bool IsCallStmt(IdentifierReference usage) { return false; } + + var indexExpr = usage.Context.GetAncestor(); + if (indexExpr != null) + { + var memberAccessStmt = usage.Context.GetAncestor(); + if (memberAccessStmt != null && + callStmt.SourceInterval.ProperlyContains(memberAccessStmt.SourceInterval) && + memberAccessStmt.SourceInterval.ProperlyContains(indexExpr.SourceInterval)) + { + return false; + } + } + var argumentList = CallStatement.GetArgumentList(callStmt); if (argumentList == null) { diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/HungarianNotationInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/HungarianNotationInspection.cs index e8d030637a..937c2972a2 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Concrete/HungarianNotationInspection.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/HungarianNotationInspection.cs @@ -85,6 +85,7 @@ public sealed class HungarianNotationInspection : InspectionBase DeclarationType.Constant, DeclarationType.Control, DeclarationType.ClassModule, + DeclarationType.Document, DeclarationType.Member, DeclarationType.Module, DeclarationType.ProceduralModule, diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/MissingMemberAnnotationInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/MissingMemberAnnotationInspection.cs index 172cb8d608..b85ba50b4f 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Concrete/MissingMemberAnnotationInspection.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/MissingMemberAnnotationInspection.cs @@ -77,10 +77,7 @@ private static bool MissesCorrespondingMemberAnnotation(Declaration declaration, private static string AttributeBaseName(Declaration declaration, AttributeNode attribute) { - var attributeName = attribute.Name; - return attributeName.StartsWith($"{declaration.IdentifierName}.") - ? attributeName.Substring(declaration.IdentifierName.Length + 1) - : attributeName; + return Attributes.AttributeBaseName(attribute.Name, declaration.IdentifierName); } } } \ No newline at end of file diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/MissingModuleAnnotationInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/MissingModuleAnnotationInspection.cs index f68af4e4f4..e0e97d08f3 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Concrete/MissingModuleAnnotationInspection.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/MissingModuleAnnotationInspection.cs @@ -61,27 +61,7 @@ protected override IEnumerable DoGetInspectionResults() private static bool IsDefaultAttribute(Declaration declaration, AttributeNode attribute) { - switch (attribute.Name) - { - case "VB_Name": - return true; - case "VB_GlobalNameSpace": - return declaration.DeclarationType.HasFlag(DeclarationType.ClassModule) - && attribute.Values[0].Equals(Tokens.False); - case "VB_Exposed": - return declaration.DeclarationType.HasFlag(DeclarationType.ClassModule) - && attribute.Values[0].Equals(Tokens.False); - case "VB_Creatable": - return declaration.DeclarationType.HasFlag(DeclarationType.ClassModule) - && attribute.Values[0].Equals(Tokens.False); - case "VB_PredeclaredId": - return (declaration.QualifiedModuleName.ComponentType == ComponentType.ClassModule - && attribute.Values[0].Equals(Tokens.False)) - || (declaration.QualifiedModuleName.ComponentType == ComponentType.UserForm - && attribute.Values[0].Equals(Tokens.True)); - default: - return false; - } + return Attributes.IsDefaultAttribute(declaration.QualifiedModuleName.ComponentType, attribute.Name, attribute.Values); } private static bool MissesCorrespondingModuleAnnotation(Declaration declaration, AttributeNode attribute) diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/ModuleWithoutFolderInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/ModuleWithoutFolderInspection.cs index abc42d03a3..a6c8e77e2b 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Concrete/ModuleWithoutFolderInspection.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/ModuleWithoutFolderInspection.cs @@ -13,8 +13,7 @@ public sealed class ModuleWithoutFolderInspection : InspectionBase { public ModuleWithoutFolderInspection(RubberduckParserState state) : base(state) - { - } + {} protected override IEnumerable DoGetInspectionResults() { @@ -22,7 +21,9 @@ protected override IEnumerable DoGetInspectionResults() .Where(w => w.Annotations.All(a => a.AnnotationType != AnnotationType.Folder)) .ToList(); - return modulesWithoutFolderAnnotation.Select(declaration => + return modulesWithoutFolderAnnotation + .Where(declaration => !IsIgnoringInspectionResultFor(declaration, AnnotationName)) + .Select(declaration => new DeclarationInspectionResult(this, string.Format(InspectionResults.ModuleWithoutFolderInspection, declaration.IdentifierName), declaration)); } } diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/MoveFieldCloserToUsageInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/MoveFieldCloserToUsageInspection.cs index ad7954720b..78dabe97ba 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Concrete/MoveFieldCloserToUsageInspection.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/MoveFieldCloserToUsageInspection.cs @@ -20,7 +20,7 @@ protected override IEnumerable DoGetInspectionResults() .Where(declaration => { if (declaration.IsWithEvents - || !new[] {DeclarationType.ClassModule, DeclarationType.ProceduralModule}.Contains(declaration.ParentDeclaration.DeclarationType) + || !new[] {DeclarationType.ClassModule, DeclarationType.Document, DeclarationType.ProceduralModule}.Contains(declaration.ParentDeclaration.DeclarationType) || IsIgnoringInspectionResultFor(declaration, AnnotationName)) { return false; diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/NonReturningFunctionInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/NonReturningFunctionInspection.cs index 716623f30c..9411104ad5 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Concrete/NonReturningFunctionInspection.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/NonReturningFunctionInspection.cs @@ -51,7 +51,7 @@ protected override IEnumerable DoGetInspectionResults() private bool IsAssignedByRefArgument(Declaration enclosingProcedure, IdentifierReference reference) { var argExpression = reference.Context.GetAncestor(); - var parameter = State.DeclarationFinder.FindParameterFromArgument(argExpression, enclosingProcedure); + var parameter = State.DeclarationFinder.FindParameterOfNonDefaultMemberFromSimpleArgumentNotPassedByValExplicitly(argExpression, enclosingProcedure); // note: not recursive, by design. return parameter != null diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/ParameterCanBeByValInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/ParameterCanBeByValInspection.cs index 33d051bb1e..93f803aca9 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Concrete/ParameterCanBeByValInspection.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/ParameterCanBeByValInspection.cs @@ -1,14 +1,17 @@ +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Rubberduck.Common; using Rubberduck.Inspections.Abstract; using Rubberduck.Inspections.Results; +using Rubberduck.Parsing; using Rubberduck.Parsing.Grammar; using Rubberduck.Parsing.Inspections.Abstract; using Rubberduck.Resources.Inspections; using Rubberduck.Parsing.Symbols; using Rubberduck.Parsing.VBA; +using Rubberduck.Parsing.VBA.Extensions; namespace Rubberduck.Inspections.Concrete { @@ -19,134 +22,207 @@ public ParameterCanBeByValInspection(RubberduckParserState state) protected override IEnumerable DoGetInspectionResults() { - var declarations = UserDeclarations.ToArray(); - var issues = new List(); - - var interfaceDeclarationMembers = State.DeclarationFinder.FindAllInterfaceMembers().ToArray(); - var interfaceScopes = State.DeclarationFinder.FindAllInterfaceImplementingMembers().Concat(interfaceDeclarationMembers).Select(s => s.Scope).ToArray(); - - issues.AddRange(GetResults(declarations, interfaceDeclarationMembers)); - - var eventMembers = declarations.Where(item => item.DeclarationType == DeclarationType.Event).ToArray(); - var formEventHandlerScopes = State.FindFormEventHandlers().Select(handler => handler.Scope).ToArray(); - var eventHandlerScopes = State.DeclarationFinder.FindEventHandlers().Concat(declarations.FindUserEventHandlers()).Select(e => e.Scope).ToArray(); - var eventScopes = eventMembers.Select(s => s.Scope) - .Concat(formEventHandlerScopes) - .Concat(eventHandlerScopes) - .ToArray(); - - issues.AddRange(GetResults(declarations, eventMembers)); - - var declareScopes = declarations.Where(item => - item.DeclarationType == DeclarationType.LibraryFunction - || item.DeclarationType == DeclarationType.LibraryProcedure) - .Select(e => e.Scope) - .ToArray(); - - issues.AddRange(declarations.OfType() - .Where(declaration => IsIssue(declaration, declarations, declareScopes, eventScopes, interfaceScopes)) - .Select(issue => new DeclarationInspectionResult(this, string.Format(InspectionResults.ParameterCanBeByValInspection, issue.IdentifierName), issue))); - - return issues; + var parameters = State.DeclarationFinder + .UserDeclarations(DeclarationType.Parameter) + .OfType().ToList(); + var parametersThatCanBeChangedToBePassedByVal = new List(); + + var interfaceDeclarationMembers = State.DeclarationFinder.FindAllInterfaceMembers().ToList(); + var interfaceScopeDeclarations = State.DeclarationFinder + .FindAllInterfaceImplementingMembers() + .Concat(interfaceDeclarationMembers) + .ToHashSet(); + + parametersThatCanBeChangedToBePassedByVal.AddRange(InterFaceMembersThatCanBeChangedToBePassedByVal(interfaceDeclarationMembers)); + + var eventMembers = State.DeclarationFinder.UserDeclarations(DeclarationType.Event).ToList(); + var formEventHandlerScopeDeclarations = State.FindFormEventHandlers(); + var eventHandlerScopeDeclarations = State.DeclarationFinder.FindEventHandlers().Concat(parameters.FindUserEventHandlers()); + var eventScopeDeclarations = eventMembers + .Concat(formEventHandlerScopeDeclarations) + .Concat(eventHandlerScopeDeclarations) + .ToHashSet(); + + parametersThatCanBeChangedToBePassedByVal.AddRange(EventMembersThatCanBeChangedToBePassedByVal(eventMembers)); + + parametersThatCanBeChangedToBePassedByVal + .AddRange(parameters.Where(parameter => CanBeChangedToBePassedByVal(parameter, eventScopeDeclarations, interfaceScopeDeclarations))); + + return parametersThatCanBeChangedToBePassedByVal + .Where(parameter => !IsIgnoringInspectionResultFor(parameter, AnnotationName)) + .Select(parameter => new DeclarationInspectionResult(this, string.Format(InspectionResults.ParameterCanBeByValInspection, parameter.IdentifierName), parameter)); } - private bool IsIssue(ParameterDeclaration declaration, Declaration[] userDeclarations, string[] declareScopes, string[] eventScopes, string[] interfaceScopes) + private bool CanBeChangedToBePassedByVal(ParameterDeclaration parameter, HashSet eventScopeDeclarations, HashSet interfaceScopeDeclarations) { - var isIssue = - !declaration.IsArray - && !declaration.IsParamArray - && (declaration.IsByRef || declaration.IsImplicitByRef) - && (declaration.AsTypeDeclaration == null || declaration.AsTypeDeclaration.DeclarationType != DeclarationType.ClassModule && declaration.AsTypeDeclaration.DeclarationType != DeclarationType.UserDefinedType && declaration.AsTypeDeclaration.DeclarationType != DeclarationType.Enumeration) - && !declareScopes.Contains(declaration.ParentScope) - && !eventScopes.Contains(declaration.ParentScope) - && !interfaceScopes.Contains(declaration.ParentScope) - && !IsUsedAsByRefParam(userDeclarations, declaration) - && (!declaration.References.Any() || !declaration.References.Any(reference => reference.IsAssignment)); + var enclosingMember = parameter.ParentScopeDeclaration; + var isIssue = !interfaceScopeDeclarations.Contains(enclosingMember) + && !eventScopeDeclarations.Contains(enclosingMember) + && CanBeChangedToBePassedByValIndividually(parameter); return isIssue; } - private IEnumerable GetResults(Declaration[] declarations, Declaration[] declarationMembers) + private bool CanBeChangedToBePassedByValIndividually(ParameterDeclaration parameter) { - foreach (var declaration in declarationMembers) - { - var declarationParameters = declarations.OfType() - .Where(d => Equals(d.ParentDeclaration, declaration)) - .OrderBy(o => o.Selection.StartLine) - .ThenBy(t => t.Selection.StartColumn) - .ToList(); + var canPossiblyBeChangedToBePassedByVal = + !parameter.IsArray + && !parameter.IsParamArray + && (parameter.IsByRef || parameter.IsImplicitByRef) + && !IsParameterOfDeclaredLibraryFunction(parameter) + && (parameter.AsTypeDeclaration == null + || (!parameter.AsTypeDeclaration.DeclarationType.HasFlag(DeclarationType.ClassModule) + && parameter.AsTypeDeclaration.DeclarationType != DeclarationType.UserDefinedType + && parameter.AsTypeDeclaration.DeclarationType != DeclarationType.Enumeration)) + && !parameter.References.Any(reference => reference.IsAssignment) + && !IsPotentiallyUsedAsByRefParameter(parameter); + return canPossiblyBeChangedToBePassedByVal; + } - if (!declarationParameters.Any()) { continue; } - var parametersAreByRef = declarationParameters.Select(s => true).ToList(); + private static bool IsParameterOfDeclaredLibraryFunction(ParameterDeclaration parameter) + { + var parentMember = parameter.ParentScopeDeclaration; + return parentMember.DeclarationType == DeclarationType.LibraryFunction + || parentMember.DeclarationType == DeclarationType.LibraryProcedure; + } - var members = declarationMembers.Any(a => a.DeclarationType == DeclarationType.Event) - ? declarations.FindHandlersForEvent(declaration).Select(s => s.Item2).ToList() - : State.DeclarationFinder.FindInterfaceImplementationMembers(declaration).Cast().ToList(); + private IEnumerable InterFaceMembersThatCanBeChangedToBePassedByVal(List interfaceMembers) + { + foreach (var memberDeclaration in interfaceMembers.OfType()) + { + var interfaceParameters = memberDeclaration.Parameters.ToList(); + if (!interfaceParameters.Any()) + { + continue; + } + + var parameterCanBeChangedToBeByVal = interfaceParameters.Select(parameter => CanBeChangedToBePassedByValIndividually(parameter)).ToList(); - foreach (var member in members) + var implementingMembers = State.DeclarationFinder.FindInterfaceImplementationMembers(memberDeclaration); + foreach (var implementingMember in implementingMembers) { - var parameters = declarations.OfType() - .Where(d => Equals(d.ParentDeclaration, member)) - .OrderBy(o => o.Selection.StartLine) - .ThenBy(t => t.Selection.StartColumn) - .ToList(); + var implementationParameters = implementingMember.Parameters.ToList(); //If you hit this assert, reopen https://github.com/rubberduck-vba/Rubberduck/issues/3906 - Debug.Assert(parametersAreByRef.Count == parameters.Count); + Debug.Assert(parameterCanBeChangedToBeByVal.Count == implementationParameters.Count); - for (var i = 0; i < parameters.Count; i++) + for (var i = 0; i < implementationParameters.Count; i++) { - parametersAreByRef[i] = parametersAreByRef[i] && - !IsUsedAsByRefParam(declarations, parameters[i]) && - ((VBAParser.ArgContext) parameters[i].Context).BYVAL() == null && - !parameters[i].References.Any(reference => reference.IsAssignment); + parameterCanBeChangedToBeByVal[i] = parameterCanBeChangedToBeByVal[i] + && CanBeChangedToBePassedByValIndividually(implementationParameters[i]); } } - for (var i = 0; i < declarationParameters.Count; i++) + for (var i = 0; i < parameterCanBeChangedToBeByVal.Count; i++) { - if (parametersAreByRef[i]) + if (parameterCanBeChangedToBeByVal[i]) { - yield return new DeclarationInspectionResult(this, - string.Format(InspectionResults.ParameterCanBeByValInspection, declarationParameters[i].IdentifierName), - declarationParameters[i]); + yield return interfaceParameters[i]; } } } } - private static bool IsUsedAsByRefParam(IEnumerable declarations, Declaration parameter) + private IEnumerable EventMembersThatCanBeChangedToBePassedByVal(IEnumerable eventMembers) { - // find the procedure calls in the procedure of the parameter. - // note: works harder than it needs to when procedure has more than a single procedure call... - // ...but caching [declarations] would be a memory leak - var items = declarations as List ?? declarations.ToList(); + foreach (var memberDeclaration in eventMembers) + { + var eventParameters = (memberDeclaration as IParameterizedDeclaration)?.Parameters.ToList(); + if (!eventParameters?.Any() ?? false) + { + continue; + } - var procedureCalls = items.Where(item => item.DeclarationType.HasFlag(DeclarationType.Member)) - .SelectMany(member => member.References.Where(reference => reference.ParentScoping.Equals(parameter.ParentScopeDeclaration))) - .GroupBy(call => call.Declaration) - .ToList(); // only check a procedure once. its declaration doesn't change if it's called 20 times anyway. + var parameterCanBeChangedToBeByVal = eventParameters.Select(parameter => parameter.IsByRef).ToList(); - foreach (var item in procedureCalls) - { - var calledProcedureArgs = items - .Where(arg => arg.DeclarationType == DeclarationType.Parameter && arg.ParentScope == item.Key.Scope) - .OrderBy(arg => arg.Selection.StartLine) - .ThenBy(arg => arg.Selection.StartColumn) - .ToArray(); + //todo: Find a better way to find the handlers. + var eventHandlers = State.DeclarationFinder + .AllUserDeclarations + .FindHandlersForEvent(memberDeclaration) + .Select(s => s.Item2) + .ToList(); - foreach (var declaration in calledProcedureArgs) + foreach (var eventHandler in eventHandlers.OfType()) { - if (((VBAParser.ArgContext) declaration.Context).BYVAL() != null) + var handlerParameters = eventHandler.Parameters.ToList(); + + //If you hit this assert, reopen https://github.com/rubberduck-vba/Rubberduck/issues/3906 + Debug.Assert(parameterCanBeChangedToBeByVal.Count == handlerParameters.Count); + + for (var i = 0; i < handlerParameters.Count; i++) { - continue; + parameterCanBeChangedToBeByVal[i] = parameterCanBeChangedToBeByVal[i] + && CanBeChangedToBePassedByValIndividually(handlerParameters[i]); } + } - if (declaration.References.Any(reference => reference.IsAssignment)) + for (var i = 0; i < parameterCanBeChangedToBeByVal.Count; i++) + { + if (parameterCanBeChangedToBeByVal[i]) { - return true; + yield return eventParameters[i]; } } } + } + + private bool IsPotentiallyUsedAsByRefParameter(ParameterDeclaration parameter) + { + return IsPotentiallyUsedAsByRefMethodParameter(parameter) + || IsPotentiallyUsedAsByRefEventParameter(parameter); + } + + private bool IsPotentiallyUsedAsByRefMethodParameter(ParameterDeclaration parameter) + { + //The condition on the text of the argument context excludes the cases where the argument is either passed explicitly by value + //or used inside a non-trivial expression, e.g. an arithmetic expression. + var argumentsBeingTheParameter = parameter.References + .Select(reference => reference.Context.GetAncestor()) + .Where(context => context != null && context.GetText().Equals(parameter.IdentifierName, StringComparison.OrdinalIgnoreCase)); + + foreach (var argument in argumentsBeingTheParameter) + { + var parameterCorrespondingToArgument = State.DeclarationFinder + .FindParameterOfNonDefaultMemberFromSimpleArgumentNotPassedByValExplicitly(argument, parameter.QualifiedModuleName); + + if (parameterCorrespondingToArgument == null) + { + //We have no idea what parameter it is passed to ar argument. So, we have to err on the safe side and assume it is passed by reference. + return true; + } + + if (parameterCorrespondingToArgument.IsByRef) + { + return true; + } + } + + return false; + } + + private bool IsPotentiallyUsedAsByRefEventParameter(ParameterDeclaration parameter) + { + //The condition on the text of the eventArgument context excludes the cases where the argument is either passed explicitly by value + //or used inside a non-trivial expression, e.g. an arithmetic expression. + var argumentsBeingTheParameter = parameter.References + .Select(reference => reference.Context.GetAncestor()) + .Where(context => context != null && context.GetText().Equals(parameter.IdentifierName, StringComparison.OrdinalIgnoreCase)); + + foreach (var argument in argumentsBeingTheParameter) + { + var parameterCorrespondingToArgument = State.DeclarationFinder + .FindParameterFromSimpleEventArgumentNotPassedByValExplicitly(argument, parameter.QualifiedModuleName); + + if (parameterCorrespondingToArgument == null) + { + //We have no idea what parameter it is passed to ar argument. So, we have to err on the safe side and assume it is passed by reference. + return true; + } + + if (parameterCorrespondingToArgument.IsByRef) + { + return true; + } + } return false; } diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/ProcedureCanBeWrittenAsFunctionInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/ProcedureCanBeWrittenAsFunctionInspection.cs index 4be4831ca2..9f1ffbcd04 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Concrete/ProcedureCanBeWrittenAsFunctionInspection.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/ProcedureCanBeWrittenAsFunctionInspection.cs @@ -26,6 +26,11 @@ public ProcedureCanBeWrittenAsFunctionInspection(RubberduckParserState state) protected override IEnumerable DoGetInspectionResults() { + if (!Listener.Contexts.Any()) + { + return Enumerable.Empty(); + } + var userDeclarations = UserDeclarations.ToList(); var builtinHandlers = State.DeclarationFinder.FindEventHandlers().ToList(); @@ -38,17 +43,31 @@ protected override IEnumerable DoGetInspectionResults() return Listener.Contexts .Where(context => context.Context.Parent is VBAParser.SubStmtContext - && contextLookup[context.Context.GetChild()].References - .Any(reference => reference.IsAssignment)) - .Select(context => contextLookup[(VBAParser.SubStmtContext)context.Context.Parent]) - .Where(decl => !IsIgnoringInspectionResultFor(decl, AnnotationName) && - !ignored.Contains(decl) && - userDeclarations.Where(item => item.IsWithEvents) + && HasArgumentReferencesWithIsAssignmentFlagged(context)) + .Select(context => GetSubStmtParentDeclaration(context)) + .Where(decl => decl != null && + !IsIgnoringInspectionResultFor(decl, AnnotationName) && + !ignored.Contains(decl) && + userDeclarations.Where(item => item.IsWithEvents) .All(withEvents => userDeclarations.FindEventProcedures(withEvents) == null) && !builtinHandlers.Contains(decl)) .Select(result => new DeclarationInspectionResult(this, string.Format(InspectionResults.ProcedureCanBeWrittenAsFunctionInspection, result.IdentifierName), result)); + + bool HasArgumentReferencesWithIsAssignmentFlagged(QualifiedContext context) + { + return contextLookup.TryGetValue(context.Context.GetChild(), out Declaration decl) + ? decl.References.Any(rf => rf.IsAssignment) + : false; + } + + Declaration GetSubStmtParentDeclaration(QualifiedContext context) + { + return contextLookup.TryGetValue(context.Context.Parent as VBAParser.SubStmtContext, out Declaration decl) + ? decl + : null; + } } public class SingleByRefParamArgListListener : VBAParserBaseListener, IInspectionListener diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/ProcedureNotUsedInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/ProcedureNotUsedInspection.cs index 2bc9832b3a..3b4f32fcd7 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Concrete/ProcedureNotUsedInspection.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/ProcedureNotUsedInspection.cs @@ -29,8 +29,10 @@ protected override IEnumerable DoGetInspectionResults() { var declarations = UserDeclarations.ToList(); - var classes = State.DeclarationFinder.UserDeclarations(DeclarationType.ClassModule).ToList(); // declarations.Where(item => item.DeclarationType == DeclarationType.ClassModule).ToList(); - var modules = State.DeclarationFinder.UserDeclarations(DeclarationType.ProceduralModule).ToList(); // declarations.Where(item => item.DeclarationType == DeclarationType.ProceduralModule).ToList(); + var classes = State.DeclarationFinder.UserDeclarations(DeclarationType.ClassModule) + .Concat(State.DeclarationFinder.UserDeclarations(DeclarationType.Document)) + .ToList(); + var modules = State.DeclarationFinder.UserDeclarations(DeclarationType.ProceduralModule).ToList(); var handlers = State.DeclarationFinder.UserDeclarations(DeclarationType.Control) .SelectMany(control => declarations.FindEventHandlers(control)).ToList(); diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/ShadowedDeclarationInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/ShadowedDeclarationInspection.cs index 869f3912b8..517045e911 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Concrete/ShadowedDeclarationInspection.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/ShadowedDeclarationInspection.cs @@ -119,8 +119,8 @@ private static bool DeclarationInReferencedProjectCanBeShadowed(Declaration orig return false; } - var originalDeclarationComponentType = originalDeclaration.QualifiedName.QualifiedModuleName.ComponentType; - var userDeclarationComponentType = userDeclaration.QualifiedName.QualifiedModuleName.ComponentType; + var originalDeclarationEnclosingType = originalDeclaration.QualifiedName.QualifiedModuleName.ComponentType; + var userDeclarationEnclosingType = userDeclaration.QualifiedName.QualifiedModuleName.ComponentType; // It is not possible to directly access a Parameter, UDT Member or Label declared in another project if (originalDeclaration.DeclarationType == DeclarationType.Parameter || originalDeclaration.DeclarationType == DeclarationType.UserDefinedTypeMember || @@ -136,20 +136,20 @@ private static bool DeclarationInReferencedProjectCanBeShadowed(Declaration orig } // It is not possible to directly access a UserForm or Document declared in another project, nor any declarations placed inside them - if (originalDeclarationComponentType == ComponentType.UserForm || originalDeclarationComponentType == ComponentType.Document) + if (originalDeclarationEnclosingType == ComponentType.UserForm || originalDeclarationEnclosingType == ComponentType.Document) { return false; } // It is not possible to directly access any declarations placed inside a Class Module - if (originalDeclaration.DeclarationType != DeclarationType.ClassModule && originalDeclarationComponentType == ComponentType.ClassModule) + if (originalDeclaration.DeclarationType != DeclarationType.ClassModule && originalDeclarationEnclosingType == ComponentType.ClassModule) { return false; } - if (userDeclaration.DeclarationType == DeclarationType.ClassModule) + if (userDeclaration.DeclarationType == DeclarationType.ClassModule || userDeclaration.DeclarationType == DeclarationType.Document) { - switch (userDeclarationComponentType) + switch (userDeclarationEnclosingType) { case ComponentType.UserForm when !ReferencedProjectTypeShadowingRelations[originalDeclaration.DeclarationType].Contains(DeclarationType.UserForm): return false; @@ -158,8 +158,8 @@ private static bool DeclarationInReferencedProjectCanBeShadowed(Declaration orig } } - if (userDeclaration.DeclarationType != DeclarationType.ClassModule || - (userDeclarationComponentType != ComponentType.UserForm && userDeclarationComponentType != ComponentType.Document)) + if ((userDeclaration.DeclarationType != DeclarationType.ClassModule && userDeclaration.DeclarationType != DeclarationType.Document) || + (userDeclarationEnclosingType != ComponentType.UserForm && userDeclarationEnclosingType != ComponentType.Document)) { if (!ReferencedProjectTypeShadowingRelations[originalDeclaration.DeclarationType].Contains(userDeclaration.DeclarationType)) { @@ -188,7 +188,7 @@ private static bool DeclarationInAnotherComponentCanBeShadowed(Declaration origi return false; } - var originalDeclarationComponentType = originalDeclaration.QualifiedName.QualifiedModuleName.ComponentType; + var originalDeclarationEnclosingType = originalDeclaration.QualifiedName.QualifiedModuleName.ComponentType; // It is not possible to directly access a Parameter, UDT Member or Label declared in another component. if (originalDeclaration.DeclarationType == DeclarationType.Parameter || originalDeclaration.DeclarationType == DeclarationType.UserDefinedTypeMember || @@ -198,27 +198,33 @@ private static bool DeclarationInAnotherComponentCanBeShadowed(Declaration origi } // It is not possible to directly access any declarations placed inside a Class Module. - if (originalDeclaration.DeclarationType != DeclarationType.ClassModule && originalDeclarationComponentType == ComponentType.ClassModule) + if (originalDeclaration.DeclarationType != DeclarationType.ClassModule && + originalDeclaration.DeclarationType != DeclarationType.Document && + originalDeclarationEnclosingType == ComponentType.ClassModule) { return false; } // It is not possible to directly access any declarations placed inside a Document Module. (Document Modules have DeclarationType ClassMoodule.) - if (originalDeclaration.DeclarationType != DeclarationType.ClassModule && originalDeclarationComponentType == ComponentType.Document) + if (originalDeclaration.DeclarationType != DeclarationType.ClassModule && + originalDeclaration.DeclarationType != DeclarationType.Document && + originalDeclarationEnclosingType == ComponentType.Document) { return false; } // It is not possible to directly access any declarations placed inside a User Form. (User Forms have DeclarationType ClassMoodule.) - if (originalDeclaration.DeclarationType != DeclarationType.ClassModule && originalDeclarationComponentType == ComponentType.UserForm) + if (originalDeclaration.DeclarationType != DeclarationType.ClassModule && + originalDeclaration.DeclarationType != DeclarationType.Document && + originalDeclarationEnclosingType == ComponentType.UserForm) { return false; } - if (originalDeclaration.DeclarationType == DeclarationType.ClassModule) + if (originalDeclaration.DeclarationType == DeclarationType.ClassModule || originalDeclaration.DeclarationType == DeclarationType.Document) { // Syntax of instantiating a new class makes it impossible to be shadowed - switch (originalDeclarationComponentType) + switch (originalDeclarationEnclosingType) { case ComponentType.ClassModule: return false; @@ -250,8 +256,12 @@ private static bool DeclarationInAnotherComponentCanBeShadowed(Declaration origi private static bool DeclarationInTheSameComponentCanBeShadowed(Declaration originalDeclaration, Declaration userDeclaration) { // Shadowing the component containing the declaration is not a problem, because it is possible to directly access declarations inside that component - if (originalDeclaration.DeclarationType == DeclarationType.ProceduralModule || originalDeclaration.DeclarationType == DeclarationType.ClassModule || - userDeclaration.DeclarationType == DeclarationType.ProceduralModule || userDeclaration.DeclarationType == DeclarationType.ClassModule) + if (originalDeclaration.DeclarationType == DeclarationType.ProceduralModule || + originalDeclaration.DeclarationType == DeclarationType.ClassModule || + originalDeclaration.DeclarationType == DeclarationType.Document || + userDeclaration.DeclarationType == DeclarationType.ProceduralModule || + userDeclaration.DeclarationType == DeclarationType.ClassModule || + userDeclaration.DeclarationType == DeclarationType.Document) { return false; } @@ -360,7 +370,7 @@ private static bool DeclarationIsLocal(Declaration declaration) }.ToHashSet(), [DeclarationType.ClassModule] = new[] { - DeclarationType.Project, DeclarationType.ProceduralModule, DeclarationType.ClassModule, DeclarationType.UserForm + DeclarationType.Project, DeclarationType.ProceduralModule, DeclarationType.ClassModule, DeclarationType.UserForm, DeclarationType.Document }.ToHashSet(), [DeclarationType.Procedure] = new[] { diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/KeywordsUsedAsMemberInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/KeywordsUsedAsMemberInspection.cs new file mode 100644 index 0000000000..9ca8b126a9 --- /dev/null +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/KeywordsUsedAsMemberInspection.cs @@ -0,0 +1,159 @@ +using System.Collections.Generic; +using System.Linq; +using Rubberduck.Inspections.Abstract; +using Rubberduck.Inspections.Results; +using Rubberduck.Parsing.Grammar; +using Rubberduck.Parsing.Inspections.Abstract; +using Rubberduck.Parsing.Symbols; +using Rubberduck.Parsing.VBA; +using Rubberduck.Resources.Inspections; + +namespace Rubberduck.Inspections.Inspections.Concrete.ThunderCode +{ + public class KeywordsUsedAsMemberInspection : InspectionBase + { + public KeywordsUsedAsMemberInspection(RubberduckParserState state) : base(state) { } + + protected override IEnumerable DoGetInspectionResults() + { + return State.DeclarationFinder.UserDeclarations(DeclarationType.UserDefinedTypeMember) + .Concat(State.DeclarationFinder.UserDeclarations(DeclarationType.EnumerationMember)) + .Where(m => ReservedKeywords.Any(k => + k.ToLowerInvariant().Equals( + m.IdentifierName.Trim().TrimStart('[').TrimEnd(']').ToLowerInvariant()))) + .Select(m => new DeclarationInspectionResult( + this, + InspectionResults.KeywordsUsedAsMemberInspection. + ThunderCodeFormat(m.IdentifierName), + m + )); + } + + // MS-VBAL 3.3.5.2 Reserved Identifiers and IDENTIFIER + private static IEnumerable ReservedKeywords = new [] + { + /* +Statement-keyword = "Call" / "Case" /"Close" / "Const"/ "Declare" / "DefBool" / "DefByte" / + "DefCur" / "DefDate" / "DefDbl" / "DefInt" / "DefLng" / "DefLngLng" / + "DefLngPtr" / "DefObj" / "DefSng" / "DefStr" / "DefVar" / "Dim" / "Do" / + "Else" / "ElseIf" / "End" / "EndIf" / "Enum" / "Erase" / "Event" / + "Exit" / "For" / "Friend" / "Function" / "Get" / "Global" / "GoSub" / + "GoTo" / "If" / "Implements"/ "Input" / "Let" / "Lock" / "Loop" / + "LSet"/ "Next" / "On" / "Open" / "Option" / "Print" / "Private" / + "Public" / "Put" / "RaiseEvent" / "ReDim" / "Resume" / "Return" / + "RSet" / "Seek" / "Select" / "Set" / "Static" / "Stop" / "Sub" / + "Type" / "Unlock" / "Wend" / "While" / "With" / "Write" + */ + + Tokens.Call, + Tokens.Case, + Tokens.Close, + Tokens.Const, + Tokens.Declare, + "DefBool", + "DefByte", + "DefCur", + "DefDate", + "DefDbl", + "DefInt", + "DefLng", + "DefLngLng", + "DefLngPtr", + "DefObj", + "DefSng", + "DefStr", + "DefVar", + Tokens.Dim, + Tokens.Do, + Tokens.Else, + Tokens.ElseIf, + Tokens.End, + "EndIf", + Tokens.Enum, + "Erase", + "Event", + Tokens.Exit, + Tokens.For, + Tokens.Friend, + Tokens.Function, + Tokens.Get, + Tokens.Global, + Tokens.GoSub, + Tokens.GoTo, + Tokens.If, + Tokens.Implements, + Tokens.Input, + Tokens.Let, + "Lock", + Tokens.Loop, + "LSet", + Tokens.Next, + Tokens.On, + Tokens.Open, + Tokens.Option, + Tokens.Print, + Tokens.Private, + Tokens.Public, + Tokens.Put, + "RaiseEvent", + Tokens.ReDim, + Tokens.Resume, + Tokens.Return, + "RSet", + "Seek", + Tokens.Select, + Tokens.Set, + Tokens.Static, + Tokens.Stop, + Tokens.Sub, + Tokens.Type, + "Unlock", + Tokens.Wend, + Tokens.While, + Tokens.With, + Tokens.Write, + + /* +rem-keyword = "Rem" marker-keyword = "Any" / "As"/ "ByRef" / "ByVal "/"Case" / "Each" / + "Else" /"In"/ "New" / "Shared" / "Until" / "WithEvents" / "Write" / "Optional" / + "ParamArray" / "Preserve" / "Spc" / "Tab" / "Then" / "To" + */ + + Tokens.Any, + Tokens.As, + Tokens.ByRef, + Tokens.ByVal, + Tokens.Case, + Tokens.Each, + Tokens.In, + Tokens.New, + "Shared", + Tokens.Until, + "WithEvents", + Tokens.Optional, + Tokens.ParamArray, + Tokens.Preserve, + Tokens.Spc, + "Tab", + Tokens.Then, + Tokens.To, + + /* +operator-identifier = "AddressOf" / "And" / "Eqv" / "Imp" / "Is" / "Like" / "New" / "Mod" / + "Not" / "Or" / "TypeOf" / "Xor" + */ + + Tokens.AddressOf, + Tokens.And, + Tokens.Eqv, + Tokens.Imp, + Tokens.Is, + Tokens.Like, + Tokens.Mod, + Tokens.Not, + Tokens.Or, + Tokens.TypeOf, + Tokens.XOr + }; + } +} diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/LineContinuationBetweenKeywordsInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/LineContinuationBetweenKeywordsInspection.cs new file mode 100644 index 0000000000..4cf4ea4de9 --- /dev/null +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/LineContinuationBetweenKeywordsInspection.cs @@ -0,0 +1,161 @@ +using System.Collections.Generic; +using System.Linq; +using Antlr4.Runtime; +using Antlr4.Runtime.Tree; +using Rubberduck.Inspections.Abstract; +using Rubberduck.Inspections.Results; +using Rubberduck.Parsing; +using Rubberduck.Parsing.Grammar; +using Rubberduck.Parsing.Inspections.Abstract; +using Rubberduck.Parsing.VBA; +using Rubberduck.Resources.Inspections; +using Rubberduck.VBEditor; + +namespace Rubberduck.Inspections.Inspections.Concrete.ThunderCode +{ + /// + /// Note that the inspection only checks a subset of possible "evil" line continatuions + /// for both simplicity and performance reasons. Exahustive inspection would likely take + /// too much effort. + /// + public class LineContinuationBetweenKeywordsInspection : ParseTreeInspectionBase + { + public LineContinuationBetweenKeywordsInspection(RubberduckParserState state) : base(state) + { + Listener = new LineContinuationBetweenKeywordsListener(); + } + + protected override IEnumerable DoGetInspectionResults() + { + return Listener.Contexts.Select(c => new QualifiedContextInspectionResult( + this, + InspectionResults.LineContinuationBetweenKeywordsInspection. + ThunderCodeFormat(), + c)); + } + + public override IInspectionListener Listener { get; } + + public class LineContinuationBetweenKeywordsListener : VBAParserBaseListener, IInspectionListener + { + private readonly List> _contexts = new List>(); + + public IReadOnlyList> Contexts => _contexts; + + public void ClearContexts() + { + _contexts.Clear(); + } + + public QualifiedModuleName CurrentModuleName { get; set; } + + public override void EnterSubStmt(VBAParser.SubStmtContext context) + { + CheckContext(context, context.END_SUB()); + base.EnterSubStmt(context); + } + + public override void EnterFunctionStmt(VBAParser.FunctionStmtContext context) + { + CheckContext(context, context.END_FUNCTION()); + base.EnterFunctionStmt(context); + } + + public override void EnterPropertyGetStmt(VBAParser.PropertyGetStmtContext context) + { + CheckContext(context, context.PROPERTY_GET()); + CheckContext(context, context.END_PROPERTY()); + base.EnterPropertyGetStmt(context); + } + + public override void EnterPropertyLetStmt(VBAParser.PropertyLetStmtContext context) + { + CheckContext(context, context.PROPERTY_LET()); + CheckContext(context, context.END_PROPERTY()); + base.EnterPropertyLetStmt(context); + } + + public override void EnterPropertySetStmt(VBAParser.PropertySetStmtContext context) + { + CheckContext(context, context.PROPERTY_SET()); + CheckContext(context, context.END_PROPERTY()); + base.EnterPropertySetStmt(context); + } + + public override void EnterSelectCaseStmt(VBAParser.SelectCaseStmtContext context) + { + CheckContext(context, context.END_SELECT()); + base.EnterSelectCaseStmt(context); + } + + public override void EnterWithStmt(VBAParser.WithStmtContext context) + { + CheckContext(context, context.END_WITH()); + base.EnterWithStmt(context); + } + + public override void EnterExitStmt(VBAParser.ExitStmtContext context) + { + CheckContext(context, context.EXIT_DO()); + CheckContext(context, context.EXIT_FOR()); + CheckContext(context, context.EXIT_FUNCTION()); + CheckContext(context, context.EXIT_PROPERTY()); + CheckContext(context, context.EXIT_SUB()); + base.EnterExitStmt(context); + } + + public override void EnterOnErrorStmt(VBAParser.OnErrorStmtContext context) + { + CheckContext(context, context.ON_ERROR()); + CheckContext(context, context.ON_LOCAL_ERROR()); + base.EnterOnErrorStmt(context); + } + + public override void EnterOptionBaseStmt(VBAParser.OptionBaseStmtContext context) + { + CheckContext(context, context.OPTION_BASE()); + base.EnterOptionBaseStmt(context); + } + + public override void EnterOptionCompareStmt(VBAParser.OptionCompareStmtContext context) + { + CheckContext(context, context.OPTION_COMPARE()); + base.EnterOptionCompareStmt(context); + } + + public override void EnterOptionExplicitStmt(VBAParser.OptionExplicitStmtContext context) + { + CheckContext(context, context.OPTION_EXPLICIT()); + base.EnterOptionExplicitStmt(context); + } + + public override void EnterOptionPrivateModuleStmt(VBAParser.OptionPrivateModuleStmtContext context) + { + CheckContext(context, context.OPTION_PRIVATE_MODULE()); + base.EnterOptionPrivateModuleStmt(context); + } + + public override void EnterEnumerationStmt(VBAParser.EnumerationStmtContext context) + { + CheckContext(context, context.END_ENUM()); + base.EnterEnumerationStmt(context); + } + + public override void EnterUdtDeclaration(VBAParser.UdtDeclarationContext context) + { + CheckContext(context, context.END_TYPE()); + base.EnterUdtDeclaration(context); + } + + + + private void CheckContext(ParserRuleContext context, IParseTree subTreeToExamine) + { + if (subTreeToExamine?.GetText().Contains("_") ?? false) + { + _contexts.Add(new QualifiedContext(CurrentModuleName, context)); + } + } + } + } +} diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/NegativeLineNumberInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/NegativeLineNumberInspection.cs new file mode 100644 index 0000000000..854459bb06 --- /dev/null +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/NegativeLineNumberInspection.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using System.Linq; +using Antlr4.Runtime; +using Antlr4.Runtime.Tree; +using Rubberduck.Inspections.Abstract; +using Rubberduck.Inspections.Results; +using Rubberduck.Parsing; +using Rubberduck.Parsing.Grammar; +using Rubberduck.Parsing.Inspections.Abstract; +using Rubberduck.Parsing.VBA; +using Rubberduck.Resources.Inspections; +using Rubberduck.VBEditor; + +namespace Rubberduck.Inspections.Inspections.Concrete.ThunderCode +{ + public class NegativeLineNumberInspection : ParseTreeInspectionBase + { + public NegativeLineNumberInspection(RubberduckParserState state) : base(state) + { + Listener = new NegativeLineNumberKeywordsListener(); + } + + protected override IEnumerable DoGetInspectionResults() + { + return Listener.Contexts.Select(c => new QualifiedContextInspectionResult( + this, + + InspectionResults.NegativeLineNumberInspection. + ThunderCodeFormat(), + c)); + } + + public override IInspectionListener Listener { get; } + + public class NegativeLineNumberKeywordsListener : VBAParserBaseListener, IInspectionListener + { + private readonly List> _contexts = new List>(); + + public IReadOnlyList> Contexts => _contexts; + + public void ClearContexts() + { + _contexts.Clear(); + } + + public QualifiedModuleName CurrentModuleName { get; set; } + + public override void EnterOnErrorStmt(VBAParser.OnErrorStmtContext context) + { + CheckContext(context, context.expression()); + base.EnterOnErrorStmt(context); + } + + public override void EnterGoToStmt(VBAParser.GoToStmtContext context) + { + CheckContext(context, context.expression()); + base.EnterGoToStmt(context); + } + + public override void EnterLineNumberLabel(VBAParser.LineNumberLabelContext context) + { + CheckContext(context, context); + base.EnterLineNumberLabel(context); + } + + private void CheckContext(ParserRuleContext context, IParseTree expression) + { + var target = expression?.GetText().Trim() ?? string.Empty; + if (target.StartsWith("-") && int.TryParse(target.Substring(1), out _)) + { + _contexts.Add(new QualifiedContext(CurrentModuleName, context)); + } + } + } + } +} diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/NonBreakingSpaceIdentifierInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/NonBreakingSpaceIdentifierInspection.cs new file mode 100644 index 0000000000..a18437308d --- /dev/null +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/NonBreakingSpaceIdentifierInspection.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Linq; +using Rubberduck.Inspections.Abstract; +using Rubberduck.Inspections.Results; +using Rubberduck.Parsing.Inspections.Abstract; +using Rubberduck.Parsing.VBA; +using Rubberduck.Resources.Inspections; + +namespace Rubberduck.Inspections.Inspections.Concrete.ThunderCode +{ + public class NonBreakingSpaceIdentifierInspection : InspectionBase + { + private const string Nbsp = "\u00A0"; + + public NonBreakingSpaceIdentifierInspection(RubberduckParserState state) : base(state) { } + + protected override IEnumerable DoGetInspectionResults() + { + return State.DeclarationFinder.AllUserDeclarations + .Where(d => d.IdentifierName.Contains(Nbsp)) + .Select(d => new DeclarationInspectionResult( + this, + InspectionResults.NonBreakingSpaceIdentifierInspection. + ThunderCodeFormat(d.IdentifierName), + d)); + } + } +} diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/OnErrorGoToMinusOneInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/OnErrorGoToMinusOneInspection.cs new file mode 100644 index 0000000000..cecaacaa5e --- /dev/null +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/OnErrorGoToMinusOneInspection.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.Linq; +using Antlr4.Runtime; +using Antlr4.Runtime.Tree; +using Rubberduck.Inspections.Abstract; +using Rubberduck.Inspections.Results; +using Rubberduck.Parsing; +using Rubberduck.Parsing.Grammar; +using Rubberduck.Parsing.Inspections.Abstract; +using Rubberduck.Parsing.VBA; +using Rubberduck.Resources.Inspections; +using Rubberduck.VBEditor; + +namespace Rubberduck.Inspections.Inspections.Concrete.ThunderCode +{ + public class OnErrorGoToMinusOneInspection : ParseTreeInspectionBase + { + public OnErrorGoToMinusOneInspection(RubberduckParserState state) : base(state) + { + Listener = new OnErrorGoToMinusOneListener(); + } + + protected override IEnumerable DoGetInspectionResults() + { + return Listener.Contexts.Select(c => new QualifiedContextInspectionResult( + this, + + InspectionResults.OnErrorGoToMinusOneInspection. + ThunderCodeFormat(), + c)); + } + + public override IInspectionListener Listener { get; } + + public class OnErrorGoToMinusOneListener : VBAParserBaseListener, IInspectionListener + { + private readonly List> _contexts = new List>(); + + public IReadOnlyList> Contexts => _contexts; + + public void ClearContexts() + { + _contexts.Clear(); + } + + public QualifiedModuleName CurrentModuleName { get; set; } + + public override void EnterOnErrorStmt(VBAParser.OnErrorStmtContext context) + { + CheckContext(context, context.expression()); + base.EnterOnErrorStmt(context); + } + + private void CheckContext(ParserRuleContext context, IParseTree expression) + { + var target = expression?.GetText().Trim() ?? string.Empty; + if (target.StartsWith("-") && int.TryParse(target.Substring(1), out var result) && result == 1) + { + _contexts.Add(new QualifiedContext(CurrentModuleName, context)); + } + } + } + } +} diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/ThunderCodeFormatExtension.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/ThunderCodeFormatExtension.cs new file mode 100644 index 0000000000..ee9581268b --- /dev/null +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/ThunderCodeFormatExtension.cs @@ -0,0 +1,12 @@ +using Rubberduck.Resources.Inspections; + +namespace Rubberduck.Inspections.Inspections.Concrete.ThunderCode +{ + public static class ThunderCodeFormatExtension + { + public static string ThunderCodeFormat(this string inspectionBase, params object[] args) + { + return string.Format(InspectionResults.ThunderCode_Base, string.Format(inspectionBase, args)); + } + } +} diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/UnassignedVariableUsageInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/UnassignedVariableUsageInspection.cs index 0cc5a1f120..b91c3ac596 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Concrete/UnassignedVariableUsageInspection.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/UnassignedVariableUsageInspection.cs @@ -54,7 +54,7 @@ protected override IEnumerable DoGetInspectionResults() private bool IsAssignedByRefArgument(Declaration enclosingProcedure, IdentifierReference reference) { var argExpression = reference.Context.GetAncestor(); - var parameter = State.DeclarationFinder.FindParameterFromArgument(argExpression, enclosingProcedure); + var parameter = State.DeclarationFinder.FindParameterOfNonDefaultMemberFromSimpleArgumentNotPassedByValExplicitly(argExpression, enclosingProcedure); // note: not recursive, by design. return parameter != null diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/UnderscoreInPublicClassModuleMemberInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/UnderscoreInPublicClassModuleMemberInspection.cs index d28ec90c9b..052f3c3efb 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Concrete/UnderscoreInPublicClassModuleMemberInspection.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/UnderscoreInPublicClassModuleMemberInspection.cs @@ -19,7 +19,7 @@ protected override IEnumerable DoGetInspectionResults() var eventHandlers = State.DeclarationFinder.FindEventHandlers().ToList(); var names = State.DeclarationFinder.UserDeclarations(Parsing.Symbols.DeclarationType.Member) - .Where(w => w.ParentDeclaration.DeclarationType == Parsing.Symbols.DeclarationType.ClassModule) + .Where(w => w.ParentDeclaration.DeclarationType.HasFlag(Parsing.Symbols.DeclarationType.ClassModule)) .Where(w => !interfaceMembers.Contains(w) && !eventHandlers.Contains(w)) .Where(w => w.Accessibility == Parsing.Symbols.Accessibility.Public || w.Accessibility == Parsing.Symbols.Accessibility.Implicit) .Where(w => w.IdentifierName.Contains('_')) diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/VariableNotAssignedInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/VariableNotAssignedInspection.cs index c4f24744ba..693b2c8976 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Concrete/VariableNotAssignedInspection.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/VariableNotAssignedInspection.cs @@ -37,7 +37,7 @@ protected override IEnumerable DoGetInspectionResults() private bool IsAssignedByRefArgument(Declaration enclosingProcedure, IdentifierReference reference) { var argExpression = reference.Context.GetAncestor(); - var parameter = State.DeclarationFinder.FindParameterFromArgument(argExpression, enclosingProcedure); + var parameter = State.DeclarationFinder.FindParameterOfNonDefaultMemberFromSimpleArgumentNotPassedByValExplicitly(argExpression, enclosingProcedure); // note: not recursive, by design. return parameter != null diff --git a/Rubberduck.CodeAnalysis/Inspections/Results/DeclarationInspectionResult.cs b/Rubberduck.CodeAnalysis/Inspections/Results/DeclarationInspectionResult.cs index b1094d70d6..fe06244018 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Results/DeclarationInspectionResult.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Results/DeclarationInspectionResult.cs @@ -1,4 +1,5 @@ -using Rubberduck.Inspections.Abstract; +using System.Collections.Generic; +using Rubberduck.Inspections.Abstract; using Rubberduck.Parsing; using Rubberduck.Parsing.Inspections.Abstract; using Rubberduck.Parsing.Symbols; @@ -6,7 +7,7 @@ namespace Rubberduck.Inspections.Results { - internal class DeclarationInspectionResult : InspectionResultBase + public class DeclarationInspectionResult : InspectionResultBase { public DeclarationInspectionResult(IInspection inspection, string description, Declaration target, QualifiedContext context = null, dynamic properties = null) : base(inspection, @@ -31,5 +32,11 @@ internal class DeclarationInspectionResult : InspectionResultBase ? target.QualifiedName : GetQualifiedMemberName(target.ParentDeclaration); } + + public override bool ChangesInvalidateResult(ICollection modifiedModules) + { + return modifiedModules.Contains(Target.QualifiedModuleName) + || base.ChangesInvalidateResult(modifiedModules); + } } } diff --git a/Rubberduck.CodeAnalysis/Inspections/Results/IdentifierReferenceInspectionResult.cs b/Rubberduck.CodeAnalysis/Inspections/Results/IdentifierReferenceInspectionResult.cs index 0ced841176..046c3fe2aa 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Results/IdentifierReferenceInspectionResult.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Results/IdentifierReferenceInspectionResult.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using Rubberduck.Inspections.Abstract; using Rubberduck.Parsing; using Rubberduck.Parsing.Inspections.Abstract; @@ -8,24 +9,30 @@ namespace Rubberduck.Inspections.Results { - internal class IdentifierReferenceInspectionResult : InspectionResultBase + public class IdentifierReferenceInspectionResult : InspectionResultBase { - public IdentifierReferenceInspectionResult(IInspection inspection, string description, RubberduckParserState state, IdentifierReference reference, dynamic properties = null) : + public IdentifierReferenceInspectionResult(IInspection inspection, string description, IDeclarationFinderProvider declarationFinderProvider, IdentifierReference reference, dynamic properties = null) : base(inspection, description, reference.QualifiedModuleName, reference.Context, reference.Declaration, new QualifiedSelection(reference.QualifiedModuleName, reference.Context.GetSelection()), - GetQualifiedMemberName(state, reference), + GetQualifiedMemberName(declarationFinderProvider, reference), (object)properties) { } - private static QualifiedMemberName? GetQualifiedMemberName(RubberduckParserState state, IdentifierReference reference) + private static QualifiedMemberName? GetQualifiedMemberName(IDeclarationFinderProvider declarationFinderProvider, IdentifierReference reference) { - var members = state.DeclarationFinder.Members(reference.QualifiedModuleName); + var members = declarationFinderProvider.DeclarationFinder.Members(reference.QualifiedModuleName); return members.SingleOrDefault(m => reference.Context.IsDescendentOf(m.Context))?.QualifiedName; } + + public override bool ChangesInvalidateResult(ICollection modifiedModules) + { + return modifiedModules.Contains(Target.QualifiedModuleName) + || base.ChangesInvalidateResult(modifiedModules); + } } } diff --git a/Rubberduck.CodeAnalysis/Inspections/Results/QualifiedContextInspectionResult.cs b/Rubberduck.CodeAnalysis/Inspections/Results/QualifiedContextInspectionResult.cs index f94c15a07f..eec677f2d1 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Results/QualifiedContextInspectionResult.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Results/QualifiedContextInspectionResult.cs @@ -5,7 +5,7 @@ namespace Rubberduck.Inspections.Results { - internal class QualifiedContextInspectionResult : InspectionResultBase + public class QualifiedContextInspectionResult : InspectionResultBase { public QualifiedContextInspectionResult(IInspection inspection, string description, QualifiedContext context, dynamic properties = null) : base(inspection, @@ -16,7 +16,6 @@ internal class QualifiedContextInspectionResult : InspectionResultBase new QualifiedSelection(context.ModuleName, context.Context.GetSelection()), context.MemberName, (object)properties) - { - } + {} } } diff --git a/Rubberduck.CodeAnalysis/QuickFixes/AddAttributeAnnotationQuickFix.cs b/Rubberduck.CodeAnalysis/QuickFixes/AddAttributeAnnotationQuickFix.cs new file mode 100644 index 0000000000..e688ad8b9c --- /dev/null +++ b/Rubberduck.CodeAnalysis/QuickFixes/AddAttributeAnnotationQuickFix.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using Rubberduck.Inspections.Abstract; +using Rubberduck.Inspections.Concrete; +using Rubberduck.Parsing.Annotations; +using Rubberduck.Parsing.Inspections.Abstract; +using Rubberduck.Parsing.Rewriter; +using Rubberduck.Parsing.Symbols; +using Rubberduck.Parsing.VBA; + +namespace Rubberduck.Inspections.QuickFixes +{ + public class AddAttributeAnnotationQuickFix : QuickFixBase + { + private readonly IAnnotationUpdater _annotationUpdater; + private readonly IAttributeAnnotationProvider _attributeAnnotationProvider; + + public AddAttributeAnnotationQuickFix(IAnnotationUpdater annotationUpdater, IAttributeAnnotationProvider attributeAnnotationProvider) + : base(typeof(MissingModuleAnnotationInspection), typeof(MissingMemberAnnotationInspection)) + { + _annotationUpdater = annotationUpdater; + _attributeAnnotationProvider = attributeAnnotationProvider; + } + + public override void Fix(IInspectionResult result, IRewriteSession rewriteSession) + { + var declaration = result.Target; + string attributeName = result.Properties.AttributeName; + IReadOnlyList attributeValues = result.Properties.AttributeValues; + var (annotationType, annotationValues) = declaration.DeclarationType.HasFlag(DeclarationType.Module) + ? _attributeAnnotationProvider.ModuleAttributeAnnotation(attributeName, attributeValues) + : _attributeAnnotationProvider.MemberAttributeAnnotation(AttributeBaseName(attributeName, declaration), attributeValues); + _annotationUpdater.AddAnnotation(rewriteSession, declaration, annotationType, annotationValues); + } + + private static string AttributeBaseName(string attributeName, Declaration declaration) + { + return Attributes.AttributeBaseName(attributeName, declaration.IdentifierName); + } + + public override string Description(IInspectionResult result) => Resources.Inspections.QuickFixes.AddAttributeAnnotationQuickFix; + + public override bool CanFixInProcedure => true; + public override bool CanFixInModule => true; + public override bool CanFixInProject => true; + } +} \ No newline at end of file diff --git a/Rubberduck.CodeAnalysis/QuickFixes/AdjustAttributeAnnotationQuickFix.cs b/Rubberduck.CodeAnalysis/QuickFixes/AdjustAttributeAnnotationQuickFix.cs new file mode 100644 index 0000000000..54d96755b1 --- /dev/null +++ b/Rubberduck.CodeAnalysis/QuickFixes/AdjustAttributeAnnotationQuickFix.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using Rubberduck.Inspections.Abstract; +using Rubberduck.Inspections.Concrete; +using Rubberduck.Parsing.Annotations; +using Rubberduck.Parsing.Inspections.Abstract; +using Rubberduck.Parsing.Rewriter; +using Rubberduck.Parsing.Symbols; +using Rubberduck.Parsing.VBA; +using Rubberduck.VBEditor.SafeComWrappers; + +namespace Rubberduck.Inspections.QuickFixes +{ + public class AdjustAttributeAnnotationQuickFix : QuickFixBase + { + private readonly IAnnotationUpdater _annotationUpdater; + private readonly IAttributeAnnotationProvider _attributeAnnotationProvider; + + public AdjustAttributeAnnotationQuickFix(IAnnotationUpdater annotationUpdater, IAttributeAnnotationProvider attributeAnnotationProvider) + : base(typeof(AttributeValueOutOfSyncInspection)) + { + _annotationUpdater = annotationUpdater; + _attributeAnnotationProvider = attributeAnnotationProvider; + } + + public override void Fix(IInspectionResult result, IRewriteSession rewriteSession) + { + IAttributeAnnotation oldAnnotation = result.Properties.Annotation; + string attributeName = result.Properties.AttributeName; + IReadOnlyList attributeValues = result.Properties.AttributeValues; + + var declaration = result.Target; + if (declaration.DeclarationType.HasFlag(DeclarationType.Module)) + { + var componentType = declaration.QualifiedModuleName.ComponentType; + if (IsDefaultAttribute(componentType, attributeName, attributeValues)) + { + _annotationUpdater.RemoveAnnotation(rewriteSession, oldAnnotation); + } + else + { + var (newAnnotationType, newAnnotationValues) = _attributeAnnotationProvider.ModuleAttributeAnnotation(attributeName, attributeValues); + _annotationUpdater.UpdateAnnotation(rewriteSession, oldAnnotation, newAnnotationType, newAnnotationValues); + } + } + else + { + var attributeBaseName = AttributeBaseName(attributeName, declaration); + var (newAnnotationType, newAnnotationValues) = _attributeAnnotationProvider.MemberAttributeAnnotation(attributeBaseName, attributeValues); + _annotationUpdater.UpdateAnnotation(rewriteSession, oldAnnotation, newAnnotationType, newAnnotationValues); + } + } + + private static bool IsDefaultAttribute(ComponentType componentType, string attributeName, IReadOnlyList attributeValues) + { + return Attributes.IsDefaultAttribute(componentType, attributeName, attributeValues); + } + + private static string AttributeBaseName(string attributeName, Declaration declaration) + { + return Attributes.AttributeBaseName(attributeName, declaration.IdentifierName); + } + + public override string Description(IInspectionResult result) => Resources.Inspections.QuickFixes.AdjustAttributeAnnotationQuickFix; + + public override bool CanFixInProcedure => true; + public override bool CanFixInModule => true; + public override bool CanFixInProject => true; + } +} \ No newline at end of file diff --git a/Rubberduck.CodeAnalysis/QuickFixes/EncapsulateFieldQuickFix.cs b/Rubberduck.CodeAnalysis/QuickFixes/EncapsulateFieldQuickFix.cs index dcbe44b31a..8b4f644a36 100644 --- a/Rubberduck.CodeAnalysis/QuickFixes/EncapsulateFieldQuickFix.cs +++ b/Rubberduck.CodeAnalysis/QuickFixes/EncapsulateFieldQuickFix.cs @@ -3,38 +3,37 @@ using Rubberduck.Parsing.Inspections.Abstract; using Rubberduck.Parsing.Rewriter; using Rubberduck.Parsing.VBA; +using Rubberduck.Refactorings; using Rubberduck.Refactorings.EncapsulateField; using Rubberduck.SmartIndenter; -using Rubberduck.UI.Refactorings.EncapsulateField; using Rubberduck.VBEditor.SafeComWrappers.Abstract; +using Rubberduck.VBEditor.Utility; namespace Rubberduck.Inspections.QuickFixes { public sealed class EncapsulateFieldQuickFix : QuickFixBase { - private readonly IVBE _vbe; private readonly RubberduckParserState _state; + private readonly ISelectionService _selectionService; private readonly IRewritingManager _rewritingManager; private readonly IIndenter _indenter; - - public EncapsulateFieldQuickFix(IVBE vbe, RubberduckParserState state, IIndenter indenter, IRewritingManager rewritingManager) + private readonly IRefactoringPresenterFactory _factory; + + public EncapsulateFieldQuickFix(RubberduckParserState state, IIndenter indenter, IRefactoringPresenterFactory factory, IRewritingManager rewritingManager, ISelectionService selectionService) : base(typeof(EncapsulatePublicFieldInspection)) { - _vbe = vbe; _state = state; + _selectionService = selectionService; _rewritingManager = rewritingManager; _indenter = indenter; + _factory = factory; } //The rewriteSession is optional since it is not used in this particular quickfix because it is a refactoring quickfix. public override void Fix(IInspectionResult result, IRewriteSession rewriteSession = null) { - using (var view = new EncapsulateFieldDialog(new EncapsulateFieldViewModel(_state, _indenter))) - { - var factory = new EncapsulateFieldPresenterFactory(_vbe, _state, view); - var refactoring = new EncapsulateFieldRefactoring(_vbe, _indenter, factory, _rewritingManager); - refactoring.Refactor(result.Target); - } + var refactoring = new EncapsulateFieldRefactoring(_state, _indenter, _factory, _rewritingManager, _selectionService); + refactoring.Refactor(result.Target); } public override string Description(IInspectionResult result) diff --git a/Rubberduck.CodeAnalysis/QuickFixes/IQuickFixFailureNotifier.cs b/Rubberduck.CodeAnalysis/QuickFixes/IQuickFixFailureNotifier.cs new file mode 100644 index 0000000000..ddfc74125e --- /dev/null +++ b/Rubberduck.CodeAnalysis/QuickFixes/IQuickFixFailureNotifier.cs @@ -0,0 +1,9 @@ +using Rubberduck.Parsing.Rewriter; + +namespace Rubberduck.Inspections.QuickFixes +{ + public interface IQuickFixFailureNotifier + { + void NotifyQuickFixExecutionFailure(RewriteSessionState sessionState); + } +} \ No newline at end of file diff --git a/Rubberduck.CodeAnalysis/QuickFixes/IgnoreOnceQuickFix.cs b/Rubberduck.CodeAnalysis/QuickFixes/IgnoreOnceQuickFix.cs index c30ffa7e94..497a984341 100644 --- a/Rubberduck.CodeAnalysis/QuickFixes/IgnoreOnceQuickFix.cs +++ b/Rubberduck.CodeAnalysis/QuickFixes/IgnoreOnceQuickFix.cs @@ -4,6 +4,7 @@ using Antlr4.Runtime.Misc; using Antlr4.Runtime.Tree; using Rubberduck.Inspections.Abstract; +using Rubberduck.Parsing; using Rubberduck.Parsing.Annotations; using Rubberduck.Parsing.Grammar; using Rubberduck.Parsing.Inspections; @@ -18,11 +19,13 @@ namespace Rubberduck.Inspections.QuickFixes public sealed class IgnoreOnceQuickFix : QuickFixBase { private readonly RubberduckParserState _state; + private readonly IAnnotationUpdater _annotationUpdater; - public IgnoreOnceQuickFix(RubberduckParserState state, IEnumerable inspections) + public IgnoreOnceQuickFix(IAnnotationUpdater annotationUpdater, RubberduckParserState state, IEnumerable inspections) : base(inspections.Select(s => s.GetType()).Where(i => i.CustomAttributes.All(a => a.AttributeType != typeof(CannotAnnotateAttribute))).ToArray()) { _state = state; + _annotationUpdater = annotationUpdater; } public override bool CanFixInProcedure => false; @@ -43,102 +46,47 @@ public override void Fix(IInspectionResult result, IRewriteSession rewriteSessio private void FixNonModule(IInspectionResult result, IRewriteSession rewriteSession) { - int insertionIndex; - string insertText; - var annotationText = $"'@Ignore {result.Inspection.AnnotationName}"; - var module = result.QualifiedSelection.QualifiedName; - var parseTree = _state.GetParseTree(module, CodeKind.CodePaneCode); - var eolListener = new EndOfLineListener(); - ParseTreeWalker.Default.Walk(eolListener, parseTree); - var previousEol = eolListener.Contexts - .OrderBy(eol => eol.Start.TokenIndex) - .LastOrDefault(eol => eol.Start.Line < result.QualifiedSelection.Selection.StartLine); - - var rewriter = rewriteSession.CheckOutModuleRewriter(module); - - if (previousEol == null) - { - // The context to get annotated is on the first line; we need to insert before token index 0. - insertionIndex = 0; - insertText = annotationText + Environment.NewLine; - rewriter.InsertBefore(insertionIndex, insertText); - return; - } + var lineToAnnotate = result.QualifiedSelection.Selection.StartLine; + var existingIgnoreAnnotation = _state.DeclarationFinder.FindAnnotations(module, lineToAnnotate) + .OfType() + .FirstOrDefault(); - var commentContext = previousEol.commentOrAnnotation(); - if (commentContext == null) + var annotationType = AnnotationType.Ignore; + if (existingIgnoreAnnotation != null) { - insertionIndex = previousEol.Start.TokenIndex; - var indent = WhitespaceAfter(previousEol); - insertText = $"{Environment.NewLine}{indent}{annotationText}"; - rewriter.InsertBefore(insertionIndex, insertText); - return; + var annotationValues = existingIgnoreAnnotation.InspectionNames.ToList(); + annotationValues.Insert(0, result.Inspection.AnnotationName); + _annotationUpdater.UpdateAnnotation(rewriteSession, existingIgnoreAnnotation, annotationType, annotationValues); } - - var ignoreAnnotation = commentContext.annotationList()?.annotation() - .FirstOrDefault(annotationContext => annotationContext.annotationName().GetText() == AnnotationType.Ignore.ToString()); - if (ignoreAnnotation == null) + else { - insertionIndex = commentContext.Stop.TokenIndex; - var indent = WhitespaceAfter(previousEol); - insertText = $"{indent}{annotationText}{Environment.NewLine}"; - rewriter.InsertAfter(insertionIndex, insertText); - return; + var annotationValues = new List { result.Inspection.AnnotationName }; + _annotationUpdater.AddAnnotation(rewriteSession, new QualifiedContext(module, result.Context), annotationType, annotationValues); } - - insertionIndex = ignoreAnnotation.annotationName().Stop.TokenIndex; - insertText = $" {result.Inspection.AnnotationName},"; - rewriter.InsertAfter(insertionIndex, insertText); - } - - private static string WhitespaceAfter(VBAParser.EndOfLineContext endOfLine) - { - var individualEndOfStatement = (VBAParser.IndividualNonEOFEndOfStatementContext) endOfLine.Parent; - var whiteSpaceOnNextLine = individualEndOfStatement.whiteSpace(0); - return whiteSpaceOnNextLine != null - ? whiteSpaceOnNextLine.GetText() - : string.Empty; } private void FixModule(IInspectionResult result, IRewriteSession rewriteSession) { - var module = result.QualifiedSelection.QualifiedName; - var moduleAnnotations = _state.GetModuleAnnotations(module); - var firstIgnoreModuleAnnotation = moduleAnnotations - .Where(annotation => annotation.AnnotationType == AnnotationType.IgnoreModule) - .OrderBy(annotation => annotation.Context.Start.TokenIndex) + var moduleDeclaration = result.Target; + var existingIgnoreModuleAnnotation = moduleDeclaration.Annotations + .OfType() .FirstOrDefault(); - var rewriter = rewriteSession.CheckOutModuleRewriter(module); - - int insertionIndex; - string insertText; - - if (firstIgnoreModuleAnnotation == null) + var annotationType = AnnotationType.IgnoreModule; + if (existingIgnoreModuleAnnotation != null) { - insertionIndex = 0; - insertText = $"'@IgnoreModule {result.Inspection.AnnotationName}{Environment.NewLine}"; - rewriter.InsertBefore(insertionIndex, insertText); - return; + var annotationValues = existingIgnoreModuleAnnotation.InspectionNames.ToList(); + annotationValues.Insert(0, result.Inspection.AnnotationName); + _annotationUpdater.UpdateAnnotation(rewriteSession, existingIgnoreModuleAnnotation, annotationType, annotationValues); } - - insertionIndex = firstIgnoreModuleAnnotation.Context.annotationName().Stop.TokenIndex; - insertText = $" {result.Inspection.AnnotationName},"; - rewriter.InsertAfter(insertionIndex, insertText); - } - - public override string Description(IInspectionResult result) => Resources.Inspections.QuickFixes.IgnoreOnce; - - private class EndOfLineListener : VBAParserBaseListener - { - private readonly IList _contexts = new List(); - public IEnumerable Contexts => _contexts; - - public override void ExitEndOfLine([NotNull] VBAParser.EndOfLineContext context) + else { - _contexts.Add(context); + var annotationValues = new List { result.Inspection.AnnotationName }; + _annotationUpdater.AddAnnotation(rewriteSession, moduleDeclaration, annotationType, annotationValues); } } + + public override string Description(IInspectionResult result) => Resources.Inspections.QuickFixes.IgnoreOnce; } } diff --git a/Rubberduck.CodeAnalysis/QuickFixes/IsMissingOnInappropriateArgumentQuickFix.cs b/Rubberduck.CodeAnalysis/QuickFixes/IsMissingOnInappropriateArgumentQuickFix.cs index 8a69d25d85..25c0fc0faa 100644 --- a/Rubberduck.CodeAnalysis/QuickFixes/IsMissingOnInappropriateArgumentQuickFix.cs +++ b/Rubberduck.CodeAnalysis/QuickFixes/IsMissingOnInappropriateArgumentQuickFix.cs @@ -107,6 +107,7 @@ private string UninitializedComparisonForParameter(ParameterDeclaration paramete switch (parameter.AsTypeDeclaration.DeclarationType) { case DeclarationType.ClassModule: + case DeclarationType.Document: return $"{parameter.IdentifierName} Is {Tokens.Nothing}"; case DeclarationType.Enumeration: var members = _declarationFinderProvider.DeclarationFinder.AllDeclarations.OfType() diff --git a/Rubberduck.CodeAnalysis/QuickFixes/MoveFieldCloserToUsageQuickFix.cs b/Rubberduck.CodeAnalysis/QuickFixes/MoveFieldCloserToUsageQuickFix.cs index 696287fea3..1a2c8f923f 100644 --- a/Rubberduck.CodeAnalysis/QuickFixes/MoveFieldCloserToUsageQuickFix.cs +++ b/Rubberduck.CodeAnalysis/QuickFixes/MoveFieldCloserToUsageQuickFix.cs @@ -7,20 +7,21 @@ using Rubberduck.Refactorings.MoveCloserToUsage; using Rubberduck.Resources.Inspections; using Rubberduck.VBEditor.SafeComWrappers.Abstract; +using Rubberduck.VBEditor.Utility; namespace Rubberduck.Inspections.QuickFixes { public sealed class MoveFieldCloserToUsageQuickFix : QuickFixBase { - private readonly IVBE _vbe; + private readonly ISelectionService _selectionService; private readonly RubberduckParserState _state; private readonly IRewritingManager _rewritingManager; private readonly IMessageBox _messageBox; - public MoveFieldCloserToUsageQuickFix(IVBE vbe, RubberduckParserState state, IMessageBox messageBox, IRewritingManager rewritingManager) + public MoveFieldCloserToUsageQuickFix(RubberduckParserState state, IMessageBox messageBox, IRewritingManager rewritingManager, ISelectionService selectionService) : base(typeof(MoveFieldCloserToUsageInspection)) { - _vbe = vbe; + _selectionService = selectionService; _state = state; _rewritingManager = rewritingManager; _messageBox = messageBox; @@ -29,7 +30,7 @@ public MoveFieldCloserToUsageQuickFix(IVBE vbe, RubberduckParserState state, IMe //The rewriteSession is optional since it is not used in this particular quickfix because it is a refactoring quickfix. public override void Fix(IInspectionResult result, IRewriteSession rewriteSession = null) { - var refactoring = new MoveCloserToUsageRefactoring(_vbe, _state, _messageBox, _rewritingManager); + var refactoring = new MoveCloserToUsageRefactoring(_state, _messageBox, _rewritingManager, _selectionService); refactoring.Refactor(result.Target); } diff --git a/Rubberduck.CodeAnalysis/QuickFixes/QuickFixFailureNotifier.cs b/Rubberduck.CodeAnalysis/QuickFixes/QuickFixFailureNotifier.cs new file mode 100644 index 0000000000..5877be4f50 --- /dev/null +++ b/Rubberduck.CodeAnalysis/QuickFixes/QuickFixFailureNotifier.cs @@ -0,0 +1,45 @@ +using System; +using Rubberduck.Interaction; +using Rubberduck.Parsing.Rewriter; + +namespace Rubberduck.Inspections.QuickFixes +{ + public class QuickFixFailureNotifier : IQuickFixFailureNotifier + { + private readonly IMessageBox _messageBox; + + public QuickFixFailureNotifier(IMessageBox messageBox) + { + _messageBox = messageBox; + } + + public void NotifyQuickFixExecutionFailure(RewriteSessionState sessionState) + { + var message = FailureMessage(sessionState); + var caption = Resources.Inspections.QuickFixes.ApplyQuickFixFailedCaption; + + _messageBox.NotifyWarn(message, caption); + } + + private static string FailureMessage(RewriteSessionState sessionState) + { + var baseFailureMessage = Resources.Inspections.QuickFixes.ApplyQuickFixesFailedMessage; + var failureReasonMessage = FailureReasonMessage(sessionState); + var message = string.IsNullOrEmpty(failureReasonMessage) + ? baseFailureMessage + : $"{baseFailureMessage}{Environment.NewLine}{Environment.NewLine}{failureReasonMessage}"; + return message; + } + + private static string FailureReasonMessage(RewriteSessionState sessionState) + { + switch (sessionState) + { + case RewriteSessionState.StaleParseTree: + return Resources.Inspections.QuickFixes.StaleModuleFailureReason; + default: + return string.Empty; + } + } + } +} \ No newline at end of file diff --git a/Rubberduck.CodeAnalysis/QuickFixes/QuickFixProvider.cs b/Rubberduck.CodeAnalysis/QuickFixes/QuickFixProvider.cs index 4429adf703..f52cc5c8e2 100644 --- a/Rubberduck.CodeAnalysis/QuickFixes/QuickFixProvider.cs +++ b/Rubberduck.CodeAnalysis/QuickFixes/QuickFixProvider.cs @@ -5,7 +5,6 @@ using Microsoft.CSharp.RuntimeBinder; using Rubberduck.Parsing.Inspections.Abstract; using Rubberduck.Parsing.Rewriter; -using Rubberduck.Parsing.VBA; using Rubberduck.Parsing.VBA.Parsing; using Rubberduck.VBEditor; @@ -14,11 +13,13 @@ namespace Rubberduck.Inspections.QuickFixes public class QuickFixProvider : IQuickFixProvider { private readonly IRewritingManager _rewritingManager; + private readonly IQuickFixFailureNotifier _failureNotifier; private readonly Dictionary> _quickFixes = new Dictionary>(); - public QuickFixProvider(IRewritingManager rewritingManager, IEnumerable quickFixes) + public QuickFixProvider(IRewritingManager rewritingManager, IQuickFixFailureNotifier failureNotifier, IEnumerable quickFixes) { _rewritingManager = rewritingManager; + _failureNotifier = failureNotifier; foreach (var quickFix in quickFixes) { foreach (var supportedInspection in quickFix.SupportedInspections) @@ -78,7 +79,15 @@ public void Fix(IQuickFix fix, IInspectionResult result) var rewriteSession = RewriteSession(fix.TargetCodeKind); fix.Fix(result, rewriteSession); - rewriteSession.TryRewrite(); + Apply(rewriteSession); + } + + private void Apply(IRewriteSession rewriteSession) + { + if (!rewriteSession.TryRewrite()) + { + _failureNotifier.NotifyQuickFixExecutionFailure(rewriteSession.Status); + } } private IRewriteSession RewriteSession(CodeKind targetCodeKind) @@ -115,7 +124,7 @@ public void FixInProcedure(IQuickFix fix, QualifiedMemberName? qualifiedMember, fix.Fix(result, rewriteSession); } - rewriteSession.TryRewrite(); + Apply(rewriteSession); } public void FixInModule(IQuickFix fix, QualifiedSelection selection, Type inspectionType, IEnumerable results) @@ -137,7 +146,7 @@ public void FixInModule(IQuickFix fix, QualifiedSelection selection, Type inspec fix.Fix(result, rewriteSession); } - rewriteSession.TryRewrite(); + Apply(rewriteSession); } public void FixInProject(IQuickFix fix, QualifiedSelection selection, Type inspectionType, IEnumerable results) @@ -159,7 +168,7 @@ public void FixInProject(IQuickFix fix, QualifiedSelection selection, Type inspe fix.Fix(result, rewriteSession); } - rewriteSession.TryRewrite(); + Apply(rewriteSession); } public void FixAll(IQuickFix fix, Type inspectionType, IEnumerable results) @@ -181,7 +190,7 @@ public void FixAll(IQuickFix fix, Type inspectionType, IEnumerable Resources.Inspections.QuickFixes.RemoveAnnotationQuickFix; + + public override bool CanFixInProcedure => false; + public override bool CanFixInModule => false; + public override bool CanFixInProject => false; + } +} \ No newline at end of file diff --git a/Rubberduck.CodeAnalysis/QuickFixes/RemoveAttributeQuickFix.cs b/Rubberduck.CodeAnalysis/QuickFixes/RemoveAttributeQuickFix.cs index 170e6134fe..352964edc0 100644 --- a/Rubberduck.CodeAnalysis/QuickFixes/RemoveAttributeQuickFix.cs +++ b/Rubberduck.CodeAnalysis/QuickFixes/RemoveAttributeQuickFix.cs @@ -14,7 +14,7 @@ public class RemoveAttributeQuickFix : QuickFixBase private readonly IAttributesUpdater _attributesUpdater; public RemoveAttributeQuickFix(IAttributesUpdater attributesUpdater) - :base(typeof(AttributeValueOutOfSyncInspection)) + :base(typeof(MissingModuleAnnotationInspection), typeof(MissingMemberAnnotationInspection)) { _attributesUpdater = attributesUpdater; } diff --git a/Rubberduck.CodeAnalysis/QuickFixes/RemoveDuplicatedAnnotationQuickFix.cs b/Rubberduck.CodeAnalysis/QuickFixes/RemoveDuplicatedAnnotationQuickFix.cs index f2255c9058..34d0b49810 100644 --- a/Rubberduck.CodeAnalysis/QuickFixes/RemoveDuplicatedAnnotationQuickFix.cs +++ b/Rubberduck.CodeAnalysis/QuickFixes/RemoveDuplicatedAnnotationQuickFix.cs @@ -1,55 +1,31 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Linq; using Rubberduck.Inspections.Abstract; using Rubberduck.Inspections.Concrete; -using Rubberduck.Parsing.Annotations; -using Rubberduck.Parsing.Grammar; using Rubberduck.Parsing.Inspections.Abstract; using Rubberduck.Parsing.Rewriter; +using Rubberduck.Parsing.VBA; namespace Rubberduck.Inspections.QuickFixes { public sealed class RemoveDuplicatedAnnotationQuickFix : QuickFixBase { - public RemoveDuplicatedAnnotationQuickFix() + private readonly IAnnotationUpdater _annotationUpdater; + + public RemoveDuplicatedAnnotationQuickFix(IAnnotationUpdater annotationUpdater) : base(typeof(DuplicatedAnnotationInspection)) - {} + { + _annotationUpdater = annotationUpdater; + } public override void Fix(IInspectionResult result, IRewriteSession rewriteSession) { - var rewriter = rewriteSession.CheckOutModuleRewriter(result.QualifiedSelection.QualifiedName); - var duplicateAnnotations = result.Target.Annotations .Where(annotation => annotation.AnnotationType == result.Properties.AnnotationType) .OrderBy(annotation => annotation.Context.Start.StartIndex) .Skip(1) .ToList(); - var duplicatesPerAnnotationList = duplicateAnnotations - .Select(annotation => (VBAParser.AnnotationListContext) annotation.Context.Parent) - .Distinct() - .ToDictionary(list => list, _ => 0); - - foreach (var annotation in duplicateAnnotations) - { - var annotationList = (VBAParser.AnnotationListContext)annotation.Context.Parent; - - RemoveAnnotationMarker(annotationList, annotation, rewriter); - - rewriter.Remove(annotation.Context); - - duplicatesPerAnnotationList[annotationList]++; - } - - foreach (var pair in duplicatesPerAnnotationList) - { - if (OnlyQuoteRemainedFromAnnotationList(pair)) - { - rewriter.Remove(pair.Key); - rewriter.Remove(((VBAParser.CommentOrAnnotationContext) pair.Key.Parent).NEWLINE()); - } - } + _annotationUpdater.RemoveAnnotations(rewriteSession, duplicateAnnotations); } public override string Description(IInspectionResult result) => @@ -58,17 +34,5 @@ public override void Fix(IInspectionResult result, IRewriteSession rewriteSessio public override bool CanFixInProcedure => true; public override bool CanFixInModule => true; public override bool CanFixInProject => true; - - private static void RemoveAnnotationMarker(VBAParser.AnnotationListContext annotationList, - IAnnotation annotation, IModuleRewriter rewriter) - { - var index = Array.IndexOf(annotationList.annotation(), annotation.Context); - rewriter.Remove(annotationList.AT(index)); - } - - private static bool OnlyQuoteRemainedFromAnnotationList(KeyValuePair pair) - { - return pair.Key.annotation().Length == pair.Value && pair.Key.commentBody() == null; - } } } diff --git a/Rubberduck.CodeAnalysis/QuickFixes/RemoveUnusedParameterQuickFix.cs b/Rubberduck.CodeAnalysis/QuickFixes/RemoveUnusedParameterQuickFix.cs index aa02c472ad..8f508b4a65 100644 --- a/Rubberduck.CodeAnalysis/QuickFixes/RemoveUnusedParameterQuickFix.cs +++ b/Rubberduck.CodeAnalysis/QuickFixes/RemoveUnusedParameterQuickFix.cs @@ -1,43 +1,36 @@ using Rubberduck.Inspections.Abstract; using Rubberduck.Inspections.Concrete; -using Rubberduck.Interaction; using Rubberduck.Parsing.Inspections.Abstract; using Rubberduck.Parsing.Rewriter; using Rubberduck.Parsing.VBA; +using Rubberduck.Refactorings; using Rubberduck.Refactorings.RemoveParameters; -using Rubberduck.UI.Refactorings.RemoveParameters; using Rubberduck.VBEditor.SafeComWrappers.Abstract; +using Rubberduck.VBEditor.Utility; namespace Rubberduck.Inspections.QuickFixes { public sealed class RemoveUnusedParameterQuickFix : QuickFixBase { - private readonly IVBE _vbe; - private readonly RubberduckParserState _state; + private readonly IDeclarationFinderProvider _declarationFinderProvider; + private readonly IRefactoringPresenterFactory _factory; private readonly IRewritingManager _rewritingManager; - private readonly IMessageBox _messageBox; + private readonly ISelectionService _selectionService; - public RemoveUnusedParameterQuickFix(IVBE vbe, RubberduckParserState state, IMessageBox messageBox, IRewritingManager rewritingManager) + public RemoveUnusedParameterQuickFix(IDeclarationFinderProvider declarationFinderProvider, IRefactoringPresenterFactory factory, IRewritingManager rewritingManager, ISelectionService selectionService) : base(typeof(ParameterNotUsedInspection)) { - _vbe = vbe; - _state = state; + _declarationFinderProvider = declarationFinderProvider; + _factory = factory; _rewritingManager = rewritingManager; - _messageBox = messageBox; + _selectionService = selectionService; } //The rewriteSession is optional since it is not used in this particular quickfix because it is a refactoring quickfix. public override void Fix(IInspectionResult result, IRewriteSession rewriteSession = null) { - using (var dialog = new RemoveParametersDialog(new RemoveParametersViewModel(_state))) - { - var refactoring = new RemoveParametersRefactoring( - _vbe, - new RemoveParametersPresenterFactory(_vbe, dialog, _state, _messageBox), - _rewritingManager); - - refactoring.QuickFix(_state, result.QualifiedSelection); - } + var refactoring = new RemoveParametersRefactoring(_declarationFinderProvider, _factory, _rewritingManager, _selectionService); + refactoring.QuickFix(result.QualifiedSelection); } public override string Description(IInspectionResult result) => Resources.Inspections.QuickFixes.RemoveUnusedParameterQuickFix; diff --git a/Rubberduck.CodeAnalysis/QuickFixes/RenameDeclarationQuickFix.cs b/Rubberduck.CodeAnalysis/QuickFixes/RenameDeclarationQuickFix.cs index f2d6d2f70b..8845771850 100644 --- a/Rubberduck.CodeAnalysis/QuickFixes/RenameDeclarationQuickFix.cs +++ b/Rubberduck.CodeAnalysis/QuickFixes/RenameDeclarationQuickFix.cs @@ -6,42 +6,41 @@ using Rubberduck.Parsing.Inspections.Abstract; using Rubberduck.Parsing.Rewriter; using Rubberduck.Parsing.VBA; +using Rubberduck.Refactorings; using Rubberduck.Refactorings.Rename; using Rubberduck.Resources; -using Rubberduck.UI.Refactorings.Rename; using Rubberduck.VBEditor.SafeComWrappers.Abstract; +using Rubberduck.VBEditor.Utility; namespace Rubberduck.Inspections.QuickFixes { public sealed class RenameDeclarationQuickFix : QuickFixBase { - private readonly IVBE _vbe; + private readonly ISelectionService _selectionService; private readonly RubberduckParserState _state; private readonly IRewritingManager _rewritingManager; private readonly IMessageBox _messageBox; - - public RenameDeclarationQuickFix(IVBE vbe, RubberduckParserState state, IMessageBox messageBox, IRewritingManager rewritingManager) + private readonly IRefactoringPresenterFactory _factory; + + public RenameDeclarationQuickFix(RubberduckParserState state, IMessageBox messageBox, IRefactoringPresenterFactory factory, IRewritingManager rewritingManager, ISelectionService selectionService) : base(typeof(HungarianNotationInspection), typeof(UseMeaningfulNameInspection), typeof(DefaultProjectNameInspection), typeof(UnderscoreInPublicClassModuleMemberInspection), typeof(ExcelUdfNameIsValidCellReferenceInspection)) { - _vbe = vbe; + _selectionService = selectionService; _state = state; _rewritingManager = rewritingManager; _messageBox = messageBox; + _factory = factory; } //The rewriteSession is optional since it is not used in this particular quickfix because it is a refactoring quickfix. public override void Fix(IInspectionResult result, IRewriteSession rewriteSession = null) { - using (var view = new RenameDialog(new RenameViewModel(_state))) - { - var factory = new RenamePresenterFactory(_vbe, view, _state); - var refactoring = new RenameRefactoring(_vbe, factory, _messageBox, _state, _state.ProjectsProvider, _rewritingManager); - refactoring.Refactor(result.Target); - } + var refactoring = new RenameRefactoring(_factory, _messageBox, _state, _state.ProjectsProvider, _rewritingManager, _selectionService); + refactoring.Refactor(result.Target); } public override string Description(IInspectionResult result) diff --git a/Rubberduck.Core/AddRemoveReferences/ReferenceModel.cs b/Rubberduck.Core/AddRemoveReferences/ReferenceModel.cs index 595230dd82..d056840fbe 100644 --- a/Rubberduck.Core/AddRemoveReferences/ReferenceModel.cs +++ b/Rubberduck.Core/AddRemoveReferences/ReferenceModel.cs @@ -183,7 +183,7 @@ public bool Matches(ReferenceInfo info) FullPath.Equals(info.FullPath, StringComparison.OrdinalIgnoreCase) || FullPath32.Equals(info.FullPath, StringComparison.OrdinalIgnoreCase) || FullPath64.Equals(info.FullPath, StringComparison.OrdinalIgnoreCase) || - Guid.Equals(info.Guid); + !Guid.Equals(Guid.Empty) && Guid.Equals(info.Guid); } private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") diff --git a/Rubberduck.Core/AutoComplete/AutoCompleteHandlerBase.cs b/Rubberduck.Core/AutoComplete/AutoCompleteHandlerBase.cs index 2eb2da1fec..66ceabb551 100644 --- a/Rubberduck.Core/AutoComplete/AutoCompleteHandlerBase.cs +++ b/Rubberduck.Core/AutoComplete/AutoCompleteHandlerBase.cs @@ -5,6 +5,9 @@ namespace Rubberduck.AutoComplete { + /// + /// A base class/interface for AC services / "handlers". + /// public abstract class AutoCompleteHandlerBase { protected AutoCompleteHandlerBase(ICodePaneHandler pane) @@ -14,6 +17,13 @@ protected AutoCompleteHandlerBase(ICodePaneHandler pane) protected ICodePaneHandler CodePaneHandler { get; } + /// + /// A method that returns false if the input isn't handled, true if it is. + /// + /// The autocompletion event info + /// The current AC settings + /// If handled, the resulting CodeString + /// public abstract bool Handle(AutoCompleteEventArgs e, AutoCompleteSettings settings, out CodeString result); } } \ No newline at end of file diff --git a/Rubberduck.Core/AutoComplete/Service/AutoCompleteService.cs b/Rubberduck.Core/AutoComplete/AutoCompleteService.cs similarity index 93% rename from Rubberduck.Core/AutoComplete/Service/AutoCompleteService.cs rename to Rubberduck.Core/AutoComplete/AutoCompleteService.cs index 2fa7c7cdd7..8bf6725244 100644 --- a/Rubberduck.Core/AutoComplete/Service/AutoCompleteService.cs +++ b/Rubberduck.Core/AutoComplete/AutoCompleteService.cs @@ -5,8 +5,12 @@ using Rubberduck.Settings; using Rubberduck.VBEditor.Events; -namespace Rubberduck.AutoComplete.Service +namespace Rubberduck.AutoComplete { + /// + /// A service responsible for dispatching CodePane work to more specialized autocompletion services. + /// Handles changes in configuration settings. + /// public class AutoCompleteService : IDisposable { private static readonly ILogger Logger = LogManager.GetCurrentClassLogger(); @@ -151,6 +155,7 @@ private bool TryHandle(AutoCompleteEventArgs e, AutoCompleteHandlerBase handler) return false; } + Logger.Debug($"Keypress was handled by {handler.GetType().Name}."); e.Handled = true; return true; @@ -169,7 +174,8 @@ public void Dispose() } private bool _isDisposed; - protected virtual void Dispose(bool disposing) + + private void Dispose(bool disposing) { if (_isDisposed || !disposing) { diff --git a/Rubberduck.Core/AutoComplete/Service/SelfClosingPair.cs b/Rubberduck.Core/AutoComplete/SelfClosingPairs/SelfClosingPair.cs similarity index 90% rename from Rubberduck.Core/AutoComplete/Service/SelfClosingPair.cs rename to Rubberduck.Core/AutoComplete/SelfClosingPairs/SelfClosingPair.cs index 1266439f2b..6951a54a2f 100644 --- a/Rubberduck.Core/AutoComplete/Service/SelfClosingPair.cs +++ b/Rubberduck.Core/AutoComplete/SelfClosingPairs/SelfClosingPair.cs @@ -1,7 +1,7 @@ -using Rubberduck.VBEditor; -using System; +using System; +using Rubberduck.VBEditor; -namespace Rubberduck.AutoComplete.Service +namespace Rubberduck.AutoComplete.SelfClosingPairs { public class SelfClosingPair : IEquatable { diff --git a/Rubberduck.Core/AutoComplete/Service/SelfClosingPairCompletionService.cs b/Rubberduck.Core/AutoComplete/SelfClosingPairs/SelfClosingPairCompletionService.cs similarity index 99% rename from Rubberduck.Core/AutoComplete/Service/SelfClosingPairCompletionService.cs rename to Rubberduck.Core/AutoComplete/SelfClosingPairs/SelfClosingPairCompletionService.cs index fbe81b7f65..5860e61e30 100644 --- a/Rubberduck.Core/AutoComplete/Service/SelfClosingPairCompletionService.cs +++ b/Rubberduck.Core/AutoComplete/SelfClosingPairs/SelfClosingPairCompletionService.cs @@ -8,7 +8,7 @@ using Rubberduck.Parsing.VBA.Parsing; using Rubberduck.VBEditor; -namespace Rubberduck.AutoComplete.Service +namespace Rubberduck.AutoComplete.SelfClosingPairs { public class SelfClosingPairCompletionService { diff --git a/Rubberduck.Core/AutoComplete/Service/SelfClosingPairHandler.cs b/Rubberduck.Core/AutoComplete/SelfClosingPairs/SelfClosingPairHandler.cs similarity index 82% rename from Rubberduck.Core/AutoComplete/Service/SelfClosingPairHandler.cs rename to Rubberduck.Core/AutoComplete/SelfClosingPairs/SelfClosingPairHandler.cs index 7b8306087b..c1e06761fa 100644 --- a/Rubberduck.Core/AutoComplete/Service/SelfClosingPairHandler.cs +++ b/Rubberduck.Core/AutoComplete/SelfClosingPairs/SelfClosingPairHandler.cs @@ -1,13 +1,16 @@ -using System.Collections.Generic; -using System.Diagnostics; +using System; +using System.Collections.Generic; using System.Linq; using Rubberduck.Settings; using Rubberduck.VBEditor; using Rubberduck.VBEditor.Events; using Rubberduck.VBEditor.SourceCodeHandling; -namespace Rubberduck.AutoComplete.Service +namespace Rubberduck.AutoComplete.SelfClosingPairs { + /// + /// An AC handler that automatically closes certain specific "pairs" of characters, e.g. double quotes, or parentheses. + /// public class SelfClosingPairHandler : AutoCompleteHandlerBase { private const int MaximumLines = 25; @@ -39,6 +42,7 @@ public override bool Handle(AutoCompleteEventArgs e, AutoCompleteSettings settin result = null; if (!_scpInputLookup.TryGetValue(e.Character, out var pair) && e.Character != '\b') { + // not an interesting keypress. return false; } @@ -50,8 +54,17 @@ public override bool Handle(AutoCompleteEventArgs e, AutoCompleteSettings settin return false; } + if (!original.CaretPosition.IsSingleCharacter) + { + // here would be an opportunity to "wrap selection" with a SCP. + // todo: WrapSelectionWith(pair)? + result = null; + return false; + } + if (pair != null) { + // found a SCP for the input key; see if we should handle it: if (!HandleInternal(e, original, pair, out result)) { return false; @@ -59,6 +72,7 @@ public override bool Handle(AutoCompleteEventArgs e, AutoCompleteSettings settin } else if (e.Character == '\b') { + // backspace - see if SCP logic needs to intervene: foreach (var scp in _selfClosingPairs) { if (HandleInternal(e, original, scp, out result)) @@ -70,9 +84,11 @@ public override bool Handle(AutoCompleteEventArgs e, AutoCompleteSettings settin if (result == null) { + // no meaningful output; let the input be handled by another handler, maybe. return false; } + // 1-based selection span in the code pane starts at column 1 but really encompasses the entire line. var snippetPosition = new Selection(result.SnippetPosition.StartLine, 1, result.SnippetPosition.EndLine, 1); result = new CodeString(result.Code, result.CaretPosition, snippetPosition); @@ -82,13 +98,6 @@ public override bool Handle(AutoCompleteEventArgs e, AutoCompleteSettings settin private bool HandleInternal(AutoCompleteEventArgs e, CodeString original, SelfClosingPair pair, out CodeString result) { - if (!original.CaretPosition.IsSingleCharacter) - { - // todo: WrapSelectionWith(pair)? - result = null; - return false; - } - // if executing the SCP against the original code yields no result, we need to bail out. if (!_scpService.Execute(pair, original, e.Character, out result)) { @@ -115,6 +124,12 @@ private bool HandleInternal(AutoCompleteEventArgs e, CodeString original, SelfCl ); } + if (original.CaretLine.EndsWith(" ") && + string.Equals(original.CaretLine, prettified.CaretLine + " ", StringComparison.InvariantCultureIgnoreCase)) + { + prettified = original; + } + // if executing the SCP against the prettified code yields no result, we need to bail out. if (!_scpService.Execute(pair, prettified, e.Character, out result)) { @@ -122,14 +137,21 @@ private bool HandleInternal(AutoCompleteEventArgs e, CodeString original, SelfCl } var reprettified = CodePaneHandler.Prettify(e.Module, result); - if (pair.OpeningChar == '(' && e.Character == pair.OpeningChar && !reprettified.Equals(result)) + if (pair.OpeningChar == '(' && e.Character == pair.OpeningChar) { + if (string.Equals(reprettified.Code, result.Code, StringComparison.InvariantCultureIgnoreCase)) + { + e.Handled = true; + result = reprettified; + return true; + } + // VBE eats it. bail out but don't swallow the keypress. e.Handled = false; result = null; return false; } - + var currentLine = reprettified.Lines[reprettified.CaretPosition.StartLine]; if (!string.IsNullOrWhiteSpace(currentLine) && currentLine.EndsWith(" ") && diff --git a/Rubberduck.Core/AutoComplete/Service/ShowIntelliSenseCommand.cs b/Rubberduck.Core/AutoComplete/ShowIntelliSenseCommand.cs similarity index 97% rename from Rubberduck.Core/AutoComplete/Service/ShowIntelliSenseCommand.cs rename to Rubberduck.Core/AutoComplete/ShowIntelliSenseCommand.cs index 63b7d057ad..624cb9d096 100644 --- a/Rubberduck.Core/AutoComplete/Service/ShowIntelliSenseCommand.cs +++ b/Rubberduck.Core/AutoComplete/ShowIntelliSenseCommand.cs @@ -3,7 +3,7 @@ using Rubberduck.UI.Command; using Rubberduck.VBEditor.SafeComWrappers.Abstract; -namespace Rubberduck.AutoComplete.Service +namespace Rubberduck.AutoComplete { public interface IShowIntelliSenseCommand { diff --git a/Rubberduck.Core/AutoComplete/Service/SmartConcatenationHandler.cs b/Rubberduck.Core/AutoComplete/SmartConcat/SmartConcatenationHandler.cs similarity index 98% rename from Rubberduck.Core/AutoComplete/Service/SmartConcatenationHandler.cs rename to Rubberduck.Core/AutoComplete/SmartConcat/SmartConcatenationHandler.cs index 042438adaf..f2595ec350 100644 --- a/Rubberduck.Core/AutoComplete/Service/SmartConcatenationHandler.cs +++ b/Rubberduck.Core/AutoComplete/SmartConcat/SmartConcatenationHandler.cs @@ -4,7 +4,7 @@ using Rubberduck.VBEditor.Events; using Rubberduck.VBEditor.SourceCodeHandling; -namespace Rubberduck.AutoComplete.Service +namespace Rubberduck.AutoComplete.SmartConcat { /// /// Adds a line continuation when {ENTER} is pressed when inside a string literal. diff --git a/Rubberduck.Core/CodeAnalysis/CodeMetrics/CodeMetricsViewModel.cs b/Rubberduck.Core/CodeAnalysis/CodeMetrics/CodeMetricsViewModel.cs index d2e836d6b9..385a897d12 100644 --- a/Rubberduck.Core/CodeAnalysis/CodeMetrics/CodeMetricsViewModel.cs +++ b/Rubberduck.Core/CodeAnalysis/CodeMetrics/CodeMetricsViewModel.cs @@ -6,136 +6,91 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using Rubberduck.Navigation.CodeExplorer; -using System.Windows; -using Rubberduck.Navigation.Folders; +using Rubberduck.Parsing.UIContext; using Rubberduck.VBEditor.SafeComWrappers.Abstract; namespace Rubberduck.CodeAnalysis.CodeMetrics { - public class CodeMetricsViewModel : ViewModelBase, IDisposable + public sealed class CodeMetricsViewModel : ViewModelBase, IDisposable { private readonly RubberduckParserState _state; private readonly ICodeMetricsAnalyst _analyst; - private readonly FolderHelper _folderHelper; private readonly IVBE _vbe; + private readonly IUiDispatcher _uiDispatcher; - public CodeMetricsViewModel(RubberduckParserState state, ICodeMetricsAnalyst analyst, FolderHelper folderHelper, IVBE vbe) + public CodeMetricsViewModel( + RubberduckParserState state, + ICodeMetricsAnalyst analyst, + IVBE vbe, + IUiDispatcher uiDispatcher) { _state = state; - _analyst = analyst; - _folderHelper = folderHelper; _state.StateChanged += OnStateChanged; + + _analyst = analyst; _vbe = vbe; - } - - private void OnStateChanged(object sender, ParserStateEventArgs e) - { - if (e.State != ParserState.Ready && e.State != ParserState.Error && e.State != ParserState.ResolverError && e.State != ParserState.UnexpectedError) - { - IsBusy = true; - } + _uiDispatcher = uiDispatcher; - if (e.State == ParserState.Ready) - { - UpdateData(); - IsBusy = false; - } + OnPropertyChanged(nameof(Projects)); + } - if (e.State == ParserState.Error || e.State == ParserState.ResolverError || e.State == ParserState.UnexpectedError) + private bool _unparsed = true; + public bool Unparsed + { + get => _unparsed; + set { - IsBusy = false; + if (_unparsed == value) + { + return; + } + _unparsed = value; + OnPropertyChanged(); } } - private void UpdateData() + private void OnStateChanged(object sender, ParserStateEventArgs e) { - IsBusy = true; - - var metricResults = _analyst.GetMetrics(_state); - resultsByDeclaration = metricResults.GroupBy(r => r.Declaration).ToDictionary(g => g.Key, g => g.ToList()); + Unparsed = false; + IsBusy = _state.Status != ParserState.Pending && _state.Status <= ParserState.ResolvedDeclarations; - if (Projects == null) + if (e.State == ParserState.ResolvedDeclarations) { - Projects = new ObservableCollection(); + Synchronize(_state.DeclarationFinder.AllUserDeclarations); } - - IsBusy = _state.Status != ParserState.Pending && _state.Status <= ParserState.ResolvedDeclarations; - - var userDeclarations = _state.DeclarationFinder.AllUserDeclarations - .GroupBy(declaration => declaration.ProjectId) - .ToList(); - - var newProjects = userDeclarations - .Where(grouping => grouping.Any(declaration => declaration.DeclarationType == DeclarationType.Project)) - .Select(grouping => - new CodeExplorerProjectViewModel(_folderHelper, - grouping.SingleOrDefault(declaration => declaration.DeclarationType == DeclarationType.Project), - grouping, - _vbe)).ToList(); - - UpdateNodes(Projects, newProjects); - - Projects = new ObservableCollection(newProjects); - - IsBusy = false; } - private void UpdateNodes(IEnumerable oldList, IEnumerable newList) + private void Synchronize(IEnumerable declarations) { - foreach (var item in newList) - { - CodeExplorerItemViewModel oldItem; + var metricResults = _analyst.GetMetrics(_state); + _resultsByDeclaration = metricResults.GroupBy(r => r.Declaration).ToDictionary(g => g.Key, g => g.ToList()); - if (item is CodeExplorerCustomFolderViewModel) - { - oldItem = oldList.FirstOrDefault(i => i.Name == item.Name); - } - else - { - oldItem = oldList.FirstOrDefault(i => - item.QualifiedSelection != null && i.QualifiedSelection != null && - i.QualifiedSelection.Value.QualifiedName.ProjectId == - item.QualifiedSelection.Value.QualifiedName.ProjectId && - i.QualifiedSelection.Value.QualifiedName.ComponentName == - item.QualifiedSelection.Value.QualifiedName.ComponentName && - i.QualifiedSelection.Value.Selection == item.QualifiedSelection.Value.Selection); - } + _uiDispatcher.Invoke(() => + { + var updates = declarations.ToList(); + var existing = Projects.OfType().ToList(); - if (oldItem != null) + foreach (var project in existing) { - item.IsExpanded = oldItem.IsExpanded; - item.IsSelected = oldItem.IsSelected; - - if (oldItem.Items.Any() && item.Items.Any()) + project.Synchronize(ref updates); + if (project.Declaration is null) { - UpdateNodes(oldItem.Items, item.Items); + Projects.Remove(project); } } - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - private bool _isDisposed; - protected virtual void Dispose(bool disposing) - { - if (_isDisposed || !disposing) - { - return; - } - _isDisposed = true; + var adding = updates.OfType().ToList(); - _state.StateChanged -= OnStateChanged; + foreach (var project in adding) + { + var model = new CodeExplorerProjectViewModel(project, ref updates, _state, _vbe, false); + Projects.Add(model); + } + }); } - private Dictionary> resultsByDeclaration; - - private CodeExplorerItemViewModel _selectedItem; - public CodeExplorerItemViewModel SelectedItem + private ICodeExplorerNode _selectedItem; + public ICodeExplorerNode SelectedItem { get => _selectedItem; set @@ -150,27 +105,15 @@ public CodeExplorerItemViewModel SelectedItem } } - private ObservableCollection _projects; - public ObservableCollection Projects - { - get => _projects; - set - { - _projects = new ObservableCollection(value.OrderBy(o => o.NameWithSignature)); + public ObservableCollection Projects { get; } = new ObservableCollection(); - OnPropertyChanged(); - OnPropertyChanged(nameof(TreeViewVisibility)); - } - } - - public Visibility TreeViewVisibility => Projects == null || Projects.Count == 0 ? Visibility.Collapsed : Visibility.Visible; - + private Dictionary> _resultsByDeclaration; public ObservableCollection Metrics { get { - var results = resultsByDeclaration?.FirstOrDefault(f => f.Key == SelectedItem.GetSelectedDeclaration()); - return !results.HasValue || results.Value.Value == null ? new ObservableCollection() : new ObservableCollection(results.Value.Value); + var results = _resultsByDeclaration?.FirstOrDefault(f => ReferenceEquals(f.Key, SelectedItem?.Declaration)); + return results?.Value == null ? new ObservableCollection() : new ObservableCollection(results.Value.Value); } } @@ -181,23 +124,27 @@ public bool IsBusy set { _isBusy = value; - EmptyUIRefreshMessageVisibility = false; OnPropertyChanged(); } } - private bool _emptyUIRefreshMessageVisibility = true; - public bool EmptyUIRefreshMessageVisibility + public void Dispose() { - get => _emptyUIRefreshMessageVisibility; - set + Dispose(true); + GC.SuppressFinalize(this); + } + + private bool _isDisposed; + + private void Dispose(bool disposing) + { + if (_isDisposed || !disposing) { - if (_emptyUIRefreshMessageVisibility != value) - { - _emptyUIRefreshMessageVisibility = value; - OnPropertyChanged(); - } + return; } + _isDisposed = true; + + _state.StateChanged -= OnStateChanged; } } } diff --git a/Rubberduck.Core/Common/RubberduckHooks.cs b/Rubberduck.Core/Common/RubberduckHooks.cs index 24b1a8105c..00337447c5 100644 --- a/Rubberduck.Core/Common/RubberduckHooks.cs +++ b/Rubberduck.Core/Common/RubberduckHooks.cs @@ -8,7 +8,6 @@ using Rubberduck.VBEditor.SafeComWrappers.Abstract; using Rubberduck.VBEditor.WindowsApi; using Rubberduck.AutoComplete; -using Rubberduck.AutoComplete.Service; namespace Rubberduck.Common { diff --git a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerComponentViewModel.cs b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerComponentViewModel.cs index 7f4f7fbb6d..96f11d95a1 100644 --- a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerComponentViewModel.cs +++ b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerComponentViewModel.cs @@ -1,25 +1,18 @@ -using System; using System.Collections.Generic; +using System.IO; using System.Linq; -using System.Runtime.InteropServices; -using System.Windows.Media.Imaging; using Rubberduck.Parsing.Symbols; -using Rubberduck.VBEditor; using Rubberduck.Parsing.Annotations; -using Rubberduck.VBEditor.ComManagement; using Rubberduck.VBEditor.SafeComWrappers; using Rubberduck.Resources.CodeExplorer; using Rubberduck.VBEditor.SafeComWrappers.Abstract; +using System; namespace Rubberduck.Navigation.CodeExplorer { - public class CodeExplorerComponentViewModel : CodeExplorerItemViewModel, ICodeExplorerDeclarationViewModel + public sealed class CodeExplorerComponentViewModel : CodeExplorerItemViewModel { - public Declaration Declaration { get; } - - public override CodeExplorerItemViewModel Parent { get; } - - private static readonly DeclarationType[] MemberTypes = + public static readonly DeclarationType[] MemberTypes = { DeclarationType.Constant, DeclarationType.Enumeration, @@ -32,200 +25,109 @@ public class CodeExplorerComponentViewModel : CodeExplorerItemViewModel, ICodeEx DeclarationType.PropertyLet, DeclarationType.PropertySet, DeclarationType.UserDefinedType, - DeclarationType.Variable, + DeclarationType.Variable }; - private readonly IProjectsProvider _projectsProvider; private readonly IVBE _vbe; - public CodeExplorerComponentViewModel(CodeExplorerItemViewModel parent, Declaration declaration, IEnumerable declarations, IProjectsProvider projectsProvider, IVBE vbe) + public CodeExplorerComponentViewModel(ICodeExplorerNode parent, Declaration declaration, ref List declarations, IVBE vbe) + : base(parent, declaration) { - Parent = parent; - Declaration = declaration; - _projectsProvider = projectsProvider; _vbe = vbe; + SetName(); + AddNewChildren(ref declarations); + } - _icon = Icons.ContainsKey(DeclarationType) - ? Icons[DeclarationType] - : GetImageSource(CodeExplorerUI.status_offline); - - Items = declarations.GroupBy(item => item.Scope).SelectMany(grouping => - grouping.Where(item => item.ParentDeclaration != null - && item.ParentScope == declaration.Scope - && MemberTypes.Contains(item.DeclarationType)) - .OrderBy(item => item.QualifiedSelection.Selection.StartLine) - .Select(item => new CodeExplorerMemberViewModel(this, item, grouping))) - .ToList(); - - _name = DeclarationType == DeclarationType.ResFile && string.IsNullOrEmpty(Declaration.IdentifierName) - ? CodeExplorerUI.CodeExplorer_ResourceFileText - : Declaration.IdentifierName; - - var qualifiedModuleName = declaration.QualifiedName.QualifiedModuleName; - try - { - switch (qualifiedModuleName.ComponentType) - { - case ComponentType.Document: - var parenthesizedName = string.Empty; - var state = DocumentState.Inaccessible; - using (var app = _vbe.HostApplication()) - { - if (app != null) - { - var document = app.GetDocument(qualifiedModuleName); - parenthesizedName = document?.DocumentName ?? string.Empty; - state = document?.State ?? DocumentState.Inaccessible; - } - } - - if (state == DocumentState.DesignView && ContainsBuiltinDocumentPropertiesProperty()) - { - CodeExplorerItemViewModel node = this; - while (node.Parent != null) - { - node = node.Parent; - } + private string _name; + public override string Name => _name; - ((CodeExplorerProjectViewModel) node).SetParenthesizedName(parenthesizedName); - } - else - { - if (!string.IsNullOrWhiteSpace(parenthesizedName)) - { - _name += " (" + parenthesizedName + ")"; - } - } - break; + public override string NameWithSignature => $"{Name}{(IsPredeclared ? " (Predeclared)" : string.Empty)}"; - case ComponentType.ResFile: - var fileName = Declaration.IdentifierName.Split('\\').Last(); - _name = $"{CodeExplorerUI.CodeExplorer_ResourceFileText} ({fileName})"; - break; + public override Comparer SortComparer => CodeExplorerItemComparer.ComponentType; - case ComponentType.RelatedDocument: - _name = $"({Declaration.IdentifierName.Split('\\').Last()})"; - break; + public bool IsPredeclared => Declaration != null && + Declaration.IsUserDefined && + Declaration.DeclarationType == DeclarationType.ClassModule && + Declaration.QualifiedName.QualifiedModuleName.ComponentType != ComponentType.Document && + Declaration.Attributes.HasPredeclaredIdAttribute(out _); - default: - _name = Declaration.IdentifierName; - break; - } - } - catch - { - // gotcha! (this means that the property either doesn't exist or we weren't able to get it for some reason) - } - } + public bool IsTestModule => Declaration.DeclarationType == DeclarationType.ProceduralModule + && Declaration.Annotations.Any(annotation => annotation.AnnotationType == AnnotationType.TestModule); - private bool ContainsBuiltinDocumentPropertiesProperty() + public override void Synchronize(ref List updated) { - var component = _projectsProvider.Component(Declaration.QualifiedName.QualifiedModuleName); - using (var properties = component.Properties) + base.Synchronize(ref updated); + if (Declaration is null) { - foreach (var property in properties) - using(property) - { - if (property.Name == "BuiltinDocumentProperties") - { - return true; - } - } - - return false; + return; } + + // Document modules might have had the underlying COM object renamed since the last reparse. Let's check... + SetName(); } - private bool _isErrorState; - public bool IsErrorState + protected override void AddNewChildren(ref List updated) { - get => _isErrorState; - set + if (updated is null) { - _isErrorState = value; - _icon = GetImageSource(CodeExplorerUI.cross_circle); + return; + } + var children = updated.Where(declaration => + !ReferenceEquals(Declaration, declaration) && + declaration.QualifiedModuleName.Equals(Declaration?.QualifiedModuleName)).ToList(); - foreach (var item in Items) - { - ((CodeExplorerMemberViewModel) item).ParentComponentHasError(); - } + updated = updated.Except(children.Concat(new [] { Declaration })).ToList(); - OnPropertyChanged(); - OnPropertyChanged("CollapsedIcon"); - OnPropertyChanged("ExpandedIcon"); + foreach (var member in children.Where(declaration => MemberTypes.Contains(declaration.DeclarationType)).ToList()) + { + AddChild(new CodeExplorerMemberViewModel(this, member, ref children)); } } - public bool IsTestModule + private void SetName() { - get + _name = Declaration?.IdentifierName ?? string.Empty; + + if (Declaration is null) { - return Declaration.DeclarationType == DeclarationType.ProceduralModule - && Declaration.Annotations.Any(annotation => annotation.AnnotationType == AnnotationType.TestModule); + return; } - } - private readonly string _name; - public override string Name => _name; - - public override string NameWithSignature => _name; + var qualifiedModuleName = Declaration.QualifiedName.QualifiedModuleName; - public override QualifiedSelection? QualifiedSelection => Declaration.QualifiedSelection; - - private ComponentType ComponentType => Declaration.QualifiedName.QualifiedModuleName.ComponentType; - - private static readonly IDictionary DeclarationTypes = new Dictionary - { - { ComponentType.ClassModule, DeclarationType.ClassModule }, - { ComponentType.StandardModule, DeclarationType.ProceduralModule }, - { ComponentType.Document, DeclarationType.Document }, - { ComponentType.UserForm, DeclarationType.UserForm }, - { ComponentType.VBForm, DeclarationType.VbForm }, - { ComponentType.MDIForm, DeclarationType.MdiForm}, - { ComponentType.UserControl, DeclarationType.UserControl}, - { ComponentType.DocObject, DeclarationType.DocObject}, - { ComponentType.ResFile, DeclarationType.ResFile}, - { ComponentType.RelatedDocument, DeclarationType.RelatedDocument}, - { ComponentType.PropPage, DeclarationType.PropPage}, - { ComponentType.ActiveXDesigner, DeclarationType.ActiveXDesigner} - }; - - private DeclarationType DeclarationType - { - get + try { - var result = DeclarationType.ClassModule; - try - { - DeclarationTypes.TryGetValue(ComponentType, out result); - } - catch (COMException exception) + switch (qualifiedModuleName.ComponentType) { - Console.WriteLine(exception); + case ComponentType.Document: + using (var app = _vbe?.HostApplication()) + { + var document = app?.GetDocument(qualifiedModuleName); + if (document != null) + { + var parenthesized = document.DocumentName ?? string.Empty; + _name = string.IsNullOrEmpty(parenthesized) ? _name : $"{_name} ({parenthesized})"; + } + } + + break; + case ComponentType.ResFile: + _name = string.IsNullOrEmpty(_name) + ? CodeExplorerUI.CodeExplorer_ResourceFileText + : $"{CodeExplorerUI.CodeExplorer_ResourceFileText} ({Path.GetFileName(_name)})"; + break; + case ComponentType.RelatedDocument: + _name = string.IsNullOrEmpty(_name) ? string.Empty : Path.GetFileName(_name); + break; } - return result; } - } - - private static readonly IDictionary Icons = new Dictionary - { - { DeclarationType.ClassModule, GetImageSource(CodeExplorerUI.ObjectClass) }, - { DeclarationType.ProceduralModule, GetImageSource(CodeExplorerUI.ObjectModule) }, - { DeclarationType.UserForm, GetImageSource(CodeExplorerUI.ProjectForm) }, - { DeclarationType.Document, GetImageSource(CodeExplorerUI.document_office) }, - { DeclarationType.VbForm, GetImageSource(CodeExplorerUI.ProjectForm)}, - { DeclarationType.MdiForm, GetImageSource(CodeExplorerUI.MdiForm)}, - { DeclarationType.UserControl, GetImageSource(CodeExplorerUI.ui_scroll_pane_form)}, - { DeclarationType.DocObject, GetImageSource(CodeExplorerUI.document_globe)}, - { DeclarationType.PropPage, GetImageSource(CodeExplorerUI.ui_tab_content)}, - { DeclarationType.ActiveXDesigner, GetImageSource(CodeExplorerUI.pencil_ruler)}, - { DeclarationType.ResFile, GetImageSource(CodeExplorerUI.document_block)}, - { DeclarationType.RelatedDocument, GetImageSource(CodeExplorerUI.document_import)} - }; + catch (Exception ex) + { + Logger.Trace(ex); + } - private BitmapImage _icon; - public override BitmapImage CollapsedIcon => _icon; - public override BitmapImage ExpandedIcon => _icon; + OnNameChanged(); + } } } diff --git a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerCustomFolderViewModel.cs b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerCustomFolderViewModel.cs index fc6333c915..dd840d0749 100644 --- a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerCustomFolderViewModel.cs +++ b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerCustomFolderViewModel.cs @@ -1,85 +1,138 @@ -using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; -using System.Windows.Media.Imaging; +using Rubberduck.Navigation.Folders; using Rubberduck.Parsing.Symbols; using Rubberduck.VBEditor; -using Rubberduck.VBEditor.ComManagement; using Rubberduck.VBEditor.SafeComWrappers.Abstract; namespace Rubberduck.Navigation.CodeExplorer { - public class CodeExplorerCustomFolderViewModel : CodeExplorerItemViewModel + [DebuggerDisplay("{" + nameof(Name) + "}")] + public sealed class CodeExplorerCustomFolderViewModel : CodeExplorerItemViewModel { private static readonly DeclarationType[] ComponentTypes = { DeclarationType.ClassModule, DeclarationType.Document, DeclarationType.ProceduralModule, - DeclarationType.UserForm, + DeclarationType.UserForm }; - private readonly IProjectsProvider _projectsProvider; private readonly IVBE _vbe; - public CodeExplorerCustomFolderViewModel(CodeExplorerItemViewModel parent, string name, string fullPath, IProjectsProvider projectsProvider, IVBE vbe) + public CodeExplorerCustomFolderViewModel( + ICodeExplorerNode parent, + string name, + string fullPath, + IVBE vbe, + ref List declarations) : base(parent, parent?.Declaration) { - _parent = parent; - _projectsProvider = projectsProvider; _vbe = vbe; - - FullPath = fullPath; + FolderDepth = parent is CodeExplorerCustomFolderViewModel folder ? folder.FolderDepth + 1 : 1; + FullPath = fullPath?.Trim('"') ?? string.Empty; Name = name.Replace("\"", string.Empty); - FolderAttribute = string.Format("@Folder(\"{0}\")", fullPath.Replace("\"", string.Empty)); - CollapsedIcon = GetImageSource(Resources.CodeExplorer.CodeExplorerUI.FolderClosed); - ExpandedIcon = GetImageSource(Resources.CodeExplorer.CodeExplorerUI.FolderOpen); + AddNewChildren(ref declarations); + } + + public override string Name { get; } + + public override string PanelTitle => FullPath ?? string.Empty; + + public override string Description => FolderAttribute ?? string.Empty; + + public string FullPath { get; } + + public string FolderAttribute => $"'@Folder(\"{FullPath.Replace("\"", string.Empty)}\")"; + + /// + /// One-based depth in the folder hierarchy. + /// + public int FolderDepth { get; } + + public override QualifiedSelection? QualifiedSelection => null; + + public override bool IsErrorState + { + get => false; + set { /* Folders can never be in an error state. */ } } - public void AddNodes(List declarations) + public override Comparer SortComparer => CodeExplorerItemComparer.Name; + + protected override void AddNewChildren(ref List declarations) { - var parents = declarations.GroupBy(item => item.ComponentName).OrderBy(item => item.Key).ToList(); - foreach (var component in parents) + var children = declarations.Where(declaration => declaration.IsInFolderOrSubFolder(FullPath)).ToList(); + declarations = declarations.Except(children).ToList(); + + var subFolders = children.Where(declaration => declaration.IsInSubFolder(FullPath)).ToList(); + + foreach (var folder in subFolders.GroupBy(declaration => declaration.CustomFolder.SubFolderRoot(FullPath))) { - try - { - var moduleName = component.Key; - var parent = declarations.Single(item => - ComponentTypes.Contains(item.DeclarationType) && item.ComponentName == moduleName); - var members = declarations.Where(item => - !ComponentTypes.Contains(item.DeclarationType) && item.ComponentName == moduleName); + var contents = folder.ToList(); + AddChild(new CodeExplorerCustomFolderViewModel(this, folder.Key, $"{FullPath}.{folder.Key}", _vbe, ref contents)); + } - AddChild(new CodeExplorerComponentViewModel(this, parent, members, _projectsProvider, _vbe)); - } - catch (InvalidOperationException exception) + children = children.Except(subFolders).ToList(); + + foreach (var declaration in children.Where(child => child.IsInFolder(FullPath)).GroupBy(item => item.ComponentName)) + { + var moduleName = declaration.Key; + var parent = children.SingleOrDefault(item => + ComponentTypes.Contains(item.DeclarationType) && item.ComponentName == moduleName); + + if (parent is null) { - Console.WriteLine(exception); + continue; } + + var members = children.Where(item => + !ComponentTypes.Contains(item.DeclarationType) && item.ComponentName == moduleName).ToList(); + + AddChild(new CodeExplorerComponentViewModel(this, parent, ref members, _vbe)); } } - public string FolderAttribute { get; } - - public string FullPath { get; } + public override void Synchronize(ref List updated) + { + SynchronizeChildren(ref updated); + } - public override string Name { get; } + protected override void SynchronizeChildren(ref List updated) + { + var children = updated.Where(declaration => declaration.IsInFolderOrSubFolder(FullPath)).ToList(); + updated = updated.Except(children).ToList(); - public override string NameWithSignature => Name; // Is this actually doing anything? Should this member be replaced with 'Name'? + if (!children.Any()) + { + Declaration = null; + return; + } - public override QualifiedSelection? QualifiedSelection => null; + var subFolders = children.Where(declaration => declaration.IsInSubFolder(FullPath)).ToList(); + children = children.Except(subFolders).ToList(); - public override BitmapImage CollapsedIcon { get; } + foreach (var subfolder in Children.OfType().ToList()) + { + subfolder.SynchronizeChildren(ref subFolders); + if (subfolder.Declaration is null) + { + RemoveChild(subfolder); + } + } - public override BitmapImage ExpandedIcon { get; } + foreach (var child in Children.OfType().ToList()) + { + child.Synchronize(ref children); + if (child.Declaration is null) + { + RemoveChild(child); + } + } - // I have to set the parent from a different location than - // the node is created because of the folder helper - internal void SetParent(CodeExplorerItemViewModel parent) - { - _parent = parent; + children = children.Concat(subFolders).ToList(); + AddNewChildren(ref children); } - - private CodeExplorerItemViewModel _parent; - public override CodeExplorerItemViewModel Parent => _parent; } } diff --git a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerItemComparer.cs b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerItemComparer.cs new file mode 100644 index 0000000000..d6b2834ee2 --- /dev/null +++ b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerItemComparer.cs @@ -0,0 +1,324 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Rubberduck.Parsing.Symbols; +using Rubberduck.VBEditor.SafeComWrappers; + +namespace Rubberduck.Navigation.CodeExplorer +{ + public static class CodeExplorerItemComparer + { + public static Comparer NodeType { get; } = new CompareByNodeType(); + + public static Comparer Name { get; } = new CompareByName(); + + public static Comparer DeclarationType { get; } = new CompareByDeclarationType(); + + public static Comparer Accessibility { get; } = new CompareByAccessibility(); + + public static Comparer CodeLine { get; } = new CompareByCodeLine(); + + public static Comparer ComponentType { get; } = new CompareByComponentType(); + + public static Comparer DeclarationTypeThenName { get; } = new CompareByDeclarationTypeAndName(); + + public static Comparer DeclarationTypeThenCodeLine { get; } = new CompareByDeclarationTypeAndCodeLine(); + + public static Comparer ReferencePriority { get; } = new CompareByReferencePriority(); + + public static Comparer ReferenceType { get; } = new CompareByReferenceType(); + } + + public class CompareByDeclarationTypeAndCodeLine : Comparer + { + private static readonly List> Comparisons = + new List> + { + (x, y) => CodeExplorerItemComparer.DeclarationType.Compare(x, y), + (x, y) => CodeExplorerItemComparer.CodeLine.Compare(x, y) + }; + + public override int Compare(ICodeExplorerNode x, ICodeExplorerNode y) + { + return x == y ? 0 : Comparisons.Select(comp => comp(x, y)).FirstOrDefault(result => result != 0); + } + } + + public class CompareByDeclarationTypeAndName : Comparer + { + private static readonly List> Comparisons = + new List> + { + (x, y) => CodeExplorerItemComparer.DeclarationType.Compare(x, y), + (x, y) => CodeExplorerItemComparer.Name.Compare(x, y), + (x, y) => CodeExplorerItemComparer.Accessibility.Compare(x, y) + }; + + public override int Compare(ICodeExplorerNode x, ICodeExplorerNode y) + { + return x == y ? 0 : Comparisons.Select(comp => comp(x, y)).FirstOrDefault(result => result != 0); + } + } + + public class CompareByName : CompareByNodeType + { + public override int Compare(ICodeExplorerNode x, ICodeExplorerNode y) + { + var node = base.Compare(x, y); + return node != 0 ? node : string.Compare(x?.NameWithSignature, y?.NameWithSignature, StringComparison.OrdinalIgnoreCase); + } + } + + public class CompareByCodeLine : CompareByNodeType + { + public override int Compare(ICodeExplorerNode x, ICodeExplorerNode y) + { + var node = base.Compare(x, y); + if (node != 0) + { + return node; + } + + var first = x?.QualifiedSelection?.Selection; + + if (first is null) + { + return -1; + } + + var second = y?.QualifiedSelection?.Selection; + + if (second is null) + { + return 1; + } + + return first.Value.CompareTo(second.Value); + } + } + + public class CompareByDeclarationType : Comparer + { + private static readonly Dictionary SortOrder = new Dictionary + { + // Some DeclarationTypes we want to treat the same, like Subs and Functions, + // or Property Gets, Lets, and Sets. + // Give them the same number. + {DeclarationType.LibraryFunction, 0}, + {DeclarationType.LibraryProcedure, 0}, + {DeclarationType.UserDefinedType, 1}, + {DeclarationType.Enumeration, 2}, + {DeclarationType.Event, 3}, + {DeclarationType.Constant, 4}, + {DeclarationType.Variable, 5}, + {DeclarationType.PropertyGet, 6}, + {DeclarationType.PropertyLet, 6}, + {DeclarationType.PropertySet, 6}, + {DeclarationType.Function, 7}, + {DeclarationType.Procedure, 7} + }; + + public override int Compare(ICodeExplorerNode x, ICodeExplorerNode y) + { + if (x == y) + { + return 0; + } + + var node = CodeExplorerItemComparer.NodeType.Compare(x, y); + if (node != 0) + { + return node; + } + + var first = x?.Declaration?.DeclarationType; + + if (first is null || !SortOrder.ContainsKey(first.Value)) + { + return -1; + } + + var second = y?.Declaration?.DeclarationType; + + if (second is null || !SortOrder.ContainsKey(second.Value)) + { + return 1; + } + + return SortOrder[first.Value].CompareTo(SortOrder[second.Value]); + } + } + + public class CompareByComponentType : Comparer + { + private static readonly Dictionary SortOrder = new Dictionary + { + // These are intended to be in the same order as the host would display them in folder view. + // Not sure about some of the VB6 specific ones. + {ComponentType.Document, 0}, + {ComponentType.UserForm, 1}, + {ComponentType.VBForm, 1}, + {ComponentType.MDIForm, 1}, + {ComponentType.StandardModule, 2}, + {ComponentType.ClassModule, 3}, + {ComponentType.UserControl, 4}, + {ComponentType.ActiveXDesigner, 4}, + {ComponentType.PropPage, 5}, + {ComponentType.DocObject, 6}, + {ComponentType.ResFile, 6}, + {ComponentType.RelatedDocument, 6}, + {ComponentType.ComComponent, 7}, + {ComponentType.Undefined, 7} + }; + + public override int Compare(ICodeExplorerNode x, ICodeExplorerNode y) + { + if (x == y) + { + return 0; + } + + var first = x?.QualifiedSelection?.QualifiedName.ComponentType; + + if (first is null || !SortOrder.ContainsKey(first.Value)) + { + return -1; + } + + var second = y?.QualifiedSelection?.QualifiedName.ComponentType; + + if (second is null || !SortOrder.ContainsKey(second.Value)) + { + return 1; + } + + var component = SortOrder[first.Value].CompareTo(SortOrder[second.Value]); + + return component == 0 ? CodeExplorerItemComparer.Name.Compare(x, y) : component; + } + } + + public class CompareByAccessibility : Comparer + { + public override int Compare(ICodeExplorerNode x, ICodeExplorerNode y) + { + if (x == y) + { + return 0; + } + + var first = x?.Declaration?.Accessibility; + + if (first is null) + { + return -1; + } + + var second = y?.Declaration?.Accessibility; + + if (second is null) + { + return 1; + } + + // Public and Implicit Subs/Functions appear the same, so treat Implicits like Publics. + if (first == Accessibility.Implicit) + { + first = Accessibility.Public; + } + + if (second == Accessibility.Implicit) + { + first = Accessibility.Public; + } + + // These are reversed because Accessibility is ordered lowest to highest. + return second.Value.CompareTo(first.Value); + } + } + + public class CompareByNodeType : Comparer + { + public override int Compare(ICodeExplorerNode x, ICodeExplorerNode y) + { + if (x == y) + { + return 0; + } + + if (x == null) + { + return -1; + } + + if (y == null) + { + return 1; + } + + // references come first + if (x is CodeExplorerReferenceFolderViewModel ^ + y is CodeExplorerReferenceFolderViewModel) + { + return x is CodeExplorerReferenceFolderViewModel ? -1 : 1; + } + + // folders come next + if (x is CodeExplorerCustomFolderViewModel ^ + y is CodeExplorerCustomFolderViewModel) + { + return x is CodeExplorerCustomFolderViewModel ? -1 : 1; + } + + return 0; + } + } + + public class CompareByReferencePriority : Comparer + { + public override int Compare(ICodeExplorerNode x, ICodeExplorerNode y) + { + if (x == y) + { + return 0; + } + + if (!(x is CodeExplorerReferenceViewModel first)) + { + return -1; + } + + if (!(y is CodeExplorerReferenceViewModel second)) + { + return 1; + } + + return (first.Reference?.Priority ?? int.MaxValue).CompareTo(second.Reference?.Priority); + } + } + + public class CompareByReferenceType : CompareByNodeType + { + public override int Compare(ICodeExplorerNode x, ICodeExplorerNode y) + { + var node = base.Compare(x, y); + if (node != 0) + { + return node; + } + + if (!(x is CodeExplorerReferenceFolderViewModel first)) + { + return -1; + } + + if (!(y is CodeExplorerReferenceFolderViewModel second)) + { + return 1; + } + + // Libraries are first, so reverse the comparison. + return second.ReferenceKind.CompareTo(first.ReferenceKind); + } + } +} diff --git a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerItemViewModel.cs b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerItemViewModel.cs index 87a8566d51..05724f905d 100644 --- a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerItemViewModel.cs +++ b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerItemViewModel.cs @@ -1,271 +1,79 @@ using System.Collections.Generic; using System.Linq; -using System.Windows; -using System.Windows.Media.Imaging; using Rubberduck.Parsing.Symbols; -using Rubberduck.UI; -using Rubberduck.VBEditor; -using Rubberduck.VBEditor.SafeComWrappers; namespace Rubberduck.Navigation.CodeExplorer { - public class CompareByName : Comparer + public abstract class CodeExplorerItemViewModel : CodeExplorerItemViewModelBase { - public override int Compare(CodeExplorerItemViewModel x, CodeExplorerItemViewModel y) - { - if (x == y) - { - return 0; - } + protected CodeExplorerItemViewModel(ICodeExplorerNode parent, Declaration declaration) : base(parent, declaration) { } - var nodeComparison = new CompareByNodeType().Compare(x, y); + public override string Name => Declaration?.IdentifierName ?? string.Empty; - return nodeComparison != 0 - ? nodeComparison - : string.CompareOrdinal(x.NameWithSignature, y.NameWithSignature); - } - } + public override string NameWithSignature => Name; - public class CompareByType : Comparer - { - private static readonly Dictionary SortOrder = new Dictionary + private bool _isErrorState; + public override bool IsErrorState { - // Some DeclarationTypes we want to treat the same, like Subs and Functions, - // or Property Gets, Lets, and Sets. - // Give them the same number. - {DeclarationType.LibraryFunction, 0}, - {DeclarationType.LibraryProcedure, 0}, - {DeclarationType.UserDefinedType, 1}, - {DeclarationType.Enumeration, 2}, - {DeclarationType.Event, 3}, - {DeclarationType.Constant, 4}, - {DeclarationType.Variable, 5}, - {DeclarationType.PropertyGet, 6}, - {DeclarationType.PropertyLet, 6}, - {DeclarationType.PropertySet, 6}, - {DeclarationType.Function, 7}, - {DeclarationType.Procedure, 7} - }; - - public override int Compare(CodeExplorerItemViewModel x, CodeExplorerItemViewModel y) - { - if (x == y) - { - return 0; - } - - var nodeComparison = new CompareByNodeType().Compare(x, y); - if (nodeComparison != 0) - { - return nodeComparison; - } - - var xNode = (ICodeExplorerDeclarationViewModel)x; - var yNode = (ICodeExplorerDeclarationViewModel)y; - - // keep separate types separate - if (xNode.Declaration.DeclarationType != yNode.Declaration.DeclarationType) + get => _isErrorState; + set { - if (SortOrder.TryGetValue(xNode.Declaration.DeclarationType, out var xValue) && - SortOrder.TryGetValue(yNode.Declaration.DeclarationType, out var yValue)) + if (_isErrorState == value) { - if (xValue != yValue) - { return xValue < yValue ? -1 : 1; } + return; } - } - - // The Tree shows Public and Private Subs/Functions with a seperate icon. - // But Public and Implicit Subs/Functions appear the same, so treat Implicts like Publics. - var xNodeAcc = xNode.Declaration.Accessibility == Accessibility.Implicit ? Accessibility.Public : xNode.Declaration.Accessibility; - var yNodeAcc = yNode.Declaration.Accessibility == Accessibility.Implicit ? Accessibility.Public : yNode.Declaration.Accessibility; - - if (xNodeAcc != yNodeAcc) - { - return xNodeAcc > yNodeAcc ? -1 : 1; - } - if (x.ExpandedIcon != y.ExpandedIcon) - { - // ReSharper disable PossibleInvalidOperationException - this will have a QualifiedSelection - var xQmn = x.QualifiedSelection.Value.QualifiedName; - var yQmn = y.QualifiedSelection.Value.QualifiedName; + _isErrorState = value; - if (xQmn.ComponentType == ComponentType.Document ^ yQmn.ComponentType == ComponentType.Document) + foreach (var child in Children) { - return xQmn.ComponentType == ComponentType.Document ? -1 : 1; + child.IsErrorState = _isErrorState; } - } - return 0; - } - } - - public class CompareBySelection : Comparer - { - public override int Compare(CodeExplorerItemViewModel x, CodeExplorerItemViewModel y) - { - if (x == y) - { - return 0; - } - - var nodeComparison = new CompareByNodeType().Compare(x, y); - if (nodeComparison != 0) - { - return nodeComparison; - } - - if (!x.QualifiedSelection.HasValue && !y.QualifiedSelection.HasValue) - { - return 0; - } - - if (x.QualifiedSelection.HasValue ^ y.QualifiedSelection.HasValue) - { - return x.QualifiedSelection.HasValue ? -1 : 1; - } - - if (x.QualifiedSelection.Value.Selection.StartLine == y.QualifiedSelection.Value.Selection.StartLine) - { - return 0; - } - - return x.QualifiedSelection.Value.Selection.StartLine < y.QualifiedSelection.Value.Selection.StartLine ? -1 : 1; - } - } - - public class CompareByNodeType : Comparer - { - public override int Compare(CodeExplorerItemViewModel x, CodeExplorerItemViewModel y) - { - if (x == y) - { - return 0; - } - - // references come first - if (x is CodeExplorerReferenceFolderViewModel ^ - y is CodeExplorerReferenceFolderViewModel) - { - return x is CodeExplorerReferenceFolderViewModel ? -1 : 1; - } - - // references always sort by priority - if (x is CodeExplorerReferenceViewModel first && - y is CodeExplorerReferenceViewModel second) - { - return first.Priority > second.Priority ? 1 : - 1; - } - - // folders come next - if (x is CodeExplorerCustomFolderViewModel ^ - y is CodeExplorerCustomFolderViewModel) - { - return x is CodeExplorerCustomFolderViewModel ? -1 : 1; - } - - // folders are always sorted by name - if (x is CodeExplorerCustomFolderViewModel && - y is CodeExplorerCustomFolderViewModel) - { - return string.CompareOrdinal(x.NameWithSignature, y.NameWithSignature); - } - - return 0; - } - } - - public abstract class CodeExplorerItemViewModel : ViewModelBase - { - private List _items = new List(); - public List Items - { - get => _items; - protected set - { - _items = value; OnPropertyChanged(); } } - private bool _isExpanded; - public bool IsExpanded + public virtual void Synchronize(ref List updated) { - get => _isExpanded; - set + if (Declaration is null) { - _isExpanded = value; - OnPropertyChanged(); + return; } - } - public bool IsSelected { get; set; } + var matching = updated.FirstOrDefault(decl => + Declaration.DeclarationType == decl?.DeclarationType && + Declaration.QualifiedName.Equals(decl.QualifiedName) && + (Declaration.ParentDeclaration is null || Declaration.ParentDeclaration.QualifiedName.Equals(decl.ParentDeclaration?.QualifiedName))); - private bool _isVisisble = true; - public bool IsVisible - { - get => _isVisisble; - set + if (matching is null) { - _isVisisble = value; - OnPropertyChanged(); + Declaration = null; + return; } - } - - public abstract string Name { get; } - public abstract string NameWithSignature { get; } - public abstract BitmapImage CollapsedIcon { get; } - public abstract BitmapImage ExpandedIcon { get; } - public abstract CodeExplorerItemViewModel Parent { get; } - public virtual FontWeight FontWeight => FontWeights.Normal; - - public abstract QualifiedSelection? QualifiedSelection { get; } + Declaration = matching; + updated.Remove(matching); + SynchronizeChildren(ref updated); + } - public CodeExplorerItemViewModel GetChild(string name) + protected virtual void SynchronizeChildren(ref List updated) { - foreach (var item in _items) + foreach (var child in Children.OfType().ToList()) { - if (item.Name == name) - { - return item; - } - var result = item.GetChild(name); - if (result != null) + child.Synchronize(ref updated); + if (child.Declaration is null) { - return result; + RemoveChild(child); + continue; } - } - - return null; - } - - public Declaration GetSelectedDeclaration() - { - return this is ICodeExplorerDeclarationViewModel viewModel - ? viewModel.Declaration - : null; - } - - public void AddChild(CodeExplorerItemViewModel item) - { - _items.Add(item); - } - - public void ReorderItems(bool sortByName, bool groupByType) - { - if (groupByType) - { - Items = sortByName - ? Items.OrderBy(o => o, new CompareByType()).ThenBy(t => t, new CompareByName()).ToList() - : Items.OrderBy(o => o, new CompareByType()).ThenBy(t => t, new CompareBySelection()).ToList(); - return; + updated.Remove(child.Declaration); } - Items = sortByName - ? Items.OrderBy(t => t, new CompareByName()).ToList() - : Items.OrderBy(t => t, new CompareBySelection()).ToList(); + AddNewChildren(ref updated); } + + protected abstract void AddNewChildren(ref List updated); } } diff --git a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerItemViewModelBase.cs b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerItemViewModelBase.cs new file mode 100644 index 0000000000..286e2876e6 --- /dev/null +++ b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerItemViewModelBase.cs @@ -0,0 +1,252 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Linq; +using System.Windows; +using NLog; +using Rubberduck.Parsing; +using Rubberduck.Parsing.Symbols; +using Rubberduck.Resources; +using Rubberduck.UI; +using Rubberduck.VBEditor; + +namespace Rubberduck.Navigation.CodeExplorer +{ + public abstract class CodeExplorerItemViewModelBase : ViewModelBase, ICodeExplorerNode + { + protected static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + protected CodeExplorerItemViewModelBase(ICodeExplorerNode parent, Declaration declaration) + { + Parent = parent; + _declaration = declaration; + UnfilteredIsExpanded = IsExpanded; + + if (parent != null) + { + Filter = parent.Filter; + } + } + + private Declaration _declaration; + public Declaration Declaration + { + get => _declaration; + protected set + { + _declaration = value; + + if (_declaration is null) + { + // No need to call OnPropertyChanged - the node's being removed. + return; + } + + OnPropertyChanged(); + } + } + + public ICodeExplorerNode Parent { get; } + + public abstract string Name { get; } + + public abstract string NameWithSignature { get; } + + public virtual string PanelTitle + { + get + { + if (Declaration is null) + { + return string.Empty; + } + + var nameWithDeclarationType = + $"{Declaration.IdentifierName} - ({RubberduckUI.ResourceManager.GetString("DeclarationType_" + Declaration.DeclarationType, CultureInfo.CurrentUICulture)})"; + + if (string.IsNullOrEmpty(Declaration.AsTypeName)) + { + return nameWithDeclarationType; + } + + var typeName = Declaration.HasTypeHint + ? SymbolList.TypeHintToTypeName[Declaration.TypeHint] + : Declaration.AsTypeName; + + return $"{nameWithDeclarationType}: {typeName}"; + } + } + + public virtual string Description => Declaration?.DescriptionString ?? string.Empty; + + protected void OnNameChanged() + { + OnPropertyChanged(nameof(Name)); + OnPropertyChanged(nameof(NameWithSignature)); + OnPropertyChanged(nameof(PanelTitle)); + OnPropertyChanged(nameof(Description)); + } + + public virtual QualifiedSelection? QualifiedSelection => Declaration?.QualifiedSelection; + + protected bool UnfilteredIsExpanded { get; private set; } + + private bool _isExpanded; + public bool IsExpanded + { + get => _isExpanded; + set + { + if (_isExpanded == value) + { + return; + } + + _isExpanded = value; + OnPropertyChanged(); + } + } + + private bool _selected; + public bool IsSelected + { + get => _selected; + set + { + _selected = value; + OnPropertyChanged(); + } + } + + public virtual bool IsDimmed + { + get => false; + set { /* no-op for base class, override as needed */ } + } + + public virtual bool IsObsolete => false; + + public abstract bool IsErrorState { get; set; } + + public virtual string ToolTip => NameWithSignature; + + public virtual FontWeight FontWeight => FontWeights.Normal; + + public ObservableCollection Children { get; } = new ObservableCollection(); + + public void AddChild(ICodeExplorerNode child) + { + if (Children.Contains(child)) + { + return; + } + + var before = Children.FirstOrDefault(existing => existing.SortComparer.Compare(existing, child) > 0); + if (before is null) + { + Children.Add(child); + return; + } + + Children.Insert(Children.IndexOf(before), child); + } + + public void AddChildren(IEnumerable children) + { + foreach (var child in children) + { + AddChild(child); + } + } + + public void RemoveChild(ICodeExplorerNode child) + { + Children.Remove(child); + } + + public void RemoveChildren(IEnumerable children) + { + foreach (var child in children) + { + RemoveChild(child); + } + } + + private CodeExplorerSortOrder _order; + public CodeExplorerSortOrder SortOrder + { + get => _order; + set + { + if (_order == value) + { + return; + } + + _order = value; + + foreach (var child in Children) + { + child.SortOrder = _order; + } + Sort(); + } + } + + public abstract Comparer SortComparer { get; } + + private void Sort() + { + if (Children.Count == 0) + { + return; + } + + var ordered = new List(Children); + ordered.Sort(Children.First().SortComparer); + + for (var index = 0; index < ordered.Count; index++) + { + var position = Children.IndexOf(ordered[index]); + if (position != index) + { + Children.Move(position, index); + } + } + } + + private string _filter = string.Empty; + public string Filter + { + get => _filter; + set + { + if (string.IsNullOrEmpty(_filter)) + { + UnfilteredIsExpanded = _isExpanded; + } + + var input = value ?? string.Empty; + if (_filter.Equals(input)) + { + return; + } + + _filter = input; + foreach (var child in Children) + { + child.Filter = input; + } + + OnPropertyChanged(); + OnPropertyChanged(nameof(Filtered)); + IsExpanded = !string.IsNullOrEmpty(_filter) ? Children.Any(child => !child.Filtered) : UnfilteredIsExpanded; + } + } + + public virtual bool Filtered => !string.IsNullOrEmpty(Filter) && + Name.IndexOf(Filter, StringComparison.OrdinalIgnoreCase) < 0 && + Children.All(node => node.Filtered); + } +} diff --git a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerMemberViewModel.cs b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerMemberViewModel.cs index 9f8f0dadc5..496384fbde 100644 --- a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerMemberViewModel.cs +++ b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerMemberViewModel.cs @@ -1,110 +1,23 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Text; -using System.Windows.Media.Imaging; using Rubberduck.Parsing.Annotations; using Rubberduck.Parsing.Grammar; using Rubberduck.Parsing.Symbols; -using Rubberduck.VBEditor; -using resx = Rubberduck.Resources.CodeExplorer.CodeExplorerUI; namespace Rubberduck.Navigation.CodeExplorer { - public class CodeExplorerMemberViewModel : CodeExplorerItemViewModel, ICodeExplorerDeclarationViewModel + public sealed class CodeExplorerMemberViewModel : CodeExplorerItemViewModel { - public Declaration Declaration { get; } - - private static readonly DeclarationType[] SubMemberTypes = - { - DeclarationType.EnumerationMember, - DeclarationType.UserDefinedTypeMember - }; - - private static readonly IDictionary,BitmapImage> Mappings = - new Dictionary, BitmapImage> - { - { Tuple.Create(DeclarationType.Constant, Accessibility.Private), GetImageSource(resx.ObjectConstantPrivate)}, - { Tuple.Create(DeclarationType.Constant, Accessibility.Public), GetImageSource(resx.ObjectConstant)}, - { Tuple.Create(DeclarationType.Enumeration, Accessibility.Public), GetImageSource(resx.ObjectEnum)}, - { Tuple.Create(DeclarationType.Enumeration, Accessibility.Private ), GetImageSource(resx.ObjectEnumPrivate)}, - { Tuple.Create(DeclarationType.EnumerationMember, Accessibility.Public), GetImageSource(resx.ObjectEnumItem)}, - { Tuple.Create(DeclarationType.Event, Accessibility.Public), GetImageSource(resx.ObjectEvent)}, - { Tuple.Create(DeclarationType.Event, Accessibility.Private ), GetImageSource(resx.ObjectEventPrivate)}, - { Tuple.Create(DeclarationType.Function, Accessibility.Public), GetImageSource(resx.ObjectMethod)}, - { Tuple.Create(DeclarationType.Function, Accessibility.Friend ), GetImageSource(resx.ObjectMethodFriend)}, - { Tuple.Create(DeclarationType.Function, Accessibility.Private ), GetImageSource(resx.ObjectMethodPrivate)}, - { Tuple.Create(DeclarationType.LibraryFunction, Accessibility.Public), GetImageSource(resx.ObjectMethodShortcut)}, - { Tuple.Create(DeclarationType.LibraryProcedure, Accessibility.Public), GetImageSource(resx.ObjectMethodShortcut)}, - { Tuple.Create(DeclarationType.LibraryFunction, Accessibility.Private), GetImageSource(resx.ObjectMethodShortcut)}, - { Tuple.Create(DeclarationType.LibraryProcedure, Accessibility.Private), GetImageSource(resx.ObjectMethodShortcut)}, - { Tuple.Create(DeclarationType.LibraryFunction, Accessibility.Friend), GetImageSource(resx.ObjectMethodShortcut)}, - { Tuple.Create(DeclarationType.LibraryProcedure, Accessibility.Friend), GetImageSource(resx.ObjectMethodShortcut)}, - { Tuple.Create(DeclarationType.Procedure, Accessibility.Public), GetImageSource(resx.ObjectMethod)}, - { Tuple.Create(DeclarationType.Procedure, Accessibility.Friend ), GetImageSource(resx.ObjectMethodFriend)}, - { Tuple.Create(DeclarationType.Procedure, Accessibility.Private ), GetImageSource(resx.ObjectMethodPrivate)}, - { Tuple.Create(DeclarationType.PropertyGet, Accessibility.Public), GetImageSource(resx.ObjectProperties)}, - { Tuple.Create(DeclarationType.PropertyGet, Accessibility.Friend ), GetImageSource(resx.ObjectPropertiesFriend)}, - { Tuple.Create(DeclarationType.PropertyGet, Accessibility.Private ), GetImageSource(resx.ObjectPropertiesPrivate)}, - { Tuple.Create(DeclarationType.PropertyLet, Accessibility.Public), GetImageSource(resx.ObjectProperties)}, - { Tuple.Create(DeclarationType.PropertyLet, Accessibility.Friend ), GetImageSource(resx.ObjectPropertiesFriend)}, - { Tuple.Create(DeclarationType.PropertyLet, Accessibility.Private ), GetImageSource(resx.ObjectPropertiesPrivate)}, - { Tuple.Create(DeclarationType.PropertySet, Accessibility.Public), GetImageSource(resx.ObjectProperties)}, - { Tuple.Create(DeclarationType.PropertySet, Accessibility.Friend ), GetImageSource(resx.ObjectPropertiesFriend)}, - { Tuple.Create(DeclarationType.PropertySet, Accessibility.Private ), GetImageSource(resx.ObjectPropertiesPrivate)}, - { Tuple.Create(DeclarationType.UserDefinedType, Accessibility.Public), GetImageSource(resx.ObjectValueType)}, - { Tuple.Create(DeclarationType.UserDefinedType, Accessibility.Private ), GetImageSource(resx.ObjectValueTypePrivate)}, - { Tuple.Create(DeclarationType.UserDefinedTypeMember, Accessibility.Public), GetImageSource(resx.ObjectField)}, - { Tuple.Create(DeclarationType.Variable, Accessibility.Private), GetImageSource(resx.ObjectFieldPrivate)}, - { Tuple.Create(DeclarationType.Variable, Accessibility.Public ), GetImageSource(resx.ObjectField)}, - }; - - public CodeExplorerMemberViewModel(CodeExplorerItemViewModel parent, Declaration declaration, IEnumerable declarations) + public CodeExplorerMemberViewModel(ICodeExplorerNode parent, Declaration declaration, ref List declarations) : base(parent, declaration) { - Parent = parent; - - Declaration = declaration; - if (declarations != null) - { - Items = declarations.Where(item => SubMemberTypes.Contains(item.DeclarationType) && item.ParentDeclaration.Equals(declaration)) - .OrderBy(item => item.Selection.StartLine) - .Select(item => new CodeExplorerMemberViewModel(this, item, null)) - .ToList(); - } - - var modifier = declaration.Accessibility == Accessibility.Global || declaration.Accessibility == Accessibility.Implicit - ? Accessibility.Public - : declaration.Accessibility; - var key = Tuple.Create(declaration.DeclarationType, modifier); - + AddNewChildren(ref declarations); Name = DetermineMemberName(declaration); - _icon = Mappings[key]; - } - - private string RemoveExtraWhiteSpace(string value) - { - var newStr = new StringBuilder(); - var trimmedJoinedString = value.Replace(" _\r\n", " ").Trim(); - - for (var i = 0; i < trimmedJoinedString.Length; i++) - { - // this will not throw because `Trim` ensures the first character is not whitespace - if (char.IsWhiteSpace(trimmedJoinedString[i]) && char.IsWhiteSpace(trimmedJoinedString[i - 1])) - { - continue; - } - - newStr.Append(trimmedJoinedString[i]); - } - - return newStr.ToString(); } public override string Name { get; } - public override CodeExplorerItemViewModel Parent { get; } - - private string _signature = null; + private string _signature; public override string NameWithSignature { get @@ -114,19 +27,20 @@ public override string NameWithSignature return _signature; } - var context = - Declaration.Context.children.FirstOrDefault(d => d is VBAParser.ArgListContext) as VBAParser.ArgListContext; + if (Declaration is ValuedDeclaration value && !string.IsNullOrEmpty(value.Expression)) + { + _signature = $"{Name} = {value.Expression}"; + return _signature; + } - if (context == null) + if (!(Declaration.Context.children.FirstOrDefault(d => d is VBAParser.ArgListContext) is VBAParser.ArgListContext context)) { _signature = Name; } - else if (Declaration.DeclarationType == DeclarationType.PropertyGet - || Declaration.DeclarationType == DeclarationType.PropertyLet - || Declaration.DeclarationType == DeclarationType.PropertySet) + else if (Declaration is PropertyDeclaration) { // 6 being the three-letter "get/let/set" + parens + space - _signature = Name.Insert(Name.Length - 6, RemoveExtraWhiteSpace(context.GetText())); + _signature = Name.Insert(Name.Length - 6, RemoveExtraWhiteSpace(context.GetText())); } else { @@ -136,48 +50,97 @@ public override string NameWithSignature } } - public override QualifiedSelection? QualifiedSelection => Declaration.QualifiedSelection; + public override bool IsObsolete => + Declaration.Annotations.Any(annotation => annotation.AnnotationType == AnnotationType.Obsolete); + + public static readonly DeclarationType[] SubMemberTypes = + { + DeclarationType.EnumerationMember, + DeclarationType.UserDefinedTypeMember + }; + + public override void Synchronize(ref List updated) + { + base.Synchronize(ref updated); + if (Declaration is null) + { + return; + } + + // Parameter list might have changed - invalidate the signature. + _signature = null; + OnNameChanged(); + } + + protected override void AddNewChildren(ref List updated) + { + if (updated == null) + { + return; + } + + var updates = updated.Where(item => + SubMemberTypes.Contains(item.DeclarationType) && item.ParentDeclaration.Equals(Declaration)).ToList(); + + updated = updated.Except(updates.Concat(new[] { Declaration })).ToList(); + + AddChildren(updates.Select(item => new CodeExplorerSubMemberViewModel(this, item))); + } + + public override Comparer SortComparer + { + get + { + switch (SortOrder) + { + case CodeExplorerSortOrder.Name: + return CodeExplorerItemComparer.Name; + case CodeExplorerSortOrder.CodeLine: + return CodeExplorerItemComparer.CodeLine; + case CodeExplorerSortOrder.DeclarationTypeThenName: + return CodeExplorerItemComparer.DeclarationTypeThenName; + case CodeExplorerSortOrder.DeclarationTypeThenCodeLine: + return CodeExplorerItemComparer.DeclarationTypeThenCodeLine; + default: + return CodeExplorerItemComparer.Name; + } + } + } + + private static string RemoveExtraWhiteSpace(string value) + { + var newStr = new StringBuilder(); + var trimmedJoinedString = value.Replace(" _\r\n", " ").Trim(); + + for (var i = 0; i < trimmedJoinedString.Length; i++) + { + // this will not throw because `Trim` ensures the first character is not whitespace + if (char.IsWhiteSpace(trimmedJoinedString[i]) && char.IsWhiteSpace(trimmedJoinedString[i - 1])) + { + continue; + } + + newStr.Append(trimmedJoinedString[i]); + } + + return newStr.ToString(); + } private static string DetermineMemberName(Declaration declaration) { - var type = declaration.DeclarationType; - switch (type) + switch (declaration.DeclarationType) { case DeclarationType.PropertyGet: - return declaration.IdentifierName + " (Get)"; + return $"{declaration.IdentifierName} ({Tokens.Get})"; case DeclarationType.PropertyLet: - return declaration.IdentifierName + " (Let)"; + return $"{declaration.IdentifierName} ({Tokens.Let})"; case DeclarationType.PropertySet: - return declaration.IdentifierName + " (Set)"; + return $"{declaration.IdentifierName} ({Tokens.Set})"; case DeclarationType.Variable: - if (declaration.IsArray) - { - return declaration.IdentifierName + "()"; - } - return declaration.IdentifierName; - case DeclarationType.EnumerationMember: - case DeclarationType.Constant: - var valuedDeclaration = (ValuedDeclaration)declaration; - return (!string.IsNullOrEmpty(valuedDeclaration.Expression)) - ? valuedDeclaration.IdentifierName + " = " + valuedDeclaration.Expression - : valuedDeclaration.IdentifierName; + return declaration.IsArray ? $"{declaration.IdentifierName}()" : declaration.IdentifierName; default: return declaration.IdentifierName; } } - - public void ParentComponentHasError() - { - _icon = GetImageSource(resx.exclamation); - OnPropertyChanged("CollapsedIcon"); - OnPropertyChanged("ExpandedIcon"); - } - - private BitmapImage _icon; - public override BitmapImage CollapsedIcon => _icon; - public override BitmapImage ExpandedIcon => _icon; - - public bool IsObsolete => - Declaration.Annotations.Any(annotation => annotation.AnnotationType == AnnotationType.Obsolete); } } diff --git a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerProjectViewModel.cs b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerProjectViewModel.cs index 7e1b844aa1..4bd9d35960 100644 --- a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerProjectViewModel.cs +++ b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerProjectViewModel.cs @@ -1,142 +1,204 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Windows; -using System.Windows.Media.Imaging; +using Rubberduck.AddRemoveReferences; using Rubberduck.Navigation.Folders; using Rubberduck.Parsing.Symbols; -using Rubberduck.VBEditor; +using Rubberduck.Parsing.VBA; using Rubberduck.VBEditor.SafeComWrappers; using Rubberduck.VBEditor.SafeComWrappers.Abstract; -using resx = Rubberduck.Resources.CodeExplorer.CodeExplorerUI; namespace Rubberduck.Navigation.CodeExplorer { - public class CodeExplorerProjectViewModel : CodeExplorerItemViewModel, ICodeExplorerDeclarationViewModel + public class CodeExplorerProjectViewModel : CodeExplorerItemViewModel { - public Declaration Declaration { get; } + public static readonly DeclarationType[] ComponentTypes = + { + DeclarationType.ClassModule, + DeclarationType.Document, + DeclarationType.ProceduralModule, + DeclarationType.UserForm + }; - private readonly CodeExplorerCustomFolderViewModel _folderTree; private readonly IVBE _vbe; - private static readonly DeclarationType[] ComponentTypes = + public CodeExplorerProjectViewModel(Declaration project, ref List declarations, RubberduckParserState state, IVBE vbe, bool references = true) : base(null, project) { - DeclarationType.ClassModule, - DeclarationType.Document, - DeclarationType.ProceduralModule, - DeclarationType.UserForm, - }; + State = state; + _vbe = vbe; + ShowReferences = references; - public CodeExplorerProjectViewModel(FolderHelper folderHelper, Declaration declaration, IEnumerable declarations, IVBE vbe, bool references = false) - { - Declaration = declaration; - _name = Declaration.IdentifierName; + SetName(); + var children = ExtractTrackedDeclarationsForProject(project, ref declarations); + AddNewChildren(ref children); IsExpanded = true; - _folderTree = folderHelper.GetFolderTree(declaration); - _vbe = vbe; + } + + private string _displayName; + private string _name; - try + public RubberduckParserState State { get; } + + public bool ShowReferences { get; } + + public override string Name => string.IsNullOrEmpty(_displayName) ? _name : $"{_name} ({_displayName})"; + + public override FontWeight FontWeight + { + get { - Items = new List(); - if (references) + if (_vbe.Kind == VBEKind.Hosted || Declaration.Project == null) { - Items.Add(new CodeExplorerReferenceFolderViewModel(this)); + return base.FontWeight; } - FillFolders(declarations.ToList()); - Items.AddRange(_folderTree.Items); - - _icon = Declaration.Project?.Protection == ProjectProtection.Locked - ? GetImageSource(resx.lock__exclamation) - : GetImageSource(resx.ObjectLibrary); + using (var vbProjects = _vbe.VBProjects) + using (var startProject = vbProjects?.StartProject) + { + return Declaration.Project.Equals(startProject) ? FontWeights.Bold : base.FontWeight; + } } - catch (NullReferenceException e) + } + + public override Comparer SortComparer => CodeExplorerItemComparer.NodeType; + + public override bool Filtered => false; + + public override void Synchronize(ref List updated) + { + if (Declaration is null || + !(updated?.OfType() + .FirstOrDefault(declaration => declaration.ProjectId.Equals(Declaration.ProjectId)) is ProjectDeclaration match)) { - Console.WriteLine(e); + Declaration = null; + return; } + + Declaration = match; + + var children = ExtractTrackedDeclarationsForProject(Declaration, ref updated); + updated = updated.Except(children.Union(new[] { Declaration })).ToList(); + + // Reference synchronization is deferred to AddNewChildren for 2 reasons. First, it doesn't make sense to sling around a List of + // declaration for something that doesn't need it. Second (and more importantly), the priority can't be set without calling + // GetProjectReferenceModels, which hits the VBE COM interfaces. So, we only want to do that once. The bonus 3rd reason is that it + // can be called from the ctor this way. + + SynchronizeChildren(ref children); + + // Have to do this again - the project might have been saved or otherwise had the ProjectDisplayName changed. + SetName(); } - private void FillFolders(IEnumerable declarations) + protected sealed override void AddNewChildren(ref List updated) { - var items = declarations.ToList(); - var groupedItems = items.Where(item => ComponentTypes.Contains(item.DeclarationType)) - .GroupBy(item => item.CustomFolder) - .OrderBy(item => item.Key); - - // set parent so we can walk up to the project node - // we haven't added the nodes yet, so this cast is valid - // ReSharper disable once PossibleInvalidCastExceptionInForeachLoop - foreach (CodeExplorerCustomFolderViewModel item in _folderTree.Items) + if (updated is null) { - item.SetParent(this); + return; } - foreach (var grouping in groupedItems) + SynchronizeReferences(); + + foreach (var rootFolder in updated.GroupBy(declaration => declaration.RootFolder()) + .Where(folder => !string.IsNullOrEmpty(folder.Key))) { - CanAddNodesToTree(_folderTree, items, grouping); + var contents = rootFolder.ToList(); + AddChild(new CodeExplorerCustomFolderViewModel(this, rootFolder.Key, rootFolder.Key, _vbe, ref contents)); } } - private bool CanAddNodesToTree(CodeExplorerCustomFolderViewModel tree, List items, IGrouping grouping) + private void SynchronizeReferences() { - foreach (var folder in tree.Items.OfType()) + if (!ShowReferences) { - if (grouping.Key.Replace("\"", string.Empty) != folder.FullPath) + return; + } + + var references = GetProjectReferenceModels(); + foreach (var child in Children.OfType()) + { + child.Synchronize(Declaration, references); + if (child.Declaration is null) { - continue; + RemoveChild(child); } + } - var parents = grouping.Where( - item => ComponentTypes.Contains(item.DeclarationType) && - item.CustomFolder.Replace("\"", string.Empty) == folder.FullPath) - .ToList(); + if (!references.Any()) + { + return; + } - folder.AddNodes(items.Where(item => parents.Contains(item) || parents.Any(parent => - (item.ParentDeclaration != null && item.ParentDeclaration.Equals(parent)) || - item.ComponentName == parent.ComponentName)).ToList()); + var types = references.GroupBy(reference => reference.Type); - return true; + foreach (var type in types) + { + AddChild(new CodeExplorerReferenceFolderViewModel(this, State?.DeclarationFinder, type.ToList(), type.Key)); } - - return tree.Items.OfType().Any(node => CanAddNodesToTree(node, items, grouping)); } - private readonly BitmapImage _icon; - public override BitmapImage CollapsedIcon => _icon; - public override BitmapImage ExpandedIcon => _icon; - - public override FontWeight FontWeight + private List GetProjectReferenceModels() { - get + var project = Declaration?.Project; + if (project == null) { - if (_vbe.Kind == VBEKind.Hosted || Declaration.Project == null) + return new List(); + } + + var referenced = new List(); + + using (var references = project.References) + { + var priority = 1; + foreach (var reference in references) { - return base.FontWeight; + referenced.Add(new ReferenceModel(reference, priority++)); + reference.Dispose(); } + } - using (var vbProjects = _vbe.VBProjects) - { - if (Declaration.Project.Equals(vbProjects.StartProject)) - { - return FontWeights.Bold; - } + return referenced; + } - return base.FontWeight; - } + private void SetName() + { + if (Declaration is null) + { + return; } + + _name = Declaration?.IdentifierName ?? string.Empty; + + // F' the flicker. Digging into the properties has some even more evil side-effects, and is a performance nightmare by comparison. + _displayName = Declaration?.ProjectDisplayName ?? string.Empty; + + OnNameChanged(); } - // projects are always at the top of the tree - public override CodeExplorerItemViewModel Parent => null; + private static readonly List UntrackedTypes = new List + { + DeclarationType.Parameter, + DeclarationType.LineLabel, + DeclarationType.UnresolvedMember, + DeclarationType.BracketedExpression, + DeclarationType.ComAlias + }; - private string _name; - public override string Name => _name; - public override string NameWithSignature => _name; - public override QualifiedSelection? QualifiedSelection => Declaration.QualifiedSelection; + private static readonly List ModuleRestrictedTypes = new List + { + DeclarationType.Variable, + DeclarationType.Control, + DeclarationType.Constant + }; - public void SetParenthesizedName(string parenthesizedName) + public static List ExtractTrackedDeclarationsForProject(Declaration project, ref List declarations) { - _name += " (" + parenthesizedName + ")"; + var owned = declarations.Where(declaration => declaration.ProjectId.Equals(project.ProjectId)).ToList(); + declarations = declarations.Except(owned).ToList(); + + return owned.Where(declaration => !UntrackedTypes.Contains(declaration.DeclarationType) && + (!ModuleRestrictedTypes.Contains(declaration.DeclarationType) || + declaration.ParentDeclaration.DeclarationType.HasFlag(DeclarationType.Module))).ToList(); } } } diff --git a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerReferenceFolderViewModel.cs b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerReferenceFolderViewModel.cs index 0ebbbd8244..f7ba828fba 100644 --- a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerReferenceFolderViewModel.cs +++ b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerReferenceFolderViewModel.cs @@ -1,44 +1,110 @@ -using System.Windows.Media.Imaging; +using System.Collections.Generic; +using System.Linq; using Rubberduck.AddRemoveReferences; +using Rubberduck.Parsing.Symbols; +using Rubberduck.Parsing.VBA.DeclarationCaching; using Rubberduck.VBEditor; +using Rubberduck.VBEditor.SafeComWrappers; namespace Rubberduck.Navigation.CodeExplorer { - public class CodeExplorerReferenceFolderViewModel : CodeExplorerItemViewModel + public sealed class CodeExplorerReferenceFolderViewModel : CodeExplorerItemViewModelBase { - private readonly CodeExplorerProjectViewModel _parent; + private readonly DeclarationFinder _finder; - public CodeExplorerReferenceFolderViewModel(CodeExplorerProjectViewModel parent) + public CodeExplorerReferenceFolderViewModel( + ICodeExplorerNode parent, + DeclarationFinder finder, + List references, + ReferenceKind type) + : base(parent, parent?.Declaration) { - _parent = parent; - CollapsedIcon = GetImageSource(Resources.CodeExplorer.CodeExplorerUI.ObjectAssembly); - ExpandedIcon = GetImageSource(Resources.CodeExplorer.CodeExplorerUI.ObjectAssembly); - AddReferenceNodes(); + _finder = finder; + ReferenceKind = type; + Synchronize(Declaration, references); } - public override string Name => "References"; - public override string NameWithSignature => "References"; - public override BitmapImage CollapsedIcon { get; } - public override BitmapImage ExpandedIcon { get; } - public override CodeExplorerItemViewModel Parent => _parent; + public ReferenceKind ReferenceKind { get; } + + public override string Name => ReferenceKind == ReferenceKind.TypeLibrary + ? Resources.CodeExplorer.CodeExplorerUI.CodeExplorer_LibraryReferences + : Resources.CodeExplorer.CodeExplorerUI.CodeExplorer_ProjectReferences; + + public override string NameWithSignature => Name; + + public override string PanelTitle => Name; + + public override string Description => string.Empty; + public override QualifiedSelection? QualifiedSelection => null; - private void AddReferenceNodes() + public override bool IsErrorState + { + get => false; + set { } + } + + public override bool Filtered => false; + + public override Comparer SortComparer => CodeExplorerItemComparer.ReferenceType; + + public void Synchronize(Declaration parent, List updated) { - var project = _parent?.Declaration?.Project; - if (project == null) + var updates = updated.Where(reference => reference.Type == ReferenceKind).ToList(); + if (!updates.Any()) { + Declaration = null; return; } - using (var references = project.References) + Declaration = parent; + + foreach (var child in Children.OfType().ToList()) + { + child.Synchronize(Declaration, updates); + if (child.Reference is null) + { + RemoveChild(child); + continue; + } + + updated.Remove(child.Reference); + } + + foreach (var reference in updates) + { + reference.IsUsed = reference.IsBuiltIn || + _finder != null && + _finder.IsReferenceUsedInProject(Declaration as ProjectDeclaration, + reference.ToReferenceInfo()); + + AddChild(new CodeExplorerReferenceViewModel(this, reference)); + updated.Remove(reference); + } + + if (!Children.Any()) { - var priority = 1; - foreach (var reference in references) + Declaration = null; + } + } + + public void UpdateChildren() + { + foreach (var library in Children.OfType()) + { + var reference = library.Reference; + if (reference == null) { - AddChild(new CodeExplorerReferenceViewModel(this, new ReferenceModel(reference, priority++))); - reference.Dispose(); + continue; } + + reference.IsUsed = reference.IsBuiltIn || + _finder != null && + _finder.IsReferenceUsedInProject( + library.Parent?.Declaration as ProjectDeclaration, + reference.ToReferenceInfo()); + + library.IsDimmed = !reference.IsUsed; } } } diff --git a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerReferenceViewModel.cs b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerReferenceViewModel.cs index 1ec0a41eaa..0ed44629cb 100644 --- a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerReferenceViewModel.cs +++ b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerReferenceViewModel.cs @@ -1,40 +1,68 @@ -using System; -using System.Windows.Media.Imaging; +using System.Collections.Generic; +using System.IO; +using System.Linq; using Rubberduck.AddRemoveReferences; -using Rubberduck.Resources.CodeExplorer; +using Rubberduck.Parsing.Symbols; using Rubberduck.VBEditor; +using Rubberduck.VBEditor.SafeComWrappers; namespace Rubberduck.Navigation.CodeExplorer { - public class CodeExplorerReferenceViewModel : CodeExplorerItemViewModel + public sealed class CodeExplorerReferenceViewModel : CodeExplorerItemViewModelBase { - private readonly ReferenceModel _reference; - - public CodeExplorerReferenceViewModel(CodeExplorerReferenceFolderViewModel parent, ReferenceModel reference) + public CodeExplorerReferenceViewModel(ICodeExplorerNode parent, ReferenceModel reference) : base(parent, parent?.Declaration) { - Parent = parent; - _reference = reference; + Reference = reference; } - public override string NameWithSignature => $"{_reference.Name} ({_reference.Version})"; - public override string Name => _reference.Description + Environment.NewLine + _reference.FullPath; - public override CodeExplorerItemViewModel Parent { get; } + public ReferenceModel Reference { get; private set; } + + public override string Name => Reference?.Name ?? string.Empty; + + public override string NameWithSignature => Reference.Type == ReferenceKind.TypeLibrary + ? $"{Name} ({Path.GetFileName(Reference.FullPath)} {Reference.Version})" + : $"{Name} ({Path.GetFileName(Reference.FullPath)})"; + + public override string PanelTitle => ToolTip; + + public override string Description => Reference?.FullPath ?? string.Empty; + public override QualifiedSelection? QualifiedSelection => null; - public override BitmapImage CollapsedIcon => GetIcon(); - public override BitmapImage ExpandedIcon => GetIcon(); + public override bool IsDimmed => !Reference?.IsUsed ?? true; + + public override bool IsErrorState + { + get => false; + set { /* References can never be in an error state (in this context). */ } + } + + public override string ToolTip => Reference?.Description ?? string.Empty; + + public int? Priority => Reference?.Priority; - public int? Priority => _reference.Priority; - public bool Locked => _reference.IsBuiltIn; + public bool Locked => Reference?.IsBuiltIn ?? false; - private BitmapImage GetIcon() + public override Comparer SortComparer => CodeExplorerItemComparer.ReferencePriority; + + public void Synchronize(Declaration project, List updated) { - if (_reference.Status.HasFlag(ReferenceStatus.Broken)) + Declaration = project; + + var used = Reference?.IsUsed ?? false; + Reference = updated.FirstOrDefault(reference => reference.Matches(Reference.ToReferenceInfo())); + + if (Reference == null) { - GetImageSource(CodeExplorerUI.BrokenReference); + return; } - return _reference.IsBuiltIn ? GetImageSource(CodeExplorerUI.LockedReference) : GetImageSource(CodeExplorerUI.Reference); + updated.Remove(Reference); + + if (used != Reference.IsUsed) + { + OnPropertyChanged(nameof(IsDimmed)); + } } } } diff --git a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerSubMemberViewModel.cs b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerSubMemberViewModel.cs new file mode 100644 index 0000000000..bca88bbf3a --- /dev/null +++ b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerSubMemberViewModel.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using Rubberduck.Parsing.Symbols; + +namespace Rubberduck.Navigation.CodeExplorer +{ + public sealed class CodeExplorerSubMemberViewModel : CodeExplorerItemViewModel + { + public static readonly DeclarationType[] SubMemberTypes = + { + DeclarationType.EnumerationMember, + DeclarationType.UserDefinedTypeMember + }; + + private readonly string _signature = string.Empty; + + public CodeExplorerSubMemberViewModel(ICodeExplorerNode parent, Declaration declaration) : base(parent, declaration) + { + if (Declaration is ValuedDeclaration value && !string.IsNullOrEmpty(value.Expression)) + { + _signature = $" = {value.Expression}"; + } + } + + public override string Name => Declaration?.IdentifierName ?? string.Empty; + + public override string NameWithSignature => $"{Name}{_signature}"; + + public override void Synchronize(ref List updated) + { + var signature = _signature; + + base.Synchronize(ref updated); + if (Declaration is null || _signature.Equals(signature)) + { + return; + } + + // Signature changed - update the UI. + OnNameChanged(); + } + + public override Comparer SortComparer => + SortOrder.HasFlag(CodeExplorerSortOrder.Name) + ? CodeExplorerItemComparer.Name + : CodeExplorerItemComparer.CodeLine; + + // Bottom level node. This is a NOP. + protected override void AddNewChildren(ref List updated) { } + } +} diff --git a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerViewModel.cs b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerViewModel.cs index 441b23bdec..3da1655903 100644 --- a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerViewModel.cs +++ b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerViewModel.cs @@ -1,101 +1,88 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Globalization; +using System.Diagnostics.CodeAnalysis; using System.Linq; using NLog; -using Rubberduck.Navigation.Folders; -using Rubberduck.Parsing; -using Rubberduck.Parsing.Annotations; using Rubberduck.Parsing.Symbols; using Rubberduck.Parsing.VBA; using Rubberduck.Settings; using Rubberduck.SettingsProvider; -using Rubberduck.Resources; using Rubberduck.UI; using Rubberduck.UI.CodeExplorer.Commands; using Rubberduck.UI.Command; -using Rubberduck.VBEditor; using Rubberduck.VBEditor.SafeComWrappers; using System.Windows; +using System.Windows.Input; using Rubberduck.Parsing.UIContext; using Rubberduck.Templates; using Rubberduck.UI.UnitTesting.Commands; using Rubberduck.VBEditor.SafeComWrappers.Abstract; -// ReSharper disable CanBeReplacedWithTryCastAndCheckForNull -// ReSharper disable ExplicitCallerInfoArgument - namespace Rubberduck.Navigation.CodeExplorer { - public sealed class CodeExplorerViewModel : ViewModelBase, IDisposable + [Flags] + public enum CodeExplorerSortOrder + { + Undefined = 0, + Name = 1, + CodeLine = 1 << 1, + DeclarationType = 1 << 2, + DeclarationTypeThenName = DeclarationType | Name, + DeclarationTypeThenCodeLine = DeclarationType | CodeLine + } + + [SuppressMessage("ReSharper", "InconsistentNaming")] + public sealed class CodeExplorerViewModel : ViewModelBase { - private readonly FolderHelper _folderHelper; + // ReSharper disable NotAccessedField.Local - The settings providers aren't used, but several enhancement requests will need them. private readonly RubberduckParserState _state; + private readonly RemoveCommand _externalRemoveCommand; + private readonly IConfigProvider _generalSettingsProvider; private readonly IConfigProvider _windowSettingsProvider; - private readonly GeneralSettings _generalSettings; - private readonly WindowSettings _windowSettings; private readonly IUiDispatcher _uiDispatcher; private readonly IVBE _vbe; private readonly ITemplateProvider _templateProvider; - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + // ReSharper restore NotAccessedField.Local public CodeExplorerViewModel( - FolderHelper folderHelper, RubberduckParserState state, RemoveCommand removeCommand, IConfigProvider generalSettingsProvider, IConfigProvider windowSettingsProvider, IUiDispatcher uiDispatcher, IVBE vbe, - ITemplateProvider templateProvider) + ITemplateProvider templateProvider, + ICodeExplorerSyncProvider syncProvider) { - _folderHelper = folderHelper; _state = state; _state.StateChanged += HandleStateChanged; _state.ModuleStateChanged += ParserState_ModuleStateChanged; + + _externalRemoveCommand = removeCommand; + _generalSettingsProvider = generalSettingsProvider; _windowSettingsProvider = windowSettingsProvider; _uiDispatcher = uiDispatcher; _vbe = vbe; _templateProvider = templateProvider; - if (generalSettingsProvider != null) - { - _generalSettings = generalSettingsProvider.Create(); - } - - if (windowSettingsProvider != null) - { - _windowSettings = windowSettingsProvider.Create(); - } - CollapseAllSubnodesCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteCollapseNodes); - ExpandAllSubnodesCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteExpandNodes); - - _externalRemoveCommand = removeCommand; + CollapseAllSubnodesCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteCollapseNodes, EvaluateCanSwitchNodeState); + ExpandAllSubnodesCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteExpandNodes, EvaluateCanSwitchNodeState); + ClearSearchCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteClearSearchCommand); if (_externalRemoveCommand != null) { - RemoveCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteRemoveComand, _externalRemoveCommand.CanExecute); + RemoveCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteRemoveCommand, _externalRemoveCommand.CanExecute); } - SetNameSortCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), param => - { - if ((bool)param) - { - SortByName = (bool)param; - SortByCodeOrder = !(bool)param; - } - }, param => !SortByName); + OnPropertyChanged(nameof(Projects)); - SetCodeOrderSortCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), param => - { - if ((bool)param) - { - SortByCodeOrder = (bool)param; - SortByName = !(bool)param; - } - }, param => !SortByCodeOrder); + SyncCodePaneCommand = syncProvider.GetSyncCommand(this); + // Force a call to EvaluateCanExecute + OnPropertyChanged(nameof(SyncCodePaneCommand)); } + public ObservableCollection Projects { get; } = new ObservableCollection(); + public ObservableCollection