@clayroach/unplugin 0.1.0-source-trace.2
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/LICENSE +21 -0
- package/README.md +93 -0
- package/dist/cjs/esbuild.js +34 -0
- package/dist/cjs/esbuild.js.map +1 -0
- package/dist/cjs/index.js +113 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/rollup.js +33 -0
- package/dist/cjs/rollup.js.map +1 -0
- package/dist/cjs/rspack.js +33 -0
- package/dist/cjs/rspack.js.map +1 -0
- package/dist/cjs/transformers/index.js +17 -0
- package/dist/cjs/transformers/index.js.map +1 -0
- package/dist/cjs/transformers/sourceTrace.js +57 -0
- package/dist/cjs/transformers/sourceTrace.js.map +1 -0
- package/dist/cjs/utils/effectDetection.js +58 -0
- package/dist/cjs/utils/effectDetection.js.map +1 -0
- package/dist/cjs/utils/hoisting.js +71 -0
- package/dist/cjs/utils/hoisting.js.map +1 -0
- package/dist/cjs/vite.js +33 -0
- package/dist/cjs/vite.js.map +1 -0
- package/dist/cjs/webpack.js +33 -0
- package/dist/cjs/webpack.js.map +1 -0
- package/dist/dts/esbuild.d.ts +30 -0
- package/dist/dts/esbuild.d.ts.map +1 -0
- package/dist/dts/index.d.ts +46 -0
- package/dist/dts/index.d.ts.map +1 -0
- package/dist/dts/rollup.d.ts +29 -0
- package/dist/dts/rollup.d.ts.map +1 -0
- package/dist/dts/rspack.d.ts +29 -0
- package/dist/dts/rspack.d.ts.map +1 -0
- package/dist/dts/transformers/index.d.ts +7 -0
- package/dist/dts/transformers/index.d.ts.map +1 -0
- package/dist/dts/transformers/sourceTrace.d.ts +41 -0
- package/dist/dts/transformers/sourceTrace.d.ts.map +1 -0
- package/dist/dts/utils/effectDetection.d.ts +35 -0
- package/dist/dts/utils/effectDetection.d.ts.map +1 -0
- package/dist/dts/utils/hoisting.d.ts +41 -0
- package/dist/dts/utils/hoisting.d.ts.map +1 -0
- package/dist/dts/vite.d.ts +29 -0
- package/dist/dts/vite.d.ts.map +1 -0
- package/dist/dts/webpack.d.ts +29 -0
- package/dist/dts/webpack.d.ts.map +1 -0
- package/dist/esm/esbuild.js +28 -0
- package/dist/esm/esbuild.js.map +1 -0
- package/dist/esm/index.js +109 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +4 -0
- package/dist/esm/rollup.js +27 -0
- package/dist/esm/rollup.js.map +1 -0
- package/dist/esm/rspack.js +27 -0
- package/dist/esm/rspack.js.map +1 -0
- package/dist/esm/transformers/index.js +7 -0
- package/dist/esm/transformers/index.js.map +1 -0
- package/dist/esm/transformers/sourceTrace.js +50 -0
- package/dist/esm/transformers/sourceTrace.js.map +1 -0
- package/dist/esm/utils/effectDetection.js +59 -0
- package/dist/esm/utils/effectDetection.js.map +1 -0
- package/dist/esm/utils/hoisting.js +70 -0
- package/dist/esm/utils/hoisting.js.map +1 -0
- package/dist/esm/vite.js +27 -0
- package/dist/esm/vite.js.map +1 -0
- package/dist/esm/webpack.js +27 -0
- package/dist/esm/webpack.js.map +1 -0
- package/esbuild/package.json +6 -0
- package/package.json +81 -0
- package/rollup/package.json +6 -0
- package/rspack/package.json +6 -0
- package/src/esbuild.ts +30 -0
- package/src/index.ts +218 -0
- package/src/rollup.ts +29 -0
- package/src/rspack.ts +29 -0
- package/src/transformers/index.ts +6 -0
- package/src/transformers/sourceTrace.ts +102 -0
- package/src/utils/effectDetection.ts +81 -0
- package/src/utils/hoisting.ts +132 -0
- package/src/vite.ts +29 -0
- package/src/webpack.ts +29 -0
- package/vite/package.json +6 -0
- package/webpack/package.json +6 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source location trace transformer.
|
|
3
|
+
*
|
|
4
|
+
* This transformer injects source location metadata into Effect.gen yield expressions.
|
|
5
|
+
* It transforms `yield* _(effect)` into `yield* _(effect, trace)` where trace is a
|
|
6
|
+
* hoisted SourceLocation object.
|
|
7
|
+
*
|
|
8
|
+
* @since 0.1.0
|
|
9
|
+
*/
|
|
10
|
+
import type { NodePath, Visitor } from "@babel/traverse"
|
|
11
|
+
import * as t from "@babel/types"
|
|
12
|
+
import { isYieldAdapterCall } from "../utils/effectDetection.js"
|
|
13
|
+
import {
|
|
14
|
+
createHoistingState,
|
|
15
|
+
extractLabelFromParent,
|
|
16
|
+
getOrCreateTraceIdentifier,
|
|
17
|
+
type HoistingState
|
|
18
|
+
} from "../utils/hoisting.js"
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Options for the source trace transformer.
|
|
22
|
+
*/
|
|
23
|
+
export interface SourceTraceOptions {
|
|
24
|
+
/**
|
|
25
|
+
* Filter function to determine if a file should be transformed.
|
|
26
|
+
* Defaults to transforming all .ts and .tsx files.
|
|
27
|
+
*/
|
|
28
|
+
readonly filter?: (filename: string) => boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* State passed through the transformer.
|
|
33
|
+
*/
|
|
34
|
+
interface TransformState {
|
|
35
|
+
filename: string
|
|
36
|
+
hoisting: HoistingState
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a Babel visitor that injects source trace metadata into Effect.gen yields.
|
|
41
|
+
*/
|
|
42
|
+
export function createSourceTraceVisitor(
|
|
43
|
+
filename: string,
|
|
44
|
+
_options?: SourceTraceOptions
|
|
45
|
+
): Visitor<TransformState> {
|
|
46
|
+
return {
|
|
47
|
+
Program: {
|
|
48
|
+
enter(_path, state) {
|
|
49
|
+
state.filename = filename
|
|
50
|
+
state.hoisting = createHoistingState()
|
|
51
|
+
},
|
|
52
|
+
exit(path, state) {
|
|
53
|
+
// Prepend all hoisted statements to the program body
|
|
54
|
+
if (state.hoisting.statements.length > 0) {
|
|
55
|
+
path.unshiftContainer("body", state.hoisting.statements)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
YieldExpression(path: NodePath<t.YieldExpression>, state) {
|
|
61
|
+
const node = path.node
|
|
62
|
+
|
|
63
|
+
// Only transform yield* _(effect) patterns
|
|
64
|
+
if (!isYieldAdapterCall(node)) {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Get source location
|
|
69
|
+
const loc = node.loc
|
|
70
|
+
if (!loc) return
|
|
71
|
+
|
|
72
|
+
// Extract label from parent (e.g., `const user = yield* _(...)`)
|
|
73
|
+
const label = extractLabelFromParent(path)
|
|
74
|
+
|
|
75
|
+
// Get or create hoisted trace identifier
|
|
76
|
+
const traceId = getOrCreateTraceIdentifier(
|
|
77
|
+
state.hoisting,
|
|
78
|
+
state.filename,
|
|
79
|
+
loc.start.line,
|
|
80
|
+
loc.start.column,
|
|
81
|
+
label
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
// Inject trace as second argument: _(effect) -> _(effect, trace)
|
|
85
|
+
const callExpr = node.argument as t.CallExpression
|
|
86
|
+
callExpr.arguments.push(t.cloneNode(traceId))
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Creates the source trace transformer plugin.
|
|
93
|
+
*/
|
|
94
|
+
export function sourceTraceTransformer(options?: SourceTraceOptions): {
|
|
95
|
+
visitor: Visitor<TransformState>
|
|
96
|
+
name: string
|
|
97
|
+
} {
|
|
98
|
+
return {
|
|
99
|
+
name: "effect-source-trace",
|
|
100
|
+
visitor: createSourceTraceVisitor("", options)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for detecting Effect patterns in AST nodes.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.1.0
|
|
5
|
+
*/
|
|
6
|
+
import type * as t from "@babel/types"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Checks if a CallExpression is `Effect.gen(...)`.
|
|
10
|
+
*/
|
|
11
|
+
export function isEffectGenCall(node: t.Node): node is t.CallExpression {
|
|
12
|
+
if (node.type !== "CallExpression") return false
|
|
13
|
+
|
|
14
|
+
const callee = node.callee
|
|
15
|
+
|
|
16
|
+
// Match Effect.gen(...)
|
|
17
|
+
if (
|
|
18
|
+
callee.type === "MemberExpression" &&
|
|
19
|
+
callee.object.type === "Identifier" &&
|
|
20
|
+
callee.object.name === "Effect" &&
|
|
21
|
+
callee.property.type === "Identifier" &&
|
|
22
|
+
callee.property.name === "gen"
|
|
23
|
+
) {
|
|
24
|
+
return true
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Match _.gen(...) where _ is an alias for Effect
|
|
28
|
+
if (
|
|
29
|
+
callee.type === "MemberExpression" &&
|
|
30
|
+
callee.property.type === "Identifier" &&
|
|
31
|
+
callee.property.name === "gen"
|
|
32
|
+
) {
|
|
33
|
+
return true
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return false
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Minimal path-like interface for AST traversal.
|
|
41
|
+
* This mirrors the essential properties of Babel's NodePath without importing the full type.
|
|
42
|
+
*/
|
|
43
|
+
interface PathLike {
|
|
44
|
+
parent?: t.Node | null
|
|
45
|
+
parentPath?: PathLike | null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Checks if a node is inside an Effect.gen generator function.
|
|
50
|
+
* This walks up the AST to find if there's an enclosing generator function
|
|
51
|
+
* that is an argument to Effect.gen.
|
|
52
|
+
*/
|
|
53
|
+
export function isInsideEffectGen(path: PathLike): boolean {
|
|
54
|
+
let current = path.parentPath
|
|
55
|
+
while (current) {
|
|
56
|
+
const node = current.parent as t.Node | undefined
|
|
57
|
+
if (node && isEffectGenCall(node)) {
|
|
58
|
+
return true
|
|
59
|
+
}
|
|
60
|
+
current = current.parentPath
|
|
61
|
+
}
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Checks if a CallExpression is a call to the adapter function `_()`.
|
|
67
|
+
* The adapter is the first argument passed to the generator function.
|
|
68
|
+
*/
|
|
69
|
+
export function isAdapterCall(node: t.CallExpression): boolean {
|
|
70
|
+
return node.callee.type === "Identifier" && node.callee.name === "_"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Checks if a YieldExpression is a `yield* _(effect)` pattern.
|
|
75
|
+
*/
|
|
76
|
+
export function isYieldAdapterCall(node: t.YieldExpression): boolean {
|
|
77
|
+
if (!node.delegate) return false // Must be yield*, not yield
|
|
78
|
+
if (!node.argument) return false
|
|
79
|
+
if (node.argument.type !== "CallExpression") return false
|
|
80
|
+
return isAdapterCall(node.argument)
|
|
81
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for hoisting trace metadata to module scope.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.1.0
|
|
5
|
+
*/
|
|
6
|
+
import * as t from "@babel/types"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* State for tracking hoisted trace declarations.
|
|
10
|
+
*/
|
|
11
|
+
export interface HoistingState {
|
|
12
|
+
/** Map from dedup key to hoisted identifier */
|
|
13
|
+
readonly hoistedTraces: Map<string, t.Identifier>
|
|
14
|
+
/** Counter for generating unique identifiers */
|
|
15
|
+
counter: number
|
|
16
|
+
/** Statements to prepend to the program body */
|
|
17
|
+
readonly statements: Array<t.Statement>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates a new hoisting state.
|
|
22
|
+
*/
|
|
23
|
+
export function createHoistingState(): HoistingState {
|
|
24
|
+
return {
|
|
25
|
+
hoistedTraces: new Map(),
|
|
26
|
+
counter: 0,
|
|
27
|
+
statements: []
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates a unique deduplication key for a source location.
|
|
33
|
+
* Uses line:column to disambiguate multiple yields on the same line.
|
|
34
|
+
*/
|
|
35
|
+
export function createDedupKey(
|
|
36
|
+
filename: string,
|
|
37
|
+
line: number,
|
|
38
|
+
column: number
|
|
39
|
+
): string {
|
|
40
|
+
return `${filename}:${line}:${column}`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Gets or creates a hoisted trace identifier for the given source location.
|
|
45
|
+
*/
|
|
46
|
+
export function getOrCreateTraceIdentifier(
|
|
47
|
+
state: HoistingState,
|
|
48
|
+
filename: string,
|
|
49
|
+
line: number,
|
|
50
|
+
column: number,
|
|
51
|
+
label?: string
|
|
52
|
+
): t.Identifier {
|
|
53
|
+
const key = createDedupKey(filename, line, column)
|
|
54
|
+
|
|
55
|
+
let identifier = state.hoistedTraces.get(key)
|
|
56
|
+
if (identifier) {
|
|
57
|
+
return identifier
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Create new identifier
|
|
61
|
+
identifier = t.identifier(`_trace${state.counter++}`)
|
|
62
|
+
state.hoistedTraces.set(key, identifier)
|
|
63
|
+
|
|
64
|
+
// Create the SourceLocation object literal
|
|
65
|
+
const properties: Array<t.ObjectProperty> = [
|
|
66
|
+
t.objectProperty(
|
|
67
|
+
t.identifier("_tag"),
|
|
68
|
+
t.stringLiteral("SourceLocation")
|
|
69
|
+
),
|
|
70
|
+
t.objectProperty(
|
|
71
|
+
t.identifier("path"),
|
|
72
|
+
t.stringLiteral(filename)
|
|
73
|
+
),
|
|
74
|
+
t.objectProperty(
|
|
75
|
+
t.identifier("line"),
|
|
76
|
+
t.numericLiteral(line)
|
|
77
|
+
),
|
|
78
|
+
t.objectProperty(
|
|
79
|
+
t.identifier("column"),
|
|
80
|
+
t.numericLiteral(column)
|
|
81
|
+
)
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
if (label) {
|
|
85
|
+
properties.push(
|
|
86
|
+
t.objectProperty(
|
|
87
|
+
t.identifier("label"),
|
|
88
|
+
t.stringLiteral(label)
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Wrap in Symbol.for call to add the type tag
|
|
94
|
+
const traceObject = t.objectExpression([
|
|
95
|
+
t.objectProperty(
|
|
96
|
+
t.callExpression(
|
|
97
|
+
t.memberExpression(t.identifier("Symbol"), t.identifier("for")),
|
|
98
|
+
[t.stringLiteral("effect/SourceLocation")]
|
|
99
|
+
),
|
|
100
|
+
t.callExpression(
|
|
101
|
+
t.memberExpression(t.identifier("Symbol"), t.identifier("for")),
|
|
102
|
+
[t.stringLiteral("effect/SourceLocation")]
|
|
103
|
+
),
|
|
104
|
+
true // computed property
|
|
105
|
+
),
|
|
106
|
+
...properties
|
|
107
|
+
])
|
|
108
|
+
|
|
109
|
+
// Create variable declaration
|
|
110
|
+
const declaration = t.variableDeclaration("const", [
|
|
111
|
+
t.variableDeclarator(identifier, traceObject)
|
|
112
|
+
])
|
|
113
|
+
|
|
114
|
+
state.statements.push(declaration)
|
|
115
|
+
|
|
116
|
+
return identifier
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Extracts a label from a VariableDeclarator if the yield is part of an assignment.
|
|
121
|
+
* For example: `const user = yield* _(fetchUser(id))` extracts "user"
|
|
122
|
+
*/
|
|
123
|
+
export function extractLabelFromParent(path: {
|
|
124
|
+
parent?: t.Node | null
|
|
125
|
+
parentPath?: { node?: t.Node | null } | null
|
|
126
|
+
}): string | undefined {
|
|
127
|
+
const parent = path.parent
|
|
128
|
+
if (parent?.type === "VariableDeclarator" && parent.id.type === "Identifier") {
|
|
129
|
+
return parent.id.name
|
|
130
|
+
}
|
|
131
|
+
return undefined
|
|
132
|
+
}
|
package/src/vite.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite plugin export for @effect/unplugin.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* // vite.config.ts
|
|
7
|
+
* import effectPlugin from "@effect/unplugin/vite"
|
|
8
|
+
*
|
|
9
|
+
* export default {
|
|
10
|
+
* plugins: [
|
|
11
|
+
* effectPlugin({
|
|
12
|
+
* sourceTrace: true
|
|
13
|
+
* })
|
|
14
|
+
* ]
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @since 0.1.0
|
|
19
|
+
*/
|
|
20
|
+
import { type EffectPluginOptions, unplugin } from "./index.js"
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Vite plugin for Effect transformations.
|
|
24
|
+
*
|
|
25
|
+
* @since 0.1.0
|
|
26
|
+
*/
|
|
27
|
+
export default unplugin.vite
|
|
28
|
+
|
|
29
|
+
export type { EffectPluginOptions }
|
package/src/webpack.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webpack plugin export for @effect/unplugin.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* // webpack.config.js
|
|
7
|
+
* const effectPlugin = require("@effect/unplugin/webpack").default
|
|
8
|
+
*
|
|
9
|
+
* module.exports = {
|
|
10
|
+
* plugins: [
|
|
11
|
+
* effectPlugin({
|
|
12
|
+
* sourceTrace: true
|
|
13
|
+
* })
|
|
14
|
+
* ]
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @since 0.1.0
|
|
19
|
+
*/
|
|
20
|
+
import { type EffectPluginOptions, unplugin } from "./index.js"
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Webpack plugin for Effect transformations.
|
|
24
|
+
*
|
|
25
|
+
* @since 0.1.0
|
|
26
|
+
*/
|
|
27
|
+
export default unplugin.webpack
|
|
28
|
+
|
|
29
|
+
export type { EffectPluginOptions }
|