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);
+ }
}
///