Skip to content

Commit efaabe8

Browse files
committed
Merge branch 'rubberduck-vba/next' into next
2 parents f7097b4 + ba240ef commit efaabe8

File tree

11 files changed

+155
-252
lines changed

11 files changed

+155
-252
lines changed

Rubberduck.Core/Navigation/CodeMetrics/CodeMetricsAnalyst.cs

Lines changed: 34 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,24 @@
1-
using System.Collections.Generic;
2-
using System.Linq;
3-
using Rubberduck.Parsing.VBA;
1+
using Antlr4.Runtime.Misc;
42
using Antlr4.Runtime.Tree;
53
using Rubberduck.Parsing.Grammar;
6-
using Rubberduck.VBEditor;
7-
using Antlr4.Runtime.Misc;
84
using Rubberduck.Parsing.Symbols;
9-
using Rubberduck.SmartIndenter;
5+
using Rubberduck.Parsing.VBA;
6+
using Rubberduck.VBEditor;
7+
using System.Collections.Generic;
8+
using System.Diagnostics;
9+
using System.Linq;
1010

1111
namespace Rubberduck.Navigation.CodeMetrics
1212
{
1313
public class CodeMetricsAnalyst : ICodeMetricsAnalyst
14-
{
15-
private readonly IIndenterSettings _indenterSettings;
16-
17-
public CodeMetricsAnalyst(IIndenterSettings indenterSettings)
18-
{
19-
_indenterSettings = indenterSettings;
20-
}
14+
{
15+
public CodeMetricsAnalyst() { }
2116

2217
public IEnumerable<ModuleMetricsResult> ModuleMetrics(RubberduckParserState state)
2318
{
2419
if (state == null || !state.AllUserDeclarations.Any())
2520
{
26-
// must not return Enumerable.Empty
21+
// can not explicitly return Enumerable.Empty, this is equivalent
2722
yield break;
2823
}
2924

@@ -42,8 +37,9 @@ public ModuleMetricsResult GetModuleResult(RubberduckParserState state, Qualifie
4237

4338
private ModuleMetricsResult GetModuleResult(QualifiedModuleName qmn, IParseTree moduleTree, DeclarationFinder declarationFinder)
4439
{
45-
// Consider rewrite as visitor? That should make subtrees easier and allow us to expand metrics
46-
var cmListener = new CodeMetricsListener(declarationFinder, _indenterSettings);
40+
// FIXME rewrite as visitor, see discussion on pulls#3522
41+
// That should make subtrees easier and allow us to expand metrics
42+
var cmListener = new CodeMetricsListener(declarationFinder);
4743
ParseTreeWalker.Default.Walk(cmListener, moduleTree);
4844
return cmListener.GetMetricsResult(qmn);
4945
}
@@ -52,31 +48,36 @@ private ModuleMetricsResult GetModuleResult(QualifiedModuleName qmn, IParseTree
5248
private class CodeMetricsListener : VBAParserBaseListener
5349
{
5450
private readonly DeclarationFinder _finder;
55-
private readonly IIndenterSettings _indenterSettings;
5651

5752
private Declaration _currentMember;
53+
private int _currentNestingLevel = 0;
54+
private int _currentMaxNesting = 0;
5855
private List<CodeMetricsResult> _results = new List<CodeMetricsResult>();
5956
private List<CodeMetricsResult> _moduleResults = new List<CodeMetricsResult>();
6057

6158
private List<MemberMetricsResult> _memberResults = new List<MemberMetricsResult>();
6259

63-
public CodeMetricsListener(DeclarationFinder finder, IIndenterSettings indenterSettings)
60+
public CodeMetricsListener(DeclarationFinder finder)
6461
{
6562
_finder = finder;
66-
_indenterSettings = indenterSettings;
6763
}
68-
69-
public override void EnterEndOfLine([NotNull] VBAParser.EndOfLineContext context)
64+
public override void EnterBlock([NotNull] VBAParser.BlockContext context)
7065
{
71-
int followingIndentationLevel = 0;
72-
// we have a proper newline
73-
if (context.NEWLINE() != null)
66+
_currentNestingLevel++;
67+
if (_currentNestingLevel > _currentMaxNesting)
7468
{
75-
// the last whitespace, which is the one in front of the next line's contents
76-
var followingWhitespace = context.whiteSpace().LastOrDefault();
77-
followingIndentationLevel = IndentationLevelFromWhitespace(followingWhitespace);
69+
_currentMaxNesting = _currentNestingLevel;
7870
}
79-
(_currentMember == null ? _moduleResults : _results).Add(new CodeMetricsResult(1, 0, followingIndentationLevel));
71+
}
72+
73+
public override void ExitBlock([NotNull] VBAParser.BlockContext context)
74+
{
75+
_currentNestingLevel--;
76+
}
77+
78+
public override void EnterEndOfLine([NotNull] VBAParser.EndOfLineContext context)
79+
{
80+
(_currentMember == null ? _moduleResults : _results).Add(new CodeMetricsResult(1, 0, 0));
8081
}
8182

8283
public override void EnterIfStmt([NotNull] VBAParser.IfStmtContext context)
@@ -160,27 +161,15 @@ public override void ExitPropertySetStmt([NotNull] VBAParser.PropertySetStmtCont
160161
{
161162
ExitMeasurableMember();
162163
}
163-
164-
public override void EnterBlockStmt([NotNull] VBAParser.BlockStmtContext context)
165-
{
166-
// there is a whitespace context here after the option of a statementLabel.
167-
// we need to account for that
168-
_results.Add(new CodeMetricsResult(0, 0, IndentationLevelFromWhitespace(context.whiteSpace())));
169-
}
170164

171-
private int IndentationLevelFromWhitespace(VBAParser.WhiteSpaceContext wsContext)
172-
{
173-
if (wsContext == null) return 0;
174-
// the only thing that contains underscores is the line-continuation at this point
175-
var lineContinuation = wsContext.children.LastOrDefault((tree) => tree.GetText().Contains("_"));
176-
var index = lineContinuation != null ? wsContext.children.IndexOf(lineContinuation) : 0;
177-
return (wsContext?.ChildCount ?? 0 - index) / _indenterSettings.IndentSpaces;
178-
}
179-
180165
private void ExitMeasurableMember()
181166
{
167+
Debug.Assert(_currentNestingLevel == 0, "Unexpected Nesting Level when exiting Measurable Member");
168+
_results.Add(new CodeMetricsResult(0, 0, _currentMaxNesting));
182169
_memberResults.Add(new MemberMetricsResult(_currentMember, _results));
183-
_results = new List<CodeMetricsResult>(); // reinitialize to drop results
170+
// reset state
171+
_results = new List<CodeMetricsResult>();
172+
_currentMaxNesting = 0;
184173
_currentMember = null;
185174
}
186175

Rubberduck.Core/UI/Command/AddTestModuleCommand.cs

Lines changed: 67 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
using Rubberduck.Parsing.VBA;
66
using Rubberduck.Settings;
77
using Rubberduck.UnitTesting;
8-
using Rubberduck.VBEditor.Extensions;
98
using Rubberduck.VBEditor.SafeComWrappers;
109
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
1110
using Rubberduck.VBEditor.SafeComWrappers.VBA;
1211
using System.Text;
1312
using Rubberduck.Parsing.Symbols;
13+
using System;
1414

1515
namespace Rubberduck.UI.Command
1616
{
@@ -23,13 +23,15 @@ public class AddTestModuleCommand : CommandBase
2323
private readonly IVBE _vbe;
2424
private readonly RubberduckParserState _state;
2525
private readonly IGeneralConfigService _configLoader;
26+
private readonly IMessageBox _messageBox;
2627

27-
public AddTestModuleCommand(IVBE vbe, RubberduckParserState state, IGeneralConfigService configLoader)
28+
public AddTestModuleCommand(IVBE vbe, RubberduckParserState state, IGeneralConfigService configLoader, IMessageBox messageBox)
2829
: base(LogManager.GetCurrentClassLogger())
2930
{
3031
_vbe = vbe;
3132
_state = state;
3233
_configLoader = configLoader;
34+
_messageBox = messageBox;
3335
}
3436

3537
private readonly string _testModuleEmptyTemplate = new StringBuilder()
@@ -137,7 +139,13 @@ private IVBProject GetProject()
137139

138140
protected override bool EvaluateCanExecute(object parameter)
139141
{
140-
return !GetProject().IsWrappingNullReference && _vbe.HostSupportsUnitTests();
142+
var project = GetProject();
143+
return !project.IsWrappingNullReference && CanExecuteCode(project);
144+
}
145+
146+
private bool CanExecuteCode(IVBProject project)
147+
{
148+
return project.Protection == ProjectProtection.Unprotected;
141149
}
142150

143151
protected override void OnExecute(object parameter)
@@ -159,71 +167,81 @@ protected override void OnExecute(object parameter)
159167
project.EnsureReferenceToAddInLibrary();
160168
}
161169

162-
using(var components = project.VBComponents)
170+
try
163171
{
164-
using (var component = components.Add(ComponentType.StandardModule))
172+
using (var components = project.VBComponents)
165173
{
166-
using (var module = component.CodeModule)
174+
using (var component = components.Add(ComponentType.StandardModule))
167175
{
168-
component.Name = GetNextTestModuleName(project);
169-
170-
var hasOptionExplicit = false;
171-
if (module.CountOfLines > 0 && module.CountOfDeclarationLines > 0)
176+
using (var module = component.CodeModule)
172177
{
173-
hasOptionExplicit = module.GetLines(1, module.CountOfDeclarationLines)
174-
.Contains("Option Explicit");
175-
}
178+
component.Name = GetNextTestModuleName(project);
176179

177-
var options = string.Concat(hasOptionExplicit ? string.Empty : "Option Explicit\r\n",
178-
"Option Private Module\r\n\r\n");
180+
var hasOptionExplicit = false;
181+
if (module.CountOfLines > 0 && module.CountOfDeclarationLines > 0)
182+
{
183+
hasOptionExplicit = module.GetLines(1, module.CountOfDeclarationLines)
184+
.Contains("Option Explicit");
185+
}
179186

180-
if (parameterIsModuleDeclaration)
181-
{
182-
var moduleCodeBuilder = new StringBuilder();
183-
var declarationsToStub = GetDeclarationsToStub((Declaration) parameter);
187+
var options = string.Concat(hasOptionExplicit ? string.Empty : "Option Explicit\r\n",
188+
"Option Private Module\r\n\r\n");
184189

185-
foreach (var declaration in declarationsToStub)
190+
if (parameterIsModuleDeclaration)
186191
{
187-
var name = string.Empty;
192+
var moduleCodeBuilder = new StringBuilder();
193+
var declarationsToStub = GetDeclarationsToStub((Declaration) parameter);
188194

189-
switch (declaration.DeclarationType)
195+
foreach (var declaration in declarationsToStub)
190196
{
191-
case DeclarationType.Procedure:
192-
case DeclarationType.Function:
193-
name = declaration.IdentifierName;
194-
break;
195-
case DeclarationType.PropertyGet:
196-
name = $"Get{declaration.IdentifierName}";
197-
break;
198-
case DeclarationType.PropertyLet:
199-
name = $"Let{declaration.IdentifierName}";
200-
break;
201-
case DeclarationType.PropertySet:
202-
name = $"Set{declaration.IdentifierName}";
203-
break;
197+
var name = string.Empty;
198+
199+
switch (declaration.DeclarationType)
200+
{
201+
case DeclarationType.Procedure:
202+
case DeclarationType.Function:
203+
name = declaration.IdentifierName;
204+
break;
205+
case DeclarationType.PropertyGet:
206+
name = $"Get{declaration.IdentifierName}";
207+
break;
208+
case DeclarationType.PropertyLet:
209+
name = $"Let{declaration.IdentifierName}";
210+
break;
211+
case DeclarationType.PropertySet:
212+
name = $"Set{declaration.IdentifierName}";
213+
break;
214+
}
215+
216+
var stub = AddTestMethodCommand.TestMethodTemplate.Replace(
217+
AddTestMethodCommand.NamePlaceholder, $"{name}_Test");
218+
moduleCodeBuilder.AppendLine(stub);
204219
}
205220

206-
var stub = AddTestMethodCommand.TestMethodTemplate.Replace(
207-
AddTestMethodCommand.NamePlaceholder, $"{name}_Test");
208-
moduleCodeBuilder.AppendLine(stub);
221+
module.AddFromString(options + GetTestModule(settings) + moduleCodeBuilder);
209222
}
223+
else
224+
{
225+
var defaultTestMethod = settings.DefaultTestStubInNewModule
226+
? AddTestMethodCommand.TestMethodTemplate.Replace(
227+
AddTestMethodCommand.NamePlaceholder,
228+
"TestMethod1")
229+
: string.Empty;
210230

211-
module.AddFromString(options + GetTestModule(settings) + moduleCodeBuilder);
231+
module.AddFromString(options + GetTestModule(settings) + defaultTestMethod);
232+
}
212233
}
213-
else
214-
{
215-
var defaultTestMethod = settings.DefaultTestStubInNewModule
216-
? AddTestMethodCommand.TestMethodTemplate.Replace(AddTestMethodCommand.NamePlaceholder,
217-
"TestMethod1")
218-
: string.Empty;
219234

220-
module.AddFromString(options + GetTestModule(settings) + defaultTestMethod);
221-
}
235+
component.Activate();
222236
}
223-
224-
component.Activate();
225237
}
226238
}
239+
catch (Exception ex)
240+
{
241+
_messageBox.Show(RubberduckUI.Command_AddTestModule_Error);
242+
Logger.Warn("Unable to add test module. An exception was thrown.");
243+
Logger.Warn(ex);
244+
}
227245
_state.OnParseRequested(this);
228246
}
229247

Rubberduck.Core/UI/RubberduckUI.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Rubberduck.Core/UI/RubberduckUI.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1809,4 +1809,7 @@ NOTE: Restart is required for the setting to take effect.</value>
18091809
<data name="GeneralSettings_CompileBeforeParse_WarnCompileOnDemandEnabled_Caption" xml:space="preserve">
18101810
<value>Compile On Demand currently enabled</value>
18111811
</data>
1812+
<data name="Command_AddTestModule_Error" xml:space="preserve">
1813+
<value>Unable to create test module. The host application may not allow for creation of new modules or the project may be locked. For details, check the log.</value>
1814+
</data>
18121815
</root>

Rubberduck.Core/UI/UnitTesting/TestExplorerViewModel.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,16 @@ public class TestExplorerViewModel : ViewModelBase, INavigateSelection, IDisposa
2323
private readonly ITestEngine _testEngine;
2424
private readonly IClipboardWriter _clipboard;
2525
private readonly ISettingsFormFactory _settingsFormFactory;
26-
//private readonly IGeneralConfigService _configService;
27-
//private readonly IOperatingSystem _operatingSystem;
26+
private readonly IMessageBox _messageBox;
2827

2928
public TestExplorerViewModel(IVBE vbe,
3029
RubberduckParserState state,
3130
ITestEngine testEngine,
3231
TestExplorerModel model,
3332
IClipboardWriter clipboard,
3433
IGeneralConfigService configService,
35-
ISettingsFormFactory settingsFormFactory)
34+
ISettingsFormFactory settingsFormFactory,
35+
IMessageBox messageBox)
3636
{
3737
_vbe = vbe;
3838
_state = state;
@@ -41,13 +41,14 @@ public TestExplorerViewModel(IVBE vbe,
4141
Model = model;
4242
_clipboard = clipboard;
4343
_settingsFormFactory = settingsFormFactory;
44+
_messageBox = messageBox;
4445

4546
_navigateCommand = new NavigateCommand(_state.ProjectsProvider);
4647

4748
RunAllTestsCommand = new RunAllTestsCommand(vbe, state, testEngine, model, null);
4849
RunAllTestsCommand.RunCompleted += RunCompleted;
4950

50-
AddTestModuleCommand = new AddTestModuleCommand(vbe, state, configService);
51+
AddTestModuleCommand = new AddTestModuleCommand(vbe, state, configService, _messageBox);
5152
AddTestMethodCommand = new AddTestMethodCommand(vbe, state);
5253
AddErrorTestMethodCommand = new AddTestMethodExpectedErrorCommand(vbe, state);
5354

Rubberduck.Main/ComClientLibrary/UI/DockableWindowHost.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,13 @@ private void OnCallBackEvent(object sender, SubClassingWindowEventArgs e)
347347
return;
348348
}
349349
var param = new LParam { Value = (uint)e.LParam };
350-
_userControl.Size = new Size(param.LowWord, param.HighWord);
350+
// The VBE passes a special value to the HighWord when docking into the VBE Codepane
351+
// instead of docking into the VBE itself.
352+
// that special value (0xffef) blows up inside the guts of Window management because it's
353+
// apparently converted to a signed short somewhere and then considered "negative"
354+
// that is why we drop the signbit for shorts off our values when creating the control Size.
355+
const ushort signBitMask = 0x8000;
356+
_userControl.Size = new Size(param.LowWord & ~signBitMask, param.HighWord & ~signBitMask);
351357
}
352358

353359
public void AddUserControl(UserControl control, IntPtr vbeHwnd)

0 commit comments

Comments
 (0)