diff --git a/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs b/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs index 13dc91a836..f138985157 100644 --- a/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs +++ b/src/JsonApiDotNetCore.SourceGenerators/SourceCodeWriter.cs @@ -66,7 +66,7 @@ public string Write(INamedTypeSymbol resourceType, ITypeSymbol idType, JsonApiEn WriteNullableEnable(); } - WriteNamespaceImports(loggerFactoryInterface, resourceType); + WriteNamespaceImports(loggerFactoryInterface, resourceType, controllerNamespace); if (controllerNamespace != null) { @@ -96,7 +96,7 @@ private void WriteNullableEnable() _sourceBuilder.AppendLine(); } - private void WriteNamespaceImports(INamedTypeSymbol loggerFactoryInterface, INamedTypeSymbol resourceType) + private void WriteNamespaceImports(INamedTypeSymbol loggerFactoryInterface, INamedTypeSymbol resourceType, string? controllerNamespace) { _sourceBuilder.AppendLine($@"using {loggerFactoryInterface.ContainingNamespace};"); @@ -104,7 +104,7 @@ private void WriteNamespaceImports(INamedTypeSymbol loggerFactoryInterface, INam _sourceBuilder.AppendLine("using JsonApiDotNetCore.Controllers;"); _sourceBuilder.AppendLine("using JsonApiDotNetCore.Services;"); - if (!resourceType.ContainingNamespace.IsGlobalNamespace) + if (!resourceType.ContainingNamespace.IsGlobalNamespace && resourceType.ContainingNamespace.ToString() != controllerNamespace) { _sourceBuilder.AppendLine($"using {resourceType.ContainingNamespace};"); } diff --git a/test/SourceGeneratorTests/ControllerGenerationTests.cs b/test/SourceGeneratorTests/ControllerGenerationTests.cs index 7576e79417..629c5d49b8 100644 --- a/test/SourceGeneratorTests/ControllerGenerationTests.cs +++ b/test/SourceGeneratorTests/ControllerGenerationTests.cs @@ -25,12 +25,12 @@ public void Can_generate_for_default_controller() .WithNamespaceImportFor(typeof(ResourceAttribute)) .InNamespace("ExampleApi.Models") .WithCode(@" - [Resource] - public sealed class Item : Identifiable - { - [Attr] - public int Value { get; set; } - }") + [Resource] + public sealed class Item : Identifiable + { + [Attr] + public int Value { get; set; } + }") .Build(); Compilation inputCompilation = new CompilationBuilder() @@ -87,12 +87,12 @@ public void Can_generate_for_read_only_controller() .WithNamespaceImportFor(typeof(JsonApiEndpoints)) .InNamespace("ExampleApi.Models") .WithCode(@" - [Resource(GenerateControllerEndpoints = JsonApiEndpoints.Query)] - public sealed class Item : Identifiable - { - [Attr] - public int Value { get; set; } - }") + [Resource(GenerateControllerEndpoints = JsonApiEndpoints.Query)] + public sealed class Item : Identifiable + { + [Attr] + public int Value { get; set; } + }") .Build(); Compilation inputCompilation = new CompilationBuilder() @@ -149,12 +149,12 @@ public void Can_generate_for_write_only_controller() .WithNamespaceImportFor(typeof(JsonApiEndpoints)) .InNamespace("ExampleApi.Models") .WithCode(@" - [Resource(GenerateControllerEndpoints = JsonApiEndpoints.Command)] - public sealed class Item : Identifiable - { - [Attr] - public int Value { get; set; } - }") + [Resource(GenerateControllerEndpoints = JsonApiEndpoints.Command)] + public sealed class Item : Identifiable + { + [Attr] + public int Value { get; set; } + }") .Build(); Compilation inputCompilation = new CompilationBuilder() @@ -211,15 +211,15 @@ public void Can_generate_for_mixed_controller() .WithNamespaceImportFor(typeof(JsonApiEndpoints)) .InNamespace("ExampleApi.Models") .WithCode(@" - [Resource(GenerateControllerEndpoints = NoRelationshipEndpoints)] - public sealed class Item : Identifiable - { - private const JsonApiEndpoints NoRelationshipEndpoints = JsonApiEndpoints.GetCollection | - JsonApiEndpoints.GetSingle | JsonApiEndpoints.Post | JsonApiEndpoints.Patch | JsonApiEndpoints.Delete; - - [Attr] - public int Value { get; set; } - }") + [Resource(GenerateControllerEndpoints = NoRelationshipEndpoints)] + public sealed class Item : Identifiable + { + private const JsonApiEndpoints NoRelationshipEndpoints = JsonApiEndpoints.GetCollection | + JsonApiEndpoints.GetSingle | JsonApiEndpoints.Post | JsonApiEndpoints.Patch | JsonApiEndpoints.Delete; + + [Attr] + public int Value { get; set; } + }") .Build(); Compilation inputCompilation = new CompilationBuilder() @@ -284,11 +284,11 @@ public void Skips_for_resource_without_ResourceAttribute() .WithNamespaceImportFor(typeof(AttrAttribute)) .InNamespace("ExampleApi.Models") .WithCode(@" - public sealed class Item : Identifiable - { - [Attr] - public int Value { get; set; } - }") + public sealed class Item : Identifiable + { + [Attr] + public int Value { get; set; } + }") .Build(); Compilation inputCompilation = new CompilationBuilder() @@ -326,12 +326,12 @@ public void Skips_for_resource_with_no_endpoints() .WithNamespaceImportFor(typeof(JsonApiEndpoints)) .InNamespace("ExampleApi.Models") .WithCode(@" - [Resource(GenerateControllerEndpoints = JsonApiEndpoints.None)] - public sealed class Item : Identifiable - { - [Attr] - public int Value { get; set; } - }") + [Resource(GenerateControllerEndpoints = JsonApiEndpoints.None)] + public sealed class Item : Identifiable + { + [Attr] + public int Value { get; set; } + }") .Build(); Compilation inputCompilation = new CompilationBuilder() @@ -366,24 +366,24 @@ public void Skips_for_missing_dependency_on_JsonApiDotNetCore() string source = new SourceCodeBuilder() .InNamespace("ExampleApi.Models") .WithCode(@" - public abstract class Identifiable - { - } - - public sealed class ResourceAttribute : System.Attribute - { - } - - public sealed class AttrAttribute : System.Attribute - { - } - - [Resource] - public sealed class Item : Identifiable - { - [Attr] - public int Value { get; set; } - }") + public abstract class Identifiable + { + } + + public sealed class ResourceAttribute : System.Attribute + { + } + + public sealed class AttrAttribute : System.Attribute + { + } + + [Resource] + public sealed class Item : Identifiable + { + [Attr] + public int Value { get; set; } + }") .Build(); Compilation inputCompilation = new CompilationBuilder() @@ -422,12 +422,12 @@ public void Skips_for_missing_dependency_on_LoggerFactory() .WithNamespaceImportFor(typeof(ResourceAttribute)) .InNamespace("ExampleApi.Models") .WithCode(@" - [Resource] - public sealed class Item : Identifiable - { - [Attr] - public int Value { get; set; } - }") + [Resource] + public sealed class Item : Identifiable + { + [Attr] + public int Value { get; set; } + }") .Build(); Compilation inputCompilation = new CompilationBuilder() @@ -465,12 +465,12 @@ public void Warns_for_resource_that_does_not_implement_IIdentifiable() .WithNamespaceImportFor(typeof(ResourceAttribute)) .InNamespace("ExampleApi.Models") .WithCode(@" - [Resource] - public sealed class Item - { - [Attr] - public int Value { get; set; } - }") + [Resource] + public sealed class Item + { + [Attr] + public int Value { get; set; } + }") .Build(); Compilation inputCompilation = new CompilationBuilder() @@ -491,7 +491,7 @@ public sealed class Item GeneratorDriverRunResult runResult = driver.GetRunResult(); runResult.Should().HaveSingleDiagnostic( - "(6,21): warning JADNC001: Type 'Item' must implement IIdentifiable when using ResourceAttribute to auto-generate ASP.NET controllers"); + "(6,17): warning JADNC001: Type 'Item' must implement IIdentifiable when using ResourceAttribute to auto-generate ASP.NET controllers"); runResult.Should().NotHaveProducedSourceCode(); } @@ -510,14 +510,14 @@ public void Adds_nullable_enable_for_nullable_reference_ID_type() .WithNamespaceImportFor(typeof(ResourceAttribute)) .InNamespace("ExampleApi.Models") .WithCode(@" - #nullable enable - - [Resource] - public sealed class Item : Identifiable - { - [Attr] - public int Value { get; set; } - }") + #nullable enable + + [Resource] + public sealed class Item : Identifiable + { + [Attr] + public int Value { get; set; } + }") .Build(); Compilation inputCompilation = new CompilationBuilder() @@ -555,12 +555,12 @@ public void Can_generate_for_custom_namespace() .WithNamespaceImportFor(typeof(ResourceAttribute)) .InNamespace("ExampleApi.Models") .WithCode(@" - [Resource(ControllerNamespace = ""Some.Path.To.Generate.Code.In"")] - public sealed class Item : Identifiable - { - [Attr] - public int Value { get; set; } - }") + [Resource(ControllerNamespace = ""Some.Path.To.Generate.Code.In"")] + public sealed class Item : Identifiable + { + [Attr] + public int Value { get; set; } + }") .Build(); Compilation inputCompilation = new CompilationBuilder() @@ -616,12 +616,12 @@ public void Can_generate_for_top_level_namespace() .WithNamespaceImportFor(typeof(ResourceAttribute)) .InNamespace("TopLevel") .WithCode(@" - [Resource] - public sealed class Item : Identifiable - { - [Attr] - public int Value { get; set; } - }") + [Resource] + public sealed class Item : Identifiable + { + [Attr] + public int Value { get; set; } + }") .Build(); Compilation inputCompilation = new CompilationBuilder() @@ -676,12 +676,70 @@ public void Can_generate_for_global_namespace() .WithNamespaceImportFor(typeof(IIdentifiable)) .WithNamespaceImportFor(typeof(ResourceAttribute)) .WithCode(@" - [Resource] - public sealed class Item : Identifiable - { - [Attr] - public int Value { get; set; } - }") + [Resource] + public sealed class Item : Identifiable + { + [Attr] + public int Value { get; set; } + }") + .Build(); + + Compilation inputCompilation = new CompilationBuilder() + .WithDefaultReferences() + .WithSourceCode(source) + .Build(); + + // @formatter:keep_existing_linebreaks restore + // @formatter:wrap_chained_method_calls restore + + // Act + driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out Compilation outputCompilation, out _); + + // Assert + inputCompilation.GetDiagnostics().Should().BeEmpty(); + outputCompilation.GetDiagnostics().Should().BeEmpty(); + + GeneratorDriverRunResult runResult = driver.GetRunResult(); + runResult.Should().NotHaveDiagnostics(); + + runResult.Should().HaveProducedSourceCode(@"// + +using Microsoft.Extensions.Logging; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; + +public sealed partial class ItemsController : JsonApiController +{ + public ItemsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, resourceGraph, loggerFactory, resourceService) + { + } +} +"); + } + + [Fact] + public void Can_generate_for_shared_namespace() + { + // Arrange + GeneratorDriver driver = CSharpGeneratorDriver.Create(new ControllerSourceGenerator()); + + // @formatter:wrap_chained_method_calls chop_always + // @formatter:keep_existing_linebreaks true + + string source = new SourceCodeBuilder() + .WithNamespaceImportFor(typeof(IIdentifiable)) + .WithNamespaceImportFor(typeof(ResourceAttribute)) + .InNamespace("ExampleApi") + .WithCode(@" + [Resource(ControllerNamespace = ""ExampleApi"")] + public sealed class Item : Identifiable + { + [Attr] + public int Value { get; set; } + }") .Build(); Compilation inputCompilation = new CompilationBuilder() @@ -709,6 +767,8 @@ public sealed class Item : Identifiable using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; +namespace ExampleApi; + public sealed partial class ItemsController : JsonApiController { public ItemsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, @@ -733,25 +793,25 @@ public void Generates_unique_file_names_for_duplicate_resource_name_in_different .WithNamespaceImportFor(typeof(IIdentifiable)) .WithNamespaceImportFor(typeof(ResourceAttribute)) .WithCode(@" - namespace The.First.One + namespace The.First.One + { + [Resource] + public sealed class Item : Identifiable { - [Resource] - public sealed class Item : Identifiable - { - [Attr] - public int Value { get; set; } - } + [Attr] + public int Value { get; set; } } + } - namespace The.Second.One + namespace The.Second.One + { + [Resource] + public sealed class Item : Identifiable { - [Resource] - public sealed class Item : Identifiable - { - [Attr] - public int Value { get; set; } - } - }") + [Attr] + public int Value { get; set; } + } + }") .Build(); Compilation inputCompilation = new CompilationBuilder()