Description
The PythonRefiner.SnakeCaseNamespaceNames method (which was removed in #7714 to avoid a breaking change) was intended to convert namespace segments to snake_case at the CodeDOM level before code generation. However, it never actually executed because CrawlTree was placed inside the if (currentElement is CodeNamespace codeNamespace && !string.IsNullOrEmpty(codeNamespace.Name)) guard.
Since RefineAsync passes the root namespace (which has an empty name), the guard fails at the root and traversal never starts.
Impact
- CodeNamespace.Name values remain in their original casing (e.g.,
exportNamespace instead of export_namespace)
- The path segmenter and import manager apply
.ToSnakeCase() downstream, so generated file paths and import statements are already correct
- The only observable effect is in the public API export surface, which reflects the raw CodeNamespace.Name
Why this wasn't fixed in #7714
Simply moving CrawlTree outside the guard (the obvious fix) causes a breaking change: GetDifferential() in CodeNamespace.cs strips the namespace prefix by character count (Name[prefixLength..]). The clientNamespaceName passed to the writer comes directly from the user's config and is NOT snake_cased. After snake_casing CodeNamespace.Name, the prefix length no longer matches, corrupting import path computation.
Proper fix
To properly fix this, both changes are needed together:
- Move
CrawlTree outside the guard in SnakeCaseNamespaceNames
- Snake_case the
clientNamespaceName before passing it to PythonWriter/PythonRelativeImportManager
This ensures prefix stripping in GetDifferential works correctly with the now-snake_cased namespace names.
Related
Split out from #7714 to avoid scope creep and breaking changes.
Description
The
PythonRefiner.SnakeCaseNamespaceNamesmethod (which was removed in #7714 to avoid a breaking change) was intended to convert namespace segments to snake_case at the CodeDOM level before code generation. However, it never actually executed becauseCrawlTreewas placed inside theif (currentElement is CodeNamespace codeNamespace && !string.IsNullOrEmpty(codeNamespace.Name))guard.Since
RefineAsyncpasses the root namespace (which has an empty name), the guard fails at the root and traversal never starts.Impact
exportNamespaceinstead ofexport_namespace).ToSnakeCase()downstream, so generated file paths and import statements are already correctWhy this wasn't fixed in #7714
Simply moving
CrawlTreeoutside the guard (the obvious fix) causes a breaking change:GetDifferential()inCodeNamespace.csstrips the namespace prefix by character count (Name[prefixLength..]). TheclientNamespaceNamepassed to the writer comes directly from the user's config and is NOT snake_cased. After snake_casing CodeNamespace.Name, the prefix length no longer matches, corrupting import path computation.Proper fix
To properly fix this, both changes are needed together:
CrawlTreeoutside the guard inSnakeCaseNamespaceNamesclientNamespaceNamebefore passing it toPythonWriter/PythonRelativeImportManagerThis ensures prefix stripping in
GetDifferentialworks correctly with the now-snake_cased namespace names.Related
Split out from #7714 to avoid scope creep and breaking changes.