Skip to content

Commit

Permalink
Go to hook implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
gasparnagy committed Feb 8, 2024
1 parent b00b7e2 commit 6ec27ae
Show file tree
Hide file tree
Showing 62 changed files with 817 additions and 203 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# [vNext]

* Support for .NET 8 projects
* New editor command: "Go To Hooks" (Ctrl B,H) to navigate to the hooks related to the scenario
* The "Go To Definition" lists hooks when invoked from scenario header (tags, header line, description)
* Initial release based on v2022.1.91 of the [SpecFlow for Visual Studio](https://github.com/SpecFlowOSS/SpecFlow.VS/) extension.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ namespace ReqnrollConnector.Discovery;

public record ConnectorResult(
ImmutableArray<StepDefinition> StepDefinitions,
ImmutableArray<Reqnroll.VisualStudio.ReqnrollConnector.Models.Hook> Hooks,
ImmutableSortedDictionary<string, string> SourceFiles,
ImmutableSortedDictionary<string, string> TypeNames,
ImmutableSortedDictionary<string, string> AnalyticsProperties,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ namespace ReqnrollConnector.Discovery;

public record DiscoveryResult(
ImmutableArray<StepDefinition> StepDefinitions,
ImmutableArray<Reqnroll.VisualStudio.ReqnrollConnector.Models.Hook> Hooks,
ImmutableSortedDictionary<string, string> SourceFiles,
ImmutableSortedDictionary<string, string> TypeNames
);
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
using Reqnroll.VisualStudio.ReqnrollConnector.Models;
using StepDefinition = ReqnrollConnector.ReqnrollProxies.StepDefinition;

namespace ReqnrollConnector.Discovery;

public class ReqnrollDiscoverer
{
private readonly IAnalyticsContainer _analytics;
private readonly ILogger _log;
private readonly SymbolReaderCache _symbolReaders;
// ReSharper disable once NotAccessedField.Local
private readonly ILogger _log;

public ReqnrollDiscoverer(ILogger log, IAnalyticsContainer analytics)
{
Expand All @@ -21,8 +25,10 @@ public ReqnrollDiscoverer(ILogger log, IAnalyticsContainer analytics)
var typeNames = ImmutableSortedDictionary.CreateBuilder<string, string>();
var sourcePaths = ImmutableSortedDictionary.CreateBuilder<string, string>();

var stepDefinitions = bindingRegistryFactory
.GetBindingRegistry(assemblyLoadContext, testAssembly, configFile)
var bindingRegistryAdapter = bindingRegistryFactory
.GetBindingRegistry(assemblyLoadContext, testAssembly, configFile);

var stepDefinitions = bindingRegistryAdapter
.GetStepDefinitions()
.Select(sdb => CreateStepDefinition(sdb,
sdb2 => GetParamTypes(sdb2.ParamTypes, parameterTypeName => GetKey(typeNames, parameterTypeName)),
Expand All @@ -33,12 +39,24 @@ public ReqnrollDiscoverer(ILogger log, IAnalyticsContainer analytics)
.OrderBy(sd => sd.SourceLocation)
.ToImmutableArray();

var hooks = bindingRegistryAdapter.
GetHooks()
.Select(sdb => CreateHook(sdb,
sourcePath => GetKey(sourcePaths, sourcePath),
assemblyLoadContext,
testAssembly)
)
.OrderBy(sd => sd.SourceLocation)
.ToImmutableArray();


_analytics.AddAnalyticsProperty("TypeNames", typeNames.Count.ToString());
_analytics.AddAnalyticsProperty("SourcePaths", sourcePaths.Count.ToString());
_analytics.AddAnalyticsProperty("StepDefinitions", stepDefinitions.Length.ToString());

return new DiscoveryResult(
stepDefinitions,
hooks,
sourcePaths.ToImmutable(),
typeNames.ToImmutable()
);
Expand All @@ -52,18 +70,35 @@ public ReqnrollDiscoverer(ILogger log, IAnalyticsContainer analytics)
var stepDefinition = new StepDefinition
(
sdb.StepDefinitionType,
sdb.Regex.Map(r => r.ToString()).Reduce((string) null!),
sdb.Regex,
sdb.Method.ToString()!,
getParameterTypes(sdb),
GetScope(sdb),
GetSourceExpression(sdb),
GetErrorMessage(sdb),
sdb.Error,
sourceLocation.Reduce((string) null!)
);

return stepDefinition;
}

private Hook CreateHook(HookBindingAdapter sdb,
Func<string, string> getSourcePathId,
AssemblyLoadContext assemblyLoadContext, Assembly testAssembly)
{
var sourceLocation = GetSourceLocation(sdb.Method, getSourcePathId, assemblyLoadContext, testAssembly);
var stepDefinition = new Hook
{
Type = sdb.HookType,
HookOrder = sdb.HookOrder,
Method = sdb.Method.ToString(),
Scope = GetScope(sdb),
SourceLocation = sourceLocation.Reduce((string)null!)
};

return stepDefinition;
}

private string GetKey(ImmutableSortedDictionary<string, string>.Builder dictionary, string value)
{
KeyValuePair<string, string> found = dictionary
Expand Down Expand Up @@ -93,23 +128,24 @@ private string GetParamType(string parameterTypeName, Func<string, string> getKe
return $"#{key}";
}

private static StepScope? GetScope(StepDefinitionBindingAdapter stepDefinitionBinding)
private static StepScope? GetScope(IScopedBindingAdapter scopedBinding)
{
if (!stepDefinitionBinding.IsScoped)
if (!scopedBinding.IsScoped)
return null;

return new StepScope(
stepDefinitionBinding.BindingScopeTag.Map(tag => $"@{tag}").Reduce((string) null!),
stepDefinitionBinding.BindingScopeFeatureTitle,
stepDefinitionBinding.BindingScopeScenarioTitle
);
return new StepScope
{
Tag = scopedBinding.BindingScopeTag,
FeatureTitle = scopedBinding.BindingScopeFeatureTitle,
ScenarioTitle = scopedBinding.BindingScopeScenarioTitle
};
}

private static string? GetSourceExpression(StepDefinitionBindingAdapter sdb)
=> sdb.GetProperty<string>("SourceExpression").Reduce(() => GetSpecifiedExpressionFromRegex(sdb)!);
=> sdb.Expression ?? GetSpecifiedExpressionFromRegex(sdb);

private static string? GetSpecifiedExpressionFromRegex(StepDefinitionBindingAdapter sdb) =>
sdb.Regex
sdb.Regex?
.Map(regex => regex.ToString())
.Map(regexString =>
{
Expand All @@ -118,11 +154,7 @@ private string GetParamType(string parameterTypeName, Func<string, string> getKe
if (regexString.EndsWith("$"))
regexString = regexString.Substring(0, regexString.Length - 1);
return regexString;
})
.Reduce((string) null!);

private static string? GetErrorMessage(StepDefinitionBindingAdapter sdb)
=> sdb.GetProperty<string>("ErrorMessage").Reduce((string)null!);
});

private Option<string> GetSourceLocation(BindingMethodAdapter bindingMethod, Func<string, string> getSourcePathId,
AssemblyLoadContext assemblyLoadContext, Assembly testAssembly)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,21 @@ public class ReflectionExecutor
{
return new ConnectorResult(
discoveryResult.StepDefinitions,
discoveryResult.Hooks,
discoveryResult.SourceFiles,
discoveryResult.TypeNames,
analyticsProperties,
errorMessage);
}
return new ConnectorResult(ImmutableArray<StepDefinition>.Empty,
ImmutableArray<Reqnroll.VisualStudio.ReqnrollConnector.Models.Hook>.Empty,
ImmutableSortedDictionary<string, string>.Empty,
ImmutableSortedDictionary<string, string>.Empty,
analytics.ToImmutable(),
errorMessage != null ? $"{errorMessage}{Environment.NewLine}{log}" : log);
})))
.Reduce(new ConnectorResult(ImmutableArray<StepDefinition>.Empty,
ImmutableArray<Reqnroll.VisualStudio.ReqnrollConnector.Models.Hook>.Empty,
ImmutableSortedDictionary<string, string>.Empty,
ImmutableSortedDictionary<string, string>.Empty,
analytics.ToImmutable(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ public IEnumerable<StepDefinitionBindingAdapter> GetStepDefinitions()
{
return (Adaptee.StepDefinitions ?? Array.Empty<StepDefinitionData>()).Select(sd => new StepDefinitionBindingAdapter(sd));
}

public IEnumerable<HookBindingAdapter> GetHooks()
{
return (Adaptee.Hooks ?? Array.Empty<HookData>()).Select(h => new HookBindingAdapter(h));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ namespace Reqnroll.Bindings.Provider.Data;

public class BindingScopeData
{
public string Tag { get; set; }
public string Tag { get; set; } // contains leading '@', e.g. '@mytag'
public string FeatureTitle { get; set; }
public string ScenarioTitle { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ public class HookData
public BindingSourceData Source { get; set; }
public BindingScopeData Scope { get; set; }
public string Type { get; set; }
public int HookOrder { get; set; }
public int? HookOrder { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Reqnroll.Bindings.Provider.Data;

namespace ReqnrollConnector.ReqnrollProxies;

public record HookBindingAdapter(HookData Adaptee) : IScopedBindingAdapter
{
public string HookType => Adaptee.Type;
public BindingMethodAdapter Method { get; } = new(Adaptee.Source?.Method);
public bool IsScoped => Adaptee.Scope != null;
public string? BindingScopeTag => Adaptee.Scope?.Tag;
public string? BindingScopeFeatureTitle => Adaptee.Scope?.FeatureTitle;
public string? BindingScopeScenarioTitle => Adaptee.Scope?.ScenarioTitle;
public int? HookOrder => Adaptee.HookOrder;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ namespace ReqnrollConnector.ReqnrollProxies;
public interface IBindingRegistryAdapter
{
IEnumerable<StepDefinitionBindingAdapter> GetStepDefinitions();
IEnumerable<HookBindingAdapter> GetHooks();
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Reqnroll.VisualStudio.ReqnrollConnector.Models;

namespace ReqnrollConnector.ReqnrollProxies;

public record StepDefinition(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,24 @@

namespace ReqnrollConnector.ReqnrollProxies;

public record StepDefinitionBindingAdapter(StepDefinitionData Adaptee)
public interface IScopedBindingAdapter
{
bool IsScoped { get; }
string? BindingScopeTag { get; }
string? BindingScopeFeatureTitle { get; }
string? BindingScopeScenarioTitle { get; }
}

public record StepDefinitionBindingAdapter(StepDefinitionData Adaptee) : IScopedBindingAdapter
{
public string StepDefinitionType => Adaptee.Type;
public string[] ParamTypes => Adaptee.ParamTypes;
public Option<string> Regex => Adaptee.Regex;
public string? Regex => Adaptee.Regex;
public string? Expression => Adaptee.Expression;
public string? Error => Adaptee.Error;
public BindingMethodAdapter Method { get; } = new(Adaptee.Source?.Method);
public bool IsScoped => Adaptee.Scope != null;
public Option<string> BindingScopeTag => Adaptee.Scope?.Tag;
public string? BindingScopeTag => Adaptee.Scope?.Tag;
public string? BindingScopeFeatureTitle => Adaptee.Scope?.FeatureTitle;
public string? BindingScopeScenarioTitle => Adaptee.Scope?.ScenarioTitle;
public virtual Option<T> GetProperty<T>(string propertyName)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ namespace Reqnroll.VisualStudio.ReqnrollConnector.Models;

public class DiscoveryResult : ConnectorResult
{
public StepDefinition[] StepDefinitions { get; set; }
public StepDefinition[] StepDefinitions { get; set; } = Array.Empty<StepDefinition>();
public Hook[] Hooks { get; set; } = Array.Empty<Hook>();
public Dictionary<string, string> SourceFiles { get; set; }
public Dictionary<string, string> TypeNames { get; set; }
}
43 changes: 43 additions & 0 deletions Connectors/Reqnroll.VisualStudio.ReqnrollConnector.Models/Hook.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#nullable disable
namespace Reqnroll.VisualStudio.ReqnrollConnector.Models;

public class Hook
{
public string Type { get; set; }
public int? HookOrder { get; set; }
public string Method { get; set; }
//public string ParamTypes { get; set; }
public StepScope Scope { get; set; }

public string SourceLocation { get; set; }

#region Equality

protected bool Equals(Hook other)
{
return Type == other.Type && HookOrder == other.HookOrder && Method == other.Method && Equals(Scope, other.Scope) && SourceLocation == other.SourceLocation;
}

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Hook)obj);
}

public override int GetHashCode()
{
unchecked
{
var hashCode = (Type != null ? Type.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ HookOrder.GetHashCode();
hashCode = (hashCode * 397) ^ (Method != null ? Method.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (Scope != null ? Scope.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (SourceLocation != null ? SourceLocation.GetHashCode() : 0);
return hashCode;
}
}

#endregion
}
Loading

0 comments on commit 6ec27ae

Please sign in to comment.