diff --git a/packages/transformers/sync-dynamic-import/package.json b/packages/transformers/sync-dynamic-import/package.json index 81e2215dd22..825b31d3b52 100644 --- a/packages/transformers/sync-dynamic-import/package.json +++ b/packages/transformers/sync-dynamic-import/package.json @@ -5,13 +5,17 @@ "source": "src/SyncDynamicImportTransformer.js", "engines": { "node": ">= 10.0.0", - "parcel": "^2.0.0-alpha.1.1" + "parcel": "^2.0.15" }, "dependencies": { "@babel/core": "^7.8.7", - "@parcel/babel-ast-utils": "2.0.15", + "@babel/parser": "^7.0.0", + "@babel/types": "^7.12.13", + "@parcel/source-map": "^2.0.0", "@parcel/plugin": "2.0.15", "@parcel/utils": "2.0.15", + "astring": "^1.6.2", + "lodash.clone": "^4.5.0", "react-loadable": "^5.5.0", "semver": "^7.3.2" } diff --git a/packages/transformers/sync-dynamic-import/src/SyncDynamicImportTransformer.js b/packages/transformers/sync-dynamic-import/src/SyncDynamicImportTransformer.js index 521c8b2726b..92ae9fd48a0 100644 --- a/packages/transformers/sync-dynamic-import/src/SyncDynamicImportTransformer.js +++ b/packages/transformers/sync-dynamic-import/src/SyncDynamicImportTransformer.js @@ -3,9 +3,9 @@ import semver from 'semver'; import {Transformer} from '@parcel/plugin'; import * as babelCore from '@babel/core'; -import {generate, parse} from '@parcel/babel-ast-utils'; import {relativeUrl} from '@parcel/utils'; import packageJson from '../package.json'; +import {generate, parse} from './babel-ast-utils'; import syncDynamicImportPlugin from './babel/babel-plugin-sync-dynamic-import'; const transformerVersion: mixed = packageJson.version; diff --git a/packages/transformers/sync-dynamic-import/src/babel-ast-utils/babelErrorUtils.js b/packages/transformers/sync-dynamic-import/src/babel-ast-utils/babelErrorUtils.js new file mode 100644 index 00000000000..9d705d10a8b --- /dev/null +++ b/packages/transformers/sync-dynamic-import/src/babel-ast-utils/babelErrorUtils.js @@ -0,0 +1,30 @@ +// @flow +import type {BaseAsset} from '@parcel/types'; + +export type BabelError = Error & { + loc?: { + line: number, + column: number, + ... + }, + source?: string, + filePath?: string, + ... +}; + +export async function babelErrorEnhancer( + error: BabelError, + asset: BaseAsset, +): Promise { + if (error.loc) { + let start = error.message.startsWith(asset.filePath) + ? asset.filePath.length + 1 + : 0; + error.message = error.message.slice(start).split('\n')[0].trim(); + } + + error.source = await asset.getCode(); + error.filePath = asset.filePath; + + return error; +} diff --git a/packages/transformers/sync-dynamic-import/src/babel-ast-utils/generator.js b/packages/transformers/sync-dynamic-import/src/babel-ast-utils/generator.js new file mode 100644 index 00000000000..0d5a989a1b6 --- /dev/null +++ b/packages/transformers/sync-dynamic-import/src/babel-ast-utils/generator.js @@ -0,0 +1,365 @@ +import {GENERATOR, EXPRESSIONS_PRECEDENCE} from 'astring'; + +export const expressionsPrecedence = { + ...EXPRESSIONS_PRECEDENCE, + // Babel extensions + NumericLiteral: EXPRESSIONS_PRECEDENCE.Literal, + StringLiteral: EXPRESSIONS_PRECEDENCE.Literal, + BooleanLiteral: EXPRESSIONS_PRECEDENCE.Literal, + NullLiteral: EXPRESSIONS_PRECEDENCE.Literal, + RegExpLiteral: EXPRESSIONS_PRECEDENCE.Literal, + BigIntLiteral: EXPRESSIONS_PRECEDENCE.Literal, + OptionalMemberExpression: EXPRESSIONS_PRECEDENCE.MemberExpression, + OptionalCallExpression: EXPRESSIONS_PRECEDENCE.CallExpression, + Import: EXPRESSIONS_PRECEDENCE.Identifier, + PrivateName: EXPRESSIONS_PRECEDENCE.Identifier, +}; + +// Convert Babel's AST format to ESTree on the fly. +// See https://babeljs.io/docs/en/babel-parser#output +export const generator = { + ...GENERATOR, + Program(node, state) { + // Monkeypatch state to fix sourcemap filenames. + let map = state.map; + state.map = (str, node) => { + if (node != null && node.loc != null) { + state.mapping.source = node.loc.filename; + } + map.call(state, str, node); + }; + + if (node.interpreter) { + state.write(`#!${node.interpreter.value}\n`); + } + + handleDirectives(node); + GENERATOR.Program.call(this, node, state); + }, + BlockStatement(node, state) { + handleDirectives(node); + GENERATOR.BlockStatement.call(this, node, state); + }, + NumericLiteral(node, state) { + node.type = 'Literal'; + node.raw = getRaw(node); + this.Literal(node, state, true); + }, + StringLiteral(node, state) { + node.type = 'Literal'; + node.raw = getRaw(node); + this.Literal(node, state, true); + }, + BooleanLiteral(node, state) { + node.type = 'Literal'; + this.Literal(node, state, true); + }, + NullLiteral(node, state) { + node.type = 'Literal'; + node.raw = 'null'; + node.value = null; + this.Literal(node, state, true); + }, + RegExpLiteral(node, state) { + node.type = 'Literal'; + node.raw = getRaw(node); + node.value = {}; + node.regex = { + pattern: node.pattern, + flags: node.flags, + }; + GENERATOR.Literal(node, state); + }, + BigIntLiteral(node, state) { + node.type = 'Literal'; + node.raw = getRaw(node); + this.Literal(node, state, true); + }, + ArrowFunctionExpression(node, state) { + if ( + node.body.type === 'OptionalMemberExpression' || + node.body.type === 'OptionalCallExpression' + ) { + // the ArrowFunctionExpression visitor in astring checks the type of the body + // Make sure they don't start with "O" + node.body.type = '_' + node.body.type; + } + GENERATOR.ArrowFunctionExpression.call(this, node, state); + }, + ObjectProperty(node, state) { + node.type = 'Property'; + node.kind = 'init'; + if (node.shorthand) { + let id = + node.value.type === 'Identifier' + ? node.value + : node.value.type === 'AssignmentPattern' && + node.value.left.type === 'Identifier' + ? node.value.left + : null; + if (!id || id.name !== node.key.name) { + node.shorthand = false; + } + } + this.Property(node, state, true); + }, + ObjectMethod(node, state) { + node.value = { + type: 'FunctionExpression', + id: node.id, + params: node.params, + body: node.body, + async: node.async, + generator: node.generator, + expression: node.expression, + }; + + node.type = 'Property'; + if (node.kind === 'method') { + node.kind = 'init'; + } + + this.Property(node, state, true); + }, + ClassMethod(node, state) { + node.value = { + type: 'FunctionExpression', + id: node.id, + params: node.params, + body: node.body, + async: node.async, + generator: node.generator, + expression: node.expression, + }; + + node.type = 'MethodDefinition'; + this.MethodDefinition(node, state, true); + }, + ClassPrivateMethod(node, state) { + node.value = { + type: 'FunctionExpression', + id: node.id, + params: node.params, + body: node.body, + async: node.async, + generator: node.generator, + expression: node.expression, + }; + + node.type = 'MethodDefinition'; + this.MethodDefinition(node, state, true); + }, + ClassProperty(node, state) { + if (node.static) { + state.write('static '); + } + + if (node.computed) { + state.write('['); + this[node.key.type](node.key, state); + state.write(']'); + } else { + this[node.key.type](node.key, state); + } + + if (node.value) { + state.write(' = '); + this[node.value.type](node.value, state, true); + } + + state.write(';'); + }, + ClassPrivateProperty(node, state) { + if (node.static) { + state.write('static '); + } + + this[node.key.type](node.key, state); + if (node.value) { + state.write(' = '); + this[node.value.type](node.value, state); + } + + state.write(';'); + }, + PrivateName(node, state) { + state.write('#' + node.name, node); + }, + Import(node, state) { + // astring doesn't support ImportExpression yet + state.write('import'); + }, + _OptionalMemberExpression(node, state) { + this.OptionalMemberExpression(node, state, true); + }, + OptionalMemberExpression(node, state) { + node.optional = true; + node.type = 'MemberExpression'; + GENERATOR.MemberExpression.call(this, node, state); + }, + MemberExpression(node, state) { + if (node.optional) node.optional = false; + GENERATOR.MemberExpression.call(this, node, state); + }, + _OptionalCallExpression(node, state) { + this.OptionalCallExpression(node, state, true); + }, + OptionalCallExpression(node, state) { + node.optional = true; + node.type = 'CallExpression'; + GENERATOR.CallExpression.call(this, node, state); + }, + CallExpression(node, state) { + if (node.optional) node.optional = false; + GENERATOR.CallExpression.call(this, node, state); + }, + ExportNamedDeclaration(node, state) { + if (node.source) { + let namespace = node.specifiers.find( + specifier => specifier.type === 'ExportNamespaceSpecifier', + ); + if (namespace) { + // Babel parser allows combining namespace specifiers and named specifiers + // e.g. `export * as foo, {bar} from 'other'`, but this is not supported by the spec. + if (node.specifiers.length > 1) { + throw new Error( + 'Namespace specifiers cannot be combined with named specifiers', + ); + } + + node.type = 'ExportAllDeclaration'; + node.exported = namespace.exported; + } + } + + GENERATOR[node.type].call(this, node, state); + }, + ReturnStatement(node, state) { + // Add parentheses if there are leading comments + if (node.argument?.leadingComments?.length > 0) { + let indent = state.indent.repeat(state.indentLevel); + state.write('return (' + state.lineEnd); + state.write(indent + state.indent); + state.indentLevel++; + this[node.argument.type](node.argument, state); + state.indentLevel--; + state.write(state.lineEnd); + state.write(indent + ');'); + } else { + GENERATOR.ReturnStatement.call(this, node, state); + } + }, + ThrowStatement(node, state) { + // Add parentheses if there are leading comments + if (node.argument?.leadingComments?.length > 0) { + let indent = state.indent.repeat(state.indentLevel); + state.write('throw (' + state.lineEnd); + state.write(indent + state.indent); + state.indentLevel++; + this[node.argument.type](node.argument, state); + state.indentLevel--; + state.write(state.lineEnd); + state.write(indent + ');'); + } else { + GENERATOR.ThrowStatement.call(this, node, state); + } + }, +}; + +// Make every node support comments. Important for preserving /*@__PURE__*/ comments for terser. +// TODO: contribute to astring. +for (let key in generator) { + let orig = generator[key]; + generator[key] = function (node, state, skipComments) { + // These are printed by astring itself + if (node.trailingComments) { + for (let c of node.trailingComments) { + if (c.type === 'CommentLine' || c.type === 'LineComment') { + c.type = 'LineComment'; + } else { + c.type = 'BlockComment'; + } + } + } + if ( + !skipComments && + node.leadingComments && + node.leadingComments.length > 0 + ) { + formatComments(state, node.leadingComments); + } + orig.call(this, node, state); + }; +} + +function handleDirectives(node) { + if (node.directives) { + for (var i = node.directives.length - 1; i >= 0; i--) { + var directive = node.directives[i]; + directive.type = 'ExpressionStatement'; + directive.expression = directive.value; + directive.expression.type = 'Literal'; + node.body.unshift(directive); + } + } +} + +// Copied from the astring source. +function formatComments(state, comments) { + // Writes into `state` the provided list of `comments`, with the given `indent` and `lineEnd` strings. + // Line comments will end with `"\n"` regardless of the value of `lineEnd`. + // Expects to start on a new unindented line. + const indent = state.indent.repeat(state.indentLevel); + const {length} = comments; + for (let i = 0; i < length; i++) { + const comment = comments[i]; + if (comment.type === 'CommentLine' || comment.type === 'LineComment') { + // Line comment + state.write('// ' + comment.value.trim() + state.lineEnd, { + ...comment, + type: 'LineComment', + }); + state.write(indent); + } else { + // Block comment + state.write('/*'); + reindent(state, comment.value, indent, state.lineEnd); + state.write('*/'); + + // Keep pure annotations on the same line + let value = comment.value.trim(); + if ( + !((value === '#__PURE__' || value === '@__PURE__') && i === length - 1) + ) { + state.write(state.lineEnd); + state.write(indent); + } + } + } +} + +function reindent(state, text, indent, lineEnd) { + // Writes into `state` the `text` string reindented with the provided `indent`. + const lines = text.split('\n'); + const end = lines.length - 1; + state.write(lines[0].trim()); + if (end > 0) { + state.write(lineEnd); + for (let i = 1; i < end; i++) { + state.write(indent + lines[i].trim() + lineEnd); + } + state.write(indent + lines[end].trim()); + } +} + +function getRaw(node) { + let extra = node.extra; + if ( + extra && + extra.raw != null && + extra.rawValue != null && + node.value === extra.rawValue + ) { + return extra.raw; + } +} diff --git a/packages/transformers/sync-dynamic-import/src/babel-ast-utils/index.js b/packages/transformers/sync-dynamic-import/src/babel-ast-utils/index.js new file mode 100644 index 00000000000..570075daa04 --- /dev/null +++ b/packages/transformers/sync-dynamic-import/src/babel-ast-utils/index.js @@ -0,0 +1,209 @@ +// @flow strict-local + +import type { + AST, + BaseAsset, + PluginOptions, + SourceLocation, + FilePath, +} from '@parcel/types'; +import type { + SourceLocation as BabelSourceLocation, + File as BabelNodeFile, +} from '@babel/types'; + +import path from 'path'; +import {parse as babelParse, type ParserPlugin} from '@babel/parser'; +import SourceMap from '@parcel/source-map'; +import {relativeUrl} from '@parcel/utils'; +import {traverseAll} from '../babylon-walk'; +import {babelErrorEnhancer} from './babelErrorUtils'; +// $FlowFixMe +import {generate as astringGenerate} from 'astring'; +// $FlowFixMe +import {generator, expressionsPrecedence} from './generator'; + +export {babelErrorEnhancer}; + +export function remapAstLocations(ast: BabelNodeFile, map: SourceMap) { + // remap ast to original mappings + // This improves sourcemap accuracy and fixes sourcemaps when scope-hoisting + traverseAll(ast.program, node => { + if (node.loc) { + if (node.loc?.start) { + let mapping = map.findClosestMapping( + node.loc.start.line, + node.loc.start.column, + ); + + if (mapping?.original) { + // $FlowFixMe + node.loc.start.line = mapping.original.line; + // $FlowFixMe + node.loc.start.column = mapping.original.column; + + // $FlowFixMe + let length = node.loc.end.column - node.loc.start.column; + + // $FlowFixMe + node.loc.end.line = mapping.original.line; + // $FlowFixMe + node.loc.end.column = mapping.original.column + length; + + // $FlowFixMe + node.loc.filename = mapping.source; + } else { + // Maintain null mappings? + node.loc = null; + } + } + } + }); +} + +export async function parse({ + asset, + code, + options, + // ATLASSIAN: allow additional plugins for the parser + plugins: additionalPlugins = [], +}: {| + asset: BaseAsset, + code: string, + options: PluginOptions, + // ATLASSIAN: allow additional plugins for the parser + plugins?: Array, +|}): Promise { + try { + const plugins = [ + 'exportDefaultFrom', + 'exportNamespaceFrom', + 'dynamicImport', + // ATLASSIAN: allow additional plugins for the parser + ...additionalPlugins, + ]; + + let program = babelParse(code, { + sourceFilename: relativeUrl(options.projectRoot, asset.filePath), + allowReturnOutsideFunction: true, + strictMode: false, + sourceType: 'module', + plugins, + }); + + let map = await asset.getMap(); + if (map) { + remapAstLocations(program, map); + } + + return { + type: 'babel', + version: '7.0.0', + program, + }; + } catch (e) { + throw await babelErrorEnhancer(e, asset); + } +} + +// astring is ~50x faster than @babel/generator. We use it with a custom +// generator to handle the Babel AST differences from ESTree. +export function generateAST({ + ast, + sourceFileName, + sourceMaps, + options, +}: {| + ast: BabelNodeFile, + sourceFileName?: FilePath, + sourceMaps?: boolean, + options: PluginOptions, +|}): {|content: string, map: SourceMap|} { + let map = new SourceMap(options.projectRoot); + let mappings = []; + let generated = astringGenerate(ast.program, { + generator, + expressionsPrecedence, + comments: true, + sourceMap: sourceMaps + ? { + file: sourceFileName, + addMapping(mapping) { + // Copy the object because astring mutates it + mappings.push({ + original: mapping.original, + generated: { + line: mapping.generated.line, + column: mapping.generated.column, + }, + name: mapping.name, + source: mapping.source, + }); + }, + } + : null, + }); + + map.addIndexedMappings(mappings); + + return { + content: generated, + map, + }; +} + +export async function generate({ + asset, + ast, + options, +}: {| + asset: BaseAsset, + ast: AST, + options: PluginOptions, +|}): Promise<{|content: string, map: ?SourceMap|}> { + let sourceFileName: string = relativeUrl(options.projectRoot, asset.filePath); + let {content, map} = generateAST({ + ast: ast.program, + sourceFileName, + sourceMaps: !!asset.env.sourceMap, + options, + }); + + let originalSourceMap = await asset.getMap(); + if (originalSourceMap) { + // The babel AST already contains the correct mappings, but not the source contents. + // We need to copy over the source contents from the original map. + let sourcesContent = originalSourceMap.getSourcesContentMap(); + for (let filePath in sourcesContent) { + let content = sourcesContent[filePath]; + if (content != null) { + map.setSourceContent(filePath, content); + } + } + } + + return {content, map}; +} + +export function convertBabelLoc( + options: PluginOptions, + loc: ?BabelSourceLocation, +): ?SourceLocation { + if (!loc) return null; + let {filename, start, end} = loc; + if (filename == null) return null; + return { + filePath: path.resolve(options.projectRoot, filename), + start: { + line: start.line, + column: start.column + 1, + }, + // - Babel's columns are exclusive, ours are inclusive (column - 1) + // - Babel has 0-based columns, ours are 1-based (column + 1) + // = +-0 + end: { + line: end.line, + column: end.column, + }, + }; +} diff --git a/packages/transformers/sync-dynamic-import/src/babylon-walk/LICENSE.md b/packages/transformers/sync-dynamic-import/src/babylon-walk/LICENSE.md new file mode 100755 index 00000000000..258e4b99d20 --- /dev/null +++ b/packages/transformers/sync-dynamic-import/src/babylon-walk/LICENSE.md @@ -0,0 +1,7 @@ +Copyright (c) 2016 Tiancheng "Timothy" Gu + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/transformers/sync-dynamic-import/src/babylon-walk/README.md b/packages/transformers/sync-dynamic-import/src/babylon-walk/README.md new file mode 100755 index 00000000000..25c02f9301e --- /dev/null +++ b/packages/transformers/sync-dynamic-import/src/babylon-walk/README.md @@ -0,0 +1,135 @@ +# babylon-walk + +Lightweight AST traversal tools for [Babylon] ASTs. + +Babylon is the parser used by the [Babel] project, which supplies the wonderful [babel-traverse] module for walking Babylon ASTs. Problem is, babel-traverse is very heavyweight, as it is designed to supply utilities to make all sorts of AST transformations possible. For simple AST walking without transformation, babel-traverse brings a lot of overhead. + +This module loosely implements the API of Acorn parser's [walk module], which is a lightweight AST walker for the ESTree AST format. + +In my tests, babylon-walk's ancestor walker (the most complex walker provided by this module) is about 8 times faster than babel-traverse, if the visitors are cached and the same AST is used for all runs. It is about 16 times faster if a fresh AST is used every run. + +[![Dependency Status](https://img.shields.io/david/pugjs/babylon-walk.svg)](https://david-dm.org/pugjs/babylon-walk) +[![NPM version](https://img.shields.io/npm/v/babylon-walk.svg)](https://www.npmjs.com/package/babylon-walk) + +[babylon]: https://github.com/babel/babylon +[babel]: https://babeljs.io/ +[babel-traverse]: https://github.com/thejameskyle/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-babel-traverse +[walk module]: https://github.com/ternjs/acorn#distwalkjs + +## Installation + +```sh +$ npm install babylon-walk +``` + +## API + +```js +var walk = require('babylon-walk'); +``` + +### walk.simple(node, visitors, state) + +Do a simple walk over the AST. `node` should be the AST node to walk, and `visitors` an object containing Babel [visitors]. Each visitor function will be called as `(node, state)`, where `node` is the AST node, and `state` is the same `state` passed to `walk.simple`. + +When `walk.simple` is called with a fresh set of visitors, it will first "explode" the visitors (e.g. expanding `Visitor(node, state) {}` to `Visitor() { enter(node, state) {} }`). This exploding process can take some time, so it is recommended to [cache your visitors] and communicate state leveraging the `state` parameter. (One difference between the linked article and babylon-walk is that the state is only accessible through the `state` variable, never as `this`.) + +All [babel-types] aliases (e.g. `Expression`) and the union syntax (e.g. `'Identifier|AssignmentPattern'(node, state) {}`) work. + +### walk.ancestor(node, visitors, state) + +Do a simple walk over the AST, but memoizing the ancestors of the node and making them available to the visitors. `node` should be the AST node to walk, and `visitors` an object containing Babel [visitors]. Each visitor function will be called as `(node, state, ancestors)`, where `node` is the AST node, `state` is the same `state` passed to `walk.ancestor`, and `ancestors` is an array of ancestors to the node (with the outermost node being `[0]` and the current node being `[ancestors.length - 1]`). If `state` is not specified in the call to `walk.ancestor`, the `state` parameter will be set to `ancestors`. + +When `walk.ancestor` is called with a fresh set of visitors, it will first "explode" the visitors (e.g. expanding `Visitor(node, state) {}` to `Visitor() { enter(node, state) {} }`). This exploding process can take some time, so it is recommended to [cache your visitors] and communicate state leveraging the `state` parameter. (One difference between the linked article and babylon-walk is that the state is only accessible through the `state` variable, never as `this`.) + +All [babel-types] aliases (e.g. `Expression`) and the union syntax (e.g. `'Identifier|AssignmentPattern'(node, state) {}`) work. + +### walk.recursive(node, visitors, state) + +Do a recursive walk over the AST, where the visitors are responsible for continuing the walk on the child nodes of their target node. `node` should be the AST node to walk, and `visitors` an object containing Babel [visitors]. Each visitor function will be called as `(node, state, c)`, where `node` is the AST node, `state` is the same `state` passed to `walk.recursive`, and `c` is a function that takes a single node as argument and continues walking _that_ node. If no visitor for a node is provided, the default walker algorithm will still be used. + +When `walk.recursive` is called with a fresh set of visitors, it will first "explode" the visitors (e.g. expanding `Visitor(node, state) {}` to `Visitor() { enter(node, state) {} }`). This exploding process can take some time, so it is recommended to [cache your visitors] and communicate state leveraging the `state` parameter. (One difference between the linked article and babylon-walk is that the state is only accessible through the `state` variable, never as `this`.) + +Unlike other babylon-walk walkers, `walk.recursive` does not call the `exit` visitor, only the `enter` (the default) visitor, of a specific node type. + +All [babel-types] aliases (e.g. `Expression`) and the union syntax (e.g. `'Identifier|AssignmentPattern'(node, state) {}`) work. + +In the following example, we are trying to count the number of functions in the outermost scope. This means, that we can simply walk all the statements and increment a counter if it is a function declaration or expression, and then stop walking. Note that we do not specify a visitor for the `Program` node, and the default algorithm for walking `Program` nodes is used (which is what we want). Also of note is how I bring the `visitors` object outside of `countFunctions` so that the object can be cached to improve performance. + +```js +import * as t from 'babel-types'; +import {parse} from 'babylon'; +import * as walk from 'babylon-walk'; + +const visitors = { + Statement(node, state, c) { + if (t.isVariableDeclaration(node)) { + for (let declarator of node.declarations) { + // Continue walking the declarator + c(declarator); + } + } else if (t.isFunctionDeclaration(node)) { + state.counter++; + } + }, + + VariableDeclarator(node, state) { + if (t.isFunction(node.init)) { + state.counter++; + } + }, +}; + +function countFunctions(node) { + const state = { + counter: 0, + }; + walk.recursive(node, visitors, state); + return state.counter; +} + +const ast = parse(` + // Counts + var a = () => {}; + + // Counts + function b() { + // Doesn't count + function c() { + } + } + + // Counts + const c = function d() {}; +`); + +countFunctions(ast); +// = 3 +``` + +### walk.traverse(node, visitors, state) + +Visitors get called as `(path, state)`. Every `Path` has these methods (similar to `@babel/traverse`): + +- `skip()` +- `replaceWith(node)` +- `remove()` + +[babel-types]: https://github.com/babel/babel/tree/master/packages/babel-types +[cache your visitors]: https://github.com/thejameskyle/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-optimizing-nested-visitors +[visitors]: https://github.com/thejameskyle/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-visitors + +## Caveat + +For those of you migrating from Acorn to Babylon, there are a few things to be aware of. + +1. The visitor caching suggestions do not apply to Acorn's walk module, but do for babylon-walk. + +2. babylon-walk does not provide any of the other functions Acorn's walk module provides (e.g. `make`, `findNode*`). + +3. babylon-walk does not use a `base` variable. The walker algorithm is the same as what babel-traverse uses. + - That means certain nodes that are not walked by Acorn, such as the `property` property of a non-computed `MemberExpression`, are walked by babylon-walk. + +## License + +MIT diff --git a/packages/transformers/sync-dynamic-import/src/babylon-walk/explode.js b/packages/transformers/sync-dynamic-import/src/babylon-walk/explode.js new file mode 100755 index 00000000000..9699e9811ea --- /dev/null +++ b/packages/transformers/sync-dynamic-import/src/babylon-walk/explode.js @@ -0,0 +1,191 @@ +// @flow +// Copied from babel-traverse, but with virtual types handling removed +// https://github.com/babel/babel/blob/07b3dc18a09f2217b38a3a63c8613add6df1b47d/packages/babel-traverse/src/visitors.js +import type {SimpleVisitors, VisitorsExploded} from './index'; + +// import * as messages from 'babel-messages'; +import * as t from '@babel/types'; +import clone from 'lodash.clone'; + +/** + * explode() will take a visitor object with all of the various shorthands + * that we support, and validates & normalizes it into a common format, ready + * to be used in traversal + * + * The various shorthands are: + * * `Identifier() { ... }` -> `Identifier: { enter() { ... } }` + * * `"Identifier|NumericLiteral": { ... }` -> `Identifier: { ... }, NumericLiteral: { ... }` + * * Aliases in `babel-types`: e.g. `Property: { ... }` -> `ObjectProperty: { ... }, ClassProperty: { ... }` + * + * Other normalizations are: + * * `enter` and `exit` functions are wrapped in arrays, to ease merging of + * visitors + */ +export default function explode( + visitor: SimpleVisitors, +): VisitorsExploded { + // $FlowFixMe + if (visitor._exploded) return visitor; + // $FlowFixMe + visitor._exploded = true; + + // normalise pipes + for (let nodeType in visitor) { + if (shouldIgnoreKey(nodeType)) continue; + + let parts = nodeType.split('|'); + if (parts.length === 1) continue; + + let fns = visitor[nodeType]; + delete visitor[nodeType]; + + for (let part of parts) { + visitor[part] = fns; + } + } + + // verify data structure + verify(visitor); + + // make sure there's no __esModule type since this is because we're using loose mode + // and it sets __esModule to be enumerable on all modules :( + delete visitor.__esModule; + + // ensure visitors are objects + ensureEntranceObjects(visitor); + + // ensure enter/exit callbacks are arrays + ensureCallbackArrays(visitor); + + // add aliases + for (let nodeType in visitor) { + if (shouldIgnoreKey(nodeType)) continue; + + let fns = visitor[nodeType]; + + let aliases = t.FLIPPED_ALIAS_KEYS[nodeType]; + + let deprecratedKey = t.DEPRECATED_KEYS[nodeType]; + if (deprecratedKey) { + throw new Error( + `Visitor defined for ${nodeType} but it has been renamed to ${deprecratedKey}`, + ); + } + + if (!aliases) continue; + + // clear it from the visitor + delete visitor[nodeType]; + + for (let alias of aliases) { + let existing = visitor[alias]; + if (existing) { + mergePair(existing, fns); + } else { + visitor[alias] = clone(fns); + } + } + } + + for (let nodeType in visitor) { + if (shouldIgnoreKey(nodeType)) continue; + + ensureCallbackArrays(visitor[nodeType]); + } + + // $FlowFixMe + return visitor; +} + +export function verify(visitor: any) { + if (visitor._verified) return; + + if (typeof visitor === 'function') { + // throw new Error(messages.get("traverseVerifyRootFunction")); + throw new Error( + "You passed `traverse()` a function when it expected a visitor object, are you sure you didn't mean `{ enter: Function }`?", + ); + } + + for (let nodeType in visitor) { + if (nodeType === 'enter' || nodeType === 'exit') { + validateVisitorMethods(nodeType, visitor[nodeType]); + } + + if (shouldIgnoreKey(nodeType)) continue; + + if (t.TYPES.indexOf(nodeType) < 0) { + // throw new Error(messages.get("traverseVerifyNodeType", nodeType)); + throw new Error( + `You gave us a visitor for the node type ${nodeType} but it's not a valid type`, + ); + } + + let visitors = visitor[nodeType]; + if (typeof visitors === 'object') { + for (let visitorKey in visitors) { + if (visitorKey === 'enter' || visitorKey === 'exit') { + // verify that it just contains functions + validateVisitorMethods( + `${nodeType}.${visitorKey}`, + visitors[visitorKey], + ); + } else { + // throw new Error(messages.get("traverseVerifyVisitorProperty", nodeType, visitorKey)); + throw new Error( + `You passed \`traverse()\` a visitor object with the property ${nodeType} that has the invalid property ${visitorKey}`, + ); + } + } + } + } + + visitor._verified = true; +} + +function validateVisitorMethods(path, val) { + let fns = [].concat(val); + for (let fn of fns) { + if (typeof fn !== 'function') { + throw new TypeError( + `Non-function found defined in ${path} with type ${typeof fn}`, + ); + } + } +} + +function ensureEntranceObjects(obj: any) { + for (let key in obj) { + if (shouldIgnoreKey(key)) continue; + + let fns = obj[key]; + if (typeof fns === 'function') { + obj[key] = {enter: fns}; + } + } +} + +function ensureCallbackArrays(obj: any) { + if (obj.enter && !Array.isArray(obj.enter)) obj.enter = [obj.enter]; + if (obj.exit && !Array.isArray(obj.exit)) obj.exit = [obj.exit]; +} + +function shouldIgnoreKey(key) { + // internal/hidden key + if (key[0] === '_') return true; + + // ignore function keys + if (key === 'enter' || key === 'exit' || key === 'shouldSkip') return true; + + // ignore other options + if (key === 'blacklist' || key === 'noScope' || key === 'skipKeys') + return true; + + return false; +} + +function mergePair(dest: any, src: any) { + for (let key in src) { + dest[key] = [].concat(dest[key] || [], src[key]); + } +} diff --git a/packages/transformers/sync-dynamic-import/src/babylon-walk/index.js b/packages/transformers/sync-dynamic-import/src/babylon-walk/index.js new file mode 100755 index 00000000000..8f82f484f97 --- /dev/null +++ b/packages/transformers/sync-dynamic-import/src/babylon-walk/index.js @@ -0,0 +1,137 @@ +// @flow +import type {Node} from '@babel/types'; +import type {SimpleVisitors, VisitorsExploded} from './types'; + +import * as t from '@babel/types'; +import explode from './explode.js'; +import traverse from './traverse'; + +export * from './traverse2'; +export * from './traverse-all'; +export * from './scope'; +export * from './types'; + +export function simple( + node: Node, + _visitors: SimpleVisitors<(any, T) => void>, + state: T, +) { + if (!node) return; + + const visitors: VisitorsExploded<(any, T) => void> = explode(_visitors); + + (function c(node) { + if (!node) return; + + const {enter, exit} = visitors[node.type] || {}; + + if (enter) { + for (let visitor of enter) { + visitor(node, state); + } + } + + for (let key of t.VISITOR_KEYS[node.type] || []) { + // $FlowFixMe + let subNode = node[key]; + if (Array.isArray(subNode)) { + for (let subSubNode of subNode) { + c(subSubNode); + } + } else { + c(subNode); + } + } + + if (exit) { + for (let visitor of exit) { + visitor(node, state); + } + } + })(node); +} + +export function ancestor( + node: Node, + _visitors: SimpleVisitors<(any, T, Array) => void>, + state: T, +) { + if (!node) return; + + const visitors = explode<(any, T, Array) => void>(_visitors); + let ancestors = []; + + (function c(node) { + if (!node) return; + + const {enter, exit} = visitors[node.type] || {}; + + let isNew = node != ancestors[ancestors.length - 1]; + if (isNew) ancestors.push(node); + + if (enter) { + for (let visitor of enter) { + // $FlowFixMe + visitor(node, state || ancestors, ancestors); + } + } + + for (let key of t.VISITOR_KEYS[node.type] || []) { + // $FlowFixMe + let subNode = node[key]; + if (Array.isArray(subNode)) { + for (let subSubNode of subNode) { + c(subSubNode); + } + } else { + c(subNode); + } + } + + if (exit) { + for (let visitor of exit) { + // $FlowFixMe + visitor(node, state || ancestors, ancestors); + } + } + + if (isNew) ancestors.pop(); + })(node); +} + +export function recursive( + node: Node, + _visitors: SimpleVisitors<(any, T, recurse: (Node) => void) => void>, + state: T, +) { + if (!node) return; + + const visitors = + explode<(any, T, recurse: (Node) => void) => void>(_visitors); + + (function c(node) { + if (!node) return; + + const {enter} = visitors[node.type] || {}; + + if (enter && enter.length) { + for (let visitor of enter) { + visitor(node, state, c); + } + } else { + for (let key of t.VISITOR_KEYS[node.type] || []) { + // $FlowFixMe + let subNode = node[key]; + if (Array.isArray(subNode)) { + for (let subSubNode of subNode) { + c(subSubNode); + } + } else { + c(subNode); + } + } + } + })(node); +} + +export {traverse}; diff --git a/packages/transformers/sync-dynamic-import/src/babylon-walk/scope.js b/packages/transformers/sync-dynamic-import/src/babylon-walk/scope.js new file mode 100644 index 00000000000..f8a10c9f6a2 --- /dev/null +++ b/packages/transformers/sync-dynamic-import/src/babylon-walk/scope.js @@ -0,0 +1,231 @@ +// @flow + +import type {Identifier, Node} from '@babel/types'; +import type {Visitors} from './types'; +import * as t from '@babel/types'; +import invariant from 'assert'; +import { + isClassDeclaration, + isFunctionDeclaration, + isIdentifier, + isImportDeclaration, + isVariableDeclaration, +} from '@babel/types'; + +export type ScopeType = 'program' | 'function' | 'arrow_function' | 'block'; +export type BindingType = 'var' | 'let' | 'const'; +export class Scope { + type: ScopeType; + names: Set = new Set(); + bindings: Map = new Map(); + references: Map> = new Map(); + parent: ?Scope; + program: Scope; + renames: Map = new Map(); + inverseRenames: Map = new Map(); + + constructor(type: ScopeType, parent: ?Scope) { + this.type = type; + this.parent = parent; + this.program = parent ? parent.program : this; + } + + has(name: string): boolean { + if (this.names.has(name)) { + return true; + } + + if (this.parent) { + return this.parent.has(name); + } + + return false; + } + + add(name: string) { + this.names.add(name); + } + + generateUid(name: string = 'temp'): string { + name = t + .toIdentifier(name) + .replace(/^_+/, '') + .replace(/[0-9]+$/g, ''); + + let uid; + let i = 0; + do { + uid = '_' + name + (i > 1 ? i : ''); + i++; + } while (this.program.names.has(uid)); + + this.program.names.add(uid); + return uid; + } + + addBinding(name: string, decl: Node, type: BindingType | 'param') { + if ( + type === 'var' && + this.type !== 'function' && + this.type !== 'arrow_function' && + this.parent + ) { + this.parent.addBinding(name, decl, type); + } else { + if (type === 'param') type = 'var'; + this.names.add(name); + this.bindings.set(name, decl); + this.program.names.add(name); + } + } + + getBinding(name: string): ?Node { + return this.bindings.get(name) ?? this.parent?.getBinding(name); + } + + addReference(identifier: Identifier) { + let references = this.references.get(identifier.name); + if (!references) { + references = new Set(); + this.references.set(identifier.name, references); + } + + references.add(identifier); + } + + rename(from: string, to: string) { + // If already renamed, update the original to the final name. + let renamed = this.inverseRenames.get(from); + if (renamed) { + this.renames.set(renamed, to); + } else { + this.renames.set(from, to); + this.inverseRenames.set(to, from); + } + } + + exit() { + // Rename declarations in this scope. + for (let [from, to] of this.renames) { + if (!this.names.has(from) && this.parent) { + this.parent.rename(from, to); + } + + let references = this.references.get(from); + if (!references) { + continue; + } + + for (let id of references) { + id.name = to; + } + } + + // Propagate unknown references to the parent scope. + let parent = this.parent; + if (parent) { + for (let [name, ids] of this.references) { + if (!this.names.has(name)) { + for (let id of ids) { + parent.addReference(id); + } + } + } + } + } +} + +export type ScopeState = {| + scope: Scope, +|}; + +export let scopeVisitor: Visitors = { + Program(node, state) { + if (!state.scope) { + state.scope = new Scope('program'); + } + }, + Scopable: { + enter(node, state, ancestors) { + if ( + !t.isScope(node, ancestors[ancestors.length - 2]) || + t.isProgram(node) || + t.isFunction(node) + ) { + return; + } + + state.scope = new Scope('block', state.scope); + }, + exit(node, state, ancestors) { + if (!t.isScope(node, ancestors[ancestors.length - 2])) { + return; + } + + state.scope.exit(); + if (state.scope.parent) { + state.scope = state.scope.parent; + } + }, + }, + Declaration: { + exit(node, {scope}) { + if (t.isFunction(node) || t.isExportDeclaration(node)) { + return; + } + + // Register declarations with the scope. + if (isVariableDeclaration(node)) { + for (let decl of node.declarations) { + let ids = t.getBindingIdentifiers(decl); + for (let id in ids) { + scope.addBinding(id, decl, node.kind); + } + } + } else { + let type: BindingType; + if (isClassDeclaration(node)) { + type = 'let'; + } else if (isImportDeclaration(node)) { + type = 'const'; + } else { + invariant(false); + } + let ids = t.getBindingIdentifiers(node); + for (let id in ids) { + scope.addBinding(id, node, type); + } + } + }, + }, + Function(node, state) { + // Add function name to outer scope + let name; + if (isFunctionDeclaration(node) && isIdentifier(node.id)) { + name = node.id.name; + state.scope.addBinding(name, node, 'var'); + } + + // Create new scope + let type = t.isArrowFunctionExpression(node) + ? 'arrow_function' + : 'function'; + state.scope = new Scope(type, state.scope); + + // Add inner bindings to inner scope + let inner = t.getBindingIdentifiers(node); + for (let id in inner) { + if (id !== name) { + state.scope.addBinding(id, inner[id], 'param'); + } + } + }, + Identifier(node, state, ancestors) { + let parent = ancestors[ancestors.length - 2]; + if (!t.isReferenced(node, parent, ancestors[ancestors.length - 3])) { + return; + } + + state.scope.addReference(node); + }, +}; diff --git a/packages/transformers/sync-dynamic-import/src/babylon-walk/traverse-all.js b/packages/transformers/sync-dynamic-import/src/babylon-walk/traverse-all.js new file mode 100644 index 00000000000..ef58932e505 --- /dev/null +++ b/packages/transformers/sync-dynamic-import/src/babylon-walk/traverse-all.js @@ -0,0 +1,24 @@ +// @flow +import type {Node} from '@babel/types'; + +import * as t from '@babel/types'; + +export function traverseAll(node: Node, visitor: (node: Node) => void): void { + if (!node) { + return; + } + + visitor(node); + + for (let key of t.VISITOR_KEYS[node.type] || []) { + // $FlowFixMe + let subNode: Node | Array = node[key]; + if (Array.isArray(subNode)) { + for (let i = 0; i < subNode.length; i++) { + traverseAll(subNode[i], visitor); + } + } else { + traverseAll(subNode, visitor); + } + } +} diff --git a/packages/transformers/sync-dynamic-import/src/babylon-walk/traverse.js b/packages/transformers/sync-dynamic-import/src/babylon-walk/traverse.js new file mode 100644 index 00000000000..86963d2a29b --- /dev/null +++ b/packages/transformers/sync-dynamic-import/src/babylon-walk/traverse.js @@ -0,0 +1,98 @@ +// @flow +import type {Node} from '@babel/types'; +import type {SimpleVisitors, VisitorsExploded} from './index'; + +import * as t from '@babel/types'; +import invariant from 'assert'; +import explode from './explode.js'; + +class Path { + node: Node; + parent: Node; + listkey: ?string; + key: number | string; + _skipped: boolean = false; + _removed: boolean = false; + + constructor( + node: Node, + parent: Node, + listkey: ?string, + key: number | string, + ) { + this.node = node; + this.parent = parent; + this.listkey = listkey; + this.key = key; + } + replaceWith(n: Node) { + this.node = n; + + // $FlowFixMe + let p = this.listkey ? this.parent[this.listkey] : this.parent; + // $FlowFixMe + p[this.key] = this.node; + } + skip() { + this._skipped = true; + } + remove() { + this._removed = true; + invariant(this.listkey && typeof this.key === 'number'); + // $FlowFixMe + this.parent[this.listkey].splice(this.key, 1); + } +} + +export default function traverse( + node: Node, + visitors: SimpleVisitors<(Path, T) => void>, + state: T, +) { + traverseWalk(explode(visitors), state, node, null, null, null); +} + +function traverseWalk( + visitors: VisitorsExploded<(Path, T) => void>, + state: T, + node: Node, + parent: ?Node, + listkey, + key, +) { + if (!node || (visitors.shouldSkip && visitors.shouldSkip(node) === true)) { + return; + } + + const {enter, exit} = visitors[node.type] || {}; + + // $FlowFixMe + const path = new Path(node, parent, listkey, key); + + if (enter) { + for (let visitor of enter) { + visitor(path, state); + if (path._skipped || path._removed) return path._removed; + } + } + + for (let key of t.VISITOR_KEYS[node.type] || []) { + // $FlowFixMe + let subNode: Node | Array = node[key]; + if (Array.isArray(subNode)) { + for (let i = 0; i < subNode.length; i++) { + if (traverseWalk(visitors, state, subNode[i], node, key, i) === true) { + i--; + } + } + } else { + traverseWalk(visitors, state, subNode, node, null, key); + } + } + + if (exit) { + for (let visitor of exit) { + visitor(path, state); + } + } +} diff --git a/packages/transformers/sync-dynamic-import/src/babylon-walk/traverse2.js b/packages/transformers/sync-dynamic-import/src/babylon-walk/traverse2.js new file mode 100644 index 00000000000..7b011e79efe --- /dev/null +++ b/packages/transformers/sync-dynamic-import/src/babylon-walk/traverse2.js @@ -0,0 +1,162 @@ +// @flow +import type {Node} from '@babel/types'; +import type {VisitorFunc, Visitors, VisitorsExploded} from './types'; + +import * as t from '@babel/types'; +import explode from './explode.js'; + +export const SKIP: symbol = Symbol('traverse.SKIP'); +export const REMOVE: symbol = Symbol('traverse.REMOVE'); + +export function traverse2( + node: Node, + visitors: Visitors | VisitorsExploded>, + state: T, +) { + let ancestors = []; + let revisit = []; + traverseWalk(explode((visitors: any)), ancestors, revisit, state, node); + + for (let fn of revisit) { + fn(); + } +} + +function traverseWalk( + visitors: VisitorsExploded>, + ancestors: Node[], + revisit: any[], + state: T, + node: Node, +) { + if (!node || (visitors.shouldSkip && visitors.shouldSkip(node) === true)) { + return; + } + + let isNew = node != ancestors[ancestors.length - 1]; + if (isNew) ancestors.push(node); + + const {enter, exit} = visitors[node.type] || {}; + + if (enter) { + for (let visitor of enter) { + let res = visitor(node, state, ancestors); + if (res != null) { + if (isNew) ancestors.pop(); + return res; + } + } + } + + for (let key of t.VISITOR_KEYS[node.type] || []) { + // $FlowFixMe + let subNode: Node | Array = node[key]; + if (Array.isArray(subNode)) { + let revisitDiff = 0; + for (let i = 0; i < subNode.length; i++) { + let res = traverseWalk(visitors, ancestors, revisit, state, subNode[i]); + if (res === REMOVE) { + subNode.splice(i, 1); + i--; + } else if (res !== SKIP && res != null) { + if (typeof res === 'function') { + revisit.push(() => { + let index = i + revisitDiff; + let r = replaceArray(subNode, index, res()); + revisitDiff += r - index; + }); + } else { + i = replaceArray(subNode, i, res); + } + } + } + } else { + let res = traverseWalk(visitors, ancestors, revisit, state, subNode); + if (res === REMOVE) { + if (isNew) ancestors.pop(); + return REMOVE; + } else if (res !== SKIP && res != null) { + if (typeof res === 'function') { + revisit.push(() => { + let n = res(); + if (n != null) { + // $FlowFixMe + node[key] = n; + } + }); + } else { + // $FlowFixMe + node[key] = res; + } + } + } + } + + if (exit) { + for (let visitor of exit) { + let res = visitor(node, state, ancestors); + if (res != null) { + if (isNew) ancestors.pop(); + return res; + } + } + } + + if (isNew) ancestors.pop(); +} + +function replaceArray(subNode, i, res) { + if (res === REMOVE) { + subNode.splice(i, 1); + i--; + } else if (Array.isArray(res)) { + subNode.splice(i, 1, ...res); + if (res.length === 0) { + i--; + } else if (res.length > 1) { + i += res.length - 1; + } + } else if (res != null) { + // $FlowFixMe + subNode[i] = res; + } + + return i; +} + +export function mergeVisitors( + a: Visitors, + b: Visitors, +): VisitorsExploded> { + let res: VisitorsExploded> = {}; + // $FlowFixMe + res._exploded = true; + + for (let visitor of [a, b]) { + let {shouldSkip, ...exploded} = explode((visitor: any)); + for (let type in exploded) { + if (!res[type]) { + res[type] = {}; + } + + if (exploded[type].enter) { + res[type].enter = [...(res[type].enter || []), ...exploded[type].enter]; + } + + if (exploded[type].exit) { + res[type].exit = [...(res[type].exit || []), ...exploded[type].exit]; + } + } + + if (shouldSkip) { + if (res.shouldSkip) { + let prev = res.shouldSkip; + res.shouldSkip = node => prev(node) || shouldSkip(node); + } else { + res.shouldSkip = shouldSkip; + } + } + } + + return res; +} diff --git a/packages/transformers/sync-dynamic-import/src/babylon-walk/types.js b/packages/transformers/sync-dynamic-import/src/babylon-walk/types.js new file mode 100644 index 00000000000..2f54bff4e7f --- /dev/null +++ b/packages/transformers/sync-dynamic-import/src/babylon-walk/types.js @@ -0,0 +1,337 @@ +// @flow +import * as t from '@babel/types'; + +export type VisitorFunc = ( + N, + S, + Array, +) => + | void + | t.Node + | Array + | symbol + | (() => void | t.Node | Array | symbol); +type SingleVisitor = + | VisitorFunc + | {| + enter?: VisitorFunc, + exit?: VisitorFunc, + |}; + +export type Visitors = { + shouldSkip?: t.Node => boolean, + ReferencedIdentifier?: SingleVisitor, + Function?: SingleVisitor, + Class?: SingleVisitor, + ArrayExpression?: SingleVisitor, + AssignmentExpression?: SingleVisitor, + BinaryExpression?: SingleVisitor, + InterpreterDirective?: SingleVisitor, + Directive?: SingleVisitor, + DirectiveLiteral?: SingleVisitor, + BlockStatement?: SingleVisitor, + BreakStatement?: SingleVisitor, + CallExpression?: SingleVisitor, + CatchClause?: SingleVisitor, + ConditionalExpression?: SingleVisitor, + ContinueStatement?: SingleVisitor, + DebuggerStatement?: SingleVisitor, + DoWhileStatement?: SingleVisitor, + EmptyStatement?: SingleVisitor, + ExpressionStatement?: SingleVisitor, + File?: SingleVisitor, + ForInStatement?: SingleVisitor, + ForStatement?: SingleVisitor, + FunctionDeclaration?: SingleVisitor, + FunctionExpression?: SingleVisitor, + Identifier?: SingleVisitor, + IfStatement?: SingleVisitor, + LabeledStatement?: SingleVisitor, + StringLiteral?: SingleVisitor, + NumericLiteral?: SingleVisitor, + NullLiteral?: SingleVisitor, + BooleanLiteral?: SingleVisitor, + RegExpLiteral?: SingleVisitor, + LogicalExpression?: SingleVisitor, + MemberExpression?: SingleVisitor, + NewExpression?: SingleVisitor, + Program?: SingleVisitor, + ObjectExpression?: SingleVisitor, + ObjectMethod?: SingleVisitor, + ObjectProperty?: SingleVisitor, + RestElement?: SingleVisitor, + ReturnStatement?: SingleVisitor, + SequenceExpression?: SingleVisitor, + ParenthesizedExpression?: SingleVisitor, + SwitchCase?: SingleVisitor, + SwitchStatement?: SingleVisitor, + ThisExpression?: SingleVisitor, + ThrowStatement?: SingleVisitor, + TryStatement?: SingleVisitor, + UnaryExpression?: SingleVisitor, + UpdateExpression?: SingleVisitor, + VariableDeclaration?: SingleVisitor, + VariableDeclarator?: SingleVisitor, + WhileStatement?: SingleVisitor, + WithStatement?: SingleVisitor, + AssignmentPattern?: SingleVisitor, + ArrayPattern?: SingleVisitor, + ArrowFunctionExpression?: SingleVisitor, + ClassBody?: SingleVisitor, + ClassExpression?: SingleVisitor, + ClassDeclaration?: SingleVisitor, + ExportAllDeclaration?: SingleVisitor, + ExportDefaultDeclaration?: SingleVisitor, + ExportNamedDeclaration?: SingleVisitor, + ExportSpecifier?: SingleVisitor, + ForOfStatement?: SingleVisitor, + ImportDeclaration?: SingleVisitor, + ImportDefaultSpecifier?: SingleVisitor, + ImportNamespaceSpecifier?: SingleVisitor, + ImportSpecifier?: SingleVisitor, + MetaProperty?: SingleVisitor, + ClassMethod?: SingleVisitor, + ObjectPattern?: SingleVisitor, + SpreadElement?: SingleVisitor, + Super?: SingleVisitor, + TaggedTemplateExpression?: SingleVisitor, + TemplateElement?: SingleVisitor, + TemplateLiteral?: SingleVisitor, + YieldExpression?: SingleVisitor, + AnyTypeAnnotation?: SingleVisitor, + ArrayTypeAnnotation?: SingleVisitor, + BooleanTypeAnnotation?: SingleVisitor, + BooleanLiteralTypeAnnotation?: SingleVisitor< + t.BooleanLiteralTypeAnnotation, + S, + >, + NullLiteralTypeAnnotation?: SingleVisitor, + ClassImplements?: SingleVisitor, + DeclareClass?: SingleVisitor, + DeclareFunction?: SingleVisitor, + DeclareInterface?: SingleVisitor, + DeclareModule?: SingleVisitor, + DeclareModuleExports?: SingleVisitor, + DeclareTypeAlias?: SingleVisitor, + DeclareOpaqueType?: SingleVisitor, + DeclareVariable?: SingleVisitor, + DeclareExportDeclaration?: SingleVisitor, + DeclareExportAllDeclaration?: SingleVisitor, + DeclaredPredicate?: SingleVisitor, + ExistsTypeAnnotation?: SingleVisitor, + FunctionTypeAnnotation?: SingleVisitor, + FunctionTypeParam?: SingleVisitor, + GenericTypeAnnotation?: SingleVisitor, + InferredPredicate?: SingleVisitor, + InterfaceExtends?: SingleVisitor, + InterfaceDeclaration?: SingleVisitor, + InterfaceTypeAnnotation?: SingleVisitor, + IntersectionTypeAnnotation?: SingleVisitor, + MixedTypeAnnotation?: SingleVisitor, + EmptyTypeAnnotation?: SingleVisitor, + NullableTypeAnnotation?: SingleVisitor, + NumberLiteralTypeAnnotation?: SingleVisitor, + NumberTypeAnnotation?: SingleVisitor, + ObjectTypeAnnotation?: SingleVisitor, + ObjectTypeInternalSlot?: SingleVisitor, + ObjectTypeCallProperty?: SingleVisitor, + ObjectTypeIndexer?: SingleVisitor, + ObjectTypeProperty?: SingleVisitor, + ObjectTypeSpreadProperty?: SingleVisitor, + OpaqueType?: SingleVisitor, + QualifiedTypeIdentifier?: SingleVisitor, + StringLiteralTypeAnnotation?: SingleVisitor, + StringTypeAnnotation?: SingleVisitor, + ThisTypeAnnotation?: SingleVisitor, + TupleTypeAnnotation?: SingleVisitor, + TypeofTypeAnnotation?: SingleVisitor, + TypeAlias?: SingleVisitor, + TypeAnnotation?: SingleVisitor, + TypeCastExpression?: SingleVisitor, + TypeParameter?: SingleVisitor, + TypeParameterDeclaration?: SingleVisitor, + TypeParameterInstantiation?: SingleVisitor, + UnionTypeAnnotation?: SingleVisitor, + Variance?: SingleVisitor, + VoidTypeAnnotation?: SingleVisitor, + EnumDeclaration?: SingleVisitor, + EnumBooleanBody?: SingleVisitor, + EnumNumberBody?: SingleVisitor, + EnumStringBody?: SingleVisitor, + EnumSymbolBody?: SingleVisitor, + EnumBooleanMember?: SingleVisitor, + EnumNumberMember?: SingleVisitor, + EnumStringMember?: SingleVisitor, + EnumDefaultedMember?: SingleVisitor, + JSXAttribute?: SingleVisitor, + JSXClosingElement?: SingleVisitor, + JSXElement?: SingleVisitor, + JSXEmptyExpression?: SingleVisitor, + JSXExpressionContainer?: SingleVisitor, + JSXSpreadChild?: SingleVisitor, + JSXIdentifier?: SingleVisitor, + JSXMemberExpression?: SingleVisitor, + JSXNamespacedName?: SingleVisitor, + JSXOpeningElement?: SingleVisitor, + JSXSpreadAttribute?: SingleVisitor, + JSXText?: SingleVisitor, + JSXFragment?: SingleVisitor, + JSXOpeningFragment?: SingleVisitor, + JSXClosingFragment?: SingleVisitor, + Noop?: SingleVisitor, + Placeholder?: SingleVisitor, + V8IntrinsicIdentifier?: SingleVisitor, + ArgumentPlaceholder?: SingleVisitor, + AwaitExpression?: SingleVisitor, + BindExpression?: SingleVisitor, + ClassProperty?: SingleVisitor, + OptionalMemberExpression?: SingleVisitor, + PipelineTopicExpression?: SingleVisitor, + PipelineBareFunction?: SingleVisitor, + PipelinePrimaryTopicReference?: SingleVisitor< + t.PipelinePrimaryTopicReference, + S, + >, + OptionalCallExpression?: SingleVisitor, + ClassPrivateProperty?: SingleVisitor, + ClassPrivateMethod?: SingleVisitor, + Import?: SingleVisitor, + Decorator?: SingleVisitor, + DoExpression?: SingleVisitor, + ExportDefaultSpecifier?: SingleVisitor, + ExportNamespaceSpecifier?: SingleVisitor, + PrivateName?: SingleVisitor, + BigIntLiteral?: SingleVisitor, + TSParameterProperty?: SingleVisitor, + TSDeclareFunction?: SingleVisitor, + TSDeclareMethod?: SingleVisitor, + TSQualifiedName?: SingleVisitor, + TSCallSignatureDeclaration?: SingleVisitor, + TSConstructSignatureDeclaration?: SingleVisitor< + t.TSConstructSignatureDeclaration, + S, + >, + TSPropertySignature?: SingleVisitor, + TSMethodSignature?: SingleVisitor, + TSIndexSignature?: SingleVisitor, + TSAnyKeyword?: SingleVisitor, + TSBooleanKeyword?: SingleVisitor, + TSBigIntKeyword?: SingleVisitor, + TSNeverKeyword?: SingleVisitor, + TSNullKeyword?: SingleVisitor, + TSNumberKeyword?: SingleVisitor, + TSObjectKeyword?: SingleVisitor, + TSStringKeyword?: SingleVisitor, + TSSymbolKeyword?: SingleVisitor, + TSUndefinedKeyword?: SingleVisitor, + TSUnknownKeyword?: SingleVisitor, + TSVoidKeyword?: SingleVisitor, + TSThisType?: SingleVisitor, + TSFunctionType?: SingleVisitor, + TSConstructorType?: SingleVisitor, + TSTypeReference?: SingleVisitor, + TSTypePredicate?: SingleVisitor, + TSTypeQuery?: SingleVisitor, + TSTypeLiteral?: SingleVisitor, + TSArrayType?: SingleVisitor, + TSTupleType?: SingleVisitor, + TSOptionalType?: SingleVisitor, + TSRestType?: SingleVisitor, + TSUnionType?: SingleVisitor, + TSIntersectionType?: SingleVisitor, + TSConditionalType?: SingleVisitor, + TSInferType?: SingleVisitor, + TSParenthesizedType?: SingleVisitor, + TSTypeOperator?: SingleVisitor, + TSIndexedAccessType?: SingleVisitor, + TSMappedType?: SingleVisitor, + TSLiteralType?: SingleVisitor, + TSExpressionWithTypeArguments?: SingleVisitor< + t.TSExpressionWithTypeArguments, + S, + >, + TSInterfaceDeclaration?: SingleVisitor, + TSInterfaceBody?: SingleVisitor, + TSTypeAliasDeclaration?: SingleVisitor, + TSAsExpression?: SingleVisitor, + TSTypeAssertion?: SingleVisitor, + TSEnumDeclaration?: SingleVisitor, + TSEnumMember?: SingleVisitor, + TSModuleDeclaration?: SingleVisitor, + TSModuleBlock?: SingleVisitor, + TSImportType?: SingleVisitor, + TSImportEqualsDeclaration?: SingleVisitor, + TSExternalModuleReference?: SingleVisitor, + TSNonNullExpression?: SingleVisitor, + TSExportAssignment?: SingleVisitor, + TSNamespaceExportDeclaration?: SingleVisitor< + t.TSNamespaceExportDeclaration, + S, + >, + TSTypeAnnotation?: SingleVisitor, + TSTypeParameterInstantiation?: SingleVisitor< + t.TSTypeParameterInstantiation, + S, + >, + TSTypeParameterDeclaration?: SingleVisitor, + TSTypeParameter?: SingleVisitor, + Expression?: SingleVisitor, + Binary?: SingleVisitor, + Scopable?: SingleVisitor, + BlockParent?: SingleVisitor, + Block?: SingleVisitor, + Statement?: SingleVisitor, + Terminatorless?: SingleVisitor, + CompletionStatement?: SingleVisitor, + Conditional?: SingleVisitor, + Loop?: SingleVisitor, + While?: SingleVisitor, + ExpressionWrapper?: SingleVisitor, + For?: SingleVisitor, + ForXStatement?: SingleVisitor, + FunctionParent?: SingleVisitor, + Pureish?: SingleVisitor, + Declaration?: SingleVisitor, + PatternLike?: SingleVisitor, + LVal?: SingleVisitor, + TSEntityName?: SingleVisitor, + Literal?: SingleVisitor, + Immutable?: SingleVisitor, + UserWhitespacable?: SingleVisitor, + Method?: SingleVisitor, + ObjectMember?: SingleVisitor, + Property?: SingleVisitor, + UnaryLike?: SingleVisitor, + Pattern?: SingleVisitor, + ModuleDeclaration?: SingleVisitor, + ExportDeclaration?: SingleVisitor, + ModuleSpecifier?: SingleVisitor, + Flow?: SingleVisitor, + FlowType?: SingleVisitor, + FlowBaseAnnotation?: SingleVisitor, + FlowDeclaration?: SingleVisitor, + FlowPredicate?: SingleVisitor, + EnumBody?: SingleVisitor, + EnumMember?: SingleVisitor, + JSX?: SingleVisitor, + Private?: SingleVisitor, + TSTypeElement?: SingleVisitor, + TSType?: SingleVisitor, + [string]: SingleVisitor, + ... +}; + +export type SimpleVisitors = { + [string]: F | {|enter?: F, exit?: F|}, + shouldSkip?: t.Node => boolean, + ... +}; + +export type VisitorsExploded = { + [string]: {| + enter?: Array, + exit?: Array, + |}, + shouldSkip?: t.Node => boolean, + ... +};