Skip to content

Commit

Permalink
When specflow.json is not available, use config from App.config Fix #162
Browse files Browse the repository at this point in the history
  • Loading branch information
Socolin committed Jul 17, 2022
1 parent e775991 commit a5bec6e
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 84 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace ReSharperPlugin.SpecflowRiderPlugin.Caching.SpecflowJsonSettings;

public enum ConfigSource
{
None,
Json,
AppConfig
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
using System.Xml.Serialization;

namespace ReSharperPlugin.SpecflowRiderPlugin.Caching.SpecflowJsonSettings
{
[XmlRoot("specFlow")]
public class SpecflowSettings
{
public SpecflowSettingsLanguage Language { get; } = new SpecflowSettingsLanguage();
public SpecflowSettingsBindingCulture BindingCulture { get; } = new SpecflowSettingsBindingCulture();
[XmlElement("language")]
public SpecflowSettingsLanguage Language { get; set; } = new();
[XmlElement("bindingCulture")]
public SpecflowSettingsBindingCulture BindingCulture { get; set; } = new();

public SpecflowSettings()
{
Language.Feature = "en";
Language.Tool = "en";
BindingCulture.Name = "en";
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using System.Xml.Serialization;

namespace ReSharperPlugin.SpecflowRiderPlugin.Caching.SpecflowJsonSettings
{
public class SpecflowSettingsBindingCulture
{
[XmlAttribute("name")]
public string Name { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
#nullable enable
using System;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Xml.XPath;
using JetBrains.Application.Threading;
using JetBrains.Collections;
using JetBrains.Diagnostics;
using JetBrains.DocumentModel;
using JetBrains.Lifetimes;
using JetBrains.ProjectModel;
Expand Down Expand Up @@ -30,52 +37,103 @@ public SpecflowSettingsFilesCache(Lifetime lifetime,

protected override bool IsApplicable(IPsiSourceFile sf)
{
return sf.Name == "specflow.json";
return GetConfigSource(sf) != ConfigSource.None;
}

public override void MergeLoaded(object data)
{
base.MergeLoaded(data);

foreach (var (key, value) in Map)
UpdateInProvider(key, value, null);
UpdateInProvider(key, value);
}

public override object Build(IPsiSourceFile sourceFile, bool isStartup)
public override object? Build(IPsiSourceFile sourceFile, bool isStartup)
{
return GetConfigSource(sourceFile) switch
{
ConfigSource.Json => LoadSpecflowJson(sourceFile),
ConfigSource.AppConfig => LoadFromAppConfig(sourceFile),
_ => null,
};
}

public override void Drop(IPsiSourceFile sourceFile)
{
UpdateInProvider(sourceFile, null);
base.Drop(sourceFile);
}

public override void Merge(IPsiSourceFile sourceFile, object? builtPart)
{
var newSettings = (SpecflowSettings?)builtPart;

UpdateInProvider(sourceFile, newSettings);

base.Merge(sourceFile, builtPart);
}

private ConfigSource GetConfigSource(IPsiSourceFile file)
{
if (file.Name == "specflow.json")
return ConfigSource.Json;
if (file.Name.Equals("app.config", StringComparison.OrdinalIgnoreCase))
return ConfigSource.AppConfig;
return ConfigSource.None;
}

private static SpecflowSettings? LoadFromAppConfig(IPsiSourceFile sourceFile)
{
var specflowSettingsText = sourceFile.Document.GetText();
try
{
var settings = JsonConvert.DeserializeObject<SpecflowSettings>(specflowSettingsText);
return settings;
var xml = sourceFile.Document.GetText();
using var sr = new StringReader(xml);
using var xmlReader = XmlReader.Create(sr);
var document = XDocument.Load(xmlReader);

var element = document.XPathSelectElement("//configuration/specFlow");
if (element == null)
return null;

var xmlSerializer = new XmlSerializer(typeof(SpecflowSettings));
using var reader = element.CreateReader();
var specflowSettings = xmlSerializer.Deserialize(reader);
return (SpecflowSettings?)specflowSettings;

}
catch (Exception)
{
//Possible invalid json
}

return new SpecflowSettings();
return null;
}

public override void Merge(IPsiSourceFile sourceFile, object builtPart)
private static SpecflowSettings? LoadSpecflowJson(IPsiSourceFile sourceFile)
{
var newSettings = (SpecflowSettings) builtPart;
Map.TryGetValue(sourceFile, out var oldSettings);

UpdateInProvider(sourceFile, newSettings, oldSettings);

base.Merge(sourceFile, builtPart);
var specflowSettingsText = sourceFile.Document.GetText();
try
{
return JsonConvert.DeserializeObject<SpecflowSettings>(specflowSettingsText);
}
catch (Exception)
{
//Possible invalid json
}
return null;
}

private void UpdateInProvider(IPsiSourceFile specflowJsonFile, SpecflowSettings newSettings, SpecflowSettings oldSettings)
private void UpdateInProvider(IPsiSourceFile file, SpecflowSettings? newSettings)
{
var specflowJsonProjectOwner = specflowJsonFile.GetProject();
var specflowJsonProjectOwner = file.GetProject();
if (specflowJsonProjectOwner == null)
return;

_settingsProvider.Update(specflowJsonProjectOwner, newSettings);
var oldSettings = _settingsProvider.GetSettings(specflowJsonProjectOwner);
if (!_settingsProvider.TryUpdate(specflowJsonProjectOwner, GetConfigSource(file), newSettings))
return;

if (oldSettings?.Language.Feature == newSettings?.Language.Feature)
if (oldSettings.Language.Feature == newSettings?.Language.Feature)
return;

var featureFilesInProject = specflowJsonProjectOwner.GetAllProjectFiles(o => o.Name.EndsWith(".feature"));
Expand All @@ -85,11 +143,11 @@ private void UpdateInProvider(IPsiSourceFile specflowJsonFile, SpecflowSettings
foreach (var featurePsiFile in featurePsiFiles)
{
var psiServices = featurePsiFile.GetPsiServices();
var cachedPsiFile = psiServices.Files.PsiFilesCache.TryGetCachedPsiFile(featurePsiFile, GherkinLanguage.Instance);
var cachedPsiFile = psiServices.Files.PsiFilesCache.TryGetCachedPsiFile(featurePsiFile, GherkinLanguage.Instance.NotNull());
if (cachedPsiFile != null)
{
cachedPsiFile.OnDocumentChanged(new DocumentChange(featurePsiFile.Document, 0, featurePsiFile.Document.GetTextLength(),
featurePsiFile.Document.GetText(), -1, TextModificationSide.NotSpecified));
featurePsiFile.Document.GetText(), -1, TextModificationSide.NotSpecified));
psiServices.Files.MarkAsDirty(featurePsiFile);
psiServices.Caches.MarkAsDirty(featurePsiFile);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
using System.Xml.Serialization;

namespace ReSharperPlugin.SpecflowRiderPlugin.Caching.SpecflowJsonSettings
{
public class SpecflowSettingsLanguage
{
private string _neutralFeature;

[XmlAttribute("feature")]
public string Feature { get; set; }
[XmlAttribute("tool")]
public string Tool { get; set; }

public string NeutralFeature => _neutralFeature ?? (_neutralFeature = Feature.Split('-')[0]);
public string NeutralFeature => _neutralFeature ??= Feature.Split('-')[0];
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#nullable enable
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -10,50 +11,86 @@ namespace ReSharperPlugin.SpecflowRiderPlugin.Caching.SpecflowJsonSettings
[ShellComponent]
public class SpecflowSettingsProvider
{
private readonly ConcurrentDictionary<IProject, SpecflowSettings> _settingsRepository = new ConcurrentDictionary<IProject, SpecflowSettings>();
private readonly ConcurrentDictionary<Lifetime, HashSet<IProject>> _projectsInSolutionRepository = new ConcurrentDictionary<Lifetime, HashSet<IProject>>();
public static readonly SpecflowSettings DefaultSettings = new();
private readonly ConcurrentDictionary<IProject, SpecflowSettings> _jsonSettingsRepository = new();
private readonly ConcurrentDictionary<IProject, SpecflowSettings> _appConfigSettingsRepository = new();
private readonly ConcurrentDictionary<Lifetime, HashSet<IProject>> _projectsInSolutionRepository = new();

public void Update(IProject project, SpecflowSettings settings)
/// <summary>
/// Update specflow settings
/// </summary>
/// <param name="project"></param>
/// <param name="source"></param>
/// <param name="settings"></param>
/// <returns>true if it changes the current project config</returns>
public bool TryUpdate(IProject project, ConfigSource source, SpecflowSettings? settings)
{
var lifetime = project.GetSolution().GetLifetime();

RegisterProjectLifetime(project, lifetime);

switch (source)
{
case ConfigSource.Json:
{
if (settings == null)
_jsonSettingsRepository.TryRemove(project, out _);
else
_jsonSettingsRepository[project] = settings;
return true;
}
case ConfigSource.AppConfig:
if (settings == null)
_appConfigSettingsRepository.TryRemove(project, out _);
else
_appConfigSettingsRepository[project] = settings;
return !_jsonSettingsRepository.ContainsKey(project);
default:
return false;
}
}

public SpecflowSettings GetSettings(IProject? project)
{
if (project == null)
return DefaultSettings;
if (_jsonSettingsRepository.TryGetValue(project, out var jsonSettings))
return jsonSettings;
if (_appConfigSettingsRepository.TryGetValue(project, out var appConfigSettings))
return appConfigSettings;
return DefaultSettings;
}

public SpecflowSettings GetDefaultSettings()
{
return _jsonSettingsRepository.FirstOrDefault().Value ?? _appConfigSettingsRepository.FirstOrDefault().Value ?? DefaultSettings;
}

private void RegisterProjectLifetime(IProject project, Lifetime lifetime)
{

if (!_projectsInSolutionRepository.TryGetValue(lifetime, out var projectsWithSettings))
{
projectsWithSettings = new HashSet<IProject>();
var addedProjects = _projectsInSolutionRepository.GetOrAdd(lifetime, projectsWithSettings);
if (addedProjects == projectsWithSettings)
{
lifetime.OnTermination(() =>
{
if (!_projectsInSolutionRepository.TryRemove(lifetime, out var projectsToRemove))
return;
{
if (!_projectsInSolutionRepository.TryRemove(lifetime, out var projectsToRemove))
return;
foreach (var projectToRemove in projectsToRemove)
_settingsRepository.TryRemove(projectToRemove, out _);
});
foreach (var projectToRemove in projectsToRemove)
{
_jsonSettingsRepository.TryRemove(projectToRemove, out _);
_appConfigSettingsRepository.TryRemove(projectToRemove, out _);
}
});
}
}

lock (projectsWithSettings)
projectsWithSettings.Add(project);

_settingsRepository[project] = settings;
}

public SpecflowSettings GetSettings(IProject project)
{
var settings = _settingsRepository.GetOrAdd(project, _ => new SpecflowSettings());

return settings;
}

public SpecflowSettings GetDefaultSettings()
{
var settings = _settingsRepository.FirstOrDefault().Value;
if (settings == null)
settings = new SpecflowSettings();

return settings;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ protected override bool AddLookupItems(GherkinSpecificCodeCompletionContext cont
{
var settings = context.BasicContext.PsiServices.GetComponent<SpecflowSettingsProvider>();
var keywordProvider = context.BasicContext.PsiServices.LanguageManager.GetService<GherkinKeywordProvider>(GherkinLanguage.Instance.NotNull());
var keywordList = keywordProvider.GetKeywordsList(context.GherkinFile.Lang ?? settings.GetSettings(context.BasicContext.File.GetProject()).Language.Feature);
var project = context.BasicContext.File.GetProject();
var keywordList = keywordProvider.GetKeywordsList(context.GherkinFile.Lang ?? settings.GetSettings(project).Language.Feature);

if (context.NodeUnderCursor is IGherkinScenario)
return AddKeywordsLookupItemsForScenario(keywordList, context, collector);
Expand Down
Loading

0 comments on commit a5bec6e

Please sign in to comment.