Skip to content

Commit f78856e

Browse files
committed
Handle Windows line endings
While we await https://clojure.atlassian.net/browse/TRDR-65, I've introduced a work-around. See rewrite-clj.reader.cljc comments for details. Fixes #93
1 parent eb20251 commit f78856e

File tree

2 files changed

+93
-7
lines changed

2 files changed

+93
-7
lines changed

src/rewrite_clj/reader.cljc

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[clojure.tools.reader.reader-types :as r]
66
[rewrite-clj.interop :as interop])
77
#?(:cljs (:import [goog.string StringBuffer])
8-
:clj (:import [java.io PushbackReader]
8+
:clj (:import [java.io PushbackReader Closeable]
99
[clojure.tools.reader.reader_types IndexingPushbackReader])))
1010

1111
#?(:clj (set! *warn-on-reflection* true))
@@ -171,11 +171,55 @@
171171

172172
;; ## Reader Types
173173

174-
(defn string-reader
175-
"Create reader for strings."
176-
[s]
177-
(r/indexing-push-back-reader
178-
(r/string-push-back-reader s)))
174+
;;
175+
;; clojure.tools.reader (at the time of this writing v1.3.5) does not seem to normalize Windows \r\n newlines
176+
;; properly to \n for Clojure
177+
;;
178+
;; ClojureScript seems to work fine - but note that for peek it can return \r instead of \n.
179+
;;
180+
;; see https://clojure.atlassian.net/browse/TRDR-65
181+
;;
182+
;; For now, we introduce a normalizing reader for Clojure.
183+
;; Once/if this isssue is fixed in in tools reader we can turf our work-around.
184+
185+
#?(:clj
186+
(deftype NewlineNormalizingReader
187+
[rdr
188+
^:unsynchronized-mutable next-char
189+
^:unsynchronized-mutable peeked-char]
190+
r/Reader
191+
(read-char [_reader]
192+
(if peeked-char
193+
(let [ch peeked-char]
194+
(set! peeked-char nil)
195+
ch)
196+
(let [ch (or next-char (r/read-char rdr))]
197+
(when next-char (set! next-char nil))
198+
(cond (identical? \return ch)
199+
(let [next-ch (r/read-char rdr)]
200+
(when (not (identical? \newline next-ch))
201+
(set! next-char next-ch))
202+
\newline)
203+
204+
(identical? \formfeed ch)
205+
\newline
206+
207+
:else
208+
ch))))
209+
210+
(peek-char [reader]
211+
(let [ch (or peeked-char (.read-char reader))]
212+
(when-not peeked-char (set! peeked-char ch))
213+
ch))))
214+
215+
#?(:clj
216+
(defn ^Closeable newline-normalizing-reader
217+
"Normalizes the following line endings to LF (line feed - 0x0A):
218+
- LF (remains LF)
219+
- CRLF (carriage return 0x0D line feed 0x0A)
220+
- FF (form feed 0x0C)"
221+
[rdr]
222+
(NewlineNormalizingReader. (r/to-rdr rdr) nil nil)))
179223

180224
#?(:clj
181225
(defn file-reader
@@ -185,4 +229,13 @@
185229
(-> (io/file f)
186230
(io/reader)
187231
(PushbackReader. 2)
232+
newline-normalizing-reader
188233
(r/indexing-push-back-reader 2))))
234+
235+
(defn string-reader
236+
"Create reader for strings."
237+
[s]
238+
(-> s
239+
r/string-push-back-reader
240+
#?@(:clj [newline-normalizing-reader])
241+
r/indexing-push-back-reader))

test/rewrite_clj/parser_test.cljc

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
[clojure.tools.reader.edn :refer [read-string]]
77
[rewrite-clj.node :as node]
88
[rewrite-clj.parser :as p])
9-
#?(:clj (:import clojure.lang.ExceptionInfo)))
9+
#?(:clj (:import [clojure.lang ExceptionInfo]
10+
[java.io File])))
1011

1112
(deftest t-parsing-the-first-few-whitespaces
1213
(are [?ws ?parsed]
@@ -527,5 +528,37 @@
527528
[3 12] [3 13] :token "x" 'x)))
528529

529530

531+
(deftest t-os-specific-line-endings
532+
(are [?in ?expected]
533+
(let [str-actual (-> ?in p/parse-string-all node/string)]
534+
(is (= ?expected str-actual) "from string")
535+
#?(:clj
536+
(is (= ?expected (let [t-file (File/createTempFile "rewrite-clj-parse-test" ".clj")]
537+
(.deleteOnExit t-file)
538+
(spit t-file ?in)
539+
(-> t-file p/parse-file-all node/string))) "from file")))
540+
"heya\r\nplaya\r\n"
541+
"heya\nplaya\n"
542+
543+
";; comment\r\n(+ 1 2 3)\r\n"
544+
";; comment\n(+ 1 2 3)\n"
545+
546+
"1\r2\r\n3\f4"
547+
"1\n2\n3\n4"
548+
549+
"\n\n\n\n\n"
550+
"\n\n\n\n\n"
551+
552+
"\r\r\r\r\r"
553+
"\n\n\n\n\n"
554+
555+
"\r\n\r\n\r\n\r\n\r\n"
556+
"\n\n\n\n\n"
557+
558+
"\f\f\f\f\f"
559+
"\n\n\n\n\n"
560+
561+
"\r\n\r\r\f\r\n"
562+
"\n\n\n\n\n"))
530563

531564

0 commit comments

Comments
 (0)