diff --git a/src/PatternKit.Generators/ComposerGenerator.cs b/src/PatternKit.Generators/ComposerGenerator.cs index 9520b9a9..374753e4 100644 --- a/src/PatternKit.Generators/ComposerGenerator.cs +++ b/src/PatternKit.Generators/ComposerGenerator.cs @@ -371,7 +371,7 @@ private bool ValidateStepSignature(StepInfo step, SourceProductionContext contex // Validate it's actually System.Func by checking namespace and name var fullName = nextType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - if (!fullName.StartsWith("global::System.Func<")) + if (!fullName.StartsWith("global::System.Func<", StringComparison.Ordinal)) { context.ReportDiagnostic(Diagnostic.Create( InvalidStepSignatureDescriptor, @@ -743,9 +743,9 @@ private bool IsAsyncMethod(IMethodSymbol method) var fullName = namedType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); return fullName == "global::System.Threading.Tasks.Task" || - fullName.StartsWith("global::System.Threading.Tasks.Task<") || + fullName.StartsWith("global::System.Threading.Tasks.Task<", StringComparison.Ordinal) || fullName == "global::System.Threading.Tasks.ValueTask" || - fullName.StartsWith("global::System.Threading.Tasks.ValueTask<"); + fullName.StartsWith("global::System.Threading.Tasks.ValueTask<", StringComparison.Ordinal); } private bool IsPartial(SyntaxNode node) diff --git a/src/PatternKit.Generators/DecoratorGenerator.cs b/src/PatternKit.Generators/DecoratorGenerator.cs index ceba9a22..fa6b6afb 100644 --- a/src/PatternKit.Generators/DecoratorGenerator.cs +++ b/src/PatternKit.Generators/DecoratorGenerator.cs @@ -200,7 +200,7 @@ private DecoratorConfig ParseDecoratorConfig(AttributeData attribute, INamedType // Determine default base type name var baseName = contractSymbol.Name; - if (baseName.StartsWith("I") && baseName.Length > 1 && char.IsUpper(baseName[1])) + if (baseName.StartsWith("I", StringComparison.Ordinal) && baseName.Length > 1 && char.IsUpper(baseName[1])) { // Interface with I prefix: IStorage -> StorageDecoratorBase baseName = baseName.Substring(1); @@ -596,8 +596,8 @@ private static bool IsAsyncMethod(IMethodSymbol method) var returnType = method.ReturnType; var typeName = returnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - return typeName.StartsWith("global::System.Threading.Tasks.Task") || - typeName.StartsWith("global::System.Threading.Tasks.ValueTask"); + return typeName.StartsWith("global::System.Threading.Tasks.Task", StringComparison.Ordinal) || + typeName.StartsWith("global::System.Threading.Tasks.ValueTask", StringComparison.Ordinal); } private static bool CanTypeAcceptNull(ITypeSymbol type) diff --git a/src/PatternKit.Generators/FacadeGenerator.cs b/src/PatternKit.Generators/FacadeGenerator.cs index 76ec8bc8..36795906 100644 --- a/src/PatternKit.Generators/FacadeGenerator.cs +++ b/src/PatternKit.Generators/FacadeGenerator.cs @@ -317,7 +317,7 @@ private static ImmutableArray CollectContractFirstMethods( : contractType.ContainingNamespace.ToDisplayString(); var defaultFacadeTypeName = contractType.TypeKind == TypeKind.Interface - ? (contractType.Name.StartsWith("I") ? contractType.Name.Substring(1) + "Impl" : contractType.Name + "Impl") + ? (contractType.Name.StartsWith("I", StringComparison.Ordinal) ? contractType.Name.Substring(1) + "Impl" : contractType.Name + "Impl") : contractType.Name + "Impl"; return new FacadeInfo( @@ -453,15 +453,15 @@ private static bool ReturnTypesCompatible(ITypeSymbol type1, ITypeSymbol type2) private static bool IsAsyncMethod(IMethodSymbol method) { var returnType = method.ReturnType.ToDisplayString(); - return returnType.StartsWith("System.Threading.Tasks.Task") || - returnType.StartsWith("System.Threading.Tasks.ValueTask"); + return returnType.StartsWith("System.Threading.Tasks.Task", StringComparison.Ordinal) || + returnType.StartsWith("System.Threading.Tasks.ValueTask", StringComparison.Ordinal); } private static bool IsTaskLike(ITypeSymbol type) { var name = type.ToDisplayString(); - return name.StartsWith("System.Threading.Tasks.Task") || - name.StartsWith("System.Threading.Tasks.ValueTask"); + return name.StartsWith("System.Threading.Tasks.Task", StringComparison.Ordinal) || + name.StartsWith("System.Threading.Tasks.ValueTask", StringComparison.Ordinal); } private static ITypeSymbol UnwrapTaskType(ITypeSymbol type) @@ -756,11 +756,11 @@ private static void GenerateContractMethod( { // Handle async return types var returnTypeStr = methodSymbol.ReturnType.ToDisplayString(); - if (returnTypeStr.StartsWith("System.Threading.Tasks.ValueTask<")) + if (returnTypeStr.StartsWith("System.Threading.Tasks.ValueTask<", StringComparison.Ordinal)) { sb.AppendLine($" return System.Threading.Tasks.ValueTask.FromResult<{GetAsyncReturnType(methodSymbol)}>(default!);"); } - else if (returnTypeStr.StartsWith("System.Threading.Tasks.Task<")) + else if (returnTypeStr.StartsWith("System.Threading.Tasks.Task<", StringComparison.Ordinal)) { sb.AppendLine($" return System.Threading.Tasks.Task.FromResult<{GetAsyncReturnType(methodSymbol)}>(default!);"); } @@ -1013,7 +1013,7 @@ private static string GenerateAutoFacade(SourceProductionContext context, Facade var fieldName = g.Key; // Generate parameter name: if field starts with underscore, remove it; otherwise, use field name as-is // The parameter will be different from the field if underscore is present - var paramName = fieldName.StartsWith("_") ? fieldName.Substring(1) : fieldName; + var paramName = fieldName.StartsWith("_", StringComparison.Ordinal) ? fieldName.Substring(1) : fieldName; var externalType = g.First().MappingMethod!.ContainingType; var typeFullName = externalType.ToDisplayString(FullyQualifiedFormat); return $"{typeFullName} {paramName}"; @@ -1025,7 +1025,7 @@ private static string GenerateAutoFacade(SourceProductionContext context, Facade // Generate constructor body with null checks // For fields without underscore, the parameter name matches the field name, requiring `this.` qualifier for disambiguation foreach (var (fieldName, paramName) in groupedByField.Select(g => - (g.Key, g.Key.StartsWith("_") ? g.Key.Substring(1) : g.Key))) + (g.Key, g.Key.StartsWith("_", StringComparison.Ordinal) ? g.Key.Substring(1) : g.Key))) { sb.AppendLine($" this.{fieldName} = {paramName} ?? throw new System.ArgumentNullException(nameof({paramName}));"); } diff --git a/src/PatternKit.Generators/PrototypeGenerator.cs b/src/PatternKit.Generators/PrototypeGenerator.cs index 819ed9bb..9827bc06 100644 --- a/src/PatternKit.Generators/PrototypeGenerator.cs +++ b/src/PatternKit.Generators/PrototypeGenerator.cs @@ -533,7 +533,7 @@ private bool IsImmutableReferenceType(ITypeSymbol type) // Check for known immutable collections (basic check) var typeName = type.ToDisplayString(); - if (typeName.StartsWith("System.Collections.Immutable.")) + if (typeName.StartsWith("System.Collections.Immutable.", StringComparison.Ordinal)) return true; // Conservative: assume mutable diff --git a/src/PatternKit.Generators/ProxyGenerator.cs b/src/PatternKit.Generators/ProxyGenerator.cs index 04e1befa..ced5af0f 100644 --- a/src/PatternKit.Generators/ProxyGenerator.cs +++ b/src/PatternKit.Generators/ProxyGenerator.cs @@ -211,7 +211,7 @@ private ProxyConfig ParseProxyConfig(AttributeData attribute, INamedTypeSymbol c // Determine default proxy type name var baseName = contractSymbol.Name; - if (baseName.StartsWith("I") && baseName.Length > 1 && char.IsUpper(baseName[1])) + if (baseName.StartsWith("I", StringComparison.Ordinal) && baseName.Length > 1 && char.IsUpper(baseName[1])) { // Interface with I prefix: IUserService -> UserServiceProxy baseName = baseName.Substring(1); @@ -592,8 +592,8 @@ private static bool IsAsyncMethod(IMethodSymbol method) var returnType = method.ReturnType; var typeName = returnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - return typeName.StartsWith("global::System.Threading.Tasks.Task") || - typeName.StartsWith("global::System.Threading.Tasks.ValueTask"); + return typeName.StartsWith("global::System.Threading.Tasks.Task", StringComparison.Ordinal) || + typeName.StartsWith("global::System.Threading.Tasks.ValueTask", StringComparison.Ordinal); } private static bool IsGenericAsyncReturnType(IMethodSymbol method) @@ -615,8 +615,8 @@ private static bool IsGenericAsyncReturnType(IMethodSymbol method) return (fullName == "global::System.Threading.Tasks.Task" || fullName == "global::System.Threading.Tasks.ValueTask") || - (namedType.Arity > 0 && (originalTypeName.StartsWith("global::System.Threading.Tasks.Task<") || - originalTypeName.StartsWith("global::System.Threading.Tasks.ValueTask<"))); + (namedType.Arity > 0 && (originalTypeName.StartsWith("global::System.Threading.Tasks.Task<", StringComparison.Ordinal) || + originalTypeName.StartsWith("global::System.Threading.Tasks.ValueTask<", StringComparison.Ordinal))); } private static bool IsCancellationToken(ITypeSymbol type) @@ -1490,7 +1490,7 @@ private List DetermineParameterPropertyNames(List paramet } else { - propName = char.ToUpper(param.Name[0]) + (param.Name.Length > 1 ? param.Name.Substring(1) : ""); + propName = char.ToUpperInvariant(param.Name[0]) + (param.Name.Length > 1 ? param.Name.Substring(1) : ""); // Avoid conflicts with reserved property names if (usedPropNames.Contains(propName)) diff --git a/src/PatternKit.Generators/Singleton/SingletonGenerator.cs b/src/PatternKit.Generators/Singleton/SingletonGenerator.cs index 35a3c6f4..39bfe1d9 100644 --- a/src/PatternKit.Generators/Singleton/SingletonGenerator.cs +++ b/src/PatternKit.Generators/Singleton/SingletonGenerator.cs @@ -312,7 +312,7 @@ private static SingletonConfig ParseSingletonConfig(AttributeData attribute) private static bool HasNameConflict(INamedTypeSymbol typeSymbol, string propertyName) { // Normalize verbatim identifiers by stripping leading @ - var normalizedName = propertyName.StartsWith("@") ? propertyName.Substring(1) : propertyName; + var normalizedName = propertyName.StartsWith("@", StringComparison.Ordinal) ? propertyName.Substring(1) : propertyName; // Check for existing members with the same name in declared members if (typeSymbol.GetMembers(normalizedName).Length > 0) diff --git a/src/PatternKit.Generators/StateMachineGenerator.cs b/src/PatternKit.Generators/StateMachineGenerator.cs index b048ef83..2ae785b1 100644 --- a/src/PatternKit.Generators/StateMachineGenerator.cs +++ b/src/PatternKit.Generators/StateMachineGenerator.cs @@ -491,7 +491,7 @@ private ImmutableArray CollectExitHooks( ulong targetValue; try { - targetValue = Convert.ToUInt64(constant.Value); + targetValue = Convert.ToUInt64(constant.Value, global::System.Globalization.CultureInfo.InvariantCulture); } catch { @@ -505,7 +505,7 @@ private ImmutableArray CollectExitHooks( try { - var fieldValue = Convert.ToUInt64(field.ConstantValue); + var fieldValue = Convert.ToUInt64(field.ConstantValue, global::System.Globalization.CultureInfo.InvariantCulture); if (fieldValue == targetValue) return field.Name; } diff --git a/src/PatternKit.Generators/VisitorGenerator.cs b/src/PatternKit.Generators/VisitorGenerator.cs index efa21c20..e529b97b 100644 --- a/src/PatternKit.Generators/VisitorGenerator.cs +++ b/src/PatternKit.Generators/VisitorGenerator.cs @@ -88,7 +88,7 @@ private static string GetDefaultVisitorInterfaceName(INamedTypeSymbol baseType) // If base is an interface with I-prefix (Hungarian notation), don't add another I if (baseType.TypeKind == TypeKind.Interface && - baseName.StartsWith("I") && + baseName.StartsWith("I", StringComparison.Ordinal) && baseName.Length > 1 && char.IsUpper(baseName[1])) { diff --git a/test/PatternKit.Generators.Tests/DecoratorGeneratorTests.cs b/test/PatternKit.Generators.Tests/DecoratorGeneratorTests.cs index 2eb02db2..4a03e9fa 100644 --- a/test/PatternKit.Generators.Tests/DecoratorGeneratorTests.cs +++ b/test/PatternKit.Generators.Tests/DecoratorGeneratorTests.cs @@ -375,10 +375,10 @@ public interface IStorage .First(gs => gs.HintName == "TestNamespace_IStorage.Decorator.g.cs") .SourceText.ToString(); - var appleIndex = generatedSource.IndexOf("void Apple"); - var bananaIndex = generatedSource.IndexOf("void Banana"); - var mangoIndex = generatedSource.IndexOf("void Mango"); - var zebraIndex = generatedSource.IndexOf("void Zebra"); + var appleIndex = generatedSource.IndexOf("void Apple", StringComparison.Ordinal); + var bananaIndex = generatedSource.IndexOf("void Banana", StringComparison.Ordinal); + var mangoIndex = generatedSource.IndexOf("void Mango", StringComparison.Ordinal); + var zebraIndex = generatedSource.IndexOf("void Zebra", StringComparison.Ordinal); ScenarioExpect.True(appleIndex < bananaIndex); ScenarioExpect.True(bananaIndex < mangoIndex); diff --git a/test/PatternKit.Generators.Tests/DispatcherGeneratorTests.cs b/test/PatternKit.Generators.Tests/DispatcherGeneratorTests.cs index 5fc0d917..d287a851 100644 --- a/test/PatternKit.Generators.Tests/DispatcherGeneratorTests.cs +++ b/test/PatternKit.Generators.Tests/DispatcherGeneratorTests.cs @@ -1214,7 +1214,7 @@ public static async Task Run() var result = task.Result; // Should either be the expected result or an error message - if (result.StartsWith("ERROR:")) + if (result.StartsWith("ERROR:", StringComparison.Ordinal)) { ScenarioExpect.Fail($"Test threw exception: {result}"); } diff --git a/test/PatternKit.Generators.Tests/FacadeGeneratorTests.cs b/test/PatternKit.Generators.Tests/FacadeGeneratorTests.cs index 39e78442..a53d95bf 100644 --- a/test/PatternKit.Generators.Tests/FacadeGeneratorTests.cs +++ b/test/PatternKit.Generators.Tests/FacadeGeneratorTests.cs @@ -924,10 +924,10 @@ public static void C() { } var generatedSource = generatedSources.First().SourceText.ToString(); // Methods should appear in alphabetical order: Alpha, Charlie, Mike, Zebra - var alphaIndex = generatedSource.IndexOf("void Alpha()"); - var charlieIndex = generatedSource.IndexOf("void Charlie()"); - var mikeIndex = generatedSource.IndexOf("void Mike()"); - var zebraIndex = generatedSource.IndexOf("void Zebra()"); + var alphaIndex = generatedSource.IndexOf("void Alpha()", StringComparison.Ordinal); + var charlieIndex = generatedSource.IndexOf("void Charlie()", StringComparison.Ordinal); + var mikeIndex = generatedSource.IndexOf("void Mike()", StringComparison.Ordinal); + var zebraIndex = generatedSource.IndexOf("void Zebra()", StringComparison.Ordinal); ScenarioExpect.True(alphaIndex < charlieIndex); ScenarioExpect.True(charlieIndex < mikeIndex); diff --git a/test/PatternKit.Generators.Tests/StateMachineGeneratorTests.cs b/test/PatternKit.Generators.Tests/StateMachineGeneratorTests.cs index 997f1290..222d9d1b 100644 --- a/test/PatternKit.Generators.Tests/StateMachineGeneratorTests.cs +++ b/test/PatternKit.Generators.Tests/StateMachineGeneratorTests.cs @@ -352,8 +352,8 @@ private void OnEnterPaid() { } ScenarioExpect.Contains("OnEnterPaid()", generatedSource); // Verify State is updated before entry hooks - var submitIndex = generatedSource.IndexOf("State = global::PatternKit.Examples.OrderState.Submitted"); - var entrySubmittedIndex = generatedSource.IndexOf("OnEnterSubmitted()"); + var submitIndex = generatedSource.IndexOf("State = global::PatternKit.Examples.OrderState.Submitted", StringComparison.Ordinal); + var entrySubmittedIndex = generatedSource.IndexOf("OnEnterSubmitted()", StringComparison.Ordinal); ScenarioExpect.True(submitIndex < entrySubmittedIndex, "State should be updated before entry hook is called"); // And the updated compilation actually compiles @@ -403,8 +403,8 @@ private void OnPay() { } ScenarioExpect.Contains("OnExitSubmitted()", generatedSource); // Verify exit hooks are called before transition action - var exitIndex = generatedSource.IndexOf("OnExitDraft()"); - var transitionIndex = generatedSource.IndexOf("OnSubmit()"); + var exitIndex = generatedSource.IndexOf("OnExitDraft()", StringComparison.Ordinal); + var transitionIndex = generatedSource.IndexOf("OnSubmit()", StringComparison.Ordinal); ScenarioExpect.True(exitIndex < transitionIndex, "Exit hook should be called before transition action"); // And the updated compilation actually compiles @@ -532,8 +532,8 @@ private void OnTransition() { } // Verify no exception is thrown for guard failures var generatedSource = result.Results[0].GeneratedSources[0].SourceText.ToString(); - var guardFailureIndex = generatedSource.IndexOf("if (!CanTransition())"); - var throwIndex = generatedSource.IndexOf("throw new global::System.InvalidOperationException($\"Guard failed", guardFailureIndex); + var guardFailureIndex = generatedSource.IndexOf("if (!CanTransition())", StringComparison.Ordinal); + var throwIndex = generatedSource.IndexOf("throw new global::System.InvalidOperationException($\"Guard failed", guardFailureIndex, StringComparison.Ordinal); ScenarioExpect.True(throwIndex == -1, "Should not throw exception on guard failure with Ignore policy"); // And the updated compilation actually compiles @@ -899,7 +899,7 @@ private async ValueTask OnTransitionAsync(CancellationToken ct) ScenarioExpect.True(hasGeneratedCode, "No code was generated"); // Check compilation diagnostics - var compDiags = updated.GetDiagnostics().Where(d => d.Id.StartsWith("PKST")).ToArray(); + var compDiags = updated.GetDiagnostics().Where(d => d.Id.StartsWith("PKST", StringComparison.Ordinal)).ToArray(); ScenarioExpect.True(compDiags.Length > 0, $"No PKST diagnostics found. Generated code: {result.Results[0].GeneratedSources.Length} files"); } diff --git a/test/PatternKit.Generators.Tests/TemplateGeneratorTests.cs b/test/PatternKit.Generators.Tests/TemplateGeneratorTests.cs index 4f0a9be7..ec73935c 100644 --- a/test/PatternKit.Generators.Tests/TemplateGeneratorTests.cs +++ b/test/PatternKit.Generators.Tests/TemplateGeneratorTests.cs @@ -182,8 +182,8 @@ private void Transform(ImportContext ctx) .First(gs => gs.HintName.Contains("ImportWorkflow")) .SourceText.ToString(); - var onStartIndex = generatedSource.IndexOf("OnStart(ctx);"); - var validateIndex = generatedSource.IndexOf("Validate(ctx);"); + var onStartIndex = generatedSource.IndexOf("OnStart(ctx);", StringComparison.Ordinal); + var validateIndex = generatedSource.IndexOf("Validate(ctx);", StringComparison.Ordinal); ScenarioExpect.True(onStartIndex < validateIndex, "BeforeAll hook should be called before steps"); } @@ -243,8 +243,8 @@ private void OnComplete(ImportContext ctx) .First(gs => gs.HintName.Contains("ImportWorkflow")) .SourceText.ToString(); - var transformIndex = generatedSource.LastIndexOf("Transform(ctx);"); - var onCompleteIndex = generatedSource.IndexOf("OnComplete(ctx);"); + var transformIndex = generatedSource.LastIndexOf("Transform(ctx);", StringComparison.Ordinal); + var onCompleteIndex = generatedSource.IndexOf("OnComplete(ctx);", StringComparison.Ordinal); ScenarioExpect.True(transformIndex < onCompleteIndex, "AfterAll hook should be called after steps"); } diff --git a/test/PatternKit.Generators.Tests/VisitorGeneratorTests.cs b/test/PatternKit.Generators.Tests/VisitorGeneratorTests.cs index 931d1f56..50725335 100644 --- a/test/PatternKit.Generators.Tests/VisitorGeneratorTests.cs +++ b/test/PatternKit.Generators.Tests/VisitorGeneratorTests.cs @@ -849,7 +849,7 @@ public partial class ValidDerived : ValidBase { } // Should have no generator diagnostics var diagnostics = run.Results.SelectMany(r => r.Diagnostics) - .Where(d => d.Id.StartsWith("PKVIS")) + .Where(d => d.Id.StartsWith("PKVIS", StringComparison.Ordinal)) .ToArray(); ScenarioExpect.Empty(diagnostics); }