Skip to content
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
28 changes: 28 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ csharp_style_expression_bodied_operators = true:suggestion
csharp_style_expression_bodied_properties = true:suggestion
csharp_style_expression_bodied_indexers = true:suggestion
csharp_style_expression_bodied_accessors = true:suggestion
csharp_style_expression_bodied_local_functions = when_on_single_line:error
dotnet_style_prefer_conditional_expression_over_return = false


# Suggest more modern language features when available
csharp_style_pattern_matching_over_is_with_cast_check = true:error
Expand Down Expand Up @@ -197,12 +200,37 @@ resharper_redundant_case_label_highlighting=do_not_show
resharper_redundant_argument_default_value_highlighting=do_not_show
resharper_explicit_caller_info_argument_highlighting=hint

csharp_style_namespace_declarations = file_scoped

dotnet_analyzer_diagnostic.severity = warning
dotnet_analyzer_diagnostic.category-Style.severity = warning

# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1848
dotnet_diagnostic.CA1848.severity = suggestion
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2201
dotnet_diagnostic.CA2201.severity = none

# disable for default arm on switches, IDE0072 still covers missing enum members
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0010
dotnet_diagnostic.IDE0010.severity = none

# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/IDE0200
dotnet_diagnostic.IDE0200.severity = none

# TODO enable GenerateDocumentationFile to properly document our code
dotnet_diagnostic.IDE0005.severity = none

dotnet_diagnostic.IDE0001.severity = none

dotnet_diagnostic.IDE0057.severity = none

[DocumentationWebHost.cs]
dotnet_diagnostic.IL3050.severity = none
dotnet_diagnostic.IL2026.severity = none

[tests/**/*.cs]
dotnet_diagnostic.IDE0058.severity = none


[*.{sh,bat,ps1}]
trim_trailing_whitespace=true
Expand Down
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
<NuGetAuditMode>all</NuGetAuditMode>
<NuGetAuditLevel>low</NuGetAuditLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>

<!-- TODO ENABLE to document our code properly <GenerateDocumentationFile>true</GenerateDocumentationFile> -->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
</PropertyGroup>

Expand Down
59 changes: 31 additions & 28 deletions src/Elastic.Markdown.Refactor/Move.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
using System.Text.RegularExpressions;
using Elastic.Markdown.IO;
using Microsoft.Extensions.Logging;
using static System.StringComparison;

namespace Elastic.Markdown.Refactor;

public record ChangeSet(IFileInfo From, IFileInfo To);
public record Change(IFileInfo Source, string OriginalContent, string NewContent);
public record LinkModification(string OldLink, string NewLink, string SourceFile, int LineNumber, int ColumnNumber);

