Skip to content

Commit 7e8b0e9

Browse files
authored
Github annotations for roslyn analyzers (#539)
* Github annotations for roslyn analyzers * attempt to fix escaping * attempt to fix escaping again * make all analyzer diagnostics warnings * Address all reslyn analyzer and formatting errors * add rider anaylzer problems * remove separate CI step, `lint` will report invalidations as github annotations * remove separate CI step, `lint` will report invalidations as github annotations * EnforceCodeStyleInBuild everywhere to catch all lint errors in the IDE * Ensure warnings is equatable so it participates in the structs equality (will be zero'd out straight after deserialization) * fix cancellation token sharing
1 parent 45e33d3 commit 7e8b0e9

File tree

70 files changed

+539
-506
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+539
-506
lines changed

.editorconfig

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ csharp_style_expression_bodied_operators = true:suggestion
143143
csharp_style_expression_bodied_properties = true:suggestion
144144
csharp_style_expression_bodied_indexers = true:suggestion
145145
csharp_style_expression_bodied_accessors = true:suggestion
146+
csharp_style_expression_bodied_local_functions = when_on_single_line:error
147+
dotnet_style_prefer_conditional_expression_over_return = false
148+
146149

147150
# Suggest more modern language features when available
148151
csharp_style_pattern_matching_over_is_with_cast_check = true:error
@@ -197,12 +200,37 @@ resharper_redundant_case_label_highlighting=do_not_show
197200
resharper_redundant_argument_default_value_highlighting=do_not_show
198201
resharper_explicit_caller_info_argument_highlighting=hint
199202

203+
csharp_style_namespace_declarations = file_scoped
204+
205+
dotnet_analyzer_diagnostic.severity = warning
206+
dotnet_analyzer_diagnostic.category-Style.severity = warning
207+
208+
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1848
209+
dotnet_diagnostic.CA1848.severity = suggestion
210+
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2201
211+
dotnet_diagnostic.CA2201.severity = none
212+
213+
# disable for default arm on switches, IDE0072 still covers missing enum members
214+
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0010
215+
dotnet_diagnostic.IDE0010.severity = none
216+
217+
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/IDE0200
218+
dotnet_diagnostic.IDE0200.severity = none
219+
220+
# TODO enable GenerateDocumentationFile to properly document our code
221+
dotnet_diagnostic.IDE0005.severity = none
222+
223+
dotnet_diagnostic.IDE0001.severity = none
224+
200225
dotnet_diagnostic.IDE0057.severity = none
201226

202227
[DocumentationWebHost.cs]
203228
dotnet_diagnostic.IL3050.severity = none
204229
dotnet_diagnostic.IL2026.severity = none
205230

231+
[tests/**/*.cs]
232+
dotnet_diagnostic.IDE0058.severity = none
233+
206234

207235
[*.{sh,bat,ps1}]
208236
trim_trailing_whitespace=true

Directory.Build.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
<ImplicitUsings>enable</ImplicitUsings>
2323
<Nullable>enable</Nullable>
24+
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
2425
</PropertyGroup>
2526

2627
<ItemGroup>

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
<NuGetAuditMode>all</NuGetAuditMode>
77
<NuGetAuditLevel>low</NuGetAuditLevel>
88
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
9-
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
109

10+
<!-- TODO ENABLE to document our code properly <GenerateDocumentationFile>true</GenerateDocumentationFile> -->
1111
<PublishRepositoryUrl>true</PublishRepositoryUrl>
1212
</PropertyGroup>
1313

src/Elastic.Markdown.Refactor/Move.cs

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@
66
using System.Text.RegularExpressions;
77
using Elastic.Markdown.IO;
88
using Microsoft.Extensions.Logging;
9+
using static System.StringComparison;
910

1011
namespace Elastic.Markdown.Refactor;
1112

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

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

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

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

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

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

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

67-
if (originalPath.StartsWith("./") && !relativePath.StartsWith("./"))
68-
newPath = "./" + relativePath;
69-
else
70-
newPath = relativePath;
67+
newPath = originalPath.StartsWith("./", OrdinalIgnoreCase) && !relativePath.StartsWith("./", OrdinalIgnoreCase)
68+
? "./" + relativePath
69+
: relativePath;
7170
}
7271
var newLink = $"[{match.Groups[1].Value}]({newPath})";
73-
var lineNumber = sourceContent.Substring(0, match.Index).Count(c => c == '\n') + 1;
72+
var lineNumber = sourceContent[..match.Index].Count(c => c == '\n') + 1;
7473
var columnNumber = match.Index - sourceContent.LastIndexOf('\n', match.Index);
7574
if (!_linkModifications.ContainsKey(changeSet))
7675
_linkModifications[changeSet] = [];
@@ -104,14 +103,14 @@ private async Task<int> MoveAndRewriteLinks(bool isDryRun, Cancel ctx)
104103
{
105104
foreach (var (oldLink, newLink, sourceFile, lineNumber, columnNumber) in linkModifications)
106105
{
107-
_logger.LogInformation(string.Format(
108-
ChangeFormatString,
106+
_logger.LogInformation(
107+
"Change \e[31m{OldLink}\e[0m to \e[32m{NewLink}\e[0m at \e[34m{SourceFile}:{LineNumber}:{Column}\e[0m",
109108
oldLink,
110109
newLink,
111110
sourceFile == changeSet.From.FullName && !isDryRun ? changeSet.To.FullName : sourceFile,
112111
lineNumber,
113112
columnNumber
114-
));
113+
);
115114
}
116115
}
117116

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

131130
}
132131

