Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance step completion with usage-based ordering #56

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#nullable disable
#nullable disable
namespace Reqnroll.VisualStudio.Editor.Completions;

public class DeveroomCompletionSource : DeveroomCompletionSourceBase
Expand Down Expand Up @@ -65,13 +65,30 @@ private T GetTagData<T>(IMappingTagSpan<DeveroomTag>[] tagSpans, string type) wh
private List<Completion> GetStepCompletions(DeveroomGherkinStep step)
{
var discoveryService = _project.GetDiscoveryService();
var bindingRegistry = discoveryService.BindingRegistryCache.Value;
var featureFiles = _project.GetProjectFiles(".feature");
var configuration = _project.GetDeveroomConfiguration();
var stepDefinitions = discoveryService.BindingRegistryCache.Value.StepDefinitions;

var sampler = new StepDefinitionSampler();

return bindingRegistry.StepDefinitions
var steps = stepDefinitions
.Where(sd => sd.IsValid && sd.StepDefinitionType == step.ScenarioBlock)
.Select(sd => new Completion(sampler.GetStepDefinitionSample(sd)))
.ToArray();

var stepDefinitionUsageFinder = new StepDefinitionUsageFinder(_ideScope);
var orderedSteps = stepDefinitionUsageFinder
.FindUsageCounts(steps, featureFiles, configuration)
.OrderByDescending(kvp => kvp.Value)
.ToList();

return orderedSteps
.Select((step, index) =>
{
var isTop5 = index < 5;
return new Completion(step.Key)
{
DisplayText = isTop5 ? $"★ {step.Key}" : step.Key,
Description = $"Step usage: {step.Value}"
};
})
.ToList();
}

Expand Down Expand Up @@ -133,19 +150,19 @@ private void AddCompletionsFromExpectedTokens(TokenType[] expectedTokens, List<C
"Used to combine steps in a readable format");
break;
case TokenType.DocStringSeparator:
AddCompletions(completions, new[] {"\"\"\"", "```"},
AddCompletions(completions, new[] { "\"\"\"", "```" },
"Doc-string separator: Provides multi-line text parameter for the step");
break;
case TokenType.TableRow:
AddCompletions(completions, new[] {"| "},
AddCompletions(completions, new[] { "| " },
"Data table and examples table cell separator");
break;
case TokenType.Language:
AddCompletions(completions, new[] {"#language: "},
AddCompletions(completions, new[] { "#language: " },
"Specifies the language of the feature file");
break;
case TokenType.TagLine:
AddCompletions(completions, new[] {"@tag1 "},
AddCompletions(completions, new[] { "@tag1 " },
"Labels a scenario, a feature or an examples block");
break;
}
Expand Down Expand Up @@ -195,4 +212,4 @@ private void AddDefaultKeywordCompletions(GherkinDialect gherkinDialect, List<Co
foreach (var blockKeyword in gherkinDialect.GetBlockKeywords())
completions.Add(new Completion(blockKeyword + ": "));
}
}
}
65 changes: 65 additions & 0 deletions Reqnroll.VisualStudio/Editor/Services/StepDefinitionUsageFinder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#nullable disable
using Reqnroll.VisualStudio.Editor.Completions;

namespace Reqnroll.VisualStudio.Editor.Services;

public class StepDefinitionUsage
Expand Down Expand Up @@ -30,12 +32,35 @@ public IEnumerable<StepDefinitionUsage> FindUsages(ProjectStepDefinitionBinding[
return featureFiles.SelectMany(ff => FindUsages(stepDefinitions, ff, configuration));
}

public Dictionary<string, int> FindUsageCounts(
ProjectStepDefinitionBinding[] stepDefinitions,
string[] featureFiles,
DeveroomConfiguration configuration)
{
return featureFiles
.Select(ff => FindUsageCounts(stepDefinitions, ff, configuration))
.SelectMany(dict => dict)
.GroupBy(kvp => kvp.Key)
.ToDictionary(
group => group.Key,
group => group.Sum(kvp => kvp.Value)
);
}

private IEnumerable<StepDefinitionUsage> FindUsages(ProjectStepDefinitionBinding[] stepDefinitions,
string featureFilePath, DeveroomConfiguration configuration) =>
LoadContent(featureFilePath, out string featureFileContent)
? FindUsagesFromContent(stepDefinitions, featureFileContent, featureFilePath, configuration)
: Enumerable.Empty<StepDefinitionUsage>();

private Dictionary<string, int> FindUsageCounts(
ProjectStepDefinitionBinding[] stepDefinitions,
string featureFilePath,
DeveroomConfiguration configuration) =>
LoadContent(featureFilePath, out string featureFileContent)
? FindUsageCountsFromContent(stepDefinitions, featureFileContent, featureFilePath, configuration)
: new Dictionary<string, int>();

private bool LoadContent(string featureFilePath, out string content)
{
if (LoadAlreadyOpenedContent(featureFilePath, out string openedContent))
Expand Down Expand Up @@ -111,6 +136,46 @@ public IEnumerable<StepDefinitionUsage> FindUsagesFromContent(ProjectStepDefinit
}
}

public Dictionary<string, int> FindUsageCountsFromContent(ProjectStepDefinitionBinding[] stepDefinitions,
string featureFileContent, string featureFilePath, DeveroomConfiguration configuration)
{
var dialectProvider = ReqnrollGherkinDialectProvider.Get(configuration.DefaultFeatureLanguage);
var parser = new DeveroomGherkinParser(dialectProvider, _ideScope.MonitoringService);

parser.ParseAndCollectErrors(featureFileContent, _ideScope.Logger, out var gherkinDocument, out _);

var featureNode = gherkinDocument?.Feature;
if (featureNode == null)
return new Dictionary<string, int>();

var dummyRegistry = ProjectBindingRegistry.FromBindings(stepDefinitions);

var featureContext = new UsageFinderContext(featureNode);

var stepUsageCounts = new Dictionary<string, int>();

foreach (var scenarioDefinition in featureNode.FlattenStepsContainers())
{
var context = new UsageFinderContext(scenarioDefinition, featureContext);

foreach (var step in scenarioDefinition.Steps)
{
var matchResult = dummyRegistry.MatchStep(step, context);
if (matchResult.HasDefined)
{
var stepText = new StepDefinitionSampler().GetStepDefinitionSample(matchResult.Items[0].MatchedStepDefinition);

if (stepUsageCounts.ContainsKey(stepText))
stepUsageCounts[stepText]++;
else
stepUsageCounts[stepText] = 1;
}
}
}

return stepUsageCounts;
}

private SourceLocation GetSourceLocation(Step step, string featureFilePath) =>
new(featureFilePath, step.Location.Line, step.Location.Column);

Expand Down
Loading