Skip to content

Tracking: classify line endings in one place instead of five drifting rules #814

Description

[agent] Filed by the scheduled architecture audit routine (ecosystems and formats). Register: register comment.

Kind: tracking. Source: review 4.4 ("CRLF policy is inconsistent"), 5.4 (toml_edit CRLF), 7.3; register E16.

Problem

Every writer that splices into a text file has to answer one question: "which line terminator does this file use?" On main @ 045d7ec the crate answers it with five different rules, plus about a dozen inline copies:

Rule Where Mixed CRLF/LF file
Any \r\n → CRLF common::detect_eol (go.sum, go.mod, requirements, yarn classic); a byte-identical private copy, pypi_uv::newline_of; and inline copies in formats/gem/hosted.rs#L65, maven_reactor.rs#L1519, [`#L1776`](https://github.com/SocketDev/socket-patch/blob/045d7ec783d788bf3c5a1310724b51e09fb6505d/crates/socket-patch-core/src/vendor/jvm/maven_reactor.rs#L1776),`` upstream/composer.rs#L259, [`upstream/gem.rs#L535`](https://github.com/SocketDev/socket-patch/blob/045d7ec783d788bf3c5a1310724b51e09fb6505d/crates/socket-patch-core/src/patch/redirect/upstream/gem.rs#L535),`` upstream/pypi.rs#L629, [`upstream/cargo.rs#L62`](https://github.com/SocketDev/socket-patch/blob/045d7ec783d788bf3c5a1310724b51e09fb6505d/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs#L62-L63)`` and #L304,`` redirect/pipenv.rs#L129, `redirect/npmrc.rs#L614`, `redirect/mod.rs#L5775`, `utils/python_script.rs#L44` CRLF
First line's terminator gradle::newline_of; yarn classic block_eol (first line of the block) whatever the first line uses
Majority LineEndings + majority_terminator, used by JsonLayout (common.rs#L217-L222) and composer lock_text.rs#L35-L40`` majority (tie → LF)
Re-expand only CRLF-only input python_lock::preserve_line_endings (15 toml_edit callers in utils, redirect and vendor); hosted cargo crlf_to_lf whole rendering LF (toml_edit) / refused (hosted cargo)
Refuse vendored pnpm refuses any CRLF (pnpm_lock.rs#L524); berry refuses mixed refused

The rules have drifted. A throwaway unit probe on main, run twice, gives three different answers for the same input:

"a\nb\r\nc\r\n": detect_eol → CRLF, pypi_uv::newline_of → CRLF, gradle::newline_of → LF, majority_terminator → CRLF, preserve_line_endings → every line LF
"a\r\nb\nc\n":   detect_eol → CRLF, pypi_uv::newline_of → CRLF, gradle::newline_of → CRLF, majority_terminator → LF,   preserve_line_endings → every line LF

They also disagree within one ecosystem. For Cargo, the hosted rewrite refuses a mixed Cargo.lock (cargo_mixed_line_endings_still_refuse), but upstream restore LF-normalizes it and re-expands every line to CRLF (upstream/cargo.rs#L62-L63, [`#L155`](https://github.com/SocketDev/socket-patch/blob/045d7ec783d788bf3c5a1310724b51e09fb6505d/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs#L155)).`` For yarn classic, hosted CRLF-expands the whole file while vendored splices with detect_eol and reverts with block_eol (#467).

Symptoms

Impact

Uniform files are safe: every rule agrees on an LF-only or CRLF-only file, which is what git autocrlf produces. The risk is on mixed files (editor merges on Windows), where each new writer picks a rule by copy-paste. That produces churn and broken byte-exact reverts, one bug report per writer. Size: about 25 sites, mostly one-liners.

Target design

utils::line_endings is the only place that classifies terminators:

  • LineEndings::of (exists);
  • fn terminator(text) -> &'static str: Crlf → \r\n, Mixed → majority_terminator, otherwise \n. It's stable under appending lines in its own style, so a revert that removes {line}{nl} still finds what the forward pass wrote;
  • fn restore_rendering(original, rendered): the toml_edit re-expansion, moved from python_lock, with one documented mixed-file rule (per replaced fragment, as Fix Poetry/PDM lock splice drift (#694, #695) #703 does).

Whether a writer refuses a mixed file stays a per-format decision, but it reads the same classifier.

Checklist

Dependencies

Child 1 can start now. Child 2 is blocked by PR #703. Child 3 should follow PR #657 and E08.

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

    agent:triagedarch-auditFiled by a scheduled architecture audit routine (see the architecture review discussion)priority:p3refactorStructural change: duplicated code or logic, missing abstraction, layering, dead code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions