Repository navigation
pep508: bound the URL token at horizontal whitespace - #59
Merged
Merged
Conversation
WS was narrowed to [ \t]+ (#18640), but URL's rule stayed \S+, so it still stopped at a line break. A hand-rolled loop in requirement.go was added to manually eat a trailing break after a URL to work around that mismatch. Change URL's rule to [^ \t]+, matching upstream packaging's _tokenizer.py exactly, and delete the loop: it is unreachable once URL itself no longer stops at a break. A URL may still contain ';' - PEP 508 relies on whitespace, not a ';' ban, to separate a URL from a following marker clause. TestParseRequirement_URLThenNewlineThenMarker_Splits pinned the old (wrong) behavior: a URL, then a line break, then a marker clause, parsed successfully. Checked against a real packaging install at the pinned commit: upstream rejects this too (the break and the marker's ';' both get absorbed into the URL, leaving unparsable text behind). Replaced with tests for the verified behavior, including the case upstream itself accepts differently than expected - a bare trailing break with nothing after it becomes part of the URL text rather than being rejected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
jonyoder
force-pushed
the
19402-url-token-whitespace
branch
from
September 22, 2026 16:21
09b48b9 to
665b369
Compare
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.
Bounds the
URLtoken to horizontal whitespace, matching upstream's[^ \t]+exactly, and deletes the hand-rolled line-break loop that patched over the old\A\S+rule.What changed
internal/pep508/tokenizer.go:URLregexp\A\S+→\A[^ \t]+(stops at space/tab only, like upstream). Rewrote the comment above it — the old text justified the rule by reference toWS = \s+, which #18640 already changed to[ \t]+; the new comment states current reality and keeps the reason a URL is allowed to contain;(mandatory whitespace, not a semicolon ban, separates it from a following marker clause).internal/pep508/requirement.go: deleted the manual\n/\rconsumption loop inparseRequirementDetails. It's dead once the regexp change makes the tokenizer itself the single source of truth for where a URL ends.internal/pep508/requirement_test.go: replacedTestParseRequirement_URLThenNewlineThenMarker_Splits, which pinned the old (wrong) behavior, with token-level tests for the corrected behavior — see below.A brief acceptance criterion turned out to be wrong — verified against real upstream
The working assumption going in was that
name @ urlfollowed by a bare\n/\r/\r\nshould be rejected, by analogy withname>=1\nbeing rejected. That's false.[^ \t]+is greedy, and\n/\raren't space/tab, so a trailing line break with nothing after it gets absorbed into the URL token itself — the parse succeeds, with the break literally in the URL string. I confirmed this by installingpypa/packagingat the exact pinned commit (4eb0753) and running it directly:name @ url\n; marker(more content after the break) is rejected upstream — that matches this repo's behavior and is what the deleted test actually exercised, just with the wrong URL value asserted.Tests now assert the real, upstream-verified behavior at the token level (
req.URLequals the URL plus the embedded break), not just parse success/failure, so a regression can't hide behind a coincidentally-correct pass/fail.Verification (module root, both individually)
Regexp change, reverted to
\A\S+with the loop already gone:Restored → PASS, all three suffixes (
\n,\r,\r\n).Loop deletion, isolated against the pre-change regexp (both files at
origin/main, loop removed only):With the loop present (true baseline) → PASS. Confirms the loop was load-bearing under the old regexp and is safely dead once the regexp changes.
Mutation proof (final state): reverted
URLto\A\S+→TestParseRequirement_URLForm_BareLineBreak_EmbeddedInURLfails. Restored → full suite green. Mutation not committed.Full suite:
go test ./... -count=1 -timeout 900sandgo test ./... -raceboth green across all packages (module-wide, not justinternal/pep508, since the tokenizer is shared withmarker/,requirement/,reqtxt/).golangci-lint run ./...at the CI-pinnedv2.11.2(confirmed against.github/workflows/ci.yml) → 0 issues.gofmt -lon changed files → empty.Independently re-reviewed and re-run by a second pass (not authored by the same session as the implementation): confirms the regex semantics, the token-level assertions, that the
;-in-URL regression guard (TestParseRequirement_URLContainingSemicolon_With/NoMarker) is untouched, and that no caller outsideinternal/pep508/depends on the old behavior —reqtxt/preprocess.goalreadyTrimSpaces each line before it reachesrequirement.Parse, so this behavior change isn't reachable through that path anyway.Product impact — corrected framing
The issue states "Product impact today: none — PPM consumes only
license/." That's false: PPM importsrequirement(2 sites:src/pyresolve/resolve.go,src/pyresolve/adapt.go) andmarker(1 site:src/pyresolve/target.go, for environment data only, never parsing user-supplied marker text). However,name @ urlspecifically cannot reachrequirement.Parsefrom PPM today:src/pyresolve/adapt.go'sAdaptRequirementsbuilds the PEP 508 string from name/extras/specs only (no URL field), and upstream of that,src/requirements/parser.go'sisUnsupportedLinefilters out any line containing@before arequirements.Requirementis built, on both the online and offline-downloader paths. Correct statement: not reachable from PPM's current line filtering today, not "no product impact."Scope
Touches only
internal/pep508/tokenizer.go,internal/pep508/requirement.go,internal/pep508/requirement_test.go. Does not touchWS,Token.Unquoted,validateQuotedStringContents, ormarker.go— that's a sibling lane (#19401). This branch went first since #19401 hadn't opened a PR yet; happy to rebase if it lands first and conflicts.NEWS
None added, deliberately — this repo has no NEWS.md/CHANGELOG (uses
chore(release):commits), and the change is library-internal, pre-GA.Relates to rstudio/package-manager#19402 (do not auto-close: PPM pins an older version and won't pick this up until someone cuts a release and bumps the dependency).
🤖 Generated with Claude Code