@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,140 @@
|
|
|
1
|
+
const PURE_ANNOTATION = "#__PURE__";
|
|
2
|
+
/**
|
|
3
|
+
* Checks if a node already has a PURE annotation.
|
|
4
|
+
*/
|
|
5
|
+
function isPureAnnotated(node) {
|
|
6
|
+
const leadingComments = node.leadingComments;
|
|
7
|
+
if (!leadingComments) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
return leadingComments.some((comment) => /[@#]__PURE__/.test(comment.value));
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Adds a PURE annotation comment to a node.
|
|
14
|
+
*/
|
|
15
|
+
function annotateAsPure(path) {
|
|
16
|
+
if (isPureAnnotated(path.node)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
path.addComment("leading", PURE_ANNOTATION);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Checks if the parent is a CallExpression or NewExpression.
|
|
23
|
+
*/
|
|
24
|
+
function hasCallableParent(path) {
|
|
25
|
+
const parentPath = path.parentPath;
|
|
26
|
+
if (!parentPath)
|
|
27
|
+
return false;
|
|
28
|
+
return parentPath.isCallExpression() || parentPath.isNewExpression();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Checks if this node is used as a callee (e.g., `foo()` where foo is the callee).
|
|
32
|
+
*/
|
|
33
|
+
function isUsedAsCallee(path) {
|
|
34
|
+
if (!hasCallableParent(path)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const parentPath = path.parentPath;
|
|
38
|
+
return parentPath.get("callee") === path;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Checks if this node is inside a callee chain (e.g., `foo()()` or `foo.bar()`).
|
|
42
|
+
*/
|
|
43
|
+
function isInCallee(path) {
|
|
44
|
+
let current = path;
|
|
45
|
+
do {
|
|
46
|
+
current = current.parentPath;
|
|
47
|
+
if (!current)
|
|
48
|
+
return false;
|
|
49
|
+
if (isUsedAsCallee(current)) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
} while (!current.isStatement() && !current.isFunction());
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Checks if this expression is executed during module initialization
|
|
57
|
+
* (not inside a function that isn't immediately invoked).
|
|
58
|
+
*/
|
|
59
|
+
function isExecutedDuringInitialization(path) {
|
|
60
|
+
let functionParent = path.getFunctionParent();
|
|
61
|
+
while (functionParent) {
|
|
62
|
+
if (!isUsedAsCallee(functionParent)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
functionParent = functionParent.getFunctionParent();
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Checks if this expression is in an assignment context
|
|
71
|
+
* (variable declaration, assignment expression, or class).
|
|
72
|
+
*/
|
|
73
|
+
function isInAssignmentContext(path) {
|
|
74
|
+
const statement = path.getStatementParent();
|
|
75
|
+
if (!statement)
|
|
76
|
+
return false;
|
|
77
|
+
let currentPath = path;
|
|
78
|
+
do {
|
|
79
|
+
currentPath = currentPath.parentPath;
|
|
80
|
+
if (!currentPath)
|
|
81
|
+
return false;
|
|
82
|
+
if (currentPath.isVariableDeclaration() ||
|
|
83
|
+
currentPath.isAssignmentExpression() ||
|
|
84
|
+
currentPath.isClass()) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
} while (currentPath !== statement);
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Visitor function for CallExpression and NewExpression nodes.
|
|
92
|
+
*/
|
|
93
|
+
function callableExpressionVisitor(path, _state) {
|
|
94
|
+
// Skip if this is used as a callee (e.g., foo()())
|
|
95
|
+
if (isUsedAsCallee(path)) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// Skip if this is inside a callee chain
|
|
99
|
+
if (isInCallee(path)) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// Skip if not executed during initialization
|
|
103
|
+
if (!isExecutedDuringInitialization(path)) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Must be in assignment context or export default
|
|
107
|
+
const statement = path.getStatementParent();
|
|
108
|
+
if (!isInAssignmentContext(path) && !statement?.isExportDefaultDeclaration()) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
annotateAsPure(path);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Creates a Babel visitor that adds PURE annotations to call expressions.
|
|
115
|
+
*/
|
|
116
|
+
export function createAnnotateEffectsVisitor(filename, _options) {
|
|
117
|
+
return {
|
|
118
|
+
Program: {
|
|
119
|
+
enter(_path, state) {
|
|
120
|
+
state.filename = filename;
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
CallExpression(path, state) {
|
|
124
|
+
callableExpressionVisitor(path, state);
|
|
125
|
+
},
|
|
126
|
+
NewExpression(path, state) {
|
|
127
|
+
callableExpressionVisitor(path, state);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Creates the annotate effects transformer plugin.
|
|
133
|
+
*/
|
|
134
|
+
export function annotateEffectsTransformer(options) {
|
|
135
|
+
return {
|
|
136
|
+
name: "effect-annotate-pure-calls",
|
|
137
|
+
visitor: createAnnotateEffectsVisitor("", options)
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=annotateEffects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"annotateEffects.js","sourceRoot":"","sources":["../../../src/transformers/annotateEffects.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import * as t from "@babel/types";
|
|
2
|
+
import { createHoistingState } from "../utils/hoisting.js";
|
|
3
|
+
/**
|
|
4
|
+
* Checks if a CallExpression is a withSpan call.
|
|
5
|
+
* Matches: Effect.withSpan(...), _.withSpan(...), or standalone withSpan(...)
|
|
6
|
+
*/
|
|
7
|
+
function isWithSpanCall(node) {
|
|
8
|
+
const callee = node.callee;
|
|
9
|
+
// Match Effect.withSpan(...) or _.withSpan(...)
|
|
10
|
+
if (callee.type === "MemberExpression" &&
|
|
11
|
+
callee.property.type === "Identifier" &&
|
|
12
|
+
callee.property.name === "withSpan") {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
// Match standalone withSpan(...)
|
|
16
|
+
if (callee.type === "Identifier" && callee.name === "withSpan") {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Determines if this is a data-first call (effect as first arg) or data-last (name as first arg).
|
|
23
|
+
* Data-first: withSpan(effect, "name", options?)
|
|
24
|
+
* Data-last: withSpan("name", options?)
|
|
25
|
+
*/
|
|
26
|
+
function isDataFirstCall(node) {
|
|
27
|
+
// If first argument is a string literal, it's data-last
|
|
28
|
+
if (node.arguments.length > 0 && t.isStringLiteral(node.arguments[0])) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
// If second argument exists and is a string literal, it's data-first
|
|
32
|
+
if (node.arguments.length > 1 && t.isStringLiteral(node.arguments[1])) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
// Default to data-last pattern
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Creates the attributes object with source location.
|
|
40
|
+
*/
|
|
41
|
+
function createSourceAttributes(filepath, line, column) {
|
|
42
|
+
return t.objectExpression([
|
|
43
|
+
t.objectProperty(t.stringLiteral("code.filepath"), t.stringLiteral(filepath)),
|
|
44
|
+
t.objectProperty(t.stringLiteral("code.lineno"), t.numericLiteral(line)),
|
|
45
|
+
t.objectProperty(t.stringLiteral("code.column"), t.numericLiteral(column))
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Merges source attributes into an existing options object or creates a new one.
|
|
50
|
+
*/
|
|
51
|
+
function mergeOrCreateOptions(existingOptions, sourceAttrs) {
|
|
52
|
+
if (!existingOptions || t.isSpreadElement(existingOptions) || t.isArgumentPlaceholder(existingOptions)) {
|
|
53
|
+
// No existing options, create new object with attributes
|
|
54
|
+
return t.objectExpression([
|
|
55
|
+
t.objectProperty(t.identifier("attributes"), sourceAttrs)
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
if (t.isObjectExpression(existingOptions)) {
|
|
59
|
+
// Check if there's an existing attributes property
|
|
60
|
+
const existingAttrsIndex = existingOptions.properties.findIndex((prop) => t.isObjectProperty(prop) &&
|
|
61
|
+
((t.isIdentifier(prop.key) && prop.key.name === "attributes") ||
|
|
62
|
+
(t.isStringLiteral(prop.key) && prop.key.value === "attributes")));
|
|
63
|
+
if (existingAttrsIndex >= 0) {
|
|
64
|
+
// Merge with existing attributes using spread
|
|
65
|
+
const existingAttrsProp = existingOptions.properties[existingAttrsIndex];
|
|
66
|
+
const mergedAttrs = t.objectExpression([
|
|
67
|
+
t.spreadElement(existingAttrsProp.value),
|
|
68
|
+
...sourceAttrs.properties
|
|
69
|
+
]);
|
|
70
|
+
// Clone the options and replace the attributes property
|
|
71
|
+
const newProperties = [...existingOptions.properties];
|
|
72
|
+
newProperties[existingAttrsIndex] = t.objectProperty(t.identifier("attributes"), mergedAttrs);
|
|
73
|
+
return t.objectExpression(newProperties);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// Add new attributes property to existing object
|
|
77
|
+
return t.objectExpression([
|
|
78
|
+
...existingOptions.properties,
|
|
79
|
+
t.objectProperty(t.identifier("attributes"), sourceAttrs)
|
|
80
|
+
]);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// If it's a variable reference, spread it and add attributes
|
|
84
|
+
return t.objectExpression([
|
|
85
|
+
t.spreadElement(existingOptions),
|
|
86
|
+
t.objectProperty(t.identifier("attributes"), sourceAttrs)
|
|
87
|
+
]);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Creates a Babel visitor that injects source location attributes into Effect.withSpan calls.
|
|
91
|
+
*/
|
|
92
|
+
export function createWithSpanTraceVisitor(filename, _options) {
|
|
93
|
+
return {
|
|
94
|
+
Program: {
|
|
95
|
+
enter(_path, state) {
|
|
96
|
+
state.filename = filename;
|
|
97
|
+
state.hoisting = createHoistingState();
|
|
98
|
+
},
|
|
99
|
+
exit(path, state) {
|
|
100
|
+
// Prepend all hoisted statements to the program body
|
|
101
|
+
if (state.hoisting.statements.length > 0) {
|
|
102
|
+
path.unshiftContainer("body", state.hoisting.statements);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
CallExpression(path, state) {
|
|
107
|
+
const node = path.node;
|
|
108
|
+
if (!isWithSpanCall(node)) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// Get source location
|
|
112
|
+
const loc = node.loc;
|
|
113
|
+
if (!loc)
|
|
114
|
+
return;
|
|
115
|
+
const line = loc.start.line;
|
|
116
|
+
const column = loc.start.column;
|
|
117
|
+
// Create source attributes
|
|
118
|
+
const sourceAttrs = createSourceAttributes(state.filename, line, column);
|
|
119
|
+
const isDataFirst = isDataFirstCall(node);
|
|
120
|
+
if (isDataFirst) {
|
|
121
|
+
// Data-first: withSpan(effect, "name", options?)
|
|
122
|
+
// Options is at index 2
|
|
123
|
+
const optionsArg = node.arguments[2];
|
|
124
|
+
const newOptions = mergeOrCreateOptions(optionsArg, sourceAttrs);
|
|
125
|
+
if (node.arguments.length >= 3) {
|
|
126
|
+
// Replace existing options
|
|
127
|
+
node.arguments[2] = newOptions;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
// Add options as third argument
|
|
131
|
+
node.arguments.push(newOptions);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// Data-last: withSpan("name", options?)
|
|
136
|
+
// Options is at index 1
|
|
137
|
+
const optionsArg = node.arguments[1];
|
|
138
|
+
const newOptions = mergeOrCreateOptions(optionsArg, sourceAttrs);
|
|
139
|
+
if (node.arguments.length >= 2) {
|
|
140
|
+
// Replace existing options
|
|
141
|
+
node.arguments[1] = newOptions;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
// Add options as second argument
|
|
145
|
+
node.arguments.push(newOptions);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Creates the withSpan trace transformer plugin.
|
|
153
|
+
*/
|
|
154
|
+
export function withSpanTraceTransformer(options) {
|
|
155
|
+
return {
|
|
156
|
+
name: "effect-withspan-trace",
|
|
157
|
+
visitor: createWithSpanTraceVisitor("", options)
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=withSpanTrace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withSpanTrace.js","sourceRoot":"","sources":["../../../src/transformers/withSpanTrace.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clayroach/unplugin",
|
|
3
|
-
"version": "0.1.0-source-trace.
|
|
3
|
+
"version": "0.1.0-source-trace.4",
|
|
4
4
|
"description": "Universal bundler plugin for Effect transformations",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"@babel/generator": "^7.26.0"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
|
-
"@clayroach/effect": "^3.19.14-source-trace.
|
|
21
|
+
"@clayroach/effect": "^3.19.14-source-trace.3"
|
|
22
22
|
},
|
|
23
23
|
"publishConfig": {
|
|
24
24
|
"provenance": true
|
package/src/index.ts
CHANGED
|
@@ -28,7 +28,9 @@ import type { Scope, TraverseOptions, Visitor } from "@babel/traverse"
|
|
|
28
28
|
import _traverse from "@babel/traverse"
|
|
29
29
|
import type { Node } from "@babel/types"
|
|
30
30
|
import { createUnplugin, type UnpluginFactory, type UnpluginInstance } from "unplugin"
|
|
31
|
+
import { createAnnotateEffectsVisitor } from "./transformers/annotateEffects.js"
|
|
31
32
|
import { createSourceTraceVisitor } from "./transformers/sourceTrace.js"
|
|
33
|
+
import { createWithSpanTraceVisitor } from "./transformers/withSpanTrace.js"
|
|
32
34
|
import type { HoistingState } from "./utils/hoisting.js"
|
|
33
35
|
|
|
34
36
|
// Define function types for Babel packages (they export differently in ESM vs CJS)
|
|
@@ -76,6 +78,14 @@ export interface EffectPluginOptions {
|
|
|
76
78
|
*/
|
|
77
79
|
readonly sourceTrace?: boolean
|
|
78
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Enable source location attributes injection into Effect.withSpan() calls.
|
|
83
|
+
* When enabled, spans will include code.filepath, code.lineno, and code.column attributes.
|
|
84
|
+
*
|
|
85
|
+
* @default true
|
|
86
|
+
*/
|
|
87
|
+
readonly spanTrace?: boolean
|
|
88
|
+
|
|
79
89
|
/**
|
|
80
90
|
* Enable @__PURE__ annotations for tree-shaking.
|
|
81
91
|
*
|
|
@@ -142,10 +152,11 @@ function shouldTransform(
|
|
|
142
152
|
*/
|
|
143
153
|
const unpluginFactory: UnpluginFactory<EffectPluginOptions | undefined> = (options = {}) => {
|
|
144
154
|
const {
|
|
145
|
-
annotateEffects
|
|
155
|
+
annotateEffects = false,
|
|
146
156
|
exclude = DEFAULT_EXCLUDE,
|
|
147
157
|
include = DEFAULT_INCLUDE,
|
|
148
|
-
sourceTrace = true
|
|
158
|
+
sourceTrace = true,
|
|
159
|
+
spanTrace = true
|
|
149
160
|
} = options
|
|
150
161
|
|
|
151
162
|
return {
|
|
@@ -169,8 +180,18 @@ const unpluginFactory: UnpluginFactory<EffectPluginOptions | undefined> = (optio
|
|
|
169
180
|
visitors.push(createSourceTraceVisitor(id))
|
|
170
181
|
}
|
|
171
182
|
|
|
183
|
+
if (spanTrace) {
|
|
184
|
+
visitors.push(createWithSpanTraceVisitor(id))
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Run annotateEffects separately (it has a simpler state)
|
|
188
|
+
if (annotateEffects) {
|
|
189
|
+
const annotateState = { filename: id }
|
|
190
|
+
traverse(ast, createAnnotateEffectsVisitor(id), undefined, annotateState)
|
|
191
|
+
}
|
|
192
|
+
|
|
172
193
|
// Combine visitors
|
|
173
|
-
if (visitors.length === 0) {
|
|
194
|
+
if (visitors.length === 0 && !annotateEffects) {
|
|
174
195
|
return null
|
|
175
196
|
}
|
|
176
197
|
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure call annotation transformer.
|
|
3
|
+
*
|
|
4
|
+
* This transformer adds `#__PURE__` comments to call expressions for better tree-shaking.
|
|
5
|
+
* It replicates the behavior of `babel-plugin-annotate-pure-calls` but integrated into
|
|
6
|
+
* the Effect unplugin.
|
|
7
|
+
*
|
|
8
|
+
* The `#__PURE__` annotation tells bundlers (Webpack, Rollup, esbuild) that a function call
|
|
9
|
+
* has no side effects and can be safely removed if the result is unused.
|
|
10
|
+
*
|
|
11
|
+
* @since 0.1.0
|
|
12
|
+
*/
|
|
13
|
+
import type { NodePath, Visitor } from "@babel/traverse"
|
|
14
|
+
import * as t from "@babel/types"
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Options for the annotate effects transformer.
|
|
18
|
+
*/
|
|
19
|
+
export interface AnnotateEffectsOptions {
|
|
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
|
+
}
|
|
32
|
+
|
|
33
|
+
const PURE_ANNOTATION = "#__PURE__"
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Checks if a node already has a PURE annotation.
|
|
37
|
+
*/
|
|
38
|
+
function isPureAnnotated(node: t.Node): boolean {
|
|
39
|
+
const leadingComments = node.leadingComments
|
|
40
|
+
if (!leadingComments) {
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
return leadingComments.some((comment) => /[@#]__PURE__/.test(comment.value))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Adds a PURE annotation comment to a node.
|
|
48
|
+
*/
|
|
49
|
+
function annotateAsPure(path: NodePath<t.CallExpression | t.NewExpression>): void {
|
|
50
|
+
if (isPureAnnotated(path.node)) {
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
path.addComment("leading", PURE_ANNOTATION)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Checks if the parent is a CallExpression or NewExpression.
|
|
58
|
+
*/
|
|
59
|
+
function hasCallableParent(path: NodePath): boolean {
|
|
60
|
+
const parentPath = path.parentPath
|
|
61
|
+
if (!parentPath) return false
|
|
62
|
+
return parentPath.isCallExpression() || parentPath.isNewExpression()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Checks if this node is used as a callee (e.g., `foo()` where foo is the callee).
|
|
67
|
+
*/
|
|
68
|
+
function isUsedAsCallee(path: NodePath): boolean {
|
|
69
|
+
if (!hasCallableParent(path)) {
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
const parentPath = path.parentPath as NodePath<t.CallExpression | t.NewExpression>
|
|
73
|
+
return parentPath.get("callee") === path
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Checks if this node is inside a callee chain (e.g., `foo()()` or `foo.bar()`).
|
|
78
|
+
*/
|
|
79
|
+
function isInCallee(path: NodePath): boolean {
|
|
80
|
+
let current: NodePath | null = path
|
|
81
|
+
do {
|
|
82
|
+
current = current.parentPath
|
|
83
|
+
if (!current) return false
|
|
84
|
+
if (isUsedAsCallee(current)) {
|
|
85
|
+
return true
|
|
86
|
+
}
|
|
87
|
+
} while (!current.isStatement() && !current.isFunction())
|
|
88
|
+
return false
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Checks if this expression is executed during module initialization
|
|
93
|
+
* (not inside a function that isn't immediately invoked).
|
|
94
|
+
*/
|
|
95
|
+
function isExecutedDuringInitialization(path: NodePath): boolean {
|
|
96
|
+
let functionParent = path.getFunctionParent()
|
|
97
|
+
while (functionParent) {
|
|
98
|
+
if (!isUsedAsCallee(functionParent)) {
|
|
99
|
+
return false
|
|
100
|
+
}
|
|
101
|
+
functionParent = functionParent.getFunctionParent()
|
|
102
|
+
}
|
|
103
|
+
return true
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Checks if this expression is in an assignment context
|
|
108
|
+
* (variable declaration, assignment expression, or class).
|
|
109
|
+
*/
|
|
110
|
+
function isInAssignmentContext(path: NodePath): boolean {
|
|
111
|
+
const statement = path.getStatementParent()
|
|
112
|
+
if (!statement) return false
|
|
113
|
+
|
|
114
|
+
let currentPath: NodePath | null = path
|
|
115
|
+
do {
|
|
116
|
+
currentPath = currentPath.parentPath
|
|
117
|
+
if (!currentPath) return false
|
|
118
|
+
|
|
119
|
+
if (
|
|
120
|
+
currentPath.isVariableDeclaration() ||
|
|
121
|
+
currentPath.isAssignmentExpression() ||
|
|
122
|
+
currentPath.isClass()
|
|
123
|
+
) {
|
|
124
|
+
return true
|
|
125
|
+
}
|
|
126
|
+
} while (currentPath !== statement)
|
|
127
|
+
|
|
128
|
+
return false
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Visitor function for CallExpression and NewExpression nodes.
|
|
133
|
+
*/
|
|
134
|
+
function callableExpressionVisitor(
|
|
135
|
+
path: NodePath<t.CallExpression | t.NewExpression>,
|
|
136
|
+
_state: TransformState
|
|
137
|
+
): void {
|
|
138
|
+
// Skip if this is used as a callee (e.g., foo()())
|
|
139
|
+
if (isUsedAsCallee(path)) {
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Skip if this is inside a callee chain
|
|
144
|
+
if (isInCallee(path)) {
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Skip if not executed during initialization
|
|
149
|
+
if (!isExecutedDuringInitialization(path)) {
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Must be in assignment context or export default
|
|
154
|
+
const statement = path.getStatementParent()
|
|
155
|
+
if (!isInAssignmentContext(path) && !statement?.isExportDefaultDeclaration()) {
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
annotateAsPure(path)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Creates a Babel visitor that adds PURE annotations to call expressions.
|
|
164
|
+
*/
|
|
165
|
+
export function createAnnotateEffectsVisitor(
|
|
166
|
+
filename: string,
|
|
167
|
+
_options?: AnnotateEffectsOptions
|
|
168
|
+
): Visitor<TransformState> {
|
|
169
|
+
return {
|
|
170
|
+
Program: {
|
|
171
|
+
enter(_path, state) {
|
|
172
|
+
state.filename = filename
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
CallExpression(path: NodePath<t.CallExpression>, state) {
|
|
177
|
+
callableExpressionVisitor(path, state)
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
NewExpression(path: NodePath<t.NewExpression>, state) {
|
|
181
|
+
callableExpressionVisitor(path, state)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Creates the annotate effects transformer plugin.
|
|
188
|
+
*/
|
|
189
|
+
export function annotateEffectsTransformer(options?: AnnotateEffectsOptions): {
|
|
190
|
+
visitor: Visitor<TransformState>
|
|
191
|
+
name: string
|
|
192
|
+
} {
|
|
193
|
+
return {
|
|
194
|
+
name: "effect-annotate-pure-calls",
|
|
195
|
+
visitor: createAnnotateEffectsVisitor("", options)
|
|
196
|
+
}
|
|
197
|
+
}
|