Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refine the client-namespace-conflict diagnostic #6161

Merged
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
update
  • Loading branch information
ArcturusZhang committed Feb 27, 2025
commit af44eb65ee5f050cbb71c7c2dbde6711ec32aff0
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@
using System.Linq;
using System.Threading;
using Microsoft.TypeSpec.Generator.ClientModel.Primitives;
using Microsoft.TypeSpec.Generator.EmitterRpc;
using Microsoft.TypeSpec.Generator.Expressions;
using Microsoft.TypeSpec.Generator.Input;
using Microsoft.TypeSpec.Generator.Primitives;
@@ -147,9 +148,56 @@ public ClientProvider(InputClient inputClient)
_subClients = new(GetSubClients);
}

protected override string BuildNamespace() => string.IsNullOrEmpty(_inputClient.Namespace) ?
base.BuildNamespace() :
ScmCodeModelPlugin.Instance.TypeFactory.GetCleanNameSpace(_inputClient.Namespace);
private const string namespaceConflictCode = "client-namespace-conflict";

private string? _namespace;
protected override string BuildNamespace()
{
return _namespace ??= BuildNamespaceCore();
}

private string BuildNamespaceCore()
{
// if namespace is empty, we fallback to the root namespace
if (string.IsNullOrEmpty(_inputClient.Namespace))
{
return base.BuildNamespace();
}
var ns = ScmCodeModelPlugin.Instance.TypeFactory.GetCleanNameSpace(_inputClient.Namespace);

// figure out if this namespace has been changed for this client
if (!IsLastSegmentTheSame(ns, _inputClient.Namespace))
{
Emitter.Instance.ReportDiagnostic(namespaceConflictCode, $"namespace {_inputClient.Namespace} conflicts with client {_inputClient.Name}, please use `@clientName` to specify a different name for the client.", _inputClient.CrossLanguageDefinitionId);
}
return ns;
}

internal static bool IsLastSegmentTheSame(string left, string right)
{
// finish this via Span API
var leftSpan = left.AsSpan();
var rightSpan = right.AsSpan();
var count = Math.Min(leftSpan.Length, rightSpan.Length);
for (int i = 1; i <= count; i++)
{
var lc = leftSpan[^i];
var rc = rightSpan[^i];
// check if each char is the same from the right-most side
// if both of them are dot, we finished scanning the last segment - and if we could be here, meaning all of them are the same, return true.
if (lc == '.' && rc == '.')
{
return true;
}
// if these are different - there is one different character, return false.
if (lc != rc)
{
return false;
}
}

return leftSpan.Length == rightSpan.Length;
}

