diff --git a/.editorconfig b/.editorconfig index a0460aa..0676743 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,6 +10,11 @@ csharp_style_namespace_declarations = file_scoped:warning # CA2007 (missing ConfigureAwait) is not actionable here — suppress app-wide. dotnet_diagnostic.CA2007.severity = none +# CA1716 is a cross-language (VB/C# interop) naming rule with no value for this C#-only app: +# interface members Get/Stop/Operator and parameters set/to are idiomatic here, and the +# LageBuch.App.Shared namespace root cannot be sanely renamed. Suppress app-wide. +dotnet_diagnostic.CA1716.severity = none + [tests/**/*.cs] # xUnit method names with underscores are idiomatic. dotnet_diagnostic.CA1707.severity = none diff --git a/src/LageBuch.App.Shared/App.axaml.cs b/src/LageBuch.App.Shared/App.axaml.cs index 85ebe38..e113673 100644 --- a/src/LageBuch.App.Shared/App.axaml.cs +++ b/src/LageBuch.App.Shared/App.axaml.cs @@ -3,9 +3,11 @@ using Avalonia.Markup.Xaml; using LageBuch.App.Shared.Views; using LageBuch.AppLogic.ViewModels; +using System.Diagnostics.CodeAnalysis; namespace LageBuch.App.Shared; +[SuppressMessage("Naming", "CA1724", Justification = "The Avalonia App class sits in the LageBuch.App.Shared namespace by design; the collision with the LageBuch.App namespace is inherent to the app's name.")] public partial class App : Application { /// diff --git a/src/LageBuch.App/AppPaths.cs b/src/LageBuch.App/AppPaths.cs index 5195702..a1cb0af 100644 --- a/src/LageBuch.App/AppPaths.cs +++ b/src/LageBuch.App/AppPaths.cs @@ -2,16 +2,16 @@ namespace LageBuch.App; internal static class AppPaths { - public static string AppDataDir => + public static string Root => GetAppDataDir(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)); - public static string MasterDataDbPath => Path.Combine(AppDataDir, "masterdata.db"); + public static string MasterDataDbPath => Path.Combine(Root, "masterdata.db"); - public static string RecentFilesJsonPath => Path.Combine(AppDataDir, "recent.json"); + public static string RecentFilesJsonPath => Path.Combine(Root, "recent.json"); - public static string LastSaveFolderJsonPath => Path.Combine(AppDataDir, "last-save-folder.json"); + public static string LastSaveFolderJsonPath => Path.Combine(Root, "last-save-folder.json"); - public static string AttachmentCacheDir => Path.Combine(AppDataDir, "attachment-cache"); + public static string AttachmentCacheDir => Path.Combine(Root, "attachment-cache"); public static string GetAppDataDir(string baseDir) { diff --git a/src/LageBuch.AppLogic/Services/IFileDialogService.cs b/src/LageBuch.AppLogic/Services/IFileDialogService.cs index 8d79b09..aa2f86a 100644 --- a/src/LageBuch.AppLogic/Services/IFileDialogService.cs +++ b/src/LageBuch.AppLogic/Services/IFileDialogService.cs @@ -1,3 +1,5 @@ +using System.Diagnostics.CodeAnalysis; + namespace LageBuch.AppLogic.Services; public interface IFileDialogService @@ -25,6 +27,7 @@ public interface IFileDialogService Task OpenFileAsync(string path); /// Opens an http(s) URL in the OS's default browser. + [SuppressMessage("Design", "CA1054", Justification = "URLs are free-form launch strings end-to-end (persisted master data, test data); System.Uri would reject non-parseable values and force churn in every caller.")] Task OpenUrlAsync(string url); /// diff --git a/src/LageBuch.AppLogic/ViewModels/AboutViewModel.cs b/src/LageBuch.AppLogic/ViewModels/AboutViewModel.cs index 949319a..aa1d33a 100644 --- a/src/LageBuch.AppLogic/ViewModels/AboutViewModel.cs +++ b/src/LageBuch.AppLogic/ViewModels/AboutViewModel.cs @@ -28,6 +28,7 @@ public AboutViewModel(IFileDialogService dialogs, string version) [SuppressMessage("Performance", "CA1822", Justification = "XAML {Binding} target in AboutView; binding requires an instance property.")] public string Descriptor => "Einsatzdokumentation"; public string Version { get; } +[SuppressMessage("Design", "CA1056", Justification = "RepositoryUrl is a display/launch string handed to IFileDialogService.OpenUrlAsync; System.Uri would add parse/validation behavior with no benefit here.")] [SuppressMessage("Performance", "CA1822", Justification = "XAML {Binding} target in AboutView; binding requires an instance property.")] public string RepositoryUrl => RepoUrl; diff --git a/src/LageBuch.AppLogic/ViewModels/LinkRow.cs b/src/LageBuch.AppLogic/ViewModels/LinkRow.cs index 1c4c213..f5bd766 100644 --- a/src/LageBuch.AppLogic/ViewModels/LinkRow.cs +++ b/src/LageBuch.AppLogic/ViewModels/LinkRow.cs @@ -1,4 +1,5 @@ using CommunityToolkit.Mvvm.ComponentModel; +using System.Diagnostics.CodeAnalysis; namespace LageBuch.AppLogic.ViewModels; @@ -7,6 +8,7 @@ public sealed partial class LinkRow : ObservableObject { private readonly Action _onChanged; + [SuppressMessage("Design", "CA1054", Justification = "URLs are free-form display strings in the domain; System.Uri would reject non-parseable values like relay links.")] public LinkRow(string name, string url, Action onChanged) { _onChanged = onChanged; diff --git a/src/LageBuch.Documents/IncidentReportDocument.cs b/src/LageBuch.Documents/IncidentReportDocument.cs index c497c3a..ae0fb71 100644 --- a/src/LageBuch.Documents/IncidentReportDocument.cs +++ b/src/LageBuch.Documents/IncidentReportDocument.cs @@ -30,11 +30,11 @@ public IncidentReportDocument(Incident incident, IReadOnlyDictionary DocumentMetadata.Default; - public void Compose(IDocumentContainer document) + public void Compose(IDocumentContainer container) { PdfLicense.Ensure(); - document.Page(page => + container.Page(page => { page.Size(PageSizes.A4); page.Margin(1.5f, Unit.Centimetre); diff --git a/src/LageBuch.Persistence/MasterData/MasterDataSet.cs b/src/LageBuch.Persistence/MasterData/MasterDataSet.cs index 124ba2b..275ce76 100644 --- a/src/LageBuch.Persistence/MasterData/MasterDataSet.cs +++ b/src/LageBuch.Persistence/MasterData/MasterDataSet.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Text.Encodings.Web; using System.Text.Json; using LageBuch.Domain.Atemschutz; @@ -7,6 +8,8 @@ namespace LageBuch.Persistence.MasterData; public sealed record Street(string Name, string District); /// A named link — Stammdaten entry so useful external resources can be opened from an Einsatz. +[SuppressMessage("Design", "CA1054", Justification = "Link URLs are free-form display data in persisted master data; System.Uri would make non-parseable values (relay or relative links) fail to load.")] +[SuppressMessage("Design", "CA1056", Justification = "Link URLs are free-form display data in persisted master data; System.Uri would make non-parseable values (relay or relative links) fail to load.")] public sealed record Link(string Name, string Url); /// One Checkliste template entry — the Stammdaten-editable source an incident's Aufbau/Abbau diff --git a/src/LageBuch.Sync/IIncidentSession.cs b/src/LageBuch.Sync/IIncidentSession.cs index cc333f5..715256b 100644 --- a/src/LageBuch.Sync/IIncidentSession.cs +++ b/src/LageBuch.Sync/IIncidentSession.cs @@ -4,6 +4,7 @@ using LageBuch.Domain.Etb; using LageBuch.Domain.Tasks; using LageBuch.Domain.ValueObjects; +using System.Diagnostics.CodeAnalysis; namespace LageBuch.Sync; @@ -35,6 +36,7 @@ public interface IIncidentSession bool IsRemote { get; } /// Raised after the incident state changes (a local mutation, or a host broadcast). + [SuppressMessage("Design", "CA1003", Justification = "In-process fire-and-forget event with C#-only subscribers; Action matches the pervasive Action event convention (GoHomeRequested etc.).")] event Action? Changed; void AddJournalEntry(EtbDirection direction, string text, string? from = null, string? to = null); diff --git a/src/LageBuch.Sync/RemoteIncidentSession.cs b/src/LageBuch.Sync/RemoteIncidentSession.cs index d9ced75..d26c502 100644 --- a/src/LageBuch.Sync/RemoteIncidentSession.cs +++ b/src/LageBuch.Sync/RemoteIncidentSession.cs @@ -1,6 +1,7 @@ using System.Net; using System.Text; using System.Text.Json.Serialization; +using System.Diagnostics.CodeAnalysis; using LageBuch.Domain; using LageBuch.Domain.Atemschutz; using LageBuch.Domain.CoMeasurement; @@ -39,6 +40,7 @@ public sealed class RemoteIncidentSession : IIncidentSession, IAsyncDisposable public bool IsRemote => true; /// Raised after the cached incident is replaced by a host broadcast (or a resync). + [SuppressMessage("Design", "CA1003", Justification = "In-process fire-and-forget event with C#-only subscribers; see IIncidentSession.Changed.")] public event Action? Changed; /// @@ -46,15 +48,18 @@ public sealed class RemoteIncidentSession : IIncidentSession, IAsyncDisposable /// disable input and show "Verbindung getrennt — verbinde neu…". A successful retry raises /// ; giving up raises . /// + [SuppressMessage("Design", "CA1003", Justification = "In-process fire-and-forget event with C#-only subscribers; see IIncidentSession.Changed.")] public event Action? Disconnected; /// Raised after a reconnect + full resync — the UI can re-enable input. + [SuppressMessage("Design", "CA1003", Justification = "In-process fire-and-forget event with C#-only subscribers; see IIncidentSession.Changed.")] public event Action? Reconnected; /// /// Raised when the connection is gone for good — reconnect attempts were exhausted or the host /// stopped sharing. The UI returns to Home (§7); nothing further arrives on this session. /// + [SuppressMessage("Design", "CA1003", Justification = "In-process fire-and-forget event with C#-only subscribers; see IIncidentSession.Changed.")] public event Action? Ended; private RemoteIncidentSession(HttpClient http, HubConnection hub, IUiDispatcher ui, SessionOperator op, Incident initial, string? cacheRoot) diff --git a/tests/LageBuch.AppLogic.Tests/LinksViewModelTests.cs b/tests/LageBuch.AppLogic.Tests/LinksViewModelTests.cs index 8f423ac..de82008 100644 --- a/tests/LageBuch.AppLogic.Tests/LinksViewModelTests.cs +++ b/tests/LageBuch.AppLogic.Tests/LinksViewModelTests.cs @@ -1,6 +1,7 @@ using LageBuch.AppLogic.Services; using LageBuch.AppLogic.ViewModels; using LageBuch.Persistence.MasterData; +using System.Diagnostics.CodeAnalysis; namespace LageBuch.AppLogic.Tests; @@ -52,6 +53,7 @@ public async Task OpenAsync_prepends_https_to_a_bare_domain() [InlineData("file:///etc/passwd")] [InlineData("javascript:alert(1)")] [InlineData("ftp://example.org")] + [SuppressMessage("Design", "CA1054", Justification = "Test exercises links with free-form (even hostile) URL strings — that is the point of the test.")] public async Task OpenAsync_refuses_a_non_http_scheme(string url) { var dialogs = new FakeDialogs();