7
7
; ; You must not remove this notice, or any other, from this software.
8
8
9
9
(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])
13
14
#? (:clj (:import [java.util.regex Pattern]
14
15
[java.io File])))
15
16
@@ -446,4 +447,130 @@ goog.events.getProxy/f<@http://localhost:9000/out/goog/events/events.js:276:16"
446
447
\t at <program> (<eval>:1)\n "
447
448
{:ua-product :nashorn }
448
449
{: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" })
449
576
)
0 commit comments