Skip to content

Commit 252ec7a

Browse files
committed
add cross platform stacktrace mapping to cljs.stacktrace
1 parent b5f085c commit 252ec7a

File tree

1 file changed

+130
-3
lines changed

1 file changed

+130
-3
lines changed

src/main/cljs/cljs/stacktrace.cljc

Lines changed: 130 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
;; You must not remove this notice, or any other, from this software.
88

99
(ns cljs.stacktrace
10-
(:require #?(:clj [cljs.util :as util]
11-
:cljs [goog.string :as gstring])
12-
[clojure.string :as string])
10+
(:require #?@(:clj [[cljs.util :as util]
11+
[clojure.java.io :as io]]
12+
:cljs [[goog.string :as gstring]])
13+
[clojure.string :as string])
1314
#?(:clj (:import [java.util.regex Pattern]
1415
[java.io File])))
1516

@@ -446,4 +447,130 @@ goog.events.getProxy/f<@http://localhost:9000/out/goog/events/events.js:276:16"
446447
\tat <program> (<eval>:1)\n"
447448
{:ua-product :nashorn}
448449
{:output-dir ".cljs_nashorn_repl"})
450+
)
451+
452+
;; -----------------------------------------------------------------------------
453+
;; Stacktrace Mapping
454+
455+
(defn mapped-line-column-call
456+
"Given a cljs.source-map source map data structure map a generated line
457+
and column back to the original line, column, and function called."
458+
[source-map line column]
459+
;; source maps are 0 indexed for columns
460+
;; multiple segments may exist at column
461+
;; the last segment seems most accurate
462+
(letfn [(get-best-column [columns column]
463+
(last (or (get columns
464+
(last (filter #(<= % (dec column))
465+
(sort (keys columns)))))
466+
(second (first columns)))))
467+
(adjust [mapped]
468+
(vec (map #(%1 %2) [inc inc identity] mapped)))]
469+
(let [default [line column nil]]
470+
;; source maps are 0 indexed for lines
471+
(if-let [columns (get source-map (dec line))]
472+
(adjust (map (get-best-column columns column) [:line :col :name]))
473+
default))))
474+
475+
(defn mapped-frame
476+
"Given opts and a canonicalized JavaScript stacktrace frame, return the
477+
ClojureScript frame."
478+
[{:keys [function file line column]} sm opts]
479+
(let [no-source-file? (if-not file true (starts-with? file "<"))
480+
[line' column' call] (mapped-line-column-call sm line column)
481+
file' (if (ends-with? file ".js")
482+
(str (subs file 0 (- (count file) 3)) ".cljs")
483+
file)]
484+
{:function function
485+
:call call
486+
:file (if no-source-file?
487+
(str "NO_SOURCE_FILE" (when file (str " " file)))
488+
file')
489+
:line line'
490+
:column column'}))
491+
492+
(defn mapped-stacktrace
493+
"Given a vector representing the canonicalized JavaScript stacktrace
494+
return the ClojureScript stacktrace. The canonical stacktrace must be
495+
in the form:
496+
497+
[{:file <string>
498+
:function <string>
499+
:line <integer>
500+
:column <integer>}*]
501+
502+
:file must be a URL path (without protocol) relative to :output-dir or a
503+
identifier delimited by angle brackets. The returned mapped stacktrace will
504+
also contain :url entries to the original sources if it can be determined
505+
from the classpath."
506+
([stacktrace sm] (mapped-stacktrace stacktrace sm nil))
507+
([stacktrace sm opts]
508+
(letfn [(call->function [x]
509+
(if (:call x)
510+
(hash-map :function (:call x))
511+
{}))
512+
(call-merge [function call]
513+
(merge-with
514+
(fn [munged-fn-name unmunged-call-name]
515+
(if (= munged-fn-name
516+
(string/replace (munge unmunged-call-name) "." "$"))
517+
unmunged-call-name
518+
munged-fn-name))
519+
function call))]
520+
(let [mapped-frames (map (memoize #(mapped-frame % sm opts)) stacktrace)]
521+
;; take each non-nil :call and optionally merge it into :function one-level
522+
;; up to avoid replacing with local symbols, we only replace munged name if
523+
;; we can munge call symbol back to it
524+
(vec (map call-merge
525+
(map #(dissoc % :call) mapped-frames)
526+
(concat (rest (map call->function mapped-frames)) [{}])))))))
527+
528+
(defn mapped-stacktrace-str
529+
"Given a vector representing the canonicalized JavaScript stacktrace
530+
print the ClojureScript stacktrace. See mapped-stacktrace."
531+
([stacktrace sm]
532+
(mapped-stacktrace-str stacktrace sm nil))
533+
([stacktrace sm opts]
534+
(with-out-str
535+
(doseq [{:keys [function file line column]}
536+
(mapped-stacktrace stacktrace sm opts)]
537+
(println "\t"
538+
(str (when function (str function " "))
539+
"(" file (when line (str ":" line))
540+
(when column (str ":" column)) ")"))))))
541+
542+
(comment
543+
(require '[cljs.closure :as cljsc]
544+
'[clojure.data.json :as json]
545+
'[cljs.source-map :as sm]
546+
'[clojure.pprint :as pp])
547+
548+
(cljsc/build "samples/hello/src"
549+
{:optimizations :none
550+
:output-dir "samples/hello/out"
551+
:output-to "samples/hello/out/hello.js"
552+
:source-map true})
553+
554+
(def sm
555+
(sm/decode
556+
(json/read-str
557+
(slurp "samples/hello/out/hello/core.js.map")
558+
:key-fn keyword)))
559+
560+
(pp/pprint sm)
561+
562+
;; maps to :line 5 :column 24
563+
(mapped-stacktrace
564+
[{:file "hello/core.js"
565+
:function "first"
566+
:line 6
567+
:column 0}]
568+
sm {:output-dir "samples/hello/out"})
569+
570+
(mapped-stacktrace-str
571+
[{:file "hello/core.js"
572+
:function "first"
573+
:line 6
574+
:column 0}]
575+
sm {:output-dir "samples/hello/out"})
449576
)

0 commit comments

Comments
 (0)