diff --git a/CHANGELOG.md b/CHANGELOG.md index 0157f639d1..5876f44c57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove LINQ usage in generated code. - Ensures descriptions are not empty in sliced OpenApi file when generating a plugin. - Plugins do not emit parameters anymore. [#4841](https://github.com/microsoft/kiota/issues/4841) +- References to C# types generated by kiota are prefixed with `global::` to avoid name collisions. [#4796](https://github.com/microsoft/kiota/issues/4796) ## [1.15.0] - 2024-06-06 diff --git a/src/Kiota.Builder/Writers/CSharp/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeClassDeclarationWriter.cs index f3431da66f..c6aa150712 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeClassDeclarationWriter.cs @@ -33,7 +33,6 @@ public override void WriteCodeElement(ClassDeclaration codeElement, LanguageWrit var derivedTypes = (codeElement.Inherits is null ? Enumerable.Empty() : new string?[] { conventions.GetTypeString(codeElement.Inherits, parentClass) }) .Union(codeElement.Implements.Select(static x => x.Name)) .OfType() - .Select(static x => x.ToFirstCharacterUpperCase()) .ToArray(); var derivation = derivedTypes.Length != 0 ? ": " + derivedTypes.Aggregate(static (x, y) => $"{x}, {y}") : string.Empty; bool hasDescription = conventions.WriteLongDescription(parentClass, writer); diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs index ead9a4484b..2645ea5550 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs @@ -365,7 +365,7 @@ private string GetDeserializationMethodName(CodeTypeBase propType, CodeMethod me { "byte[]" => "GetByteArrayValue()", _ when conventions.IsPrimitiveType(propertyType) => $"Get{propertyType.TrimEnd(CSharpConventionService.NullableMarker).ToFirstCharacterUpperCase()}Value()", - _ => $"GetObjectValue<{propertyType.ToFirstCharacterUpperCase()}>({propertyType}.CreateFromDiscriminatorValue)", + _ => $"GetObjectValue<{propertyType}>({propertyType}.CreateFromDiscriminatorValue)", }; } protected void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requestParams, CodeClass parentClass, bool isVoid, string returnTypeWithoutCollectionInformation, LanguageWriter writer) @@ -671,7 +671,7 @@ private string GetSerializationMethodName(CodeTypeBase propType, CodeMethod meth { "byte[]" => "WriteByteArrayValue", _ when conventions.IsPrimitiveType(propertyType) => $"Write{propertyType.TrimEnd(CSharpConventionService.NullableMarker).ToFirstCharacterUpperCase()}Value", - _ => $"WriteObjectValue<{propertyType.ToFirstCharacterUpperCase()}{(includeNullableRef ? "?" : string.Empty)}>", + _ => $"WriteObjectValue<{propertyType}{(includeNullableRef ? "?" : string.Empty)}>", }; } } diff --git a/src/Kiota.Builder/Writers/CSharp/TypeDefinitionExtensions.cs b/src/Kiota.Builder/Writers/CSharp/TypeDefinitionExtensions.cs index c6ef2fac85..3f07164c29 100644 --- a/src/Kiota.Builder/Writers/CSharp/TypeDefinitionExtensions.cs +++ b/src/Kiota.Builder/Writers/CSharp/TypeDefinitionExtensions.cs @@ -33,7 +33,7 @@ private static StringBuilder AppendTypeName(ITypeDefinition typeDefinition, Stri case CodeNamespace codeNamespace: { if (!string.IsNullOrEmpty(codeNamespace.Name)) - fullNameBuilder.Insert(0, $"{codeNamespace.Name}."); + fullNameBuilder.Insert(0, $"global::{codeNamespace.Name}."); return fullNameBuilder; } diff --git a/tests/Kiota.Builder.Tests/Writers/CLI/CliCodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/CLI/CliCodeMethodWriterTests.cs index 1755ff0519..a5b9e04432 100644 --- a/tests/Kiota.Builder.Tests/Writers/CLI/CliCodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/CLI/CliCodeMethodWriterTests.cs @@ -241,7 +241,7 @@ public void WritesIndexerCommands() writer.Write(method); var result = tw.ToString(); - Assert.Contains("var builder = new Test.Name.Sub.TestClass", result); + Assert.Contains("var builder = new global::Test.Name.Sub.TestClass", result); Assert.Contains("var commands = new List();", result); Assert.Contains("commands.Add(builder.BuildTestMethod1());", result); Assert.Contains("commands.AddRange(builder.BuildTestMethod2());", result); @@ -325,7 +325,7 @@ public void WritesMatchingIndexerCommandsIntoExecutableCommand() writer.Write(method); var result = tw.ToString(); - Assert.Contains("var testItemIdx = new Test.Name.Sub.TestItemRequestBuilder();", result); + Assert.Contains("var testItemIdx = new global::Test.Name.Sub.TestItemRequestBuilder();", result); Assert.Contains("var command = testItemIdx.BuildTestMethod1();", result); Assert.Contains("var cmds = testItemIdx.BuildTestMethod2();", result); Assert.DoesNotContain("execCommands.AddRange(cmds.Item1)", result); @@ -418,12 +418,12 @@ public void WritesMatchingIndexerCommandsIntoContainerCommand() writer.Write(method); var result = tw.ToString(); - Assert.Contains("var testItemIndexer = new Test.Name.Sub.TestIndexItemRequestBuilder();", result); + Assert.Contains("var testItemIndexer = new global::Test.Name.Sub.TestIndexItemRequestBuilder();", result); Assert.Contains("var command = testItemIndexer.BuildTestMethod1();", result); Assert.Contains("var cmds = testItemIndexer.BuildTestMethod2();", result); Assert.DoesNotContain("execCommands.AddRange(cmds.Item1);", result); Assert.Contains("nonExecCommands.AddRange(cmds.Item2);", result); - Assert.Contains("var builder = new Test.TestNavItemRequestBuilder", result); + Assert.Contains("var builder = new global::Test.TestNavItemRequestBuilder", result); Assert.Contains("nonExecCommands.Add(builder.BuildTestMethod11());", result); Assert.Contains("return command;", result); Assert.DoesNotContain("nonExecCommands.Add(builder.BuildTestMethod3());", result); @@ -553,7 +553,7 @@ public void WritesNavCommandThatSkipsReusedNavCommandInstance() var result = tw.ToString(); Assert.Contains("var command = new Command(\"user\");", result); - Assert.Contains("var builder = new Test.TestNavItemRequestBuilder();", result); + Assert.Contains("var builder = new global::Test.TestNavItemRequestBuilder();", result); Assert.Contains("execCommands.Add(builder.BuildExecutableTestMethod());", result); Assert.Contains("return command;", result); Assert.DoesNotContain("BuildNavTestMethod", result); @@ -596,7 +596,7 @@ public void WritesContainerCommands() var result = tw.ToString(); Assert.Contains("var command = new Command(\"user\");", result); - Assert.Contains("var builder = new Test.Name.Sub1.Sub2.TestClass1", result); + Assert.Contains("var builder = new global::Test.Name.Sub1.Sub2.TestClass1", result); Assert.Contains("nonExecCommands.Add(builder.BuildTestMethod1());", result); Assert.Contains("nonExecCommands.Add(builder.BuildTestMethod2());", result); Assert.Contains("return command;", result); @@ -637,7 +637,7 @@ public void WritesRequestBuilderWithParametersCommands() var result = tw.ToString(); Assert.Contains("var command = new Command(\"user\");", result); - Assert.Contains("var builder = new Test.Name.Sub1.Sub2.TestClass1", result); + Assert.Contains("var builder = new global::Test.Name.Sub1.Sub2.TestClass1", result); Assert.Contains("nonExecCommands.Add(builder.BuildTestMethod1());", result); Assert.Contains("nonExecCommands.Add(builder.BuildTestMethod2());", result); Assert.Contains("return command;", result); @@ -702,9 +702,9 @@ public void WritesContainerCommandWithConflictingTypes() var result = tw.ToString(); Assert.Contains("var command = new Command(\"user\");", result); - Assert.Contains("var builder = new Test.A.B.C.D.E.F.TestRequestBuilder", result); + Assert.Contains("var builder = new global::Test.A.B.C.D.E.F.TestRequestBuilder", result); // Test case insensitive match - Assert.Contains("var builder = new Test.A.B.C.D.E.F.TestRequestBuilder2", result); + Assert.Contains("var builder = new global::Test.A.B.C.D.E.F.TestRequestBuilder2", result); Assert.Contains("nonExecCommands.Add(builder.BuildTestMethod1());", result); Assert.Contains("foreach (var cmd in nonExecCommands)", result); Assert.Contains("command.AddCommand(cmd);", result); @@ -1085,7 +1085,7 @@ public void WritesExecutableCommandForPostRequestWithModelBody() Assert.Contains("command.AddOption(bodyOption);", result); Assert.Contains("var body = invocationContext.ParseResult.GetValueForOption(bodyOption) ?? string.Empty;", result); Assert.Contains("using var stream = new MemoryStream(Encoding.UTF8.GetBytes(body));", result); - Assert.Contains("var model = parseNode.GetObjectValue(Test.Content.CreateFromDiscriminatorValue);", result); + Assert.Contains("var model = parseNode.GetObjectValue(global::Test.Content.CreateFromDiscriminatorValue);", result); Assert.Contains("if (model is null)", result); Assert.Contains("Console.Error.WriteLine(\"No model data to send.\")", result); Assert.Contains("var requestInfo = CreatePostRequestInformation", result); @@ -1171,7 +1171,7 @@ public void WritesExecutableCommandForPostRequestWithModelBodyAndContentType() Assert.Contains("var body = invocationContext.ParseResult.GetValueForOption(bodyOption) ?? string.Empty;", result); Assert.Contains("var contentType = invocationContext.ParseResult.GetValueForOption(contentTypeOption);", result); Assert.Contains("using var stream = new MemoryStream(Encoding.UTF8.GetBytes(body));", result); - Assert.Contains("var model = parseNode.GetObjectValue(Test.Content.CreateFromDiscriminatorValue);", result); + Assert.Contains("var model = parseNode.GetObjectValue(global::Test.Content.CreateFromDiscriminatorValue);", result); Assert.Contains("if (model is null)", result); Assert.Contains("Console.Error.WriteLine(\"No model data to send.\")", result); Assert.Contains("var requestInfo = CreatePostRequestInformation(model, contentType", result); @@ -1242,7 +1242,7 @@ public void WritesExecutableCommandForPostRequestWithCollectionModel() Assert.Contains("command.AddOption(bodyOption);", result); Assert.Contains("var body = invocationContext.ParseResult.GetValueForOption(bodyOption) ?? string.Empty;", result); Assert.Contains("using var stream = new MemoryStream(Encoding.UTF8.GetBytes(body));", result); - Assert.Contains("var model = parseNode.GetCollectionOfObjectValues(Test.Content.CreateFromDiscriminatorValue)?.ToList();", result); + Assert.Contains("var model = parseNode.GetCollectionOfObjectValues(global::Test.Content.CreateFromDiscriminatorValue)?.ToList();", result); Assert.Contains("if (model is null)", result); Assert.Contains("Console.Error.WriteLine(\"No model data to send.\")", result); Assert.Contains("var requestInfo = CreatePostRequestInformation", result); diff --git a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs index 17fb96c17a..e1529d3112 100644 --- a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs @@ -737,8 +737,8 @@ public void WritesModelFactoryBodyAndDisambiguateAmbiguousImportedTypes() Assert.Contains("var mappingValue = parseNode.GetChildNode(\"@odata.type\")?.GetStringValue()", result); Assert.Contains("return mappingValue switch", result); - Assert.Contains("\"namespaceLevelOne.ConflictingModel\" => new namespaceLevelOne.ConflictingModel(),", result); //Assert the disambiguation happens due to the enum imported - Assert.Contains("_ => new models.ConflictingModelBaseClass()", result); + Assert.Contains("\"namespaceLevelOne.ConflictingModel\" => new global::namespaceLevelOne.ConflictingModel(),", result); //Assert the disambiguation happens due to the enum imported + Assert.Contains("_ => new global::models.ConflictingModelBaseClass()", result); AssertExtensions.CurlyBracesAreClosed(result); } [Fact] @@ -1532,7 +1532,7 @@ public void WritesConstructorWithEnumValue() writer.Write(method); var result = tw.ToString(); Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); - Assert.Contains($"{propName.ToFirstCharacterUpperCase()} = {modelsNamespace.Name}.{codeEnum.Name.ToFirstCharacterUpperCase()}.{defaultValue.CleanupSymbolName()}", result);//ensure symbol is cleaned up + Assert.Contains($"{propName.ToFirstCharacterUpperCase()} = global::{modelsNamespace.Name}.{codeEnum.Name.ToFirstCharacterUpperCase()}.{defaultValue.CleanupSymbolName()}", result);//ensure symbol is cleaned up } [Fact] public void WritesConstructorAndIncludesSanitizedEnumValue() @@ -1557,7 +1557,7 @@ public void WritesConstructorAndIncludesSanitizedEnumValue() var result = tw.ToString(); Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); Assert.Contains("PictureSize.Slash", result);//ensure symbol is cleaned up - Assert.Contains($"{propName.ToFirstCharacterUpperCase()} = {modelsNamespace.Name}.{codeEnum.Name.ToFirstCharacterUpperCase()}.{defaultValue.CleanupSymbolName()}", result);//ensure symbol is cleaned up + Assert.Contains($"{propName.ToFirstCharacterUpperCase()} = global::{modelsNamespace.Name}.{codeEnum.Name.ToFirstCharacterUpperCase()}.{defaultValue.CleanupSymbolName()}", result);//ensure symbol is cleaned up } [Fact] public void WritesConstructorAndDisambiguatesEnumType() @@ -1581,8 +1581,8 @@ public void WritesConstructorAndDisambiguatesEnumType() writer.Write(method); var result = tw.ToString(); Assert.Contains(parentClass.Name.ToFirstCharacterUpperCase(), result); - Assert.Contains("models.TestEnum.First;", result);//ensure enum is fully qualified due to conflicting property name - Assert.Contains($"{propName.ToFirstCharacterUpperCase()} = models.TestEnum.First;", result);//ensure enum is fully qualified due to conflicting property name + Assert.Contains("global::models.TestEnum.First;", result);//ensure enum is fully qualified due to conflicting property name + Assert.Contains($"{propName.ToFirstCharacterUpperCase()} = global::models.TestEnum.First;", result);//ensure enum is fully qualified due to conflicting property name } [Fact] public void DoesNotWriteConstructorWithDefaultFromComposedType() diff --git a/tests/Kiota.Builder.Tests/Writers/CSharp/CodePropertyWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/CSharp/CodePropertyWriterTests.cs index 96524a9e7f..c0fec9dc0d 100644 --- a/tests/Kiota.Builder.Tests/Writers/CSharp/CodePropertyWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/CSharp/CodePropertyWriterTests.cs @@ -74,7 +74,7 @@ public void WritesRequestBuilder() writer.Write(property); var result = tw.ToString(); Assert.Contains("get =>", result); - Assert.Contains($"new {rootNamespace.Name}.{TypeName}", result); + Assert.Contains($"new global::{rootNamespace.Name}.{TypeName}", result); Assert.Contains("RequestAdapter", result); Assert.Contains("PathParameters", result); } @@ -103,7 +103,7 @@ public void MapsCustomPropertiesToBackingStore() property.Kind = CodePropertyKind.Custom; writer.Write(property); var result = tw.ToString(); - Assert.Contains("get { return BackingStore?.Get<" + rootNamespace.Name + ".SomeCustomClass>(\"propertyName\"); }", result); + Assert.Contains("get { return BackingStore?.Get(\"propertyName\"); }", result); Assert.Contains("set { BackingStore?.Set(\"propertyName\", value);", result); } [Fact] @@ -113,7 +113,7 @@ public void MapsAdditionalDataPropertiesToBackingStore() property.Kind = CodePropertyKind.AdditionalData; writer.Write(property); var result = tw.ToString(); - Assert.Contains("get { return BackingStore.Get<" + rootNamespace.Name + ".SomeCustomClass>(\"propertyName\") ?? new Dictionary(); }", result); + Assert.Contains("get { return BackingStore.Get(\"propertyName\") ?? new Dictionary(); }", result); Assert.Contains("set { BackingStore.Set(\"propertyName\", value);", result); } [Fact] diff --git a/tests/Kiota.Builder.Tests/Writers/CSharp/TypeDefinitionExtensionsTests.cs b/tests/Kiota.Builder.Tests/Writers/CSharp/TypeDefinitionExtensionsTests.cs index 41c397b34f..92d6f5f914 100644 --- a/tests/Kiota.Builder.Tests/Writers/CSharp/TypeDefinitionExtensionsTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/CSharp/TypeDefinitionExtensionsTests.cs @@ -37,7 +37,7 @@ public void ReturnsFullNameForTypeInNamespace() var fullName = TypeDefinitionExtensions.GetFullName(myClass); - Assert.Equal("MyNamespace.MyClass", fullName); + Assert.Equal("global::MyNamespace.MyClass", fullName); } [Fact] @@ -62,8 +62,8 @@ public void ReturnsFullNameForNestedTypes() var parentClassFullName = TypeDefinitionExtensions.GetFullName(myParentClass); var nestedClassFullName = TypeDefinitionExtensions.GetFullName(myNestedClass); - Assert.Equal("MyNamespace.MyParentClass", parentClassFullName); - Assert.Equal("MyNamespace.MyParentClass.MyNestedClass", nestedClassFullName); + Assert.Equal("global::MyNamespace.MyParentClass", parentClassFullName); + Assert.Equal("global::MyNamespace.MyParentClass.MyNestedClass", nestedClassFullName); } [Fact] @@ -117,7 +117,7 @@ public void CapitalizesTypeNamesInTypeHierarchyButNotTheNamespace() var nestedClassFullName = TypeDefinitionExtensions.GetFullName(myNestedClass); - Assert.Equal("myNamespace.MyParentClass.MyNestedClass", nestedClassFullName); + Assert.Equal("global::myNamespace.MyParentClass.MyNestedClass", nestedClassFullName); } [Fact] @@ -137,4 +137,20 @@ public void DoesNotAppendNamespaceSegmentIfNamespaceNameIsEmpty() Assert.Equal("MyClass", fullName); } + + [Fact] + public void PrependsGlobalNamespaceAliasToNamespaces() + { + var rootNamespace = CodeNamespace.InitRootNamespace(); + var myNamespace = rootNamespace.AddNamespace("MyNamespace"); + var myClass = new CodeClass() + { + Name = "MyClass" + }; + myNamespace.AddClass(myClass); + + var fullName = TypeDefinitionExtensions.GetFullName(myClass); + + Assert.StartsWith("global::", fullName, StringComparison.Ordinal); + } }