diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..30d0b48
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,28 @@
+# noinspection EditorConfigKeyCorrectness
+[*.{cs,cshtml,razor}]
+# MA0048: File may only contain a single top-level type
+dotnet_diagnostic.MA0048.severity = none
+
+# MA0008: Structs must have struct layout
+dotnet_diagnostic.MA0008.severity = none
+
+# MA0006: Use string.Equals instead of ==
+dotnet_diagnostic.MA0006.severity = none
+
+# MA0004: synchronization context (always needed in Blazor)
+dotnet_diagnostic.MA0004.severity = none
+
+# MA0015 - Specify the parameter name in ArgumentException
+dotnet_diagnostic.MA0015.severity = none
+
+# MA0051 - Method is too long
+dotnet_diagnostic.MA0051.severity = none
+# MA0051.maximum_lines_per_method = 60
+# MA0051.maximum_statements_per_method = 40
+# MA0051.skip_local_functions = false # skip local functions when counting statements
+
+# MA0045 - sync in async
+dotnet_diagnostic.MA0045.severity = info
+
+# MA0080 - cancellation token AsyncEnumerable iteration
+dotnet_diagnostic.MA0080.severity = info
\ No newline at end of file
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 055836a..59de1ca 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -14,9 +14,9 @@
-
-
-
+
+
+
@@ -36,5 +36,9 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
\ No newline at end of file
diff --git a/Splitracker.Domain/ActionShorthand.cs b/Splitracker.Domain/ActionShorthand.cs
index 4b0d18a..52f9466 100644
--- a/Splitracker.Domain/ActionShorthand.cs
+++ b/Splitracker.Domain/ActionShorthand.cs
@@ -1,4 +1,5 @@
using System;
+using System.Globalization;
using System.Text.RegularExpressions;
namespace Splitracker.Domain;
@@ -95,19 +96,19 @@ public int Roll(Random random)
return null;
}
- if (m.Groups["const"] is { Success: true, ValueSpan: var rawConst } && int.TryParse(rawConst, out var @const))
+ if (m.Groups["const"] is { Success: true, ValueSpan: var rawConst } && int.TryParse(rawConst, provider: CultureInfo.InvariantCulture, out var @const))
{
return new(0, 0, @const);
}
var numDice =
m.Groups["numd"] is { Success: true, ValueSpan: var rawNumDice } &&
- int.TryParse(rawNumDice, out var parsedNumDice)
+ int.TryParse(rawNumDice, provider: CultureInfo.InvariantCulture, out var parsedNumDice)
? parsedNumDice
: 1;
var numSides =
m.Groups["nums"] is { Success: true, ValueSpan: var rawNumSides } &&
- int.TryParse(rawNumSides, out var parsedNumSides)
+ int.TryParse(rawNumSides, provider: CultureInfo.InvariantCulture, out var parsedNumSides)
? parsedNumSides
: 6;
var sign =
@@ -116,7 +117,7 @@ public int Roll(Random random)
: 1;
var bonus =
m.Groups["bonus"] is { Success: true, ValueSpan: var rawBonus } &&
- int.TryParse(rawBonus, out var parsedBonus)
+ int.TryParse(rawBonus, provider: CultureInfo.InvariantCulture, out var parsedBonus)
? parsedBonus * sign
: 0;
diff --git a/Splitracker.Domain/Group.cs b/Splitracker.Domain/Group.cs
index e13967f..f4824dd 100644
--- a/Splitracker.Domain/Group.cs
+++ b/Splitracker.Domain/Group.cs
@@ -14,7 +14,9 @@ public record Group(
public record GroupMembership(string UserId, GroupRole Role);
+#pragma warning disable MA0048
public enum GroupRole
+#pragma warning restore MA0048
{
Member,
GameMaster,
diff --git a/Splitracker.Domain/NameGenerationService.cs b/Splitracker.Domain/NameGenerationService.cs
index 6c50a08..7d4a71a 100644
--- a/Splitracker.Domain/NameGenerationService.cs
+++ b/Splitracker.Domain/NameGenerationService.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
@@ -53,7 +54,9 @@ public INamingScheme InferNamingScheme(IEnumerable names)
static (string Name, Scheme? Scheme, int Parsed) parsed(string name)
{
var match = suffixPattern().Match(name);
- if (match.Groups["num"] is { Success: true } num && int.TryParse(num.ValueSpan, out var parsed))
+ if (match.Groups["num"] is { Success: true } num && int.TryParse(num.ValueSpan,
+ provider: CultureInfo.InvariantCulture,
+ out var parsed))
{
return (name, Scheme.Number, parsed);
}
diff --git a/Splitracker.Domain/PointsVec.cs b/Splitracker.Domain/PointsVec.cs
index 3693742..c8674e6 100644
--- a/Splitracker.Domain/PointsVec.cs
+++ b/Splitracker.Domain/PointsVec.cs
@@ -1,4 +1,5 @@
using System;
+using System.Globalization;
using System.Text.RegularExpressions;
namespace Splitracker.Domain;
@@ -87,7 +88,7 @@ public static PointsVec From(ReadOnlySpan input, PointType defaultType)
currentType = PointType.V;
break;
default:
- if (int.TryParse(input.Slice(match.Index, match.Length), out var numericValue))
+ if (int.TryParse(input.Slice(match.Index, match.Length), provider: CultureInfo.InvariantCulture, out var numericValue))
{
vec = currentType switch
{
diff --git a/Splitracker.Persistence/Characters/RavenCharacterHandle.cs b/Splitracker.Persistence/Characters/RavenCharacterHandle.cs
index 529bb35..8a9c37c 100644
--- a/Splitracker.Persistence/Characters/RavenCharacterHandle.cs
+++ b/Splitracker.Persistence/Characters/RavenCharacterHandle.cs
@@ -1,13 +1,15 @@
-using Splitracker.Domain;
+using System.Diagnostics.CodeAnalysis;
+using Splitracker.Domain;
using Splitracker.Persistence.Generic;
namespace Splitracker.Persistence.Characters;
///
-/// Mutable container for a . Triggers the event whenever
+/// Mutable container for a . Triggers the Updated event whenever
/// anything about the character changes.
///
-class RavenCharacterHandle(Character character)
+[SuppressMessage("Design", "MA0095:A class that implements IEquatable should override Equals(object)")]
+sealed class RavenCharacterHandle(Character character)
: PrefixHandleBase(character),
IPrefixHandle,
ICharacterHandle
diff --git a/Splitracker.Persistence/Characters/RavenCharacterRepository.cs b/Splitracker.Persistence/Characters/RavenCharacterRepository.cs
index 3357ef0..13667aa 100644
--- a/Splitracker.Persistence/Characters/RavenCharacterRepository.cs
+++ b/Splitracker.Persistence/Characters/RavenCharacterRepository.cs
@@ -32,7 +32,7 @@ NameGenerationService nameGeneration
{
internal const string CollectionName = "Characters";
- readonly ConcurrentDictionary> handles = new();
+ readonly ConcurrentDictionary> handles = new(StringComparer.Ordinal);
bool isOwner(string characterId, string userId) =>
characterId.StartsWith(CharacterDocIdPrefix(userId), StringComparison.Ordinal);
@@ -261,7 +261,7 @@ void stopChanneling(Pool model, Domain.Pool domain, string channelingId)
.ToListAsync();
var baseName = nameGeneration.InferTemplateName(model.Name);
var scheme = nameGeneration.InferNamingScheme(relatedNames ?? []);
- newInstanceName = scheme.GenerateNext().Replace("\uFFFC", baseName);
+ newInstanceName = scheme.GenerateNext().Replace("\uFFFC", baseName, StringComparison.Ordinal);
}
else
{
@@ -320,7 +320,7 @@ public async Task ApplyAsync(ClaimsPrincipal principal, DeleteTag deleteTagComma
var affectedCharacters = await session.Query()
.Where(c =>
c.Id.StartsWith(CharacterDocIdPrefix(userId))
- && c.TagIds.Contains(deleteTagCommand.TagId))
+ && c.TagIds.Contains(deleteTagCommand.TagId, StringComparer.Ordinal))
.ToListAsync();
foreach (var character in affectedCharacters)
{
@@ -358,7 +358,7 @@ static IAsyncDocumentQuery byNameSearch(IAsyncDocumentSession session
internal static async Task> FetchTemplatesAsync(IAsyncDocumentSession session, IEnumerable characters)
{
- var templateIds = characters.Select(c => c.TemplateId).OfType().ToHashSet();
+ var templateIds = characters.Select(c => c.TemplateId).OfType().ToHashSet(StringComparer.Ordinal);
if (templateIds.Count == 0)
{
return ImmutableDictionary.Empty;
@@ -390,12 +390,12 @@ public async Task OpenAsync(ClaimsPrincipal principa
) ?? throw new InvalidOperationException("Failed to open a handle.");
}
- readonly ConcurrentDictionary> singleHandles = new();
+ readonly ConcurrentDictionary> singleHandles = new(StringComparer.Ordinal);
public async Task OpenSingleAsync(ClaimsPrincipal principal, string characterId)
{
var userId = await repository.GetUserIdAsync(principal);
- if (!characterId.StartsWith(CharacterDocIdPrefix(userId)))
+ if (!characterId.StartsWith(CharacterDocIdPrefix(userId), StringComparison.Ordinal))
{
return null;
}
diff --git a/Splitracker.Persistence/Generic/HandleBase.cs b/Splitracker.Persistence/Generic/HandleBase.cs
index 1c615bf..dbf803e 100644
--- a/Splitracker.Persistence/Generic/HandleBase.cs
+++ b/Splitracker.Persistence/Generic/HandleBase.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
namespace Splitracker.Persistence.Generic;
@@ -8,12 +9,13 @@ abstract class HandleBase : IAsyncDisposable, IDisposable
{
readonly TSubscription subscription;
- public HandleBase(TSubscription subscription)
+ protected HandleBase(TSubscription subscription)
{
this.subscription = subscription;
subscription.Updated += OnUpdated;
}
+ [SuppressMessage("Usage", "MA0091:Sender should be \'this\' for instance events")]
void OnUpdated(object? sender, EventArgs e)
{
Updated?.Invoke(sender, e);
diff --git a/Splitracker.Persistence/Generic/PrefixHandleBase.cs b/Splitracker.Persistence/Generic/PrefixHandleBase.cs
index 24b60a5..68f6ddd 100644
--- a/Splitracker.Persistence/Generic/PrefixHandleBase.cs
+++ b/Splitracker.Persistence/Generic/PrefixHandleBase.cs
@@ -1,7 +1,9 @@
using System;
+using System.Diagnostics.CodeAnalysis;
namespace Splitracker.Persistence.Generic;
+[SuppressMessage("Design", "MA0077:A class that provides Equals(T) should implement IEquatable")]
abstract class PrefixHandleBase(TValue value) : IDisposable, IEquatable
where TSelf : PrefixHandleBase
where TValue : class
@@ -47,7 +49,7 @@ public override bool Equals(object? obj)
public override int GetHashCode()
{
- return Id.GetHashCode();
+ return Id.GetHashCode(StringComparison.Ordinal);
}
public static bool operator ==(PrefixHandleBase? left, PrefixHandleBase? right)
diff --git a/Splitracker.Persistence/Generic/PrefixRepositoryHandle.cs b/Splitracker.Persistence/Generic/PrefixRepositoryHandle.cs
index 4b2743c..0b1751e 100644
--- a/Splitracker.Persistence/Generic/PrefixRepositoryHandle.cs
+++ b/Splitracker.Persistence/Generic/PrefixRepositoryHandle.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
namespace Splitracker.Persistence.Generic;
@@ -9,7 +10,7 @@ abstract class PrefixRepositoryHandle : IAsyncDisposable,
{
protected readonly TSubscription Subscription;
- public PrefixRepositoryHandle(TSubscription subscription)
+ protected PrefixRepositoryHandle(TSubscription subscription)
{
Subscription = subscription;
subscription.Added += OnAdded;
@@ -19,11 +20,13 @@ public PrefixRepositoryHandle(TSubscription subscription)
public event EventHandler? Added;
public event EventHandler? Deleted;
+ [SuppressMessage("Usage", "MA0091:Sender should be \'this\' for instance events")]
void OnAdded(object? sender, EventArgs e)
{
Added?.Invoke(sender, e);
}
+ [SuppressMessage("Usage", "MA0091:Sender should be \'this\' for instance events")]
void OnDeleted(object? sender, EventArgs e)
{
Deleted?.Invoke(sender, e);
diff --git a/Splitracker.Persistence/Generic/SubscriptionBase.cs b/Splitracker.Persistence/Generic/SubscriptionBase.cs
index 9ff212a..e63e265 100644
--- a/Splitracker.Persistence/Generic/SubscriptionBase.cs
+++ b/Splitracker.Persistence/Generic/SubscriptionBase.cs
@@ -23,13 +23,13 @@ abstract class SubscriptionBase : IDisposable, ISubscrip
bool lifetimeBoundToHandles;
IImmutableDictionary ravenSubscriptions;
- public SubscriptionBase(ILogger log, TValue initialValue, IDocumentStore store, IEnumerable documentIdsToSubscribeTo)
+ protected SubscriptionBase(ILogger log, TValue initialValue, IDocumentStore store, IEnumerable documentIdsToSubscribeTo)
{
Log = log;
Store = store;
CurrentValue = initialValue;
ravenSubscriptions = documentIdsToSubscribeTo
- .Distinct()
+ .Distinct(StringComparer.Ordinal)
.ToImmutableDictionary(
id => id,
id => store.Changes().ForDocument(id).Subscribe(this)
@@ -148,9 +148,9 @@ void synchronizeSubscriptions(TValue value)
try
{
var existingSubscriptions = ravenSubscriptions;
- var existingKeys = existingSubscriptions.Keys.ToHashSet();
+ var existingKeys = existingSubscriptions.Keys.ToHashSet(StringComparer.Ordinal);
var requiredKeys = DocumentIdsToSubscribeToFor(value)
- .ToHashSet();
+ .ToHashSet(StringComparer.Ordinal);
foreach (var key in requiredKeys.ToList())
{
if (existingKeys.Remove(key))
diff --git a/Splitracker.Persistence/Groups/RavenGroupRepository.cs b/Splitracker.Persistence/Groups/RavenGroupRepository.cs
index c4cdf0f..d168f03 100644
--- a/Splitracker.Persistence/Groups/RavenGroupRepository.cs
+++ b/Splitracker.Persistence/Groups/RavenGroupRepository.cs
@@ -30,7 +30,7 @@ IUserRepository repository
#region Reading
- readonly ConcurrentDictionary> handles = new();
+ readonly ConcurrentDictionary> handles = new(StringComparer.Ordinal);
public async Task OpenSingleAsync(ClaimsPrincipal principal, string groupId)
{
@@ -157,7 +157,7 @@ public async Task JoinWithExistingCharacterAsync(ClaimsPrincipal principal, Doma
{
var userId = await repository.GetUserIdAsync(principal);
- if (!character.Id.StartsWith(RavenCharacterRepository.CharacterDocIdPrefix(userId)))
+ if (!character.Id.StartsWith(RavenCharacterRepository.CharacterDocIdPrefix(userId), StringComparison.Ordinal))
{
throw new DataAccessControlException(character.Id, userId);
}
@@ -218,7 +218,7 @@ async Task joinGroupAsync(string groupId, string userId, string characterId, IAs
group.Members.Add(new() { UserId = userId, Role = Model.GroupRole.Member });
}
- if (group.CharacterIds.Contains(characterId))
+ if (group.CharacterIds.Contains(characterId, StringComparer.Ordinal))
{
log.Log(LogLevel.Warning, "Character {CharacterId} is already a member of group {GroupId}", characterId, groupId);
}
@@ -246,7 +246,7 @@ public async Task LeaveGroupAsync(ClaimsPrincipal principal, Domain.Group group,
throw new DataAccessControlException("User is not a member of the group.", group.Id, userId);
}
- if (role != Model.GroupRole.GameMaster && !character.Id.StartsWith(characterDocIdPrefix))
+ if (role != Model.GroupRole.GameMaster && !character.Id.StartsWith(characterDocIdPrefix, StringComparison.Ordinal))
{
throw new DataAccessControlException($"User is not allowed to remove character {character.Id} from group.", group.Id, userId);
}
@@ -270,7 +270,7 @@ void enforceGroupInvariants(Model.Group dbGroup)
{
// List of character IDs does not contain duplicates
var characterIds = dbGroup.CharacterIds;
- if (characterIds.Count != characterIds.Distinct().Count())
+ if (characterIds.Count != characterIds.Distinct(StringComparer.Ordinal).Count())
{
throw new InvalidOperationException(
$"Group {dbGroup.Id} has duplicate character IDs in list of characters.");
diff --git a/Splitracker.Persistence/Model/CharacterModelMapper.cs b/Splitracker.Persistence/Model/CharacterModelMapper.cs
index 25be7b0..f174440 100644
--- a/Splitracker.Persistence/Model/CharacterModelMapper.cs
+++ b/Splitracker.Persistence/Model/CharacterModelMapper.cs
@@ -36,14 +36,14 @@ public static Domain.Character ToDomain(this Character model, Character? templat
var template = templateModel?.ToDomain((Character?)null) ?? Prototype;
var actionShorthands = model.ActionShorthands
.Select(s => s.ToDomain())
- .ToDictionary(s => s.Id);
+ .ToDictionary(s => s.Id, comparer: StringComparer.Ordinal);
foreach (var templateActionShorthand in template.ActionShorthands.Values)
{
actionShorthands.TryAdd(templateActionShorthand.Id, templateActionShorthand);
}
return new(model.Id,
- model.Name.Replace("\uFFFC", template.Name),
+ model.Name.Replace("\uFFFC", template.Name, StringComparison.Ordinal),
model.CustomColor ?? template.CustomColor,
model.Lp.ToDomainLp(template.Lp.BaseCapacity),
model.Fo.ToDomainFo(template.Fo.BaseCapacity),
diff --git a/Splitracker.Persistence/PersistenceServiceProviderConfig.cs b/Splitracker.Persistence/PersistenceServiceProviderConfig.cs
index 9b323bd..17a86cd 100644
--- a/Splitracker.Persistence/PersistenceServiceProviderConfig.cs
+++ b/Splitracker.Persistence/PersistenceServiceProviderConfig.cs
@@ -77,7 +77,7 @@ internal static void CustomizeStore(IDocumentStore store)
store.Conventions.FindCollectionName = type =>
{
var defaultName = DocumentConventions.DefaultGetCollectionName(type);
- return defaultName.EndsWith("Models") ? $"{defaultName[..^6]}s" : defaultName;
+ return defaultName.EndsWith("Models", StringComparison.Ordinal) ? $"{defaultName[..^6]}s" : defaultName;
};
}
diff --git a/Splitracker.Persistence/RavenOptions.cs b/Splitracker.Persistence/RavenOptions.cs
index 5d9f380..d49e21b 100644
--- a/Splitracker.Persistence/RavenOptions.cs
+++ b/Splitracker.Persistence/RavenOptions.cs
@@ -1,10 +1,12 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
+using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
namespace Splitracker.Persistence;
[UsedImplicitly(ImplicitUseTargetFlags.Itself)]
+[SuppressMessage("Design", "MA0016:Prefer returning collection abstraction instead of implementation")]
public class RavenOptions
{
[Required]
diff --git a/Splitracker.Persistence/Tags/RavenTagRepository.cs b/Splitracker.Persistence/Tags/RavenTagRepository.cs
index 12f85e2..11f6cda 100644
--- a/Splitracker.Persistence/Tags/RavenTagRepository.cs
+++ b/Splitracker.Persistence/Tags/RavenTagRepository.cs
@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
@@ -20,7 +21,7 @@ class RavenTagRepository(IDocumentStore store, ILogger log,
: ITagRepository
{
const string CollectionName = "Tags";
- readonly ConcurrentDictionary> handles = new();
+ readonly ConcurrentDictionary> handles = new(StringComparer.Ordinal);
public async Task OpenAsync(ClaimsPrincipal principal)
{
@@ -125,7 +126,8 @@ class RavenTagRepositoryHandle(RavenTagRepositorySubscription subscription)
public IReadOnlyList Tags => Subscription.Handles;
}
-class RavenTagHandle(Tag value) : PrefixHandleBase(value), IPrefixHandle, ITagHandle
+[SuppressMessage("Design", "MA0095:A class that implements IEquatable should override Equals(object)")]
+sealed class RavenTagHandle(Tag value) : PrefixHandleBase(value), IPrefixHandle, ITagHandle
{
public static RavenTagHandle Create(Tag value) => new(value);
diff --git a/Splitracker.Persistence/Timelines/RavenTimelineRepository.cs b/Splitracker.Persistence/Timelines/RavenTimelineRepository.cs
index b65abb2..3da930b 100644
--- a/Splitracker.Persistence/Timelines/RavenTimelineRepository.cs
+++ b/Splitracker.Persistence/Timelines/RavenTimelineRepository.cs
@@ -32,7 +32,7 @@ IUserRepository repository
#region Reading
- readonly ConcurrentDictionary> handles = new();
+ readonly ConcurrentDictionary> handles = new(StringComparer.Ordinal);
public async Task OpenSingleAsync(ClaimsPrincipal principal, string groupId)
{
@@ -383,7 +383,7 @@ void enforceTimelineInvariants(Model.Timeline dbTimeline)
.Where(t => t.CharacterId is not null)
.Select(t => t.CharacterId)
.Concat(dbTimeline.ReadyCharacterIds)
- .GroupBy(t => t)
+ .GroupBy(t => t, StringComparer.Ordinal)
.Where(g => g.Count() > 1);
foreach (var offendingCharacterId in offendingCharacterIds)
{
@@ -395,7 +395,7 @@ void enforceTimelineInvariants(Model.Timeline dbTimeline)
// Each effect must only have a single EffectEnds Tick in the timeline
var offendingEffectTicks = dbTimeline.Ticks
.Where(t => t.EffectId is not null)
- .GroupBy(t => t.EffectId)
+ .GroupBy(t => t.EffectId, StringComparer.Ordinal)
.Where(g => g.Count(t => t.Type == TickType.EffectEnds) > 1);
foreach (var offendingEffectTick in offendingEffectTicks)
{
diff --git a/Splitracker.UI/Shared/Characters/CharacterEditForm.razor b/Splitracker.UI/Shared/Characters/CharacterEditForm.razor
index 9c9a5f7..5cd6837 100644
--- a/Splitracker.UI/Shared/Characters/CharacterEditForm.razor
+++ b/Splitracker.UI/Shared/Characters/CharacterEditForm.razor
@@ -165,7 +165,7 @@
Label="Tag hinzufügen"
ValueChanged="@addTag"
Clearable="@true"
- OnClearButtonClick="@(() => tagSearchField!.Value = null!)"
+ OnClearButtonClick="@(async () => await tagSearchField!.Clear())"
SearchFunc="@searchTags"
ToStringFunc="@(t => t is { Name: { } n } ? n : "")"
OnKeyUp="@tagSearchKeyUp"
@@ -448,7 +448,7 @@
newTags.Remove((Tag)chip.Value);
if (tagSearchField != null)
{
- tagSearchField.Value = null!;
+ _ = tagSearchField.Clear();
}
}
diff --git a/Splitracker.UI/Shared/Characters/ShortRest.razor b/Splitracker.UI/Shared/Characters/ShortRest.razor
index c67a739..67642c1 100644
--- a/Splitracker.UI/Shared/Characters/ShortRest.razor
+++ b/Splitracker.UI/Shared/Characters/ShortRest.razor
@@ -22,7 +22,6 @@
public required int ExhaustedPoints { get; set; }
[CascadingParameter]
- [EditorRequired]
public required ICharacterCommandRouter Router { get; set; }
async Task OnShortRest()
diff --git a/Splitracker.UI/Shared/Sections/SectionRegistry.cs b/Splitracker.UI/Shared/Sections/SectionRegistry.cs
index 10e3a2e..0a581c8 100644
--- a/Splitracker.UI/Shared/Sections/SectionRegistry.cs
+++ b/Splitracker.UI/Shared/Sections/SectionRegistry.cs
@@ -9,7 +9,7 @@ internal class SectionRegistry
{
static readonly ConditionalWeakTable Registries = new();
- readonly Dictionary>> subscriptions = new();
+ readonly Dictionary>> subscriptions = new(StringComparer.Ordinal);
public static SectionRegistry GetRegistry(RenderHandle renderHandle)
{
diff --git a/Splitracker.UI/Shared/Timelines/MultiTimelineItem.razor.cs b/Splitracker.UI/Shared/Timelines/MultiTimelineItem.razor.cs
index 7f3a49b..227d244 100644
--- a/Splitracker.UI/Shared/Timelines/MultiTimelineItem.razor.cs
+++ b/Splitracker.UI/Shared/Timelines/MultiTimelineItem.razor.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using MudBlazor;
@@ -25,7 +26,7 @@ public sealed partial class MultiTimelineItem : MudComponentBase, IDisposable
string dotClassnames =>
new CssBuilder("multi-timeline-item-dot")
.AddClass($"multi-timeline-dot-size-{Size.ToDescriptionString()}")
- .AddClass($"multi-elevation-{Elevation.ToString()}")
+ .AddClass($"multi-elevation-{Elevation.ToString(CultureInfo.InvariantCulture)}")
.Build();
string dotInnerClassnames =>
diff --git a/Splitracker.UI/Shared/Timelines/TimelinePreview.razor.cs b/Splitracker.UI/Shared/Timelines/TimelinePreview.razor.cs
index 93f8044..ead6eb5 100644
--- a/Splitracker.UI/Shared/Timelines/TimelinePreview.razor.cs
+++ b/Splitracker.UI/Shared/Timelines/TimelinePreview.razor.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
@@ -137,7 +138,7 @@ static bool matchesFocusLock(Tick tick, Tick focusLock) =>
#region Action Card Data
- readonly Dictionary characterActionData = new();
+ readonly Dictionary characterActionData = new(StringComparer.Ordinal);
CharacterActionData getCharacterActionData(Character character) =>
characterActionData.TryGetValue(character.Id, out var data) ? data : CharacterActionData.Default;
@@ -189,7 +190,7 @@ or ActionTemplateType.Reset
if(tick.At != currentTick)
{
currentTick = tick.At;
- yield return currentTick.ToString();
+ yield return currentTick.ToString(CultureInfo.InvariantCulture);
}
else
{
@@ -211,7 +212,7 @@ or ActionTemplateType.Reset
var currentTick = startTick - 1;
var nextOffset = 0;
var nextTrack = 1;
- var effectTracks = new Dictionary();
+ var effectTracks = new Dictionary(StringComparer.Ordinal);
foreach (var tick in timeline)
{
if (tick.At > currentTick + 1)