public class Move(IFileSystem readFileSystem, IFileSystem writeFileSystem, DocumentationSet documentationSet, ILoggerFactory loggerFactory)
public partial class Move(IFileSystem readFileSystem, IFileSystem writeFileSystem, DocumentationSet documentationSet, ILoggerFactory loggerFactory)
{
private const string ChangeFormatString = "Change \e[31m{0}\e[0m to \e[32m{1}\e[0m at \e[34m{2}:{3}:{4}\e[0m";

private readonly ILogger _logger = loggerFactory.CreateLogger<Move>();
private readonly Dictionary<ChangeSet, List<Change>> _changes = [];
Expand All @@ -35,7 +35,7 @@ public async Task<int> Execute(string source, string target, bool isDryRun, Canc
foreach (var (fromFile, toFile) in fromFiles.Zip(toFiles))
{
var changeSet = new ChangeSet(fromFile, toFile);
_logger.LogInformation($"Requested to move from '{fromFile}' to '{toFile}");
_logger.LogInformation("Requested to move from '{FromFile}' to '{ToFile}'", fromFile, toFile);
await SetupChanges(changeSet, ctx);
}

Expand All @@ -49,7 +49,7 @@ private async Task SetupChanges(ChangeSet changeSet, Cancel ctx)

var sourceContent = await readFileSystem.File.ReadAllTextAsync(sourcePath, ctx);

var markdownLinkRegex = new Regex(@"\[([^\]]*)\]\(((?:\.{0,2}\/)?[^:)]+\.md(?:#[^)]*)?)\)", RegexOptions.Compiled);
var markdownLinkRegex = MarkdownLinkRegex();

var change = Regex.Replace(sourceContent, markdownLinkRegex.ToString(), match =>
{
Expand All @@ -64,13 +64,12 @@ private async Task SetupChanges(ChangeSet changeSet, Cancel ctx)
var fullPath = Path.GetFullPath(Path.Combine(sourceDirectory, originalPath));
var relativePath = Path.GetRelativePath(targetDirectory, fullPath);

if (originalPath.StartsWith("./") && !relativePath.StartsWith("./"))
newPath = "./" + relativePath;
else
newPath = relativePath;
newPath = originalPath.StartsWith("./", OrdinalIgnoreCase) && !relativePath.StartsWith("./", OrdinalIgnoreCase)
? "./" + relativePath
: relativePath;
}
var newLink = $"[{match.Groups[1].Value}]({newPath})";
var lineNumber = sourceContent.Substring(0, match.Index).Count(c => c == '\n') + 1;
var lineNumber = sourceContent[..match.Index].Count(c => c == '\n') + 1;
var columnNumber = match.Index - sourceContent.LastIndexOf('\n', match.Index);
if (!_linkModifications.ContainsKey(changeSet))
_linkModifications[changeSet] = [];
Expand Down Expand Up @@ -104,14 +103,14 @@ private async Task<int> MoveAndRewriteLinks(bool isDryRun, Cancel ctx)
{
foreach (var (oldLink, newLink, sourceFile, lineNumber, columnNumber) in linkModifications)
{
_logger.LogInformation(string.Format(
ChangeFormatString,
_logger.LogInformation(
"Change \e[31m{OldLink}\e[0m to \e[32m{NewLink}\e[0m at \e[34m{SourceFile}:{LineNumber}:{Column}\e[0m",
oldLink,
newLink,
sourceFile == changeSet.From.FullName && !isDryRun ? changeSet.To.FullName : sourceFile,
lineNumber,
columnNumber
));
);
}
}

Expand All @@ -125,13 +124,13 @@ private async Task<int> MoveAndRewriteLinks(bool isDryRun, Cancel ctx)
foreach (var (filePath, _, newContent) in changes)
{
if (!filePath.Directory!.Exists)
writeFileSystem.Directory.CreateDirectory(filePath.Directory.FullName);
_ = writeFileSystem.Directory.CreateDirectory(filePath.Directory.FullName);
await writeFileSystem.File.WriteAllTextAsync(filePath.FullName, newContent, ctx);

}

var targetDirectory = Path.GetDirectoryName(changeSet.To.FullName);
readFileSystem.Directory.CreateDirectory(targetDirectory!);
_ = readFileSystem.Directory.CreateDirectory(targetDirectory!);
readFileSystem.File.Move(changeSet.From.FullName, changeSet.To.FullName);
}
}
Expand Down Expand Up @@ -172,15 +171,16 @@ private bool ValidateInputs(string source, string target, out IFileInfo[] fromFi
//from does not exist at all
if (!fromFile.Exists && !fromDirectory.Exists)
{
_logger.LogError(!string.IsNullOrEmpty(fromFile.Extension)
? $"Source file '{fromFile}' does not exist"
: $"Source directory '{fromDirectory}' does not exist");
if (!string.IsNullOrEmpty(fromFile.Extension))
_logger.LogError("Source file '{File}' does not exist", fromFile);
else
_logger.LogError("Source directory '{Directory}' does not exist", fromDirectory);
return false;
}
//moving file
if (fromFile.Exists)
{
if (!fromFile.Extension.Equals(".md", StringComparison.OrdinalIgnoreCase))
if (!fromFile.Extension.Equals(".md", OrdinalIgnoreCase))
{
_logger.LogError("Source path must be a markdown file. Directory paths are not supported yet");
return false;
Expand All @@ -190,14 +190,14 @@ private bool ValidateInputs(string source, string target, out IFileInfo[] fromFi
if (toFile.Extension == string.Empty)
toFile = readFileSystem.FileInfo.New(Path.Combine(toDirectory.FullName, fromFile.Name));

if (!toFile.Extension.Equals(".md", StringComparison.OrdinalIgnoreCase))
if (!toFile.Extension.Equals(".md", OrdinalIgnoreCase))
{
_logger.LogError($"Target path '{toFile.FullName}' must be a markdown file.");
_logger.LogError("Target path '{FullName}' must be a markdown file.", toFile.FullName);
return false;
}
if (toFile.Exists)
{
_logger.LogError($"Target file {target} already exists");
_logger.LogError("Target file {Target} already exists", target);
return false;
}
fromFiles = [fromFile];
Expand All @@ -208,22 +208,22 @@ private bool ValidateInputs(string source, string target, out IFileInfo[] fromFi
{
if (toDirectory.Exists)
{
_logger.LogError($"Target directory '{toDirectory.FullName}' already exists.");
_logger.LogError("Target directory '{FullName}' already exists.", toDirectory.FullName);
return false;
}

if (toDirectory.FullName.StartsWith(fromDirectory.FullName))
if (toDirectory.FullName.StartsWith(fromDirectory.FullName, OrdinalIgnoreCase))
{
_logger.LogError($"Can not move source directory '{toDirectory.FullName}' to a {toFile.FullName}");
_logger.LogError("Can not move source directory '{SourceDirectory}' to a '{TargetFile}'", toDirectory.FullName, toFile.FullName);
return false;
}

fromFiles = fromDirectory.GetFiles("*.md", SearchOption.AllDirectories);
toFiles = fromFiles.Select(f =>
toFiles = [.. fromFiles.Select(f =>
{
var relative = Path.GetRelativePath(fromDirectory.FullName, f.FullName);
return readFileSystem.FileInfo.New(Path.Combine(toDirectory.FullName, relative));
}).ToArray();
})];
}

return true;
Expand Down Expand Up @@ -293,12 +293,12 @@ private string ReplaceLinks(
else
{
var relativeTarget = Path.GetRelativePath(Path.GetDirectoryName(value.FilePath)!, target);
newLink = originalPath.StartsWith("./") && !relativeTarget.StartsWith("./")
newLink = originalPath.StartsWith("./", OrdinalIgnoreCase) && !relativeTarget.StartsWith("./", OrdinalIgnoreCase)
? $"[{match.Groups[1].Value}](./{relativeTarget}{anchor})"
: $"[{match.Groups[1].Value}]({relativeTarget}{anchor})";
}

var lineNumber = content.Substring(0, match.Index).Count(c => c == '\n') + 1;
var lineNumber = content[..match.Index].Count(c => c == '\n') + 1;
var columnNumber = match.Index - content.LastIndexOf('\n', match.Index);
if (!_linkModifications.ContainsKey(changeSet))
_linkModifications[changeSet] = [];
Expand All @@ -311,4 +311,7 @@ private string ReplaceLinks(
));
return newLink;
});

[GeneratedRegex(@"\[([^\]]*)\]\(((?:\.{0,2}\/)?[^:)]+\.md(?:#[^)]*)?)\)", RegexOptions.Compiled)]
private static partial Regex MarkdownLinkRegex();
}
8 changes: 4 additions & 4 deletions src/Elastic.Markdown/CrossLinks/CrossLinkResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public class CrossLinkResolver(ConfigurationFile configuration, ILoggerFactory l
private readonly string[] _links = configuration.CrossLinkRepositories;
private FrozenDictionary<string, LinkReference> _linkReferences = new Dictionary<string, LinkReference>().ToFrozenDictionary();
private readonly ILogger _logger = logger.CreateLogger(nameof(CrossLinkResolver));
private readonly HashSet<string> _declaredRepositories = new();
private readonly HashSet<string> _declaredRepositories = [];

public static LinkReference Deserialize(string json) =>
JsonSerializer.Deserialize(json, SourceGenerationContext.Default.LinkReference)!;
Expand All @@ -62,11 +62,11 @@ public async Task FetchLinks()
var dictionary = new Dictionary<string, LinkReference>();
foreach (var link in _links)
{
_declaredRepositories.Add(link);
_ = _declaredRepositories.Add(link);
try
{
var url = $"https://elastic-docs-link-index.s3.us-east-2.amazonaws.com/elastic/{link}/main/links.json";
_logger.LogInformation($"Fetching {url}");
_logger.LogInformation("Fetching {Url}", url);
var json = await client.GetStringAsync(url);
var linkReference = Deserialize(json);
dictionary.Add(link, linkReference);
Expand Down Expand Up @@ -172,7 +172,7 @@ private static string ToTargetUrlPath(string lookupPath)
//https://docs-v3-preview.elastic.dev/elastic/docs-content/tree/main/cloud-account/change-your-password
var path = lookupPath.Replace(".md", "");
if (path.EndsWith("/index"))
path = path.Substring(0, path.Length - 6);
path = path[..^6];
if (path == "index")
path = string.Empty;
return path;
Expand Down
28 changes: 15 additions & 13 deletions src/Elastic.Markdown/Diagnostics/DiagnosticsChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@

namespace Elastic.Markdown.Diagnostics;

public class DiagnosticsChannel
public sealed class DiagnosticsChannel : IDisposable
{
private readonly Channel<Diagnostic> _channel;
private readonly CancellationTokenSource _ctxSource;
public ChannelReader<Diagnostic> Reader => _channel.Reader;

public CancellationToken CancellationToken => _ctxSource.Token;
public Cancel CancellationToken => _ctxSource.Token;

public DiagnosticsChannel()
{
Expand All @@ -25,11 +25,11 @@ public DiagnosticsChannel()

public void TryComplete(Exception? exception = null)
{
_channel.Writer.TryComplete(exception);
_ = _channel.Writer.TryComplete(exception);
_ctxSource.Cancel();
}

public ValueTask<bool> WaitToWrite() => _channel.Writer.WaitToWriteAsync();
public ValueTask<bool> WaitToWrite(Cancel ctx) => _channel.Writer.WaitToWriteAsync(ctx);

public void Write(Diagnostic diagnostic)
{
Expand All @@ -39,6 +39,8 @@ public void Write(Diagnostic diagnostic)
//TODO
}
}

public void Dispose() => _ctxSource.Dispose();
}

public enum Severity { Error, Warning }
Expand Down Expand Up @@ -70,17 +72,17 @@ public class DiagnosticsCollector(IReadOnlyCollection<IDiagnosticsOutput> output

private Task? _started;

public HashSet<string> OffendingFiles { get; } = new();
public HashSet<string> OffendingFiles { get; } = [];

public ConcurrentBag<string> CrossLinks { get; } = new();
public ConcurrentBag<string> CrossLinks { get; } = [];

public Task StartAsync(Cancel ctx)
public Task StartAsync(Cancel cancellationToken)
{
if (_started is not null)
return _started;
_started = Task.Run(async () =>
{
await Channel.WaitToWrite();
_ = await Channel.WaitToWrite(cancellationToken);
while (!Channel.CancellationToken.IsCancellationRequested)
{
try
Expand All @@ -95,7 +97,7 @@ public Task StartAsync(Cancel ctx)
}

Drain();
}, ctx);
}, cancellationToken);
return _started;

void Drain()
Expand All @@ -104,7 +106,7 @@ void Drain()
{
IncrementSeverityCount(item);
HandleItem(item);
OffendingFiles.Add(item.File);
_ = OffendingFiles.Add(item.File);
foreach (var output in outputs)
output.Write(item);
}
Expand All @@ -114,14 +116,14 @@ void Drain()
private void IncrementSeverityCount(Diagnostic item)
{
if (item.Severity == Severity.Error)
Interlocked.Increment(ref _errors);
_ = Interlocked.Increment(ref _errors);
else if (item.Severity == Severity.Warning)
Interlocked.Increment(ref _warnings);
_ = Interlocked.Increment(ref _warnings);
}

protected virtual void HandleItem(Diagnostic diagnostic) { }

public virtual async Task StopAsync(CancellationToken cancellationToken)
public virtual async Task StopAsync(Cancel cancellationToken)
{
if (_started is not null)
await _started;
Expand Down
Loading