diff --git a/.editorconfig b/.editorconfig
index 241b4c6a8..cb796a5e9 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -14,6 +14,9 @@ indent_size = 4
insert_final_newline = true
[*.cs]
+// TODO comments should have an associated issue URL
+dotnet_diagnostic.TODO001.issueRegex = https://github.com/digdir/dialogporten/issues/\d+
+
# Attributes should be on a line above the item they are associated with
place_attribute_on_same_line = false
diff --git a/Digdir.Domain.Dialogporten.sln b/Digdir.Domain.Dialogporten.sln
index 04d95ab2f..af4d515db 100644
--- a/Digdir.Domain.Dialogporten.sln
+++ b/Digdir.Domain.Dialogporten.sln
@@ -67,6 +67,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Digdir.Library.Utils.AspNet
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Digdir.Tool.Dialogporten.SlackNotifier.Tests", "tests\Digdir.Tool.Dialogporten.SlackNotifier.Tests\Digdir.Tool.Dialogporten.SlackNotifier.Tests.csproj", "{F7DF2792-9C83-49F7-B7DD-556E8EC577DB}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Digdir.Library.Analyzers", "src\Digdir.Library.Analyzers\Digdir.Library.Analyzers\Digdir.Library.Analyzers.csproj", "{88783EA8-EE64-490B-9C1E-E2365E861A88}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -165,6 +167,10 @@ Global
{F7DF2792-9C83-49F7-B7DD-556E8EC577DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7DF2792-9C83-49F7-B7DD-556E8EC577DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7DF2792-9C83-49F7-B7DD-556E8EC577DB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {88783EA8-EE64-490B-9C1E-E2365E861A88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {88783EA8-EE64-490B-9C1E-E2365E861A88}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {88783EA8-EE64-490B-9C1E-E2365E861A88}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {88783EA8-EE64-490B-9C1E-E2365E861A88}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -196,6 +202,7 @@ Global
{E389C7C8-9610-40AC-86DC-769B1B7DC78E} = {CADB8189-4AA1-4732-844A-C41DBF3EC8B7}
{6A485C65-3613-4A49-A16F-2789119F6F38} = {096E9B69-6783-4446-A895-0B6D7729A0D9}
{F7DF2792-9C83-49F7-B7DD-556E8EC577DB} = {CADB8189-4AA1-4732-844A-C41DBF3EC8B7}
+ {88783EA8-EE64-490B-9C1E-E2365E861A88} = {096E9B69-6783-4446-A895-0B6D7729A0D9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B2FE67FF-7622-4AFB-AD8E-961B6A39D888}
diff --git a/Directory.Build.props b/Directory.Build.props
index 53097d9cb..afd687852 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -15,4 +15,8 @@
8
+
+
+
diff --git a/src/Digdir.Domain.Dialogporten.Application/Common/Extensions/ClaimsPrincipalExtensions.cs b/src/Digdir.Domain.Dialogporten.Application/Common/Extensions/ClaimsPrincipalExtensions.cs
index 0ebda5a25..f4691fb49 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Common/Extensions/ClaimsPrincipalExtensions.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Common/Extensions/ClaimsPrincipalExtensions.cs
@@ -28,9 +28,8 @@ public static class ClaimsPrincipalExtensions
private const char ScopeClaimSeparator = ' ';
private const string PidClaim = "pid";
-
- // TODO: This scope is also defined in WebAPI/GQL. Can this be fetched from a common auth lib?
- // https://github.com/digdir/dialogporten/issues/647
+ // TODO: https://github.com/digdir/dialogporten/issues/647
+ // This scope is also defined in WebAPI/GQL. Can this be fetched from a common auth lib?
// This could be done for all claims/scopes/prefixes etc, there are duplicates
public const string ServiceProviderScope = "digdir:dialogporten.serviceprovider";
diff --git a/src/Digdir.Domain.Dialogporten.Application/Common/IUserRegistry.cs b/src/Digdir.Domain.Dialogporten.Application/Common/IUserRegistry.cs
index 1b3fa2512..ef459f90a 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Common/IUserRegistry.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Common/IUserRegistry.cs
@@ -62,8 +62,11 @@ public async Task GetCurrentUserInformation(CancellationToken c
var userId = GetCurrentUserId();
var name = userId.Type switch
{
- UserIdType.Person or UserIdType.ServiceOwnerOnBehalfOfPerson => await _partyNameRegistry.GetName(userId.ExternalIdWithPrefix, cancellationToken),
- UserIdType.SystemUser => "System User",// TODO: Implement when SystemUsers are introduced?
+ UserIdType.Person or UserIdType.ServiceOwnerOnBehalfOfPerson =>
+ await _partyNameRegistry.GetName(userId.ExternalIdWithPrefix, cancellationToken),
+ // TODO: https://github.com/digdir/dialogporten/issues/
+ // Implement when SystemUsers are introduced?
+ UserIdType.SystemUser => "System User",
UserIdType.Unknown => throw new UnreachableException(),
UserIdType.ServiceOwner => throw new UnreachableException(),
_ => throw new UnreachableException()
diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs
index e3c9d64e5..ded15eb1a 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs
@@ -118,8 +118,8 @@ public async Task Handle(GetDialogQuery request, CancellationTo
return new EntityDeleted(request.DialogId);
}
- // TODO: What if name lookup fails
- // https://github.com/digdir/dialogporten/issues/387
+ // TODO: https://github.com/digdir/dialogporten/issues/387
+ // What if name lookup fails?
dialog.UpdateSeenAt(
currentUserInformation.UserId.ExternalIdWithPrefix,
currentUserInformation.UserId.Type,
diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogCommand.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogCommand.cs
index 59bc62a8a..59340d292 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogCommand.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogCommand.cs
@@ -93,8 +93,8 @@ public async Task Handle(UpdateDialogCommand request, Cancel
if (dialog.Deleted)
{
- // TODO: When restoration is implemented, add a hint to the error message.
- // https://github.com/digdir/dialogporten/pull/406
+ // TODO: https://github.com/digdir/dialogporten/issues/...
+ // When restoration is implemented, add a hint to the error message.
return new BadRequest($"Entity '{nameof(DialogEntity)}' with key '{request.Id}' is removed, and cannot be updated.");
}
diff --git a/src/Digdir.Domain.Dialogporten.ChangeDataCapture/ChangeDataCapture/Checkpoints/CheckpointSynchronizer.cs b/src/Digdir.Domain.Dialogporten.ChangeDataCapture/ChangeDataCapture/Checkpoints/CheckpointSynchronizer.cs
index 201d0ffc1..e5188b5ad 100644
--- a/src/Digdir.Domain.Dialogporten.ChangeDataCapture/ChangeDataCapture/Checkpoints/CheckpointSynchronizer.cs
+++ b/src/Digdir.Domain.Dialogporten.ChangeDataCapture/ChangeDataCapture/Checkpoints/CheckpointSynchronizer.cs
@@ -33,7 +33,8 @@ public override async Task StartAsync(CancellationToken cancellationToken)
// This method will block the application from starting until
// snapshots are loaded and permissions are verified
- // TODO: Remove when checkpoint migration exists
+ // TODO: https://github.com/digdir/dialogporten/issues/...
+ // Remove when checkpoint migration exists
// Ensure table exists
await _snapshotRepository.EnsureCheckpointTableExists(cancellationToken);
diff --git a/src/Digdir.Domain.Dialogporten.ChangeDataCapture/ChangeDataCapture/Checkpoints/ICheckpointRepository.cs b/src/Digdir.Domain.Dialogporten.ChangeDataCapture/ChangeDataCapture/Checkpoints/ICheckpointRepository.cs
index 15d4f6dbc..881543c38 100644
--- a/src/Digdir.Domain.Dialogporten.ChangeDataCapture/ChangeDataCapture/Checkpoints/ICheckpointRepository.cs
+++ b/src/Digdir.Domain.Dialogporten.ChangeDataCapture/ChangeDataCapture/Checkpoints/ICheckpointRepository.cs
@@ -25,7 +25,8 @@ public CheckpointRepository(
public async Task EnsureCheckpointTableExists(CancellationToken ct = default)
{
- // TODO: Dette burde ligge sammen med OutboxMessage opprettelsen i infrastruktur via EF
+ // TODO: https://github.com/digdir/dialogporten/issues/...
+ // This should be located together with the OutboxMessage creation in the infrastructure via EF
await using var command = _dataSource.CreateCommand(
$"""
CREATE TABLE IF NOT EXISTS cdc_checkpoint (
diff --git a/src/Digdir.Domain.Dialogporten.ChangeDataCapture/ICdcSink.cs b/src/Digdir.Domain.Dialogporten.ChangeDataCapture/ICdcSink.cs
index 8478bf5d8..353755325 100644
--- a/src/Digdir.Domain.Dialogporten.ChangeDataCapture/ICdcSink.cs
+++ b/src/Digdir.Domain.Dialogporten.ChangeDataCapture/ICdcSink.cs
@@ -40,8 +40,10 @@ public MassTransitSink(ISendEndpointProvider sender)
public async Task Send(IReadOnlyCollection outboxMessages, CancellationToken cancellationToken)
{
- // TODO: Configure uri
- // TODO: Configure transaction
+ // TODO: https://github.com/digdir/dialogporten/issues/...
+ // Configure uri
+ // TODO: https://github.com/digdir/dialogporten/issues/...
+ // Configure transaction
var endpoint = await _sender.GetSendEndpoint(new Uri("exchange:Digdir.Domain.Dialogporten.Service"));
await endpoint.SendBatch(
outboxMessages,
diff --git a/src/Digdir.Domain.Dialogporten.Domain/Outboxes/OutboxMessage.cs b/src/Digdir.Domain.Dialogporten.Domain/Outboxes/OutboxMessage.cs
index 18934a8ed..99598ee99 100644
--- a/src/Digdir.Domain.Dialogporten.Domain/Outboxes/OutboxMessage.cs
+++ b/src/Digdir.Domain.Dialogporten.Domain/Outboxes/OutboxMessage.cs
@@ -23,7 +23,8 @@ public static OutboxMessage Create(TDomainEvent domainEvent)
CreatedAt = domainEvent.OccuredAt,
EventType = eventType.FullName!,
EventPayload = JsonSerializer.Serialize(domainEvent, eventType),
- // TODO: Set correlation id
+ // TODO: https://github.com/digdir/dialogporten/issues/...
+ // Set correlation id
CorrelationId = Guid.NewGuid().ToString()
};
}
diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Authorization/DecisionRequestHelper.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Authorization/DecisionRequestHelper.cs
index 92648cc79..83f624f97 100644
--- a/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Authorization/DecisionRequestHelper.cs
+++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Authorization/DecisionRequestHelper.cs
@@ -191,7 +191,7 @@ private static XacmlJsonCategory CreateResourceCategory(string id, string servic
}
else if (ns == AttributeIdAppInstance)
{
- // TODO!
+ // TODO: https://github.com/digdir/dialogporten/issues/...
// For app instances, we the syntax of the value is "{partyID}/{instanceID}".
// We do not have Altinn partyID in the request, so we cannot support this.
// This means we cannot easily support instance specific authorizations for apps.
diff --git a/src/Digdir.Library.Analyzers/Digdir.Library.Analyzers/AnalyzerReleases.Shipped.md b/src/Digdir.Library.Analyzers/Digdir.Library.Analyzers/AnalyzerReleases.Shipped.md
new file mode 100644
index 000000000..b8228327a
--- /dev/null
+++ b/src/Digdir.Library.Analyzers/Digdir.Library.Analyzers/AnalyzerReleases.Shipped.md
@@ -0,0 +1,7 @@
+## Release 1.0
+
+### New Rules
+
+Rule ID | Category | Severity | Notes
+--------|----------|----------|-------
+TODO001 | Documentation | Error | TodoCommentAnalyzer
diff --git a/src/Digdir.Library.Analyzers/Digdir.Library.Analyzers/AnalyzerReleases.Unshipped.md b/src/Digdir.Library.Analyzers/Digdir.Library.Analyzers/AnalyzerReleases.Unshipped.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/Digdir.Library.Analyzers/Digdir.Library.Analyzers/AssemblyInfo.cs b/src/Digdir.Library.Analyzers/Digdir.Library.Analyzers/AssemblyInfo.cs
new file mode 100644
index 000000000..d839acb3d
--- /dev/null
+++ b/src/Digdir.Library.Analyzers/Digdir.Library.Analyzers/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System.Resources;
+
+[assembly: NeutralResourcesLanguage("en")]
diff --git a/src/Digdir.Library.Analyzers/Digdir.Library.Analyzers/Digdir.Library.Analyzers.csproj b/src/Digdir.Library.Analyzers/Digdir.Library.Analyzers/Digdir.Library.Analyzers.csproj
new file mode 100644
index 000000000..e45ce58a3
--- /dev/null
+++ b/src/Digdir.Library.Analyzers/Digdir.Library.Analyzers/Digdir.Library.Analyzers.csproj
@@ -0,0 +1,23 @@
+
+
+
+ false
+ latest
+
+ true
+ true
+
+ Digdir.Library.Analyzers
+ Digdir.Library.Analyzers
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
diff --git a/src/Digdir.Library.Analyzers/Digdir.Library.Analyzers/Properties/launchSettings.json b/src/Digdir.Library.Analyzers/Digdir.Library.Analyzers/Properties/launchSettings.json
new file mode 100644
index 000000000..dbf130ef7
--- /dev/null
+++ b/src/Digdir.Library.Analyzers/Digdir.Library.Analyzers/Properties/launchSettings.json
@@ -0,0 +1,9 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "DebugRoslynAnalyzers": {
+ "commandName": "DebugRoslynComponent",
+ "targetProject": "../Digdir.Domain.Dialogporten.Application/Digdir.Domain.Dialogporten.Application.csproj",
+ }
+ }
+}
diff --git a/src/Digdir.Library.Analyzers/Digdir.Library.Analyzers/TodoCommentAnalyzer.cs b/src/Digdir.Library.Analyzers/Digdir.Library.Analyzers/TodoCommentAnalyzer.cs
new file mode 100644
index 000000000..e17330d15
--- /dev/null
+++ b/src/Digdir.Library.Analyzers/Digdir.Library.Analyzers/TodoCommentAnalyzer.cs
@@ -0,0 +1,57 @@
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Diagnostics;
+using static System.Text.RegularExpressions.Regex;
+
+namespace Digdir.Library.Analyzers;
+
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public class TodoCommentAnalyzer : DiagnosticAnalyzer
+{
+ public const string DiagnosticId = "TODO001";
+ private static readonly LocalizableString Title = "TODO comment must have an issue URL";
+ private static readonly LocalizableString MessageFormat = "TODO comment must be followed by an issue URL (on the same line)";
+ private static readonly LocalizableString Description = "TODO comments should be followed by an issue URL (on the same line).";
+
+ private const string Category = "Documentation";
+ private const string IssueRegexOptionKey = "dotnet_diagnostic.TODO001.issueRegex";
+
+ private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, Title, MessageFormat, Category,
+ DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description);
+
+ public override ImmutableArray SupportedDiagnostics => [Rule];
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+ context.RegisterSyntaxTreeAction(AnalyzeSyntaxTree);
+ }
+ private static void AnalyzeSyntaxTree(SyntaxTreeAnalysisContext context)
+ {
+ var options = context.Options.AnalyzerConfigOptionsProvider.GetOptions(context.Tree);
+ if (!options.TryGetValue(IssueRegexOptionKey, out var issueRegex) || string.IsNullOrEmpty(issueRegex))
+ {
+ return;
+ }
+
+ var trivia = context
+ .Tree
+ .GetRoot(context.CancellationToken)
+ .DescendantTrivia()
+ .Where(t => t.IsKind(SyntaxKind.SingleLineCommentTrivia));
+
+ foreach (var comment in trivia)
+ {
+ var commentText = comment.ToString();
+
+ if (!commentText.Contains("TODO")) continue;
+
+ if (IsMatch(commentText, issueRegex)) continue;
+
+ var diagnostic = Diagnostic.Create(Rule, comment.GetLocation());
+ context.ReportDiagnostic(diagnostic);
+ }
+ }
+}
\ No newline at end of file