Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -91,20 +91,22 @@ dotnet_diagnostic.SA1214.severity = suggestion
# StyleCop: disable field access modifier rules
dotnet_diagnostic.SA1401.severity = suggestion

# Line length guidelines - shows visual vertical line at 120 characters
# Line length guidelines
# This is a modern standard for C# (Roslyn, ASP.NET Core, etc.)
# Provides visual feedback without breaking builds
[*.cs]
guidelines = 120
max_line_length = 120


# SA1010: Opening square brackets should be spaced correctly
dotnet_diagnostic.SA1010.severity = none

# String comparison: flag missing StringComparison on string methods (suggestion-only;
# promote to warning after fixing pre-existing violations — see issue #34)
# String comparison: require explicit StringComparison on string methods.
# All pre-existing violations were fixed in #34. Severity is warning so
# TreatWarningsAsErrors catches any new violations at build time.
# CA1307: Specify StringComparison for clarity
dotnet_diagnostic.CA1307.severity = suggestion
dotnet_diagnostic.CA1307.severity = warning
# CA1310: Specify StringComparison for correctness
dotnet_diagnostic.CA1310.severity = suggestion
dotnet_diagnostic.CA1310.severity = warning
Comment thread
daviburg marked this conversation as resolved.


12 changes: 6 additions & 6 deletions Server.Tests/DirectClientApiUrlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ public void DirectClientUrl_TrimsTrailingSlash()

string actual = $"{runtimeUrl.TrimEnd('/')}{operationPath}";

Assert.IsTrue(actual.Contains("/abc123/datasets"), $"URL should not have double slashes: {actual}");
Assert.IsFalse(actual.Contains("//datasets"), $"URL has double slashes: {actual}");
Assert.IsTrue(actual.Contains("/abc123/datasets", StringComparison.Ordinal), $"URL should not have double slashes: {actual}");
Assert.IsFalse(actual.Contains("//datasets", StringComparison.Ordinal), $"URL has double slashes: {actual}");
}

[TestMethod]
Expand All @@ -44,9 +44,9 @@ public void ArmUrl_RequiresSubscriptionAndResourceGroup()

string url = $"{baseUrl}/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/connections/{armConnectionName}/dynamicInvoke?api-version={ApiVersion}";

Assert.IsTrue(url.Contains("subscriptions/sub-123"));
Assert.IsTrue(url.Contains("resourceGroups/rg-test"));
Assert.IsTrue(url.Contains("connections/abc123"));
Assert.IsTrue(url.Contains("subscriptions/sub-123", StringComparison.Ordinal));
Assert.IsTrue(url.Contains("resourceGroups/rg-test", StringComparison.Ordinal));
Assert.IsTrue(url.Contains("connections/abc123", StringComparison.Ordinal));
}

[TestMethod]
Expand All @@ -61,6 +61,6 @@ public void ArmUrl_HasEmptySegments_WhenConfigMissing()
string url = $"{baseUrl}/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/connections/{armConnectionName}/dynamicInvoke?api-version={ApiVersion}";

// URL has empty segments — this would fail with Azure
Assert.IsTrue(url.Contains("subscriptions//resourceGroups//"), "ARM URL has empty segments when config is missing");
Assert.IsTrue(url.Contains("subscriptions//resourceGroups//", StringComparison.Ordinal), "ARM URL has empty segments when config is missing");
}
}
60 changes: 30 additions & 30 deletions Server.Tests/SchemaToClassGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ public void GenerateClass_SimpleStringProperties_ProducesCorrectOutput()
targetNamespace: "MyApp.Models");

