@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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +93 -0
  3. package/dist/cjs/esbuild.js +34 -0
  4. package/dist/cjs/esbuild.js.map +1 -0
  5. package/dist/cjs/index.js +113 -0
  6. package/dist/cjs/index.js.map +1 -0
  7. package/dist/cjs/rollup.js +33 -0
  8. package/dist/cjs/rollup.js.map +1 -0
  9. package/dist/cjs/rspack.js +33 -0
  10. package/dist/cjs/rspack.js.map +1 -0
  11. package/dist/cjs/transformers/index.js +17 -0
  12. package/dist/cjs/transformers/index.js.map +1 -0
  13. package/dist/cjs/transformers/sourceTrace.js +57 -0
  14. package/dist/cjs/transformers/sourceTrace.js.map +1 -0
  15. package/dist/cjs/utils/effectDetection.js +58 -0
  16. package/dist/cjs/utils/effectDetection.js.map +1 -0
  17. package/dist/cjs/utils/hoisting.js +71 -0
  18. package/dist/cjs/utils/hoisting.js.map +1 -0
  19. package/dist/cjs/vite.js +33 -0
  20. package/dist/cjs/vite.js.map +1 -0
  21. package/dist/cjs/webpack.js +33 -0
  22. package/dist/cjs/webpack.js.map +1 -0
  23. package/dist/dts/esbuild.d.ts +30 -0
  24. package/dist/dts/esbuild.d.ts.map +1 -0
  25. package/dist/dts/index.d.ts +46 -0
  26. package/dist/dts/index.d.ts.map +1 -0
  27. package/dist/dts/rollup.d.ts +29 -0
  28. package/dist/dts/rollup.d.ts.map +1 -0
  29. package/dist/dts/rspack.d.ts +29 -0
  30. package/dist/dts/rspack.d.ts.map +1 -0
  31. package/dist/dts/transformers/index.d.ts +7 -0
  32. package/dist/dts/transformers/index.d.ts.map +1 -0
  33. package/dist/dts/transformers/sourceTrace.d.ts +41 -0
  34. package/dist/dts/transformers/sourceTrace.d.ts.map +1 -0
  35. package/dist/dts/utils/effectDetection.d.ts +35 -0
  36. package/dist/dts/utils/effectDetection.d.ts.map +1 -0
  37. package/dist/dts/utils/hoisting.d.ts +41 -0
  38. package/dist/dts/utils/hoisting.d.ts.map +1 -0
  39. package/dist/dts/vite.d.ts +29 -0
  40. package/dist/dts/vite.d.ts.map +1 -0
  41. package/dist/dts/webpack.d.ts +29 -0
  42. package/dist/dts/webpack.d.ts.map +1 -0
  43. package/dist/esm/esbuild.js +28 -0
  44. package/dist/esm/esbuild.js.map +1 -0
  45. package/dist/esm/index.js +109 -0
  46. package/dist/esm/index.js.map +1 -0
  47. package/dist/esm/package.json +4 -0
  48. package/dist/esm/rollup.js +27 -0
  49. package/dist/esm/rollup.js.map +1 -0
  50. package/dist/esm/rspack.js +27 -0
  51. package/dist/esm/rspack.js.map +1 -0
  52. package/dist/esm/transformers/index.js +7 -0
  53. package/dist/esm/transformers/index.js.map +1 -0
  54. package/dist/esm/transformers/sourceTrace.js +50 -0
  55. package/dist/esm/transformers/sourceTrace.js.map +1 -0
  56. package/dist/esm/utils/effectDetection.js +59 -0
  57. package/dist/esm/utils/effectDetection.js.map +1 -0
  58. package/dist/esm/utils/hoisting.js +70 -0
  59. package/dist/esm/utils/hoisting.js.map +1 -0
  60. package/dist/esm/vite.js +27 -0
  61. package/dist/esm/vite.js.map +1 -0
  62. package/dist/esm/webpack.js +27 -0
  63. package/dist/esm/webpack.js.map +1 -0
  64. package/esbuild/package.json +6 -0
  65. package/package.json +81 -0
  66. package/rollup/package.json +6 -0
  67. package/rspack/package.json +6 -0
  68. package/src/esbuild.ts +30 -0
  69. package/src/index.ts +218 -0
  70. package/src/rollup.ts +29 -0
  71. package/src/rspack.ts +29 -0
  72. package/src/transformers/index.ts +6 -0
  73. package/src/transformers/sourceTrace.ts +102 -0
  74. package/src/utils/effectDetection.ts +81 -0
  75. package/src/utils/hoisting.ts +132 -0
  76. package/src/vite.ts +29 -0
  77. package/src/webpack.ts +29 -0
  78. package/vite/package.json +6 -0
  79. 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 }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sideEffects": [],
3
+ "main": "../dist/cjs/vite.js",
4
+ "module": "../dist/esm/vite.js",
5
+ "types": "../dist/dts/vite.d.ts"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "sideEffects": [],
3
+ "main": "../dist/cjs/webpack.js",
4
+ "module": "../dist/esm/webpack.js",
5
+ "types": "../dist/dts/webpack.d.ts"
6
+ }