From 11d65892219967f5522a92bec8f10b72d8c7427d Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Sat, 15 Aug 2026 16:32:13 +0200 Subject: [PATCH 1/3] Drop the CRLF conversion and cover the CRLF path with a fixture Converting the shipped examples buys nothing measurable: the rulesets parse identically either way, the tool already keeps the line endings a user's own file came with, and BeamNG ships 150 LF files of 4943 itself. Meanwhile no fixture had a carriage return in it, so the suite never exercised CRLF on a format whose real files mostly are. --- .gitattributes | 11 +++++++ .github/scripts/replace_newlines.sh | 3 -- .github/workflows/build-and-release.yaml | 3 -- examples/README.md | 8 +++++ .../regression_jbeam/crlf-line-endings.jbeam | 25 +++++++++++++++ jbeam-edit.cabal | 1 + test/CrlfSpec.hs | 32 +++++++++++++++++++ 7 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 .gitattributes delete mode 100644 .github/scripts/replace_newlines.sh create mode 100644 examples/regression_jbeam/crlf-line-endings.jbeam create mode 100644 test/CrlfSpec.hs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..b46c9ad1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# The repository stores LF. Windows checkouts convert on the way in and out, +# which is fine for everything except the two cases below. +* text=auto + +# Bash refuses to run a script with carriage returns. +*.sh text eol=lf + +# Deliberately CRLF, and pinned as binary so no checkout or commit normalises +# it away. Real BeamNG files are overwhelmingly CRLF and every other fixture +# here is LF, so this is the only one that exercises that path. +examples/regression_jbeam/crlf-line-endings.jbeam -text diff --git a/.github/scripts/replace_newlines.sh b/.github/scripts/replace_newlines.sh deleted file mode 100644 index 00c4c242..00000000 --- a/.github/scripts/replace_newlines.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -find examples/formatted_jbeam examples/transformed_jbeam -type f -exec sed -i 's/$/\r/' {} + diff --git a/.github/workflows/build-and-release.yaml b/.github/workflows/build-and-release.yaml index dbecf4e3..d3d570fa 100644 --- a/.github/workflows/build-and-release.yaml +++ b/.github/workflows/build-and-release.yaml @@ -97,9 +97,6 @@ jobs: run: | echo "Running benchmarks for jbeam-edit" cabal bench --project-file cabal.project.release --benchmark-options="--verbosity=1" - - name: Enforce CRLF newlines on windows - run: bash ./.github/scripts/replace_newlines.sh - shell: bash - name: Test executable shell: bash run: bash ./.github/scripts/prepare_installer.sh diff --git a/examples/README.md b/examples/README.md index a03b431b..b939677c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -143,6 +143,14 @@ The yaml configuration file allows the user to configure custom transformation s **File:** `examples/jbeam-edit.yaml` +## Line endings + +These files use LF, while most files that ship with BeamNG use CRLF. Nothing +breaks either way. jbeam-edit reads both, and formatting a `.jbeam` file keeps +whichever line endings that file already had, so your own files are untouched. +BeamNG itself is not consistent about it: 150 of the 4943 jbeam files in the +stock vehicles are LF. + --- For complete documentation, refer to the root [README.md](../README.md). diff --git a/examples/regression_jbeam/crlf-line-endings.jbeam b/examples/regression_jbeam/crlf-line-endings.jbeam new file mode 100644 index 00000000..9ac469b8 --- /dev/null +++ b/examples/regression_jbeam/crlf-line-endings.jbeam @@ -0,0 +1,25 @@ +{ +"testpart":{ + "nodes":[ + ["id", "posX", "posY", "posZ"], + // Synthetic regression-test fixture, not vetted by the jbeam + // maintainer and not intended as a demo/example. + // + // Stored with CRLF line endings on purpose, and pinned as binary in + // .gitattributes so no checkout or commit converts them away. 4793 of + // the 4943 jbeam files in the stock vehicles are CRLF, and every other + // fixture here is LF, so without this one the parser only ever sees LF. + /* + A block comment, because the parser has had a CRLF-specific bug in one + of these before. + */ + {"nodeWeight":1.0}, + ["nl0", 0.9, -1.0, 0.1], + ["nl1", 0.9, 0.0, 0.1, {"group":"test"}], + ], + "beams":[ + ["id1:", "id2:"], + ["nl0", "nl1"], + ], +}, +} diff --git a/jbeam-edit.cabal b/jbeam-edit.cabal index c101eea4..1e216a49 100644 --- a/jbeam-edit.cabal +++ b/jbeam-edit.cabal @@ -264,6 +264,7 @@ test-suite jbeam-edit-test Core.NodeCursorSpec Core.NodePathSpec Core.NodeSpec + CrlfSpec Formatting.RulesSpec FormattingSpec Parsing.DSLSpec diff --git a/test/CrlfSpec.hs b/test/CrlfSpec.hs new file mode 100644 index 00000000..46179e1a --- /dev/null +++ b/test/CrlfSpec.hs @@ -0,0 +1,32 @@ +module CrlfSpec (spec) where + +import Data.ByteString.Lazy qualified as LBS +import Data.Either (isRight) +import JbeamEdit.Parsing.Jbeam (parseNodes) +import Test.Hspec + +carriageReturn :: LBS.ByteString -> Bool +carriageReturn = LBS.elem 13 + +{- | Line endings are the file's business, not the content's, so the same file +read as CRLF and as LF has to parse to the same tree. Every other fixture here +is LF while 4793 of the 4943 jbeam files in the stock vehicles are CRLF, so +without this one the ordinary suite never sees a carriage return at all, and +the parser has had a CRLF-specific bug in a block comment before. +-} +spec :: Spec +spec = do + crlf <- runIO $ LBS.readFile "examples/regression_jbeam/crlf-line-endings.jbeam" + let lf = LBS.filter (/= 13) crlf + describe "a jbeam file with CRLF line endings" $ do + it "still has its carriage returns" $ + -- Guards against a checkout or a .gitattributes change quietly + -- normalising the fixture, which would leave the rest passing vacuously. + crlf `shouldSatisfy` carriageReturn + + it "parses to the same tree as the same file with LF" $ do + lf `shouldNotSatisfy` carriageReturn + parseNodes crlf `shouldBe` parseNodes lf + + it "parses at all" $ + parseNodes crlf `shouldSatisfy` isRight From 4e1534730346f55d0b4603d324b9b4039d354d18 Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Sat, 22 Aug 2026 17:24:03 +0200 Subject: [PATCH 2/3] Let dump-ast always write LF Reading the previous output back to decide the line ending was a way of managing what git now manages through .gitattributes. The conversion itself stays: the formatter returns whatever the source used, and two fender fixtures have CRLF sources. --- tools/dump_ast/Main.hs | 32 +++++--------------------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/tools/dump_ast/Main.hs b/tools/dump_ast/Main.hs index 6ce7d402..3677eb12 100644 --- a/tools/dump_ast/Main.hs +++ b/tools/dump_ast/Main.hs @@ -7,18 +7,16 @@ import Data.List (isPrefixOf, isSuffixOf) import Data.Map qualified as M import Data.Text qualified as T import Data.Text.Lazy qualified as LT -import JbeamEdit.Core.Newline import JbeamEdit.Formatting import JbeamEdit.Parsing.DSL (parseDSL) import JbeamEdit.Parsing.Jbeam (parseNodes) import JbeamEdit.Transformation import JbeamEdit.Transformation.Config import JbeamEdit.Transformation.Types -import System.Directory (doesFileExist, getDirectoryContents) +import System.Directory (getDirectoryContents) import System.Exit (exitFailure) import System.FilePath (dropExtension, takeBaseName, ()) -import System.IO (IOMode (..), Newline (..)) -import System.IO qualified as IO (hClose, openFile, readFile) +import System.IO qualified as IO (readFile) import System.OsPath qualified as OS (unsafeEncodeUtf, ()) import Text.Pretty.Simple ( StringOutputStyle (..), @@ -82,35 +80,15 @@ getDirectoryContents' path = filter (not . isPrefixOf ".#") <$> getDirectoryCont saveDump :: String -> String -> IO () saveDump outFile formatted = do putStrLn ("creating " ++ outFile) - existing <- doesFileExist outFile - lineEnding <- - if existing - then checkFileNewline outFile - else pure LF - let converted = - if lineEnding == CRLF - then toCRLF (toLF formatted) - else toLF formatted - writeFile outFile converted - where - checkFileNewline existing = - do - handle <- IO.openFile existing ReadMode - contents <- LBS.hGetContents handle - let !newline = detectNewline contents - IO.hClose handle - pure newline + writeFile outFile (toLF formatted) +-- The repository stores LF (`.gitattributes`), and the formatter hands back +-- whatever the source file used, which is CRLF for two of the fender fixtures. toLF :: String -> String toLF ('\r' : '\n' : rest) = '\n' : toLF rest toLF (c : rest) = c : toLF rest toLF [] = [] -toCRLF :: String -> String -toCRLF ('\n' : rest) = '\r' : '\n' : toCRLF rest -toCRLF (c : rest) = c : toCRLF rest -toCRLF [] = [] - saveAstDump :: Show a => String -> a -> IO () saveAstDump outFile contents = let formatted = From 07174d6b7f32a95af14e33b2665ac7cdc9963b6d Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Sat, 22 Aug 2026 17:30:34 +0200 Subject: [PATCH 3/3] Drop the LSP mentions from CLAUDE.md --- CLAUDE.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 1829cc97..d858e40b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -49,7 +49,7 @@ Say which of the two you mean when you call a change user-visible. # Build (default) cabal build -# Build with dev config (tests, LSP, transformation) +# Build with dev config (tests, dump-ast, transformation) cabal build --project-file=cabal.project.dev # Run tests @@ -82,7 +82,6 @@ src/JbeamEdit/ IOUtils.hs src-extra/transformation/ # Experimental node transformation (flag-gated) -src-extra/language-server/ # Experimental LSP server (flag-gated) exe/ # Main executable entry point test/ # HSpec test suite @@ -116,7 +115,7 @@ Whenever editing any `.md` file in the repo, ensure all tables have aligned, pad ## Testing — REQUIRED -Always use `cabal.project.dev` (enables tests, LSP, transformation flags): +Always use `cabal.project.dev` (enables tests, dump-ast and transformation flags): ```bash cabal test --project-file=cabal.project.dev