Skip to content

fix(core): a Gradle strictly version is classified Normal, so plain fix advances a forced version #147

Description

@justin13888

What happens

crates/dependable-core/src/parsers/gradle_catalog.rs reads Gradle's rich-version keys at line 234:

for key in ["strictly", "require", "prefer"] {

and positioned() at line 287 hardcodes kind: DependencyKind::Normal, with a real, non-zero-width span.

strictly is Gradle's forced-version mechanism — the standard way a security pin on a transitive dependency is written in a version catalog. Classified as Normal, it is an ordinary rewritable dependency to fix.

The consequence

gradle/libs.versions.toml:

[versions]
junit = { strictly = "5.10.0" }

A plain dependable fix, no flags, advances it. Traced: fix.rs's forced binding is kind == DependencyKind::Override && !overrides, which is false here, so the edit is pushed and commit writes it. rewrite_constraint has no guard either — Jvm's bare_version() is Minimum, a three-component version is not partial and carries no wildcard or tilde, so it falls through to the rewrite.

git grep "DependencyKind::Override" over the parsers returns only package_json.rs. So the forced-version protection is enforced on the kind tag, which only npm-family manifests ever set — not on the semantics of a forced version. Cargo [patch]/[replace], pnpm-workspace.yaml overrides: and Composer replace/conflict are safe only by omission: no parser reads them at all. Gradle rich versions are the one case that is read, misclassified, and writable.

Why it is being filed now

Found reviewing PR #122, which adds fix --overrides and a README section stating that npm-family overrides/resolutions are "the only maps dependable treats this way; nothing in Cargo, Go, Python, or the rest declares one", and that fix never rewrites one by default, --all included.

The code defect is pre-existing#122 does not touch gradle_catalog.rs. What #122 contributes is a published guarantee a user would act on. That PR narrows its README wording to the ecosystems the guarantee actually covers; this issue is the code half.

Direction

Give positioned() a kind, and classify a strictly version as DependencyKind::Override — it is a forced version by Gradle's own definition. require and prefer are not: require is a minimum and prefer is a soft preference, so both should stay Normal.

That distinction is currently unavailable, because version_literal collapses all three keys into one literal before the caller sees which key it came from. Separating them is the first step, and is worth doing regardless — a prefer and a strictly presently produce identical items, which also makes issue #107's exact-pin narrowing read a soft preference as a hard pin.

Worth deciding at the same time: whether DependencyKind::Override is the right tag for a non-npm forced version, or whether the guard in fix.rs should key on a broader "this version was forced" predicate that both npm overrides and Gradle strictly satisfy.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions