Skip to content

Commit 3b71436

Browse files
committed
feat(evasive-transform): expose makeEvasiveTransformVisitor
Extracts `makeEvasiveTransformVisitor ` from `transformAst` and exports it via a new `./visitor.js` subpath. `transformAst` is reimplemented as a thin wrapper that calls `makeEvasiveTransformVisitor ` and traverses — no logic change, just inversion of control. Additional tidy-ups in the same diff: - `SourceType` now includes `commonjs`, and `parseAst` passes `allowReturnOutsideFunction: true` for that source type (which it does by default, if we didn't have it being set for `script` as well) - `SourceMapOption` type removed; its usages are replaced with `object | string` (actual referenced type does not exist) - Inline `import()` type references in JSDoc replaced with `@import` declarations at the top of each file. - `stripInternal: true` added to `tsconfig.json` so `@internal` items are excluded from generated `.d.ts` output. - `@types/babel__generator` and `@types/babel__traverse` (prod) added as dependencies (they were missing)
1 parent 5aa2a37 commit 3b71436

11 files changed

Lines changed: 138 additions & 59 deletions

File tree

.changeset/itchy-meteors-cover.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@endo/evasive-transform': minor
3+
---
4+
5+
Expose `makeTransformCommentsVisitor()` via the `visitor.js` entry point for use with `@endo/parser-pipeline`.

packages/evasive-transform/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"module": "./index.js",
2323
"exports": {
2424
".": "./index.js",
25+
"./visitor.js": "./visitor.js",
2526
"./package.json": "./package.json"
2627
},
2728
"scripts": {
@@ -38,6 +39,7 @@
3839
"devDependencies": {
3940
"@babel/types": "~7.28.2",
4041
"@endo/ses-ava": "workspace:^",
42+
"@types/babel__generator": "~7.27.0",
4143
"ava": "catalog:dev",
4244
"c8": "catalog:dev",
4345
"eslint": "catalog:dev",
@@ -72,6 +74,7 @@
7274
"dependencies": {
7375
"@babel/generator": "^7.28.3",
7476
"@babel/parser": "~7.28.3",
75-
"@babel/traverse": "~7.28.3"
77+
"@babel/traverse": "~7.28.3",
78+
"@types/babel__traverse": "~7.28.0"
7679
}
7780
}

packages/evasive-transform/src/generate.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,13 @@ const generator = /** @type {typeof import('@babel/generator')['default']} */ (
1717
babelGenerator.default || babelGenerator
1818
);
1919

20-
/**
21-
* @typedef {NonNullable<import('@babel/generator').GeneratorOptions['inputSourceMap']>} SourceMapOption
22-
*/
23-
2420
/**
2521
* Options for {@link generateCode} with source map
2622
*
2723
* @typedef GenerateAstOptionsWithSourceMap
2824
* @property {string} [source]
2925
* @property {string} sourceUrl - If present, we will generate a source map
30-
* @property {SourceMapOption | undefined} [sourceMap] - If present, the generated source map will be a transform over the given source map.
26+
* @property {object | string | undefined} [sourceMap] - If present, the generated source map will be a transform over the given source map.
3127
* @internal
3228
*/
3329

@@ -58,7 +54,7 @@ const generator = /** @type {typeof import('@babel/generator')['default']} */ (
5854

5955
/**
6056
* Generates new code from a Babel AST; returns code and source map
61-
*@overload
57+
* @overload
6258
* @param {import('@babel/types').File} ast - Babel "File" AST
6359
* @param {GenerateAstOptionsWithSourceMap} options - Options for the transform
6460
* @returns {TransformedResultWithSourceMap}
@@ -67,7 +63,7 @@ const generator = /** @type {typeof import('@babel/generator')['default']} */ (
6763

6864
/**
6965
* Generates new code from a Babel AST; returns code only
70-
*@overload
66+
* @overload
7167
* @param {import('@babel/types').File} ast - Babel "File" AST
7268
* @param {GenerateAstOptions} [options] - Options for the transform
7369
* @returns {TransformedResult}
@@ -93,6 +89,7 @@ export const generate = (ast, options) => {
9389
{
9490
sourceFileName: sourceUrl,
9591
sourceMaps: Boolean(sourceUrl),
92+
// @ts-expect-error undocumented option
9693
inputSourceMap,
9794
retainLines: true,
9895
...(source === undefined ? {} : { experimental_preserveFormat: true }),

packages/evasive-transform/src/index.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
/**
88
* @import {TransformedResult, TransformedResultWithSourceMap} from './generate.js'
9-
* @import {SourceMapOption} from './generate.js'
109
*/
1110

1211
import { transformAst } from './transform-ast.js';
@@ -17,13 +16,13 @@ import { generate } from './generate.js';
1716
* Options for {@link evadeCensorSync}
1817
*
1918
* @typedef EvadeCensorOptions
20-
* @property {SourceMapOption | undefined} [sourceMap] - Original source map in JSON string or object form
19+
* @property {object | string | undefined} [sourceMap] - Original source map in JSON string or object form
2120
* @property {string | undefined} [sourceUrl] - URL or filepath of the original source in `code`
2221
* @property {boolean | undefined} [elideComments] - Empties the comments but preserves interior newlines.
2322
* @property {import('./parse-ast.js').SourceType | undefined} [sourceType] - Module source type
2423
* @property {boolean | undefined} [onlyComments] - if true, will limit transformation to
2524
comment contents, preserving code positions within each line
26-
* @property {(path: import('@babel/traverse').NodePath) => void} [customVisitor] - A visitor function to be called on each node, in addition to the standard transforms. Receives the same path argument as a normal Babel visitor.
25+
* @property {(path: import('@babel/traverse').NodePath) => void} [customVisitor] - A visitor function to be called on each node, in addition to the standard transforms. Receives the same path argument as a normal Babel visitor.
2726
* @property {boolean | undefined} [useLocationUnmap] - deprecated, vestigial
2827
* @public
2928
*/

packages/evasive-transform/src/parse-ast.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const { parse: parseBabel } = babelParser;
1212
* This is a subset of `@babel/parser`'s `ParserOptions['sourceType']`, but
1313
* re-implemented here for decoupling purposes.
1414
*
15-
* @typedef {'module' | 'script'} SourceType
15+
* @typedef {'module' | 'script' | 'commonjs'} SourceType
1616
* @public
1717
*/
1818

@@ -37,7 +37,8 @@ export function parseAst(source, opts = {}) {
3737
return parseBabel(source, {
3838
tokens: true,
3939
createParenthesizedExpressions: true,
40-
allowReturnOutsideFunction: opts.sourceType === 'script',
40+
allowReturnOutsideFunction:
41+
opts.sourceType === 'script' || opts.sourceType === 'commonjs',
4142
...(opts.sourceType !== undefined && { sourceType: opts.sourceType }),
4243
});
4344
}

packages/evasive-transform/src/transform-ast.js

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ import {
1414
evadeMethod,
1515
} from './transform-code.js';
1616

17+
/**
18+
* @import {Visitor, NodePath} from '@babel/traverse'
19+
* @import {File} from '@babel/types'
20+
*/
21+
1722
// TODO The following is sufficient on Node.js, but for compatibility with
1823
// `node -r esm`, we must use the pattern below.
1924
// Restore after https://github.com/Agoric/agoric-sdk/issues/8671.
@@ -24,48 +29,35 @@ const traverse = /** @type {typeof import('@babel/traverse')['default']} */ (
2429
);
2530

2631
/**
27-
* Options for {@link transformAst}
32+
* Options shared by {@link makeEvasiveTransformVisitor} and {@link transformAst}.
2833
*
29-
* @internal
30-
* @typedef {TransformAstOptionsWithoutSourceMap} TransformAstOptions
31-
*/
32-
33-
/**
34-
* Options for {@link transformAst}
35-
*
36-
* @internal
37-
* @typedef TransformAstOptionsWithoutSourceMap
34+
* @typedef TransformAstOptions
3835
* @property {boolean} [elideComments]
3936
* @property {boolean} [onlyComments]
40-
* @property {(path: import('@babel/traverse').NodePath) => void} [customVisitor] - A visitor function to be called on each node, in addition to the standard transforms. Receives the same path argument as a normal Babel visitor.
37+
* @property {(path: NodePath) => void} [customVisitor] - A visitor function to be called on each node, in addition to the standard transforms. Receives the same path argument as a normal Babel visitor.
4138
*/
4239

4340
/**
44-
* Performs transformations on the given AST
45-
*
46-
* This function mutates `ast`.
41+
* Factory for a Babel {@link Visitor} which performs evasive transformations on the AST.
4742
*
48-
* @internal
49-
* @param {import('@babel/types').File} ast - AST, as generated by Babel
50-
* @param {TransformAstOptions} [opts]
51-
* @returns {void}
43+
* @param {TransformAstOptions} options
44+
* @returns {Visitor}
5245
*/
53-
export function transformAst(
54-
ast,
55-
{ elideComments = false, onlyComments = false, customVisitor } = {},
56-
) {
46+
export const makeEvasiveTransformVisitor = ({
47+
elideComments = false,
48+
onlyComments = false,
49+
customVisitor,
50+
} = {}) => {
5751
const transformComment = elideComments ? elideComment : evadeComment;
58-
traverse(ast, {
52+
return {
5953
enter(p) {
6054
const { leadingComments, innerComments, trailingComments, type } = p.node;
61-
// discriminated union
6255
if ('comments' in p.node) {
6356
(p.node.comments || []).forEach(node => transformComment(node));
6457
}
65-
// Rewrite all comments.
6658
(leadingComments || []).forEach(node => transformComment(node));
6759
if (type.startsWith('Comment')) {
68-
transformComment(p.node);
60+
transformComment(/** @type {any} */ (p.node));
6961
}
7062
(innerComments || []).forEach(node => transformComment(node));
7163
(trailingComments || []).forEach(node => transformComment(node));
@@ -78,5 +70,27 @@ export function transformAst(
7870
customVisitor?.(p);
7971
}
8072
},
73+
};
74+
};
75+
76+
/**
77+
* Performs transformations on the given AST
78+
*
79+
* This function mutates `ast`.
80+
*
81+
* @internal
82+
* @param {File} ast - AST, as generated by Babel
83+
* @param {TransformAstOptions} [opts]
84+
* @returns {void}
85+
*/
86+
export function transformAst(
87+
ast,
88+
{ elideComments = false, onlyComments = false, customVisitor } = {},
89+
) {
90+
const visitor = makeEvasiveTransformVisitor({
91+
elideComments,
92+
onlyComments,
93+
customVisitor,
8194
});
95+
traverse(ast, visitor);
8296
}

packages/evasive-transform/src/transform-code.js

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/**
2+
* @import {BinaryExpression, Expression, Node, TemplateElement} from '@babel/types'
3+
* @import {NodePath} from '@babel/traverse'
4+
*/
5+
16
const evadeRegexp = /import\s*\(|<!--|-->/g;
27
// The replacement collection for regexp patterns matching the evadeRegexp is only applied to the first matched character, so it is necessary for the regexpReplacements to be maintained together with the evadeRegexp.
38
const regexpReplacements = {
@@ -11,8 +16,8 @@ const regexpReplacements = {
1116
* to sever references), updating the target's end position as if it had zero
1217
* length.
1318
*
14-
* @param {import('@babel/types').Node} target
15-
* @param {import('@babel/types').Node} src
19+
* @param {Node} target
20+
* @param {Node} src
1621
*/
1722
const adoptStartFrom = (target, src) => {
1823
try {
@@ -37,9 +42,9 @@ const adoptStartFrom = (target, src) => {
3742
/**
3843
* Creates a BinaryExpression adding two expressions
3944
*
40-
* @param {import('@babel/types').Expression} left
45+
* @param {Expression} left
4146
* @param {string} rightString
42-
* @returns {import('@babel/types').BinaryExpression}
47+
* @returns {BinaryExpression}
4348
*/
4449
const addStringToExpressions = (left, rightString) => ({
4550
type: 'BinaryExpression',
@@ -55,15 +60,15 @@ const addStringToExpressions = (left, rightString) => ({
5560
* Break up problematic substrings into concatenation expressions, e.g.
5661
* `"import("` -> `"im"+"port("`.
5762
*
58-
* @param {import('@babel/traverse').NodePath} p
63+
* @param {NodePath} p
5964
*/
6065
export const evadeStrings = p => {
6166
const { node } = p;
6267
if (node.type !== 'StringLiteral') {
6368
return;
6469
}
6570
const { value } = node;
66-
/** @type {import('@babel/types').Expression | undefined} */
71+
/** @type {Expression | undefined} */
6772
let expr;
6873
let lastIndex = 0;
6974
for (const match of value.matchAll(evadeRegexp)) {
@@ -85,10 +90,9 @@ export const evadeStrings = p => {
8590
* Break up problematic substrings in template literals with empty-string
8691
* expressions, e.g. `import(` -> `im${''}port(`.
8792
*
88-
* @param {import('@babel/traverse').NodePath} p
93+
* @param {NodePath} p
8994
*/
9095
export const evadeTemplates = p => {
91-
/** @type {import('@babel/types').TemplateLiteral} */
9296
const node = p.node;
9397
// The transform is only meaning-preserving if not part of a
9498
// TaggedTemplateExpression, so these need to be excluded until a motivating
@@ -107,11 +111,14 @@ export const evadeTemplates = p => {
107111
// Check if any quasi needs transformation
108112
if (!quasis.some(quasi => quasi.value.raw.search(evadeRegexp) !== -1)) return;
109113

110-
/** @type {import('@babel/types').TemplateElement[]} */
114+
/** @type {TemplateElement[]} */
111115
const newQuasis = [];
112-
/** @type {import('@babel/types').Expression[]} */
116+
/** @type {Expression[]} */
113117
const newExpressions = [];
114118

119+
/**
120+
* @param {string} quasiValue
121+
*/
115122
const addQuasi = quasiValue => {
116123
// Insert empty expression to break the pattern
117124
newExpressions.push({
@@ -130,6 +137,7 @@ export const evadeTemplates = p => {
130137
});
131138
};
132139

140+
// eslint-disable-next-line @endo/restrict-comparison-operands
133141
for (let i = 0; i < quasis.length; i += 1) {
134142
const quasi = quasis[i];
135143
// We're not currently preserving raw vs. cooked literal data.
@@ -158,6 +166,7 @@ export const evadeTemplates = p => {
158166
}
159167

160168
// Add original expression between quasis
169+
// eslint-disable-next-line @endo/restrict-comparison-operands
161170
if (i < node.expressions.length) {
162171
// @ts-ignore whatever was there, must still be allowed.
163172
newExpressions.push(node.expressions[i]);
@@ -169,7 +178,7 @@ export const evadeTemplates = p => {
169178
newQuasis[newQuasis.length - 1].tail = true;
170179
}
171180

172-
/** @type {import('@babel/types').Node} */
181+
/** @type {Node} */
173182
const replacement = {
174183
type: 'TemplateLiteral',
175184
quasis: newQuasis,
@@ -185,7 +194,7 @@ export const evadeTemplates = p => {
185194
*
186195
* `/import(/` -> `/im[p]ort(/`
187196
*
188-
* @param {import('@babel/traverse').NodePath} p
197+
* @param {NodePath} p
189198
* @returns {void}
190199
*/
191200
export const evadeRegexpLiteral = p => {
@@ -207,7 +216,7 @@ export const evadeRegexpLiteral = p => {
207216
* Prevents `-->` from appearing in output by transforming
208217
* `x-->y` to `(0,x--)>y`.
209218
*
210-
* @param {import('@babel/traverse').NodePath} p
219+
* @param {NodePath} p
211220
* @returns {void}
212221
*/
213222
export const evadeDecrementGreater = p => {
@@ -228,20 +237,20 @@ export const evadeDecrementGreater = p => {
228237
};
229238

230239
const EVADE_METHODS = ['import', 'eval'];
240+
231241
/**
232-
* @param {import('@babel/traverse').NodePath} p
242+
* @param {NodePath} p
233243
*/
234244
export const evadeMethod = p => {
235-
const { node } = p;
236245
// find class and object definitions with a method name we need to evade.
237246
// E.g. import() -> `['import']()`
238247
const isMethod = p.isObjectMethod() || p.isClassMethod();
239248
if (
240249
isMethod &&
241-
node.key.type === 'Identifier' &&
242-
EVADE_METHODS.includes(node.key.name)
250+
p.node.key.type === 'Identifier' &&
251+
EVADE_METHODS.includes(p.node.key.name)
243252
) {
244-
node.computed = true;
245-
node.key = { type: 'StringLiteral', value: node.key.name };
253+
p.node.computed = true;
254+
p.node.key = { type: 'StringLiteral', value: p.node.key.name };
246255
}
247256
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* Low-level Babel visitor factory for use with `@endo/parser-pipeline`.
3+
*
4+
* Exports {@link makeEvasiveTransformVisitor} to build a
5+
* pipeline-compatible `TransformPass` from the evasive-transform logic.
6+
* The actual `createEvasiveTransformPass` wrapper lives in
7+
* `@endo/parser-pipeline`.
8+
*
9+
* @module
10+
*/
11+
12+
export { makeEvasiveTransformVisitor } from './transform-ast.js';

packages/evasive-transform/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
22
"extends": "../../tsconfig.eslint-base.json",
33
"compilerOptions": {
4-
"allowSyntheticDefaultImports": true
4+
"allowSyntheticDefaultImports": true,
5+
"stripInternal": true
56
},
67
"include": [
78
"*.js",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { makeEvasiveTransformVisitor } from './src/visitor.js';

0 commit comments

Comments
 (0)