fix(cli): resolve --filter dependencies at any namespace depth - #2417
Open
twoRoger wants to merge 1 commit into
Open
fix(cli): resolve --filter dependencies at any namespace depth#2417twoRoger wants to merge 1 commit into
twoRoger wants to merge 1 commit into
Conversation
--filter only understood names of the form `package.Message`. Dependencies were rebuilt as `resolvedType.parent.name + "." + resolvedType.name`, so a message in a deeper namespace such as `example.values.Value` produced the bogus name `values.Value` and the filter threw, which pbjs reports before falling back to the unfiltered root. Replace the string-based lookup with the reflection API: - resolve each configured name with `root.lookup(name, [protobuf.Type])`, which handles full names of any namespace depth; - build the transitive closure over `field.resolvedType`, using a Set of reflection objects as the cycle guard, which covers repeated, map value, oneof and group fields alike; - prune the tree recursively through the public `Namespace#remove` and `Namespace#add` instead of assigning to the private `_nestedArray`, so that the root's fully qualified object cache stays in sync and removed types are no longer reachable through lookup; - keep namespaces that retained objects need for their full name, reducing an unselected `Type` or `Service` to a plain `Namespace` so no message or service code is emitted for it. The edition in effect before the replacement is pinned on the moved children, otherwise `Namespace#add` stamps them with the default edition and changes field presence. Validate the configuration up front and reject anything that is not a non-empty array of non-empty strings, so that an empty selection no longer silently produces an empty root. Names are resolved before the tree is touched, leaving the root intact for the existing fallback in pbjs when a filter cannot be applied. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2416
Summary
--filtercould not resolve a dependency whose package has more than one segment, so any schema using a package likeexample.valuesended up producing a completely unfiltered root.Root cause
Dependency names were rebuilt from the immediate parent of the resolved type:
For
.example.values.Valuethe immediate parent is onlyvalues, producing the bogus namevalues.Value. That name was then consumed by a lookup that splits into exactly two segments and indexesroot.nesteddirectly, so nothing belowpackage.Messagecould be addressed.Pruning had the same one-level limitation: it filtered
root._nestedArrayand then a single level ofns._nestedArray.Fix
The filter now works on reflection objects instead of reconstructed names:
root.lookup(name, [protobuf.Type]), which handles full names of any namespace depth;field.resolvedType, with aSetof reflection objects as the cycle guard.fieldsArraycovers plain, repeated, map value, oneof and group fields alike;Namespace#removeandNamespace#addrather than assigning to the private_nestedArray. This keepsRoot#_fullyQualifiedObjectsin sync, so pruned types are no longer reachable throughlookup— previously they were, because that cache is a lookup fallback;TypeorServicethat only holds retained descendants is reduced to a plainNamespace, so no message or service code is emitted for it.One subtlety worth calling out:
Namespace#addstamps direct children of a plain namespace with the default edition. A type nested inside another type has no explicit edition of its own, so moving it into the replacement namespace as-is would silently change it fromproto3toproto2and flip field presence fromIMPLICITtoEXPLICIT. The edition in effect before the replacement is therefore pinned on the replacement and on each moved child.The configuration is also validated up front and rejected unless it is a non-empty array of non-empty strings — otherwise an empty selection would now silently produce an empty root. Names are resolved before anything is removed, so a filter that cannot be applied leaves the root untouched for the existing fallback in
pbjs.No behavior outside
--filterchanges;cli/pbjs.jsis untouched.Tests
Added to
tests/cli-pbjs.js:example.values;Every filter assertion also reloads the pruned root through
Root.fromJSON(root.toJSON()).resolveAll(). Resolving the pruned root in place is not sufficient — its objects are already resolved andNamespace#removedoes not invalidate them — so reloading is what actually proves no retained field points at a pruned type.The existing
--filtertest, which covers a single-segment package and a message without a package, is unchanged and still passes.Commands run
npm install npm --prefix cli install npm run build npm run build:tests npm run lint:sources -- --max-warnings 0 npm run lint:types npm testnpm run build:testsproduces an unrelated diff intests/data/test.jsthat also reproduces on a cleanmaster, so it is not included here.