// Assert
Assert.IsTrue(result.Contains("public class PostMessageInput : DynamicPostMessageRequest"), message: "Should inherit from base class.");
Assert.IsTrue(result.Contains("[JsonPropertyName(\"messageBody\")]"), message: "Should have JsonPropertyName for messageBody.");
Assert.IsTrue(result.Contains("[JsonPropertyName(\"subject\")]"), message: "Should have JsonPropertyName for subject.");
Assert.IsTrue(result.Contains("public string? MessageBody { get; set; }"), message: "Should have PascalCase property.");
Assert.IsTrue(result.Contains("public string? Subject { get; set; }"), message: "Should have PascalCase property.");
Assert.IsTrue(result.Contains("namespace MyApp.Models;"), message: "Should have target namespace.");
Assert.IsTrue(result.Contains("using Azure.Connectors.Sdk.Teams;"), message: "Should import base class namespace.");
Assert.IsTrue(result.Contains("public class PostMessageInput : DynamicPostMessageRequest", StringComparison.Ordinal), message: "Should inherit from base class.");
Assert.IsTrue(result.Contains("[JsonPropertyName(\"messageBody\")]", StringComparison.Ordinal), message: "Should have JsonPropertyName for messageBody.");
Assert.IsTrue(result.Contains("[JsonPropertyName(\"subject\")]", StringComparison.Ordinal), message: "Should have JsonPropertyName for subject.");
Assert.IsTrue(result.Contains("public string? MessageBody { get; set; }", StringComparison.Ordinal), message: "Should have PascalCase property.");
Assert.IsTrue(result.Contains("public string? Subject { get; set; }", StringComparison.Ordinal), message: "Should have PascalCase property.");
Assert.IsTrue(result.Contains("namespace MyApp.Models;", StringComparison.Ordinal), message: "Should have target namespace.");
Assert.IsTrue(result.Contains("using Azure.Connectors.Sdk.Teams;", StringComparison.Ordinal), message: "Should import base class namespace.");
}

[TestMethod]
Expand All @@ -69,7 +69,7 @@ public void GenerateClass_SameNamespace_OmitsUsingDirective()
targetNamespace: "MyApp.Models");

// Assert
Assert.IsFalse(result.Contains("using MyApp.Models;"), message: "Should not import own namespace.");
Assert.IsFalse(result.Contains("using MyApp.Models;", StringComparison.Ordinal), message: "Should not import own namespace.");
}

[TestMethod]
Expand Down Expand Up @@ -97,9 +97,9 @@ public void GenerateClass_IntegerAndBooleanTypes_MapsCorrectly()
targetNamespace: "App");

// Assert
Assert.IsTrue(result.Contains("public int? RetryCount { get; set; }"), message: "integer should map to int?.");
Assert.IsTrue(result.Contains("public bool? IsEnabled { get; set; }"), message: "boolean should map to bool?.");
Assert.IsTrue(result.Contains("public double? Threshold { get; set; }"), message: "number should map to double?.");
Assert.IsTrue(result.Contains("public int? RetryCount { get; set; }", StringComparison.Ordinal), message: "integer should map to int?.");
Assert.IsTrue(result.Contains("public bool? IsEnabled { get; set; }", StringComparison.Ordinal), message: "boolean should map to bool?.");
Assert.IsTrue(result.Contains("public double? Threshold { get; set; }", StringComparison.Ordinal), message: "number should map to double?.");
}

[TestMethod]
Expand Down Expand Up @@ -132,10 +132,10 @@ public void GenerateClass_NestedObject_GeneratesNestedClass()
targetNamespace: "App");

// Assert
Assert.IsTrue(result.Contains("public Recipient? Recipient { get; set; }"), message: "Nested object should be a typed reference.");
Assert.IsTrue(result.Contains("public class Recipient"), message: "Nested class should be generated.");
Assert.IsTrue(result.Contains("[JsonPropertyName(\"groupId\")]"), message: "Nested properties should have JsonPropertyName.");
Assert.IsTrue(result.Contains("public string? GroupId { get; set; }"), message: "Nested properties should be PascalCase.");
Assert.IsTrue(result.Contains("public Recipient? Recipient { get; set; }", StringComparison.Ordinal), message: "Nested object should be a typed reference.");
Assert.IsTrue(result.Contains("public class Recipient", StringComparison.Ordinal), message: "Nested class should be generated.");
Assert.IsTrue(result.Contains("[JsonPropertyName(\"groupId\")]", StringComparison.Ordinal), message: "Nested properties should have JsonPropertyName.");
Assert.IsTrue(result.Contains("public string? GroupId { get; set; }", StringComparison.Ordinal), message: "Nested properties should be PascalCase.");
}

[TestMethod]
Expand Down Expand Up @@ -164,7 +164,7 @@ public void GenerateClass_ArrayType_MapsToList()
targetNamespace: "App");

// Assert
Assert.IsTrue(result.Contains("public List<string>? Tags { get; set; }"), message: "Array of strings should map to List<string>?.");
Assert.IsTrue(result.Contains("public List<string>? Tags { get; set; }", StringComparison.Ordinal), message: "Array of strings should map to List<string>?.");
}

[TestMethod]
Expand Down Expand Up @@ -192,7 +192,7 @@ public void GenerateClass_ObjectWithoutProperties_MapsToJsonElement()
targetNamespace: "App");

