Skip to content

Commit 3146a94

Browse files
committed
Merge branch 'next' of https://github.com/rubberduck-vba/Rubberduck into BugBlipper
2 parents e8093e3 + 6c18761 commit 3146a94

File tree

4 files changed

+277
-23
lines changed

4 files changed

+277
-23
lines changed

RetailCoder.VBE/Inspections/ConvertToProcedureQuickFix.cs

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
using Antlr4.Runtime;
1+
using System;
2+
using Antlr4.Runtime;
23
using Rubberduck.Parsing;
34
using Rubberduck.Parsing.Grammar;
45
using Rubberduck.VBEditor;
56
using System.Collections.Generic;
67
using System.Linq;
78
using System.Text.RegularExpressions;
9+
using Microsoft.Vbe.Interop;
10+
using Rubberduck.Parsing.Symbols;
811

912
namespace Rubberduck.Inspections
1013
{
@@ -25,25 +28,42 @@ public ConvertToProcedureQuickFix(ParserRuleContext context, QualifiedSelection
2528

2629
public override void Fix()
2730
{
28-
var context = (VBAParser.FunctionStmtContext)Context;
29-
var visibility = context.visibility() == null ? string.Empty : context.visibility().GetText() + ' ';
30-
var name = ' ' + context.ambiguousIdentifier().GetText();
31-
var args = context.argList().GetText();
32-
var asType = context.asTypeClause() == null ? string.Empty : ' ' + context.asTypeClause().GetText();
31+
dynamic functionContext = Context as VBAParser.FunctionStmtContext;
32+
dynamic propertyGetContext = Context as VBAParser.PropertyGetStmtContext;
3333

34-
var oldSignature = visibility + Tokens.Function + name + args + asType;
35-
var newSignature = visibility + Tokens.Sub + name + args;
34+
var context = functionContext ?? propertyGetContext;
35+
if (context == null)
36+
{
37+
throw new InvalidOperationException(string.Format("Context type '{0}' is not valid for {1}.", Context.GetType(), GetType()));
38+
}
3639

37-
var procedure = Context.GetText();
40+
string token = functionContext != null
41+
? Tokens.Function
42+
: Tokens.Property + ' ' + Tokens.Get;
43+
string endToken = token == Tokens.Function
44+
? token
45+
: Tokens.Property;
46+
47+
string visibility = context.visibility() == null ? string.Empty : context.visibility().GetText() + ' ';
48+
string name = ' ' + context.ambiguousIdentifier().GetText();
49+
bool hasTypeHint = context.typeHint() != null;
50+
51+
string args = context.argList().GetText();
52+
string asType = context.asTypeClause() == null ? string.Empty : ' ' + context.asTypeClause().GetText();
53+
54+
string oldSignature = visibility + token + name + (hasTypeHint ? context.typeHint().GetText() : string.Empty) + args + asType;
55+
string newSignature = visibility + Tokens.Sub + name + args;
56+
57+
string procedure = Context.GetText();
3858
string noReturnStatements = procedure;
3959
_returnStatements.ToList().ForEach(returnStatement =>
4060
noReturnStatements = Regex.Replace(noReturnStatements, @"[ \t\f]*" + returnStatement + @"[ \t\f]*\r?\n?", ""));
41-
var result = noReturnStatements.Replace(oldSignature, newSignature)
42-
.Replace(Tokens.End + ' ' + Tokens.Function, Tokens.End + ' ' + Tokens.Sub)
43-
.Replace(Tokens.Exit + ' ' + Tokens.Function, Tokens.Exit + ' ' + Tokens.Sub);
61+
string result = noReturnStatements.Replace(oldSignature, newSignature)
62+
.Replace(Tokens.End + ' ' + endToken, Tokens.End + ' ' + Tokens.Sub)
63+
.Replace(Tokens.Exit + ' ' + endToken, Tokens.Exit + ' ' + Tokens.Sub);
4464

45-
var module = Selection.QualifiedName.Component.CodeModule;
46-
var selection = Context.GetSelection();
65+
CodeModule module = Selection.QualifiedName.Component.CodeModule;
66+
Selection selection = Context.GetSelection();
4767

4868
module.DeleteLines(selection.StartLine, selection.LineCount);
4969
module.InsertLines(selection.StartLine, result);

Rubberduck.Parsing/Symbols/IdentifierReferenceResolver.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,30 @@ private bool IsStaticClass(Declaration declaration)
11071107
&& (declaration.ParentDeclaration.HasPredeclaredId || declaration.IsBuiltIn);
11081108
}
11091109

1110+
private readonly IReadOnlyList<string> SpecialCasedTokens = new[]{
1111+
Tokens.Error,
1112+
Tokens.Hex,
1113+
Tokens.Oct,
1114+
Tokens.Str,
1115+
Tokens.CurDir,
1116+
Tokens.Command,
1117+
Tokens.Environ,
1118+
Tokens.Chr,
1119+
Tokens.ChrW,
1120+
Tokens.Format,
1121+
Tokens.LCase,
1122+
Tokens.Left,
1123+
Tokens.LeftB,
1124+
Tokens.LTrim,
1125+
Tokens.Mid,
1126+
Tokens.MidB,
1127+
Tokens.Trim,
1128+
Tokens.Right,
1129+
Tokens.RightB,
1130+
Tokens.RTrim,
1131+
Tokens.UCase
1132+
};
1133+
11101134
private Declaration FindProjectScopeDeclaration(string identifierName, Declaration localScope = null, ContextAccessorType accessorType = ContextAccessorType.GetValueOrReference, bool hasStringQualifier = false)
11111135
{
11121136
var matches = _declarationFinder.MatchName(identifierName).Where(item =>
@@ -1115,7 +1139,7 @@ private Declaration FindProjectScopeDeclaration(string identifierName, Declarati
11151139
|| IsStaticClass(item)
11161140
|| item.ParentScopeDeclaration.Equals(localScope)).ToList();
11171141

1118-
if (matches.Count == 1)
1142+
if (matches.Count == 1 && !SpecialCasedTokens.Contains(matches.Single().IdentifierName))
11191143
{
11201144
return matches.Single();
11211145
}
@@ -1132,7 +1156,7 @@ private Declaration FindProjectScopeDeclaration(string identifierName, Declarati
11321156
}
11331157

11341158
result = matches.Where(item => IsBuiltInDeclarationInScope(item, localScope)).ToList();
1135-
if (result.Count == 1)
1159+
if (result.Count == 1 && !SpecialCasedTokens.Contains(matches.Single().IdentifierName))
11361160
{
11371161
return result.SingleOrDefault();
11381162
}

RubberduckTests/Grammar/ResolverTests.cs

Lines changed: 212 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using Microsoft.VisualStudio.TestTools.UnitTesting;
55
using Rubberduck.Parsing.Symbols;
66
using Rubberduck.Parsing.VBA;
7-
using RubberduckTests.Inspections;
87
using RubberduckTests.Mocks;
98

109
namespace RubberduckTests.Grammar
@@ -31,7 +30,7 @@ private RubberduckParserState Resolve(string code, vbext_ComponentType moduleTyp
3130
private RubberduckParserState Resolve(params string[] classes)
3231
{
3332
var builder = new MockVbeBuilder();
34-
var projectBuilder = builder.ProjectBuilder("TestProject", vbext_ProjectProtection.vbext_pp_none);
33+
var projectBuilder = builder.ProjectBuilder("TestProject1", vbext_ProjectProtection.vbext_pp_none);
3534
for (var i = 0; i < classes.Length; i++)
3635
{
3736
projectBuilder.AddComponent("Class" + (i + 1), vbext_ComponentType.vbext_ct_ClassModule, classes[i]);
@@ -971,6 +970,217 @@ End Sub
971970
Assert.AreEqual(string.Empty, usage.Annotations);
972971
}
973972

973+
[TestMethod]
974+
public void GivenUDT_NamedAfterProject_LocalResolvesToUDT()
975+
{
976+
var code = @"
977+
Private Type TestProject1
978+
Foo As Integer
979+
Bar As String
980+
End Type
981+
982+
Public Sub DoSomething()
983+
Dim Foo As TestProject1
984+
Foo.Bar = ""DoSomething""
985+
Foo.Foo = 42
986+
End Sub
987+
";
988+
var state = Resolve(code);
989+
990+
var declaration = state.AllUserDeclarations.Single(item =>
991+
item.DeclarationType == DeclarationType.UserDefinedType);
992+
993+
if (declaration.ProjectName != declaration.IdentifierName)
994+
{
995+
Assert.Inconclusive("UDT should be named after project.");
996+
}
997+
998+
var usage = declaration.References.SingleOrDefault();
999+
1000+
Assert.IsNotNull(usage);
1001+
}
1002+
1003+
[TestMethod]
1004+
public void GivenUDT_NamedAfterProject_FieldResolvesToUDT_EvenIfHiddenByLocal()
1005+
{
1006+
var code = @"
1007+
Private Type TestProject1
1008+
Foo As Integer
1009+
Bar As String
1010+
End Type
1011+
1012+
Private Foo As TestProject1
1013+
1014+
Public Sub DoSomething()
1015+
Dim Foo As TestProject1
1016+
Foo.Bar = ""DoSomething""
1017+
Foo.Foo = 42
1018+
End Sub
1019+
";
1020+
var state = Resolve(code);
1021+
1022+
var declaration = state.AllUserDeclarations.Single(item =>
1023+
item.DeclarationType == DeclarationType.UserDefinedType);
9741024

1025+
if (declaration.ProjectName != declaration.IdentifierName)
1026+
{
1027+
Assert.Inconclusive("UDT should be named after project.");
1028+
}
1029+
1030+
var usages = declaration.References;
1031+
1032+
Assert.AreEqual(2, usages.Count());
1033+
}
1034+
1035+
[TestMethod]
1036+
public void GivenLocalVariable_NamedAfterUDTMember_ResolvesToLocalVariable()
1037+
{
1038+
var code = @"
1039+
Private Type TestProject1
1040+
Foo As Integer
1041+
Bar As String
1042+
End Type
1043+
1044+
Public Sub DoSomething()
1045+
Dim Foo As TestProject1
1046+
Foo.Bar = ""DoSomething""
1047+
Foo.Foo = 42
1048+
End Sub
1049+
";
1050+
var state = Resolve(code);
1051+
1052+
var declaration = state.AllUserDeclarations.Single(item =>
1053+
item.DeclarationType == DeclarationType.Variable);
1054+
1055+
if (declaration.ProjectName != declaration.AsTypeName)
1056+
{
1057+
Assert.Inconclusive("variable should be named after project.");
1058+
}
1059+
var usages = declaration.References;
1060+
1061+
Assert.AreEqual(2, usages.Count());
1062+
}
1063+
1064+
[TestMethod]
1065+
public void GivenLocalVariable_NamedAfterUDTMember_MemberCallResolvesToUDTMember()
1066+
{
1067+
var code = @"
1068+
Private Type TestProject1
1069+
Foo As Integer
1070+
Bar As String
1071+
End Type
1072+
1073+
Public Sub DoSomething()
1074+
Dim Foo As TestProject1
1075+
Foo.Bar = ""DoSomething""
1076+
Foo.Foo = 42
1077+
End Sub
1078+
";
1079+
var state = Resolve(code);
1080+
1081+
var declaration = state.AllUserDeclarations.Single(item =>
1082+
item.DeclarationType == DeclarationType.UserDefinedTypeMember
1083+
&& item.IdentifierName == "Foo");
1084+
1085+
var usages = declaration.References.Where(item =>
1086+
item.ParentScoping.IdentifierName == "DoSomething");
1087+
1088+
Assert.AreEqual(1, usages.Count());
1089+
}
1090+
1091+
[TestMethod]
1092+
public void GivenUDTMember_OfUDTType_ResolvesToDeclaredUDT()
1093+
{
1094+
var code = @"
1095+
Private Type TestProject1
1096+
Foo As Integer
1097+
Bar As String
1098+
End Type
1099+
1100+
Private Type Foo
1101+
Foo As TestProject1
1102+
End Type
1103+
1104+
Public Sub DoSomething()
1105+
Dim Foo As Foo
1106+
Foo.Foo.Bar = ""DoSomething""
1107+
Foo.Foo.Foo = 42
1108+
End Sub
1109+
";
1110+
var state = Resolve(code);
1111+
1112+
var declaration = state.AllUserDeclarations.Single(item =>
1113+
item.DeclarationType == DeclarationType.UserDefinedTypeMember
1114+
&& item.IdentifierName == "Foo"
1115+
&& item.AsTypeName == item.ProjectName
1116+
&& item.IdentifierName == item.ParentDeclaration.IdentifierName);
1117+
1118+
var usages = declaration.References.Where(item =>
1119+
item.ParentScoping.IdentifierName == "DoSomething");
1120+
1121+
Assert.AreEqual(2, usages.Count());
1122+
}
1123+
1124+
[TestMethod]
1125+
public void GivenUDT_NamedAfterModule_LocalAsTypeResolvesToUDT()
1126+
{
1127+
var code = @"
1128+
Private Type TestProject1
1129+
Foo As Integer
1130+
Bar As String
1131+
End Type
1132+
1133+
Private Type TestModule1
1134+
Foo As TestProject1
1135+
End Type
1136+
1137+
Public Sub DoSomething()
1138+
Dim Foo As TestModule1
1139+
Foo.Foo.Bar = ""DoSomething""
1140+
Foo.Foo.Foo = 42
1141+
End Sub
1142+
";
1143+
var state = Resolve(code);
1144+
1145+
var declaration = state.AllUserDeclarations.Single(item =>
1146+
item.DeclarationType == DeclarationType.UserDefinedType
1147+
&& item.IdentifierName == item.ComponentName);
1148+
1149+
var usages = declaration.References.Where(item =>
1150+
item.ParentScoping.IdentifierName == "DoSomething");
1151+
1152+
Assert.AreEqual(1, usages.Count());
1153+
}
1154+
1155+
[TestMethod]
1156+
public void GivenUDTMember_NamedAfterUDTType_NamedAfterModule_LocalAsTypeResolvesToUDT()
1157+
{
1158+
var code = @"
1159+
Private Type TestProject1
1160+
Foo As Integer
1161+
Bar As String
1162+
End Type
1163+
1164+
Private Type TestModule1
1165+
TestModule1 As TestProject1
1166+
End Type
1167+
1168+
Public Sub DoSomething()
1169+
Dim TestModule1 As TestModule1
1170+
TestModule1.TestModule1.Bar = ""DoSomething""
1171+
TestModule1.TestModule1.Foo = 42
1172+
End Sub
1173+
";
1174+
var state = Resolve(code);
1175+
1176+
var declaration = state.AllUserDeclarations.Single(item =>
1177+
item.DeclarationType == DeclarationType.UserDefinedType
1178+
&& item.IdentifierName == item.ComponentName);
1179+
1180+
var usages = declaration.References.Where(item =>
1181+
item.ParentScoping.IdentifierName == "DoSomething");
1182+
1183+
Assert.AreEqual(1, usages.Count());
1184+
}
9751185
}
9761186
}

0 commit comments

Comments
 (0)