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

Allow EditorConfigFile to be used in-memory without access or use of physical file system. #25

Merged
merged 3 commits into from
Nov 24, 2023
Merged
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
2 changes: 2 additions & 0 deletions src/EditorConfig.Core/ConfigSection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public ConfigSection(string name, IEditorConfigFile origin, Dictionary<string, s

private static string FixGlob(string glob, string directory)
{
if (string.IsNullOrEmpty(directory)) return glob;

switch (glob.IndexOf('/'))
{
case -1: glob = "**/" + glob; break;
Expand Down
38 changes: 31 additions & 7 deletions src/EditorConfig.Core/EditorConfigFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,25 +62,49 @@ public class EditorConfigFile : IEditorConfigFile
/// <inheritdoc cref="IEditorConfigFile.IsRoot"/>
public bool IsRoot => _isRoot;

internal EditorConfigFile(string path, string cacheKey = null)
private EditorConfigFile(
string fileName,
string directory,
TextReader reader,
string cacheKey = null)
{
Directory = Path.GetDirectoryName(path);
FileName = Path.GetFileName(path);
Directory = directory;
FileName = fileName;
CacheKey = cacheKey;
Parse(path);
ReadAndParse(reader);

if (_globalDict.TryGetValue("root", out var value))
bool.TryParse(value, out _isRoot);
}

private void Parse(string file)
/// <summary> Parses EditorConfig file from the file path. </summary>
/// <param name="path"> File path in a physical file system. </param>
/// <returns> Parsed EditorConfig file. </returns>
public static EditorConfigFile Parse(string path) => Parse(path, cacheKey: null);

/// <summary> Parses EditorConfig file from the text reader. </summary>
/// <param name="reader"> Text reader. </param>
/// <param name="directory"> EditorConfig base directory to match file sections to. Default is null. </param>
/// <param name="fileName"> EditorConfig file name. Default is '.editorconfig'. </param>
/// <returns> Parsed EditorConfig file. </returns>
public static EditorConfigFile Parse(TextReader reader, string directory = null, string fileName = ".editorconfig") =>
new(fileName, directory, reader);

internal static EditorConfigFile Parse(string path, string cacheKey)
{
var lines = File.ReadLines(file);
using var file = File.OpenRead(path);
using var reader = new StreamReader(file);
return new EditorConfigFile(
Path.GetFileName(path), Path.GetDirectoryName(path),
reader, cacheKey);
}

private void ReadAndParse(TextReader reader)
{
var activeDict = _globalDict;
var sectionName = string.Empty;
var reset = false;
foreach (var line in lines)
while (reader.ReadLine() is { } line)
{
if (string.IsNullOrWhiteSpace(line)) continue;

Expand Down
4 changes: 2 additions & 2 deletions src/EditorConfig.Core/EditorConfigFileCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ private static string GetFileHash(string filename)
/// <returns></returns>
public static EditorConfigFile GetOrCreate(string file)
{
if (!File.Exists(file)) return new EditorConfigFile(file);
if (!File.Exists(file)) return EditorConfigFile.Parse(file);

var key = $"{file}_{GetFileHash(file)}";
return FileCache.GetOrAdd(key, _ => new EditorConfigFile(file, key));
return FileCache.GetOrAdd(key, _ => EditorConfigFile.Parse(file, key));
}
}
6 changes: 3 additions & 3 deletions src/EditorConfig.Core/EditorConfigParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class EditorConfigParser
/// <param name="configFileName">The name of the file(s) holding the editorconfiguration values</param>
/// <param name="developmentVersion">Only used in testing, development to pass an older version to the parsing routine</param>
public EditorConfigParser(string configFileName = ".editorconfig", Version developmentVersion = null)
: this(f => new EditorConfigFile(f), configFileName, developmentVersion)
: this(EditorConfigFile.Parse, configFileName, developmentVersion)
{

}
Expand Down Expand Up @@ -91,13 +91,13 @@ public FileConfiguration Parse(string fileName, IEnumerable<EditorConfigFile> ed
var sections =
from configFile in editorConfigFiles
from section in configFile.Sections
where IsMatch(section.Glob, fullPath, configFile.Directory)
where IsMatch(section.Glob, fullPath)
select section;

return new FileConfiguration(ParseVersion, file, sections.ToList());
}

private bool IsMatch(string glob, string fileName, string directory)
private bool IsMatch(string glob, string fileName)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"directory" parameter wasn't used here. So I removed it just to be sure that EditorConfig without a directory can be used as well. I.e. matching section glob directly to the file name.

{
var matcher = GlobMatcher.Create(glob, _globOptions);
var isMatch = matcher.IsMatch(fileName);
Expand Down
58 changes: 58 additions & 0 deletions src/EditorConfig.Tests/InMemory/InMemoryConfigTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System.IO;
using EditorConfig.Core;
using FluentAssertions;
using Microsoft.VisualBasic;
using NUnit.Framework;

namespace EditorConfig.Tests.InMemory
{
[TestFixture]
public class CachingTests : EditorConfigTestBase
{
[Test]
public void InMemoryConfigIsUsable()
{
var configContent = @"""
root = true

[*.cs]
end_of_line = lf
""";
var stringReader = new StringReader(configContent);
var editorConfigFile = EditorConfigFile.Parse(stringReader);

var parser = new EditorConfigParser();
var config = parser.Parse("myfile.cs", new[] { editorConfigFile });

config.EditorConfigFiles.Should().ContainSingle(f => f.IsRoot);
config.EndOfLine.Should().Be(EndOfLine.LF);
}

[Test]
public void InMemoryConfigIsUsableWithVirtualPath()
{
var virtualDirectory = Path.Combine(Directory.GetDirectoryRoot("."), "VirtualPath");

var configContent = @"""
root = true

[*.cs]
end_of_line = lf
""";
var stringReader = new StringReader(configContent);
var editorConfigFile = EditorConfigFile.Parse(stringReader, virtualDirectory);

var parser = new EditorConfigParser();

var file = Path.Combine(virtualDirectory, "myfile.cs");
var config1 = parser.Parse(file, new[] { editorConfigFile });
config1.EditorConfigFiles.Should().ContainSingle(f => f.IsRoot);
config1.EndOfLine.Should().Be(EndOfLine.LF);

var directoryOutOfScope = Path.Combine(Directory.GetDirectoryRoot("."), "DifferentDirectory");
var fileOutOfScope = Path.Combine(directoryOutOfScope, "myfile.cs");
var config2 = parser.Parse(fileOutOfScope, new[] { editorConfigFile });
config2.EditorConfigFiles.Should().BeEmpty();
}
}
}
Loading