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)