private IReadOnlyList<ParameterProvider> GetSubClientInternalConstructorParameters()
{
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@

using System;
using System.IO;
using Microsoft.TypeSpec.Generator.Input.EmitterRpc;

namespace Microsoft.TypeSpec.Generator.Input
{
@@ -12,7 +11,6 @@ public class InputLibrary
private const string CodeModelInputFileName = "tspCodeModel.json";

private readonly string _codeModelPath;
private readonly Emitter _emitter;

// for mocking
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
@@ -21,16 +19,15 @@ protected InputLibrary()
{
}

public InputLibrary(string codeModelPath, Emitter emitter)
public InputLibrary(string codeModelPath)
{
_codeModelPath = codeModelPath;
_emitter = emitter;
}

private InputNamespace? _inputNamespace;
public virtual InputNamespace InputNamespace => _inputNamespace ??= Load(_codeModelPath, _emitter);
public virtual InputNamespace InputNamespace => _inputNamespace ??= Load(_codeModelPath);

internal InputNamespace Load(string outputDirectory, Emitter emitter)
internal InputNamespace Load(string outputDirectory)
{
var codeModelFile = Path.Combine(outputDirectory, CodeModelInputFileName);
if (!File.Exists(codeModelFile))
@@ -40,7 +37,7 @@ internal InputNamespace Load(string outputDirectory, Emitter emitter)

// Read and deserialize tspCodeModel.json
var json = File.ReadAllText(codeModelFile);
return TypeSpecSerialization.Deserialize(json, emitter) ?? throw new InvalidOperationException($"Deserializing {codeModelFile} has failed.");
return TypeSpecSerialization.Deserialize(json) ?? throw new InvalidOperationException($"Deserializing {codeModelFile} has failed.");
}

private bool? _hasMultipartFormDataOperation;
Original file line number Diff line number Diff line change
@@ -5,20 +5,15 @@
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.TypeSpec.Generator.Input.EmitterRpc;

namespace Microsoft.TypeSpec.Generator.Input
{
internal sealed class TypeSpecInputClientConverter : JsonConverter<InputClient>
{
private const string namespaceConflictCode = "client-namespace-conflict";

private readonly Emitter _emitter;
private readonly TypeSpecReferenceHandler _referenceHandler;

public TypeSpecInputClientConverter(TypeSpecReferenceHandler referenceHandler, Emitter emitter)
public TypeSpecInputClientConverter(TypeSpecReferenceHandler referenceHandler)
{
_emitter = emitter;
_referenceHandler = referenceHandler;
}

@@ -80,11 +75,7 @@ public override void Write(Utf8JsonWriter writer, InputClient value, JsonSeriali
var lastSegment = GetLastSegment(client.Namespace);
if (lastSegment == client.Name)
{
// invalid namespace segment found
// report the diagnostic
_emitter.ReportDiagnostic(namespaceConflictCode, $"namespace {client.Namespace} conflicts with client {client.Name}, please use `@clientName` to specify a different name for the client.", crossLanguageDefinitionId);
// check if the list is already there
// get the list out
// invalid namespace segment found, add it into the list
var invalidNamespaceSegments = (List<string>)resolver.ResolveReference(TypeSpecSerialization.InvalidNamespaceSegmentsKey);
invalidNamespaceSegments.Add(client.Name);
}
Original file line number Diff line number Diff line change
@@ -4,14 +4,13 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using AutoRest.CSharp.Common.Input;
using Microsoft.TypeSpec.Generator.Input.EmitterRpc;

namespace Microsoft.TypeSpec.Generator.Input
{
public static class TypeSpecSerialization
{
internal const string InvalidNamespaceSegmentsKey = "InvalidNamespaceSegments";
public static InputNamespace? Deserialize(string json, Emitter emitter)
public static InputNamespace? Deserialize(string json)
{
var referenceHandler = new TypeSpecReferenceHandler();
var options = new JsonSerializerOptions
@@ -32,7 +31,7 @@ public static class TypeSpecSerialization
new TypeSpecInputConstantConverter(referenceHandler),
new TypeSpecInputLiteralTypeConverter(referenceHandler),
new TypeSpecInputUnionTypeConverter(referenceHandler),
new TypeSpecInputClientConverter(referenceHandler, emitter),
new TypeSpecInputClientConverter(referenceHandler),
new TypeSpecInputOperationConverter(referenceHandler),
new TypeSpecInputParameterConverter(referenceHandler),
new TypeSpecInputPrimitiveTypeConverter(referenceHandler),
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.TypeSpec.Generator.Input.EmitterRpc;
using Microsoft.TypeSpec.Generator.EmitterRpc;
using Microsoft.TypeSpec.Generator.SourceInput;

namespace Microsoft.TypeSpec.Generator
Original file line number Diff line number Diff line change
@@ -6,7 +6,6 @@
using System.ComponentModel.Composition;
using Microsoft.CodeAnalysis;
using Microsoft.TypeSpec.Generator.Input;
using Microsoft.TypeSpec.Generator.Input.EmitterRpc;
using Microsoft.TypeSpec.Generator.Primitives;
using Microsoft.TypeSpec.Generator.Providers;
using Microsoft.TypeSpec.Generator.SourceInput;
@@ -45,7 +44,7 @@ internal static CodeModelPlugin Instance
public CodeModelPlugin(GeneratorContext context)
{
Configuration = context.Configuration;
_inputLibrary = new InputLibrary(Configuration.OutputDirectory, Emitter.Instance);
_inputLibrary = new InputLibrary(Configuration.OutputDirectory);
TypeFactory = new TypeFactory();
}

Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
using System.IO;
using System.Text.Json;

namespace Microsoft.TypeSpec.Generator.Input.EmitterRpc
namespace Microsoft.TypeSpec.Generator.EmitterRpc
{
public sealed class Emitter : IDisposable
{
Original file line number Diff line number Diff line change
@@ -110,7 +110,7 @@ public string? Deprecated
public CSharpType Type => _type ??=
new(
_name ??= CustomCodeView?.Name ?? BuildName(),
CustomCodeView?.BuildNamespace() ?? BuildNamespace(),
CustomCodeView?.Type.Namespace ?? BuildNamespace(),
this is EnumProvider ||
DeclarationModifiers.HasFlag(TypeSignatureModifiers.Struct) ||
DeclarationModifiers.HasFlag(TypeSignatureModifiers.Enum),
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
using System.Diagnostics;
using System.Threading.Tasks;
using CommandLine;
using Microsoft.TypeSpec.Generator.Input.EmitterRpc;
using Microsoft.TypeSpec.Generator.EmitterRpc;

namespace Microsoft.TypeSpec.Generator
{