diff --git a/src/TickerQ.SourceGenerator/TickerQIncrementalSourceGenerator.cs b/src/TickerQ.SourceGenerator/TickerQIncrementalSourceGenerator.cs index 0d43d98d..26348dff 100644 --- a/src/TickerQ.SourceGenerator/TickerQIncrementalSourceGenerator.cs +++ b/src/TickerQ.SourceGenerator/TickerQIncrementalSourceGenerator.cs @@ -33,36 +33,44 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var compilationAndMethods = context.CompilationProvider .Combine(tickerMethods.Collect()); - context.RegisterSourceOutput(compilationAndMethods, (productionContext, source) => + var configOptionsProvider = context.AnalyzerConfigOptionsProvider; + + context.RegisterSourceOutput(compilationAndMethods.Combine(configOptionsProvider), (productionContext, source) => { - var (compilation, methodPairs) = source; + var ((compilation, methodPairs), configOptions) = source; if (compilation.Assembly.Name == "TickerQ") return; + // Prefer RootNamespace from build properties, fall back to sanitized assembly name + configOptions.GlobalOptions.TryGetValue("build_property.RootNamespace", out var rootNamespace); + var effectiveNamespace = !string.IsNullOrEmpty(rootNamespace) + ? rootNamespace + : SourceGeneratorUtilities.SanitizeNamespace(compilation.Assembly.Name); + // Generate constructor calls (no need for class conflict detection since we always use full names) - var constructorCalls = BuildConstructorMethodCalls(methodPairs, compilation, compilation.Assembly.Name).ToList(); + var constructorCalls = BuildConstructorMethodCalls(methodPairs, compilation, effectiveNamespace).ToList(); // Generate delegates and detect type conflicts for generic types - var initialDelegatesWithMetadata = BuildTickerFunctionDelegates(methodPairs, compilation, productionContext, compilation.Assembly.Name).ToList(); + var initialDelegatesWithMetadata = BuildTickerFunctionDelegates(methodPairs, compilation, productionContext, effectiveNamespace).ToList(); var typeNames = initialDelegatesWithMetadata.Select(d => d.Item2.GenericTypeName).Where(t => !string.IsNullOrEmpty(t)).ToList(); var typeNameConflicts = DetectTypeNameConflicts(typeNames); - + // Regenerate delegates with type conflict information for generic types - var delegatesWithMetadata = BuildTickerFunctionDelegates(methodPairs, compilation, productionContext, compilation.Assembly.Name, null, typeNameConflicts).ToList(); - + var delegatesWithMetadata = BuildTickerFunctionDelegates(methodPairs, compilation, productionContext, effectiveNamespace, null, typeNameConflicts).ToList(); + // Collect namespaces from the source types var sourceNamespaces = NamespaceCollector.CollectNamespacesFromSourceTypes(methodPairs, compilation); - + // Extract data once to avoid multiple enumeration var delegateCodes = delegatesWithMetadata.Select(x => x.DelegateCode).ToList(); var requestTypes = delegatesWithMetadata.Select(x => x.Item2).ToList(); - + var generatedCode = GenerateSourceWithFullNamespaces( delegateCodes, constructorCalls, requestTypes, - compilation.Assembly.Name, + effectiveNamespace, typeNameConflicts ); diff --git a/src/TickerQ.SourceGenerator/Utilities/SourceGeneratorUtilities.cs b/src/TickerQ.SourceGenerator/Utilities/SourceGeneratorUtilities.cs index 5e77a77d..7c0be993 100644 --- a/src/TickerQ.SourceGenerator/Utilities/SourceGeneratorUtilities.cs +++ b/src/TickerQ.SourceGenerator/Utilities/SourceGeneratorUtilities.cs @@ -213,6 +213,39 @@ private static string ExtractNamespaceFromTypeName(string fullTypeName) var lastDotIndex = fullTypeName.LastIndexOf('.'); return lastDotIndex > 0 ? fullTypeName.Substring(0, lastDotIndex) : string.Empty; } + + /// + /// Sanitizes an assembly name into a valid C# namespace identifier. + /// Replaces invalid characters (e.g. dashes) with underscores, and prepends + /// an underscore if the result starts with a digit. + /// + public static string SanitizeNamespace(string name) + { + if (string.IsNullOrEmpty(name)) + return name; + + var sb = new System.Text.StringBuilder(name.Length); + for (var i = 0; i < name.Length; i++) + { + var c = name[i]; + if (c == '.' || char.IsLetterOrDigit(c) || c == '_') + sb.Append(c); + else + sb.Append('_'); + } + + var result = sb.ToString(); + + // Each segment between dots must not start with a digit + var segments = result.Split('.'); + for (var i = 0; i < segments.Length; i++) + { + if (segments[i].Length > 0 && char.IsDigit(segments[i][0])) + segments[i] = "_" + segments[i]; + } + + return string.Join(".", segments); + } } ///