@clayroach/unplugin 0.1.0-source-trace.2 → 0.1.0-source-trace.4
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/dist/cjs/index.js +16 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/transformers/annotateEffects.js +141 -0
- package/dist/cjs/transformers/annotateEffects.js.map +1 -0
- package/dist/cjs/transformers/withSpanTrace.js +144 -0
- package/dist/cjs/transformers/withSpanTrace.js.map +1 -0
- package/dist/dts/index.d.ts +7 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/transformers/annotateEffects.d.ts +41 -0
- package/dist/dts/transformers/annotateEffects.d.ts.map +1 -0
- package/dist/dts/transformers/withSpanTrace.d.ts +39 -0
- package/dist/dts/transformers/withSpanTrace.d.ts.map +1 -0
- package/dist/esm/index.js +12 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/transformers/annotateEffects.js +140 -0
- package/dist/esm/transformers/annotateEffects.js.map +1 -0
- package/dist/esm/transformers/withSpanTrace.js +160 -0
- package/dist/esm/transformers/withSpanTrace.js.map +1 -0
- package/package.json +2 -2
- package/src/index.ts +24 -3
- package/src/transformers/annotateEffects.ts +197 -0
- package/src/transformers/withSpanTrace.ts +236 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WithSpan source location trace transformer.
|
|
3
|
+
*
|
|
4
|
+
* This transformer injects source location metadata as attributes into Effect.withSpan() calls.
|
|
5
|
+
* It transforms `Effect.withSpan("name")` into `Effect.withSpan("name", { attributes: { "code.filepath": ..., "code.lineno": ... }})`.
|
|
6
|
+
*
|
|
7
|
+
* @since 0.1.0
|
|
8
|
+
*/
|
|
9
|
+
import type { NodePath, Visitor } from "@babel/traverse"
|
|
10
|
+
import * as t from "@babel/types"
|
|
11
|
+
import {
|
|
12
|
+
createHoistingState,
|
|
13
|
+
type HoistingState
|
|
14
|
+
} from "../utils/hoisting.js"
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Options for the withSpan trace transformer.
|
|
18
|
+
*/
|
|
19
|
+
export interface WithSpanTraceOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Filter function to determine if a file should be transformed.
|
|
22
|
+
*/
|
|
23
|
+
readonly filter?: (filename: string) => boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* State passed through the transformer.
|
|
28
|
+
*/
|
|
29
|
+
interface TransformState {
|
|
30
|
+
filename: string
|
|
31
|
+
hoisting: HoistingState
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Checks if a CallExpression is a withSpan call.
|
|
36
|
+
* Matches: Effect.withSpan(...), _.withSpan(...), or standalone withSpan(...)
|
|
37
|
+
*/
|
|
38
|
+
function isWithSpanCall(node: t.CallExpression): boolean {
|
|
39
|
+
const callee = node.callee
|
|
40
|
+
|
|
41
|
+
// Match Effect.withSpan(...) or _.withSpan(...)
|
|
42
|
+
if (
|
|
43
|
+
callee.type === "MemberExpression" &&
|
|
44
|
+
callee.property.type === "Identifier" &&
|
|
45
|
+
callee.property.name === "withSpan"
|
|
46
|
+
) {
|
|
47
|
+
return true
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Match standalone withSpan(...)
|
|
51
|
+
if (callee.type === "Identifier" && callee.name === "withSpan") {
|
|
52
|
+
return true
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Determines if this is a data-first call (effect as first arg) or data-last (name as first arg).
|
|
60
|
+
* Data-first: withSpan(effect, "name", options?)
|
|
61
|
+
* Data-last: withSpan("name", options?)
|
|
62
|
+
*/
|
|
63
|
+
function isDataFirstCall(node: t.CallExpression): boolean {
|
|
64
|
+
// If first argument is a string literal, it's data-last
|
|
65
|
+
if (node.arguments.length > 0 && t.isStringLiteral(node.arguments[0])) {
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
// If second argument exists and is a string literal, it's data-first
|
|
69
|
+
if (node.arguments.length > 1 && t.isStringLiteral(node.arguments[1])) {
|
|
70
|
+
return true
|
|
71
|
+
}
|
|
72
|
+
// Default to data-last pattern
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Creates the attributes object with source location.
|
|
78
|
+
*/
|
|
79
|
+
function createSourceAttributes(
|
|
80
|
+
filepath: string,
|
|
81
|
+
line: number,
|
|
82
|
+
column: number
|
|
83
|
+
): t.ObjectExpression {
|
|
84
|
+
return t.objectExpression([
|
|
85
|
+
t.objectProperty(
|
|
86
|
+
t.stringLiteral("code.filepath"),
|
|
87
|
+
t.stringLiteral(filepath)
|
|
88
|
+
),
|
|
89
|
+
t.objectProperty(
|
|
90
|
+
t.stringLiteral("code.lineno"),
|
|
91
|
+
t.numericLiteral(line)
|
|
92
|
+
),
|
|
93
|
+
t.objectProperty(
|
|
94
|
+
t.stringLiteral("code.column"),
|
|
95
|
+
t.numericLiteral(column)
|
|
96
|
+
)
|
|
97
|
+
])
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Merges source attributes into an existing options object or creates a new one.
|
|
102
|
+
*/
|
|
103
|
+
function mergeOrCreateOptions(
|
|
104
|
+
existingOptions: t.Expression | t.SpreadElement | t.ArgumentPlaceholder | undefined,
|
|
105
|
+
sourceAttrs: t.ObjectExpression
|
|
106
|
+
): t.ObjectExpression {
|
|
107
|
+
if (!existingOptions || t.isSpreadElement(existingOptions) || t.isArgumentPlaceholder(existingOptions)) {
|
|
108
|
+
// No existing options, create new object with attributes
|
|
109
|
+
return t.objectExpression([
|
|
110
|
+
t.objectProperty(t.identifier("attributes"), sourceAttrs)
|
|
111
|
+
])
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (t.isObjectExpression(existingOptions)) {
|
|
115
|
+
// Check if there's an existing attributes property
|
|
116
|
+
const existingAttrsIndex = existingOptions.properties.findIndex(
|
|
117
|
+
(prop) =>
|
|
118
|
+
t.isObjectProperty(prop) &&
|
|
119
|
+
((t.isIdentifier(prop.key) && prop.key.name === "attributes") ||
|
|
120
|
+
(t.isStringLiteral(prop.key) && prop.key.value === "attributes"))
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if (existingAttrsIndex >= 0) {
|
|
124
|
+
// Merge with existing attributes using spread
|
|
125
|
+
const existingAttrsProp = existingOptions.properties[existingAttrsIndex] as t.ObjectProperty
|
|
126
|
+
const mergedAttrs = t.objectExpression([
|
|
127
|
+
t.spreadElement(existingAttrsProp.value as t.Expression),
|
|
128
|
+
...sourceAttrs.properties
|
|
129
|
+
])
|
|
130
|
+
|
|
131
|
+
// Clone the options and replace the attributes property
|
|
132
|
+
const newProperties = [...existingOptions.properties]
|
|
133
|
+
newProperties[existingAttrsIndex] = t.objectProperty(
|
|
134
|
+
t.identifier("attributes"),
|
|
135
|
+
mergedAttrs
|
|
136
|
+
)
|
|
137
|
+
return t.objectExpression(newProperties)
|
|
138
|
+
} else {
|
|
139
|
+
// Add new attributes property to existing object
|
|
140
|
+
return t.objectExpression([
|
|
141
|
+
...existingOptions.properties,
|
|
142
|
+
t.objectProperty(t.identifier("attributes"), sourceAttrs)
|
|
143
|
+
])
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// If it's a variable reference, spread it and add attributes
|
|
148
|
+
return t.objectExpression([
|
|
149
|
+
t.spreadElement(existingOptions),
|
|
150
|
+
t.objectProperty(t.identifier("attributes"), sourceAttrs)
|
|
151
|
+
])
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Creates a Babel visitor that injects source location attributes into Effect.withSpan calls.
|
|
156
|
+
*/
|
|
157
|
+
export function createWithSpanTraceVisitor(
|
|
158
|
+
filename: string,
|
|
159
|
+
_options?: WithSpanTraceOptions
|
|
160
|
+
): Visitor<TransformState> {
|
|
161
|
+
return {
|
|
162
|
+
Program: {
|
|
163
|
+
enter(_path, state) {
|
|
164
|
+
state.filename = filename
|
|
165
|
+
state.hoisting = createHoistingState()
|
|
166
|
+
},
|
|
167
|
+
exit(path, state) {
|
|
168
|
+
// Prepend all hoisted statements to the program body
|
|
169
|
+
if (state.hoisting.statements.length > 0) {
|
|
170
|
+
path.unshiftContainer("body", state.hoisting.statements)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
CallExpression(path: NodePath<t.CallExpression>, state) {
|
|
176
|
+
const node = path.node
|
|
177
|
+
|
|
178
|
+
if (!isWithSpanCall(node)) {
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Get source location
|
|
183
|
+
const loc = node.loc
|
|
184
|
+
if (!loc) return
|
|
185
|
+
|
|
186
|
+
const line = loc.start.line
|
|
187
|
+
const column = loc.start.column
|
|
188
|
+
|
|
189
|
+
// Create source attributes
|
|
190
|
+
const sourceAttrs = createSourceAttributes(state.filename, line, column)
|
|
191
|
+
|
|
192
|
+
const isDataFirst = isDataFirstCall(node)
|
|
193
|
+
|
|
194
|
+
if (isDataFirst) {
|
|
195
|
+
// Data-first: withSpan(effect, "name", options?)
|
|
196
|
+
// Options is at index 2
|
|
197
|
+
const optionsArg = node.arguments[2]
|
|
198
|
+
const newOptions = mergeOrCreateOptions(optionsArg, sourceAttrs)
|
|
199
|
+
|
|
200
|
+
if (node.arguments.length >= 3) {
|
|
201
|
+
// Replace existing options
|
|
202
|
+
node.arguments[2] = newOptions
|
|
203
|
+
} else {
|
|
204
|
+
// Add options as third argument
|
|
205
|
+
node.arguments.push(newOptions)
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
// Data-last: withSpan("name", options?)
|
|
209
|
+
// Options is at index 1
|
|
210
|
+
const optionsArg = node.arguments[1]
|
|
211
|
+
const newOptions = mergeOrCreateOptions(optionsArg, sourceAttrs)
|
|
212
|
+
|
|
213
|
+
if (node.arguments.length >= 2) {
|
|
214
|
+
// Replace existing options
|
|
215
|
+
node.arguments[1] = newOptions
|
|
216
|
+
} else {
|
|
217
|
+
// Add options as second argument
|
|
218
|
+
node.arguments.push(newOptions)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Creates the withSpan trace transformer plugin.
|
|
227
|
+
*/
|
|
228
|
+
export function withSpanTraceTransformer(options?: WithSpanTraceOptions): {
|
|
229
|
+
visitor: Visitor<TransformState>
|
|
230
|
+
name: string
|
|
231
|
+
} {
|
|
232
|
+
return {
|
|
233
|
+
name: "effect-withspan-trace",
|
|
234
|
+
visitor: createWithSpanTraceVisitor("", options)
|
|
235
|
+
}
|
|
236
|
+
}
|