133132
var targetDirectory = Path.GetDirectoryName(changeSet.To.FullName);
134-
readFileSystem.Directory.CreateDirectory(targetDirectory!);
133+
_ = readFileSystem.Directory.CreateDirectory(targetDirectory!);
135134
readFileSystem.File.Move(changeSet.From.FullName, changeSet.To.FullName);
136135
}
137136
}
@@ -172,15 +171,16 @@ private bool ValidateInputs(string source, string target, out IFileInfo[] fromFi
172171
//from does not exist at all
173172
if (!fromFile.Exists && !fromDirectory.Exists)
174173
{
175-
_logger.LogError(!string.IsNullOrEmpty(fromFile.Extension)
176-
? $"Source file '{fromFile}' does not exist"
177-
: $"Source directory '{fromDirectory}' does not exist");
174+
if (!string.IsNullOrEmpty(fromFile.Extension))
175+
_logger.LogError("Source file '{File}' does not exist", fromFile);
176+
else
177+
_logger.LogError("Source directory '{Directory}' does not exist", fromDirectory);
178178
return false;
179179
}
180180
//moving file
181181
if (fromFile.Exists)
182182
{
183-
if (!fromFile.Extension.Equals(".md", StringComparison.OrdinalIgnoreCase))
183+
if (!fromFile.Extension.Equals(".md", OrdinalIgnoreCase))
184184
{
185185
_logger.LogError("Source path must be a markdown file. Directory paths are not supported yet");
186186
return false;
@@ -190,14 +190,14 @@ private bool ValidateInputs(string source, string target, out IFileInfo[] fromFi
190190
if (toFile.Extension == string.Empty)
191191
toFile = readFileSystem.FileInfo.New(Path.Combine(toDirectory.FullName, fromFile.Name));
192192

193-
if (!toFile.Extension.Equals(".md", StringComparison.OrdinalIgnoreCase))
193+
if (!toFile.Extension.Equals(".md", OrdinalIgnoreCase))
194194
{
195-
_logger.LogError($"Target path '{toFile.FullName}' must be a markdown file.");
195+
_logger.LogError("Target path '{FullName}' must be a markdown file.", toFile.FullName);
196196
return false;
197197
}
198198
if (toFile.Exists)
199199
{
200-
_logger.LogError($"Target file {target} already exists");
200+
_logger.LogError("Target file {Target} already exists", target);
201201
return false;
202202
}
203203
fromFiles = [fromFile];
@@ -208,22 +208,22 @@ private bool ValidateInputs(string source, string target, out IFileInfo[] fromFi
208208
{
209209
if (toDirectory.Exists)
210210
{
211-
_logger.LogError($"Target directory '{toDirectory.FullName}' already exists.");
211+
_logger.LogError("Target directory '{FullName}' already exists.", toDirectory.FullName);
212212
return false;
213213
}
214214

215-
if (toDirectory.FullName.StartsWith(fromDirectory.FullName))
215+
if (toDirectory.FullName.StartsWith(fromDirectory.FullName, OrdinalIgnoreCase))
216216
{
217-
_logger.LogError($"Can not move source directory '{toDirectory.FullName}' to a {toFile.FullName}");
217+
_logger.LogError("Can not move source directory '{SourceDirectory}' to a '{TargetFile}'", toDirectory.FullName, toFile.FullName);
218218
return false;
219219
}
220220

221221
fromFiles = fromDirectory.GetFiles("*.md", SearchOption.AllDirectories);
222-
toFiles = fromFiles.Select(f =>
222+
toFiles = [.. fromFiles.Select(f =>
223223
{
224224
var relative = Path.GetRelativePath(fromDirectory.FullName, f.FullName);
225225
return readFileSystem.FileInfo.New(Path.Combine(toDirectory.FullName, relative));
226-
}).ToArray();
226+
})];
227227
}
228228

229229
return true;
@@ -293,12 +293,12 @@ private string ReplaceLinks(
293293
else
294294
{
295295
var relativeTarget = Path.GetRelativePath(Path.GetDirectoryName(value.FilePath)!, target);
296-
newLink = originalPath.StartsWith("./") && !relativeTarget.StartsWith("./")
296+
newLink = originalPath.StartsWith("./", OrdinalIgnoreCase) && !relativeTarget.StartsWith("./", OrdinalIgnoreCase)
297297
? $"[{match.Groups[1].Value}](./{relativeTarget}{anchor})"
298298
: $"[{match.Groups[1].Value}]({relativeTarget}{anchor})";
299299
}
300300

301-
var lineNumber = content.Substring(0, match.Index).Count(c => c == '\n') + 1;
301+
var lineNumber = content[..match.Index].Count(c => c == '\n') + 1;
302302
var columnNumber = match.Index - content.LastIndexOf('\n', match.Index);
303303
if (!_linkModifications.ContainsKey(changeSet))
304304
_linkModifications[changeSet] = [];
@@ -311,4 +311,7 @@ private string ReplaceLinks(
311311
));
312312
return newLink;
313313
});
314+
315+
[GeneratedRegex(@"\[([^\]]*)\]\(((?:\.{0,2}\/)?[^:)]+\.md(?:#[^)]*)?)\)", RegexOptions.Compiled)]
316+
private static partial Regex MarkdownLinkRegex();
314317
}

src/Elastic.Markdown/CrossLinks/CrossLinkResolver.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public class CrossLinkResolver(ConfigurationFile configuration, ILoggerFactory l
5151
private readonly string[] _links = configuration.CrossLinkRepositories;
5252
private FrozenDictionary<string, LinkReference> _linkReferences = new Dictionary<string, LinkReference>().ToFrozenDictionary();
5353
private readonly ILogger _logger = logger.CreateLogger(nameof(CrossLinkResolver));
54-
private readonly HashSet<string> _declaredRepositories = new();
54+
private readonly HashSet<string> _declaredRepositories = [];
5555

5656
public static LinkReference Deserialize(string json) =>
5757
JsonSerializer.Deserialize(json, SourceGenerationContext.Default.LinkReference)!;
@@ -62,11 +62,11 @@ public async Task FetchLinks()
6262
var dictionary = new Dictionary<string, LinkReference>();
6363
foreach (var link in _links)
6464
{
65-
_declaredRepositories.Add(link);
65+
_ = _declaredRepositories.Add(link);
6666
try
6767
{
6868
var url = $"https://elastic-docs-link-index.s3.us-east-2.amazonaws.com/elastic/{link}/main/links.json";
69-
_logger.LogInformation($"Fetching {url}");
69+
_logger.LogInformation("Fetching {Url}", url);
7070
var json = await client.GetStringAsync(url);
7171
var linkReference = Deserialize(json);
7272
dictionary.Add(link, linkReference);
@@ -172,7 +172,7 @@ private static string ToTargetUrlPath(string lookupPath)
172172
//https://docs-v3-preview.elastic.dev/elastic/docs-content/tree/main/cloud-account/change-your-password
173173
var path = lookupPath.Replace(".md", "");
174174
if (path.EndsWith("/index"))
175-
path = path.Substring(0, path.Length - 6);
175+
path = path[..^6];
176176
if (path == "index")
177177
path = string.Empty;
178178
return path;

src/Elastic.Markdown/Diagnostics/DiagnosticsChannel.cs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88

99
namespace Elastic.Markdown.Diagnostics;
1010

11-
public class DiagnosticsChannel
11+
public sealed class DiagnosticsChannel : IDisposable
1212
{
1313
private readonly Channel<Diagnostic> _channel;
1414
private readonly CancellationTokenSource _ctxSource;
1515
public ChannelReader<Diagnostic> Reader => _channel.Reader;
1616

17-
public CancellationToken CancellationToken => _ctxSource.Token;
17+
public Cancel CancellationToken => _ctxSource.Token;
1818

1919
public DiagnosticsChannel()
2020
{
@@ -25,11 +25,11 @@ public DiagnosticsChannel()
2525

2626
public void TryComplete(Exception? exception = null)
2727
{
28-
_channel.Writer.TryComplete(exception);
28+
_ = _channel.Writer.TryComplete(exception);
2929
_ctxSource.Cancel();
3030
}
3131

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

3434
public void Write(Diagnostic diagnostic)
3535
{
@@ -39,6 +39,8 @@ public void Write(Diagnostic diagnostic)
3939
//TODO
4040
}
4141
}
42+
43+
public void Dispose() => _ctxSource.Dispose();
4244
}
4345

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

7173
private Task? _started;
7274

73-
public HashSet<string> OffendingFiles { get; } = new();
75+
public HashSet<string> OffendingFiles { get; } = [];
7476

75-
public ConcurrentBag<string> CrossLinks { get; } = new();
77+
public ConcurrentBag<string> CrossLinks { get; } = [];
7678

77-
public Task StartAsync(Cancel ctx)
79+
public Task StartAsync(Cancel cancellationToken)
7880
{
7981
if (_started is not null)
8082
return _started;
8183
_started = Task.Run(async () =>
8284
{
83-
await Channel.WaitToWrite();
85+
_ = await Channel.WaitToWrite(cancellationToken);
8486
while (!Channel.CancellationToken.IsCancellationRequested)
8587
{
8688
try
@@ -95,7 +97,7 @@ public Task StartAsync(Cancel ctx)
9597
}
9698

9799
Drain();
98-
}, ctx);
100+
}, cancellationToken);
99101
return _started;
100102

101103
void Drain()
@@ -104,7 +106,7 @@ void Drain()
104106
{
105107
IncrementSeverityCount(item);
106108
HandleItem(item);
107-
OffendingFiles.Add(item.File);
109+
_ = OffendingFiles.Add(item.File);
108110
foreach (var output in outputs)
109111
output.Write(item);
110112
}
@@ -114,14 +116,14 @@ void Drain()
114116
private void IncrementSeverityCount(Diagnostic item)
115117
{
116118
if (item.Severity == Severity.Error)
117-
Interlocked.Increment(ref _errors);
119+
_ = Interlocked.Increment(ref _errors);
118120
else if (item.Severity == Severity.Warning)
119-
Interlocked.Increment(ref _warnings);
121+
_ = Interlocked.Increment(ref _warnings);
120122
}
121123

122124
protected virtual void HandleItem(Diagnostic diagnostic) { }
123125

124-
public virtual async Task StopAsync(CancellationToken cancellationToken)
126+
public virtual async Task StopAsync(Cancel cancellationToken)
125127
{
126128
if (_started is not null)
127129
await _started;

0 commit comments

Comments
 (0)