@forge-kit/plugin-source-map-prase 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -0
- package/dist/app.js +79 -0
- package/dist/icon.svg +3 -0
- package/dist/vite.svg +1 -0
- package/package.json +54 -0
- package/src/App.less +101 -0
- package/src/App.tsx +141 -0
- package/src/components/code-highlight/index.tsx +41 -0
- package/src/components/map-input-panel/index.less +95 -0
- package/src/components/map-input-panel/index.tsx +128 -0
- package/src/components/panel-card/index.tsx +25 -0
- package/src/components/trace-chain/index.less +128 -0
- package/src/components/trace-chain/index.tsx +151 -0
- package/src/main.tsx +29 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/source-map/base/constants.ts +2 -0
- package/src/utils/source-map/base/path.ts +17 -0
- package/src/utils/source-map/base/registry.ts +38 -0
- package/src/utils/source-map/base/types.ts +37 -0
- package/src/utils/source-map/domain/chain-slots.ts +59 -0
- package/src/utils/source-map/domain/source-content.ts +75 -0
- package/src/utils/source-map/domain/trace-resolver.ts +165 -0
- package/src/utils/source-map/domain/view-model.ts +20 -0
- package/src/utils/source-map/facade/source-map-utils.ts +212 -0
- package/src/utils/source-map/index.ts +18 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
.trace-chain-root {
|
|
2
|
+
flex: 1;
|
|
3
|
+
min-height: 0;
|
|
4
|
+
height: 100%;
|
|
5
|
+
overflow: auto;
|
|
6
|
+
padding: 4px;
|
|
7
|
+
-webkit-app-region: no-drag;
|
|
8
|
+
|
|
9
|
+
.trace-chain-item {
|
|
10
|
+
margin-bottom: 10px;
|
|
11
|
+
|
|
12
|
+
&[data-dragging="true"] {
|
|
13
|
+
opacity: 0.85;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.trace-chain-empty {
|
|
18
|
+
min-height: 180px;
|
|
19
|
+
border: 1px dashed var(--gray-6);
|
|
20
|
+
border-radius: var(--radius-3);
|
|
21
|
+
color: var(--gray-11);
|
|
22
|
+
font-size: 13px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.trace-chain-node {
|
|
26
|
+
padding: 10px;
|
|
27
|
+
border-radius: var(--radius-3);
|
|
28
|
+
border: 1px solid var(--gray-6);
|
|
29
|
+
background:
|
|
30
|
+
linear-gradient(160deg, color-mix(in srgb, var(--gray-3) 90%, transparent), color-mix(in srgb, var(--gray-2) 90%, transparent));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.trace-chain-drag-handle {
|
|
34
|
+
min-width: 46px;
|
|
35
|
+
height: 24px;
|
|
36
|
+
border-radius: 999px;
|
|
37
|
+
border: 1px solid var(--gray-7);
|
|
38
|
+
background: var(--gray-4);
|
|
39
|
+
color: var(--gray-12);
|
|
40
|
+
font-size: 12px;
|
|
41
|
+
font-weight: 600;
|
|
42
|
+
user-select: none;
|
|
43
|
+
cursor: grab;
|
|
44
|
+
|
|
45
|
+
&:active {
|
|
46
|
+
cursor: grabbing;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.trace-chain-content {
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
width: 100%;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.trace-chain-main-title {
|
|
56
|
+
display: block;
|
|
57
|
+
font-size: 13px;
|
|
58
|
+
font-weight: 700;
|
|
59
|
+
color: var(--gray-12);
|
|
60
|
+
line-height: 1.35;
|
|
61
|
+
word-break: break-all;
|
|
62
|
+
user-select: text;
|
|
63
|
+
cursor: text;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.trace-chain-header {
|
|
67
|
+
width: 100%;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.trace-chain-row {
|
|
71
|
+
display: grid;
|
|
72
|
+
grid-template-columns: 34px minmax(0, 1fr);
|
|
73
|
+
gap: 6px;
|
|
74
|
+
padding: 2px 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.trace-chain-row[data-row-type="output"] {
|
|
78
|
+
margin-top: 2px;
|
|
79
|
+
padding-top: 8px;
|
|
80
|
+
border-top: 1px dashed color-mix(in srgb, var(--gray-6) 78%, transparent);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.trace-chain-label {
|
|
84
|
+
min-width: 30px;
|
|
85
|
+
font-size: 12px;
|
|
86
|
+
color: var(--gray-10);
|
|
87
|
+
user-select: none;
|
|
88
|
+
margin-top: 1px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.trace-chain-detail {
|
|
92
|
+
min-width: 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.trace-chain-output-head {
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.trace-chain-file {
|
|
99
|
+
font-size: 13px;
|
|
100
|
+
font-weight: 600;
|
|
101
|
+
color: var(--gray-12);
|
|
102
|
+
white-space: normal;
|
|
103
|
+
overflow-wrap: anywhere;
|
|
104
|
+
word-break: break-all;
|
|
105
|
+
user-select: text;
|
|
106
|
+
cursor: text;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.trace-chain-position {
|
|
110
|
+
font-size: 12px;
|
|
111
|
+
color: var(--gray-11);
|
|
112
|
+
white-space: normal;
|
|
113
|
+
overflow-wrap: anywhere;
|
|
114
|
+
user-select: text;
|
|
115
|
+
cursor: text;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.trace-chain-error {
|
|
119
|
+
font-size: 12px;
|
|
120
|
+
color: #ff7b7b;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.trace-chain-pending {
|
|
124
|
+
font-size: 12px;
|
|
125
|
+
color: var(--gray-10);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {Box, Button, Flex, Text} from "@forge-kit/component";
|
|
3
|
+
import {DndContext, PointerSensor, closestCenter, useSensor, useSensors, type DragEndEvent} from "@dnd-kit/core";
|
|
4
|
+
import {SortableContext, arrayMove, useSortable, verticalListSortingStrategy} from "@dnd-kit/sortable";
|
|
5
|
+
import {CSS} from "@dnd-kit/utilities";
|
|
6
|
+
import "./index.less";
|
|
7
|
+
|
|
8
|
+
interface TraceChainNode {
|
|
9
|
+
fileName: string
|
|
10
|
+
lineNumber: number
|
|
11
|
+
columnNumber: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface TraceChainHop {
|
|
15
|
+
input: TraceChainNode
|
|
16
|
+
output?: TraceChainNode
|
|
17
|
+
mapFileName?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
interface TraceChainHopRowProps {
|
|
22
|
+
type: "input" | "output"
|
|
23
|
+
node?: TraceChainNode
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const TraceChainHopRow: React.FC<TraceChainHopRowProps> = (props) => {
|
|
27
|
+
const {type, node} = props
|
|
28
|
+
const isInput = type === "input"
|
|
29
|
+
const label = isInput ? "输入" : "输出"
|
|
30
|
+
const fileName = node?.fileName || (isInput ? "-" : "未命中映射")
|
|
31
|
+
const position = node
|
|
32
|
+
? `line ${node.lineNumber}, column ${node.columnNumber}`
|
|
33
|
+
: "line -, column -"
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Box className="trace-chain-row" data-row-type={type}>
|
|
37
|
+
<Box className="trace-chain-label">{label}</Box>
|
|
38
|
+
<Flex className="trace-chain-detail" direction="column" gap="1">
|
|
39
|
+
<Flex className="trace-chain-output-head" align="start" justify="start" gap="2">
|
|
40
|
+
<Box className="trace-chain-file">{fileName}</Box>
|
|
41
|
+
</Flex>
|
|
42
|
+
<Box className="trace-chain-position">{position}</Box>
|
|
43
|
+
</Flex>
|
|
44
|
+
</Box>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface SortableTraceChainItemProps {
|
|
49
|
+
slot: TraceChainSlot
|
|
50
|
+
hop?: TraceChainHop
|
|
51
|
+
index: number
|
|
52
|
+
onDelete?: (slotId: string) => void
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const SortableTraceChainItem: React.FC<SortableTraceChainItemProps> = (props) => {
|
|
56
|
+
const {slot, hop, index, onDelete} = props
|
|
57
|
+
const {id, error} = slot
|
|
58
|
+
|
|
59
|
+
const {
|
|
60
|
+
attributes,
|
|
61
|
+
listeners,
|
|
62
|
+
setNodeRef,
|
|
63
|
+
transform,
|
|
64
|
+
transition,
|
|
65
|
+
isDragging,
|
|
66
|
+
} = useSortable({id})
|
|
67
|
+
|
|
68
|
+
const style: React.CSSProperties = {
|
|
69
|
+
transform: CSS.Transform.toString(transform),
|
|
70
|
+
transition,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<Box ref={setNodeRef} style={style} className="trace-chain-item" data-dragging={isDragging ? "true" : "false"}>
|
|
75
|
+
<Flex className="trace-chain-node" gap="2">
|
|
76
|
+
<Flex className="trace-chain-drag-handle" align="center" justify="center" {...attributes} {...listeners}>#{index + 1}</Flex>
|
|
77
|
+
<Flex className="trace-chain-content" direction="column" gap="2">
|
|
78
|
+
<Flex className="trace-chain-header" justify="between" align="start" gap="2">
|
|
79
|
+
<Text className="trace-chain-main-title">{slot.mapFileName || "(未命名 map 文件)"}</Text>
|
|
80
|
+
<Button size="1" variant="outline" onClick={() => onDelete?.(id)}>删除</Button>
|
|
81
|
+
</Flex>
|
|
82
|
+
{error && <Text className="trace-chain-error">{error}</Text>}
|
|
83
|
+
{hop && (
|
|
84
|
+
<React.Fragment>
|
|
85
|
+
<TraceChainHopRow type="input" node={hop.input}/>
|
|
86
|
+
<TraceChainHopRow type="output" node={hop.output}/>
|
|
87
|
+
</React.Fragment>
|
|
88
|
+
)}
|
|
89
|
+
{!hop && <Text className="trace-chain-pending">等待解析结果</Text>}
|
|
90
|
+
</Flex>
|
|
91
|
+
</Flex>
|
|
92
|
+
</Box>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
interface TraceChainSlot {
|
|
98
|
+
id: string
|
|
99
|
+
mapFileName: string
|
|
100
|
+
error?: string
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface TraceChainProps {
|
|
104
|
+
slots?: TraceChainSlot[]
|
|
105
|
+
data?: TraceChainHop[]
|
|
106
|
+
onReorder?: (nextIds: string[]) => void
|
|
107
|
+
onDelete?: (slotId: string) => void
|
|
108
|
+
className?: string
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
export const TraceChain: React.FC<TraceChainProps> = (props) => {
|
|
113
|
+
const {slots = [], data = [], onReorder, onDelete, className} = props
|
|
114
|
+
const sensors = useSensors(useSensor(PointerSensor, {activationConstraint: {distance: 6}}))
|
|
115
|
+
|
|
116
|
+
if (slots.length === 0) {
|
|
117
|
+
return (
|
|
118
|
+
<Box className={className || "trace-chain-root"}>
|
|
119
|
+
<Flex className="trace-chain-empty" align="center" justify="center">暂无解析链路,上传 map 并点击“解析链路”。</Flex>
|
|
120
|
+
</Box>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const handleDragEnd = (event: DragEndEvent) => {
|
|
125
|
+
const {active, over} = event
|
|
126
|
+
if (!over || active.id === over.id) return
|
|
127
|
+
const oldIndex = slots.findIndex((item) => item.id === String(active.id))
|
|
128
|
+
const newIndex = slots.findIndex((item) => item.id === String(over.id))
|
|
129
|
+
if (oldIndex < 0 || newIndex < 0) return
|
|
130
|
+
const nextSlots = arrayMove(slots, oldIndex, newIndex)
|
|
131
|
+
onReorder?.(nextSlots.map((item) => item.id))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
|
136
|
+
<SortableContext items={slots.map((item) => item.id)} strategy={verticalListSortingStrategy}>
|
|
137
|
+
<Box className={className || "trace-chain-root"}>
|
|
138
|
+
{slots.map((slot, index) => (
|
|
139
|
+
<SortableTraceChainItem
|
|
140
|
+
key={slot.id}
|
|
141
|
+
slot={slot}
|
|
142
|
+
hop={data[index]}
|
|
143
|
+
index={index}
|
|
144
|
+
onDelete={onDelete}
|
|
145
|
+
/>
|
|
146
|
+
))}
|
|
147
|
+
</Box>
|
|
148
|
+
</SortableContext>
|
|
149
|
+
</DndContext>
|
|
150
|
+
)
|
|
151
|
+
}
|
package/src/main.tsx
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {StrictMode} from 'react'
|
|
2
|
+
import ReactDOM from "react-dom/client";
|
|
3
|
+
import {definePlugin} from '@forge-kit/types'
|
|
4
|
+
import {App} from '@/App.tsx'
|
|
5
|
+
|
|
6
|
+
let rootInstance: ReturnType<typeof ReactDOM.createRoot> | null = null;
|
|
7
|
+
|
|
8
|
+
const plugin = definePlugin({
|
|
9
|
+
bundleId: "com.forge-kit.plugin.source-map-prase",
|
|
10
|
+
name: 'Source Map 解析工具',
|
|
11
|
+
icon: `${import.meta.env.BASE_URL}icon.svg`,
|
|
12
|
+
description: "Source Map 解析工具",
|
|
13
|
+
mount: (container) => {
|
|
14
|
+
rootInstance = ReactDOM.createRoot(container);
|
|
15
|
+
rootInstance.render(
|
|
16
|
+
<StrictMode>
|
|
17
|
+
<App/>
|
|
18
|
+
</StrictMode>,
|
|
19
|
+
)
|
|
20
|
+
},
|
|
21
|
+
unmount: () => {
|
|
22
|
+
if (!rootInstance) return
|
|
23
|
+
rootInstance.unmount();
|
|
24
|
+
rootInstance = null;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
if (import.meta.env.DEV) plugin.mount(document.getElementById('root')!)
|
|
28
|
+
|
|
29
|
+
export default plugin
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const noop = ()=>{}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const normalizePath = (input: string) => input.replace(/\\/g, "/").trim()
|
|
2
|
+
|
|
3
|
+
export const getBaseName = (input: string) => {
|
|
4
|
+
const normalized = normalizePath(input)
|
|
5
|
+
const tokens = normalized.split("/")
|
|
6
|
+
return tokens[tokens.length - 1] || normalized
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const stripMapExt = (input: string) => input.endsWith(".map") ? input.slice(0, -4) : input
|
|
10
|
+
|
|
11
|
+
export const joinPath = (...parts: string[]) => {
|
|
12
|
+
return parts
|
|
13
|
+
.map((part) => normalizePath(part))
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.join("/")
|
|
16
|
+
.replace(/\/{2,}/g, "/")
|
|
17
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type {SourceMapRecord, SourceMapRegistry} from "./types";
|
|
2
|
+
import {getBaseName, normalizePath} from "./path";
|
|
3
|
+
|
|
4
|
+
export const registerSourceMap = (registry: SourceMapRegistry, key: string, sourceMapRecord: SourceMapRecord) => {
|
|
5
|
+
const normalized = normalizePath(key)
|
|
6
|
+
if (!normalized) return registry
|
|
7
|
+
|
|
8
|
+
const nextRegistry: SourceMapRegistry = {
|
|
9
|
+
...registry,
|
|
10
|
+
[normalized]: sourceMapRecord,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const baseName = getBaseName(normalized)
|
|
14
|
+
if (!nextRegistry[baseName]) {
|
|
15
|
+
nextRegistry[baseName] = sourceMapRecord
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return nextRegistry
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const listSourceMapRecords = (sourceMaps: SourceMapRegistry): SourceMapRecord[] => {
|
|
22
|
+
const map = new Map<string, SourceMapRecord>()
|
|
23
|
+
Object.values(sourceMaps).forEach((record) => {
|
|
24
|
+
if (!map.has(record.mapFileName)) {
|
|
25
|
+
map.set(record.mapFileName, record)
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
return Array.from(map.values())
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const getSourceMapByFileName = (sourceMaps: SourceMapRegistry, fileName: string) => {
|
|
32
|
+
const normalized = normalizePath(fileName)
|
|
33
|
+
return sourceMaps[normalized] ?? sourceMaps[getBaseName(normalized)]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const getSourceMapByMapFileName = (sourceMaps: SourceMapRegistry, mapFileName: string) => {
|
|
37
|
+
return listSourceMapRecords(sourceMaps).find((item) => item.mapFileName === mapFileName)
|
|
38
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type {RawSourceMap} from "source-map-js";
|
|
2
|
+
|
|
3
|
+
export interface TraceLocation {
|
|
4
|
+
fileName: string
|
|
5
|
+
lineNumber: number
|
|
6
|
+
columnNumber: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ResolveTraceOptions {
|
|
10
|
+
maxDepth?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface TraceHop {
|
|
14
|
+
input: TraceLocation
|
|
15
|
+
output?: TraceLocation
|
|
16
|
+
mapFileName?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SourceMapRecord {
|
|
20
|
+
rawSourceMap: RawSourceMap
|
|
21
|
+
mapFileName: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ResolvedSourceMeta {
|
|
25
|
+
sourceFile: string
|
|
26
|
+
lineNumber: number
|
|
27
|
+
columnNumber: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ChainMapSlot {
|
|
31
|
+
id: string
|
|
32
|
+
mapFileName: string
|
|
33
|
+
sourceMapRecord?: SourceMapRecord
|
|
34
|
+
error?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type SourceMapRegistry = Record<string, SourceMapRecord>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {normalizePath, stripMapExt} from "../base/path";
|
|
2
|
+
import {registerSourceMap} from "../base/registry";
|
|
3
|
+
import type {ChainMapSlot, SourceMapRegistry} from "../base/types";
|
|
4
|
+
|
|
5
|
+
export const createChainSlotFromFile = async (file: File): Promise<ChainMapSlot> => {
|
|
6
|
+
const slotId = `${file.name}-${Math.random().toString(36).slice(2, 8)}`
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const fileText = await file.text()
|
|
10
|
+
const rawSourceMap = JSON.parse(fileText)
|
|
11
|
+
|
|
12
|
+
if (!rawSourceMap || typeof rawSourceMap !== "object" || typeof rawSourceMap.mappings !== "string") {
|
|
13
|
+
throw new Error("invalid source-map json")
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
id: slotId,
|
|
18
|
+
mapFileName: file.name,
|
|
19
|
+
sourceMapRecord: {rawSourceMap, mapFileName: file.name},
|
|
20
|
+
}
|
|
21
|
+
} catch (error) {
|
|
22
|
+
const message = error instanceof Error ? error.message : "unknown error"
|
|
23
|
+
return {
|
|
24
|
+
id: slotId,
|
|
25
|
+
mapFileName: file.name,
|
|
26
|
+
error: `${file.name}: ${message}`,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const buildRegistryFromChainSlots = (slots: ChainMapSlot[]) => {
|
|
32
|
+
let registry: SourceMapRegistry = {}
|
|
33
|
+
const orderedMapFileNames: string[] = []
|
|
34
|
+
|
|
35
|
+
slots.forEach((slot) => {
|
|
36
|
+
const {sourceMapRecord: record} = slot
|
|
37
|
+
if (!record) return
|
|
38
|
+
|
|
39
|
+
orderedMapFileNames.push(record.mapFileName)
|
|
40
|
+
registry = registerSourceMap(registry, record.mapFileName, record)
|
|
41
|
+
registry = registerSourceMap(registry, stripMapExt(record.mapFileName), record)
|
|
42
|
+
|
|
43
|
+
if (record.rawSourceMap.file) {
|
|
44
|
+
registry = registerSourceMap(registry, record.rawSourceMap.file, record)
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
return {registry, orderedMapFileNames}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const getAutoEntryFileNameFromSlots = (slots: ChainMapSlot[]) => {
|
|
52
|
+
const firstValidSlot = slots.find((slot) => slot.sourceMapRecord)
|
|
53
|
+
if (!firstValidSlot?.sourceMapRecord) return ""
|
|
54
|
+
|
|
55
|
+
const mappedFile = firstValidSlot.sourceMapRecord.rawSourceMap.file
|
|
56
|
+
if (mappedFile) return normalizePath(mappedFile)
|
|
57
|
+
|
|
58
|
+
return stripMapExt(firstValidSlot.sourceMapRecord.mapFileName)
|
|
59
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {getBaseName, joinPath, normalizePath} from "../base/path";
|
|
2
|
+
import {listSourceMapRecords} from "../base/registry";
|
|
3
|
+
import {toDisplayLocation} from "./view-model";
|
|
4
|
+
import type {SourceMapRecord, SourceMapRegistry, TraceHop, TraceLocation} from "../base/types";
|
|
5
|
+
|
|
6
|
+
interface SourceCodeMatch {
|
|
7
|
+
code: string
|
|
8
|
+
sourceFile: string
|
|
9
|
+
location: TraceLocation
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const findSourceCodeMatch = (hop: TraceHop, sourceMapRecord: SourceMapRecord): SourceCodeMatch | null => {
|
|
13
|
+
if (!hop.output) return null
|
|
14
|
+
|
|
15
|
+
const {sources = [], sourcesContent = [], sourceRoot = ""} = sourceMapRecord.rawSourceMap
|
|
16
|
+
const outputFile = normalizePath(hop.output.fileName)
|
|
17
|
+
const outputBaseName = getBaseName(outputFile)
|
|
18
|
+
|
|
19
|
+
const sourceIndex = sources.findIndex((source) => {
|
|
20
|
+
const normalizedSource = normalizePath(source)
|
|
21
|
+
const sourceWithRoot = joinPath(sourceRoot, normalizedSource)
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
normalizedSource === outputFile
|
|
25
|
+
|| sourceWithRoot === outputFile
|
|
26
|
+
|| outputFile.endsWith(`/${normalizedSource}`)
|
|
27
|
+
|| outputFile.endsWith(`/${sourceWithRoot}`)
|
|
28
|
+
|| getBaseName(normalizedSource) === outputBaseName
|
|
29
|
+
|| getBaseName(sourceWithRoot) === outputBaseName
|
|
30
|
+
)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
if (sourceIndex < 0) return null
|
|
34
|
+
|
|
35
|
+
const code = sourcesContent[sourceIndex]
|
|
36
|
+
if (!code) return null
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
code,
|
|
40
|
+
sourceFile: sources[sourceIndex],
|
|
41
|
+
location: toDisplayLocation(hop.output),
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const resolveSourceCodeFromTrace = (traceHops: TraceHop[], sourceMaps: SourceMapRegistry) => {
|
|
46
|
+
const sourceMapRecords = listSourceMapRecords(sourceMaps)
|
|
47
|
+
|
|
48
|
+
for (let index = traceHops.length - 1; index >= 0; index--) {
|
|
49
|
+
const hop = traceHops[index]
|
|
50
|
+
if (!hop.output || !hop.mapFileName) continue
|
|
51
|
+
|
|
52
|
+
const sourceMapRecord = sourceMapRecords.find((item) => item.mapFileName === hop.mapFileName)
|
|
53
|
+
if (!sourceMapRecord) continue
|
|
54
|
+
|
|
55
|
+
const match = findSourceCodeMatch(hop, sourceMapRecord)
|
|
56
|
+
if (!match) continue
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
code: match.code,
|
|
60
|
+
sourceFile: match.sourceFile,
|
|
61
|
+
lineNumber: match.location.lineNumber,
|
|
62
|
+
columnNumber: match.location.columnNumber,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const getLastResolvedOutput = (traceHops: TraceHop[]) => {
|
|
70
|
+
for (let index = traceHops.length - 1; index >= 0; index--) {
|
|
71
|
+
const output = traceHops[index].output
|
|
72
|
+
if (output) return output
|
|
73
|
+
}
|
|
74
|
+
return null
|
|
75
|
+
}
|