JustDummies

Just dummies — but seriously powerful ones.

Documentation sections

JD015: StringConstraintsAdmitNoValue

PropertyValue
CategoryConstraints (JustDummies.Constraints)
Severity🟠 Warning
Enabled by defaultYes

The constants written at the call site admit no value, so the chain throws a ConflictingAnyConstraintException the moment the arrange line runs — either because the anchored fragments cannot fit the declared length, or because a declared character constraint admits none of the values a OneOf(...) supplies.

This is the case ADR-0014 opens in a new tab names by hand as the one an analyzer should carry and the type system cannot:

Any.String().WithLength(3).StartingWith("ORD-") conflicts because the prefix needs four characters, while Any.String().WithLength(12).StartingWith("ORD-") is valid; the call site and the static types are identical in both.

Only the argument’s value tells them apart — which is what makes it value-dependent, and what puts it on the analyzer’s side of the ADR’s line.

Noncompliant

Any.String().WithLength(3).StartingWith("ORD-")                        // the prefix needs 4 characters
Any.String().WithMaxLength(6).StartingWith("SKU-").EndingWith("-EUR")  // 8 characters into a cap of 6
Any.String().AlphaNumeric().OneOf("ORD-1", "ORD-2")                    // the family allows neither value
Any.String().OneOf("abc", "def").InUpperCase()                           // the casing allows neither value
Any.String().StartingWith(" ").WithLength(1).NotBlank()                // the prefix fills it, and carries no non-blank character
Any.String().WithLength(0).NotBlank()                                  // nowhere at all to put one
Any.String().WithoutNumeric().InLowerCase().OneOf("1", "A")            // neither alone empties the pool; together they do

Compliant

Any.String().WithLength(12).StartingWith("ORD-")
Any.String().WithMaxLength(12).StartingWith("SKU-").EndingWith("-EUR")
Any.String().OneOf("ORD-1", "ORD-2")                                   // drop the family: the values are yours
Any.String().AlphaNumeric().OneOf("ORD1", "ORD2")
Any.String().StartingWith("A").WithLength(1).NotBlank()                // the prefix already carries one
Any.String().StartingWith(" ").WithLength(2).NotBlank()                // one filler position is left, which is all it asks
Any.String().WithoutNumeric().InLowerCase().OneOf("1", "A", "a")       // "a" survives both

The two checks, and what the library actually does

Length budget. The fragments are laid out side by side — prefix + filler + contained values + filler + suffix, never overlapping — so the length they need is the sum of their lengths, and that sum must fit the declared length.

Two things beside the fragments enter that sum, because they enter the generator’s own:

  • A repeated prefix or suffix counts once. Each owns a single slot, so re-declaring the same literal is a no-op and declaring a different one is refused outright — StartingWith("ORD-").StartingWith("ORD-").WithLength(4) draws ORD-. Containing accumulates instead, so Containing("XY").Containing("XY") genuinely needs four characters.
  • NotBlank() is owed a filler position of its own, but only where no anchored literal already carries a non-blank character. StartingWith(" ").WithLength(1).NotBlank() has nowhere to put it; StartingWith("A").WithLength(1).NotBlank() needs nothing more, because the prefix settles the guarantee. NonEmpty() and NotBlank() each set a floor of one even with no fragment at all, so WithLength(0).NotBlank() is reported too.

This is the arithmetic the generator performs, and JD030 reads the same floor from the same place, so the interval one names and the budget the other enforces cannot disagree.

The budget is skipped once OneOf(...) is declared: a terminal value set changes what the fragments are checked against — they are matched against the pooled values rather than laid out — so OneOf("aba").WithMaxLength(3).Containing("ab").Containing("ba") is legal even though the fragments sum to 4.

An emptied value set. A character family, a custom pool, a subtraction or a casing that admits none of the values a constant OneOf(...) writes. There is no filler beside a value set — the caller supplies the whole string — so the constraint has nothing of its own to govern and simply removes every value, which the run time refuses at declaration. The remedy is to drop the constraint: the values are yours, and it contributes nothing.

That is the counterpart of the exemption below, and the reason the two read differently. An anchored literal claims its own region of a shaped string while the family claims the rest, so the two never meet; a value set claims the whole string, so the family’s region is that supplied value itself and the two must agree (ADR-0079 opens in a new tab).

Reported only when every value is refused. A pool one value survives still draws, so it is a narrowing rather than a contradiction, and JD029 names the values it removed at the severity a working chain deserves.

The constraints are weighed together, not only one by one. A value must satisfy every one of them, so the pool is their intersection — and an intersection can be empty while no single set entering it is. WithoutNumeric().InLowerCase().OneOf("1", "A") is refused by the library because the first constraint removes "1" and the second removes "A", yet each of them on its own leaves a value standing. Where one constraint is answerable for the whole pool it is named alone, because that sentence says what to remove; otherwise every constraint that refuses at least one value is named together. One that refuses none takes no part and is left out — naming it would point at a constraint whose removal changes nothing. Which of the named ones is the smallest set answering for it is a set cover, and this rule does not go there (ADR-0046 opens in a new tab).

What it does not flag

A character family, a subtraction or a casing an anchored fragment contradicts. Those constrain the characters the generator draws, and a fragment fixed by StartingWith, EndingWith or Containing is a literal you wrote rather than a draw — so AlphaNumeric().StartingWith("ORD-") is legal, and yields ORD- followed by alphanumeric characters only, the hyphen appearing in the prefix and nowhere else (ADR-0079 opens in a new tab). The same holds for WithoutNumeric().StartingWith("ORD-1") and for InLowerCase().StartingWith("ORD-"). This rule used to report all three; it must not, because doing so refuses at build time a chain the run time honours.

  • A non-constant fragment or length.
  • A chain split across statements or variables. The chain must be written as one expression: following a generator through a local would need dataflow, and a rule that claims a chain is unsatisfiable must see every constraint it carries.
  • A conflict-asserting negative test, where the illegal chain is the whole body of a lambda argument.

Read the source, or correct it there opens in a new tab· Mirrored from lib-v1.0.0-preview.6