// Assert
Assert.IsTrue(result.Contains("public JsonElement? Metadata { get; set; }"), message: "Object without properties should map to JsonElement?.");
Assert.IsTrue(result.Contains("public JsonElement? Metadata { get; set; }", StringComparison.Ordinal), message: "Object without properties should map to JsonElement?.");
}

[TestMethod]
Expand Down Expand Up @@ -221,7 +221,7 @@ public void GenerateClass_DateTimeFormat_MapsToDateTimeOffset()
targetNamespace: "App");

// Assert
Assert.IsTrue(result.Contains("public DateTimeOffset? CreatedAt { get; set; }"), message: "date-time format should map to DateTimeOffset?.");
Assert.IsTrue(result.Contains("public DateTimeOffset? CreatedAt { get; set; }", StringComparison.Ordinal), message: "date-time format should map to DateTimeOffset?.");
}

[TestMethod]
Expand Down Expand Up @@ -251,8 +251,8 @@ public void GenerateClass_XmlDocFromDescription_IncludedInOutput()
targetNamespace: "App");

// Assert
Assert.IsTrue(result.Contains("/// A test object."), message: "Class-level description should be in XML doc.");
Assert.IsTrue(result.Contains("/// The display name."), message: "Property-level description should be in XML doc.");
Assert.IsTrue(result.Contains("/// A test object.", StringComparison.Ordinal), message: "Class-level description should be in XML doc.");
Assert.IsTrue(result.Contains("/// The display name.", StringComparison.Ordinal), message: "Property-level description should be in XML doc.");
}

[TestMethod]
Expand All @@ -276,9 +276,9 @@ public void GenerateClass_EmptyProperties_EmptyClass()
targetNamespace: "App");

// Assert
Assert.IsTrue(result.Contains("public class EmptyInput : DynamicEmptyRequest"), message: "Should still generate the class.");
Assert.IsTrue(result.Contains("{"), message: "Should have opening brace.");
Assert.IsTrue(result.Contains("}"), message: "Should have closing brace.");
Assert.IsTrue(result.Contains("public class EmptyInput : DynamicEmptyRequest", StringComparison.Ordinal), message: "Should still generate the class.");
Assert.IsTrue(result.Contains("{", StringComparison.Ordinal), message: "Should have opening brace.");
Assert.IsTrue(result.Contains("}", StringComparison.Ordinal), message: "Should have closing brace.");
}

[TestMethod]
Expand Down Expand Up @@ -307,7 +307,7 @@ public void GenerateClass_XmsSummary_UsedAsDescription()
targetNamespace: "App");

// Assert
Assert.IsTrue(result.Contains("/// Current status of the item."), message: "x-ms-summary should be used as description.");
Assert.IsTrue(result.Contains("/// Current status of the item.", StringComparison.Ordinal), message: "x-ms-summary should be used as description.");
}

[TestMethod]
Expand Down Expand Up @@ -366,8 +366,8 @@ public void GenerateClass_DescriptionWithSpecialChars_EscapedInXmlDoc()
targetNamespace: "App");

// Assert
Assert.IsTrue(result.Contains("&lt;b&gt;"), message: "Angle brackets should be XML-escaped.");
Assert.IsTrue(result.Contains("&amp;"), message: "Ampersand should be XML-escaped.");
Assert.IsTrue(result.Contains("&lt;b&gt;", StringComparison.Ordinal), message: "Angle brackets should be XML-escaped.");
Assert.IsTrue(result.Contains("&amp;", StringComparison.Ordinal), message: "Ampersand should be XML-escaped.");
}

[TestMethod]
Expand Down Expand Up @@ -402,10 +402,10 @@ public void GenerateClass_ArrayOfObjects_GeneratesItemClass()
targetNamespace: "App");

// Assert
Assert.IsTrue(result.Contains("public List<AttachmentsItem>? Attachments { get; set; }"), message: "Array of objects should reference the generated item class.");
Assert.IsTrue(result.Contains("public class AttachmentsItem"), message: "Item class should be generated.");
Assert.IsTrue(result.Contains("public string? Name { get; set; }"), message: "Item class should have string property.");
Assert.IsTrue(result.Contains("public int? Size { get; set; }"), message: "Item class should have int property.");
Assert.IsTrue(result.Contains("public List<AttachmentsItem>? Attachments { get; set; }", StringComparison.Ordinal), message: "Array of objects should reference the generated item class.");
Assert.IsTrue(result.Contains("public class AttachmentsItem", StringComparison.Ordinal), message: "Item class should be generated.");
Assert.IsTrue(result.Contains("public string? Name { get; set; }", StringComparison.Ordinal), message: "Item class should have string property.");
Assert.IsTrue(result.Contains("public int? Size { get; set; }", StringComparison.Ordinal), message: "Item class should have int property.");
}

[TestMethod]
Expand Down
12 changes: 6 additions & 6 deletions Server.Tests/SourceCodeUpdateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ public void ApplySourceCodeUpdate_FullyQualifiedType_ReplacedWithGeneratedClass(

// Assert
Assert.IsTrue(
result.Contains("new PostMessageInput()"),
result.Contains("new PostMessageInput()", StringComparison.Ordinal),
message: "Should replace the fully-qualified new expression.");
Assert.IsFalse(
result.Contains("DynamicPostMessageRequest"),
result.Contains("DynamicPostMessageRequest", StringComparison.Ordinal),
message: "Original type name should be gone.");
Assert.IsTrue(
result.Contains("AdditionalProperties"),
result.Contains("AdditionalProperties", StringComparison.Ordinal),
message: "AdditionalProperties access should remain unchanged.");
}

Expand All @@ -50,7 +50,7 @@ public void ApplySourceCodeUpdate_ShortTypeName_ReplacedWithGeneratedClass()

// Assert
Assert.IsTrue(
result.Contains("new PostMessageInput()"),
result.Contains("new PostMessageInput()", StringComparison.Ordinal),
message: "Should replace short-form new expression.");
}

Expand Down Expand Up @@ -114,10 +114,10 @@ public void Process(DynamicPostMessageRequest existingParam)

// Assert — only `new DynamicPostMessageRequest()` should be replaced
Assert.IsTrue(
result.Contains("new PostMessageInput()"),
result.Contains("new PostMessageInput()", StringComparison.Ordinal),
message: "new expression should be replaced.");
Assert.IsTrue(
result.Contains("DynamicPostMessageRequest existingParam"),
result.Contains("DynamicPostMessageRequest existingParam", StringComparison.Ordinal),
message: "Parameter type should remain unchanged — it's the base type, still valid.");
}
}
4 changes: 3 additions & 1 deletion Server/BufferManager.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Collections.Concurrent;

public class BufferManager
namespace SdkLspServer;

internal class BufferManager
{
private readonly ConcurrentDictionary<string, string> buffers = new();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace SdkLspServer.Handlers.CodeActionHandler;
/// Detection is fast (syntax + cached SRM metadata). No schema fetch or code generation
/// happens here — that's deferred to GenerateDynamicSchemaCommandHandler when the user clicks.
/// </summary>
public class DynamicSchemaCodeActionHandler(
internal class DynamicSchemaCodeActionHandler(
SdkIndex? sdkIndex,
BufferManager bufferManager,
ITelemetryService telemetryService) : CodeActionHandlerBase
Expand Down Expand Up @@ -238,7 +238,7 @@ private static (string? TypeName, string? Namespace) ExtractTypeNameFromSyntax(T

// Infer connector from namespace: "Azure.Connectors.Sdk.Teams" → "teams"
string? connectorName = null;
string typeNamespace = matchingFullName.Contains('.')
string typeNamespace = matchingFullName.Contains('.', StringComparison.Ordinal)
? matchingFullName[..matchingFullName.LastIndexOf('.')]
: string.Empty;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace SdkLspServer.Handlers.CodeActionHandler;
/// clicks the Code Action. Does the schema fetch + class generation + writes the file
/// to disk and updates the source document to use the generated type.
/// </summary>
public class GenerateDynamicSchemaCommandHandler : ExecuteCommandHandlerBase
internal class GenerateDynamicSchemaCommandHandler : ExecuteCommandHandlerBase
{
/// <summary>
/// The command identifier used in CodeAction.Command and registered with the LSP server.
Expand Down
2 changes: 1 addition & 1 deletion Server/Handlers/CodeLensHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace SdkLspServer.Handlers;
/// for SDK methods and types. CodeLens items appear as clickable text above methods
/// and provide quick actions like documentation links and usage examples.
/// </summary>
public class CodeLensHandler(SdkIndex? sdkIndex, BufferManager bufferManager, CodeLensConfig codeLensConfig, ITelemetryService telemetryService, Services.CompilationService compilationService) : CodeLensHandlerBase
internal class CodeLensHandler(SdkIndex? sdkIndex, BufferManager bufferManager, CodeLensConfig codeLensConfig, ITelemetryService telemetryService, Services.CompilationService compilationService) : CodeLensHandlerBase
{
private readonly SdkIndex? sdkIndex = sdkIndex;
private readonly BufferManager bufferManager = bufferManager;
Expand Down
Loading
Loading