Babel plugin that transforms typeof expressions to properly handle ES6 symbols
npx @tessl/cli install tessl/npm-babel-plugin-transform-es2015-typeof-symbol@6.23.0A Babel plugin that transforms typeof expressions to properly handle ES6 symbols. This plugin provides ES2015 compatibility for the typeof operator when working with symbols by wrapping typeof expressions with a method that replicates native behavior, returning "symbol" for symbols and preserving standard typeof behavior for all other types.
npm install --save-dev babel-plugin-transform-es2015-typeof-symbol// The plugin is imported by Babel automatically when configured
export default function ({ types: t }) { ... }{
"plugins": ["transform-es2015-typeof-symbol"]
}babel --plugins transform-es2015-typeof-symbol script.jsrequire("babel-core").transform("code", {
plugins: ["transform-es2015-typeof-symbol"]
});Input:
var s = Symbol("s");
assert.ok(typeof s === "symbol");
assert.equal(typeof s, "symbol");
typeof s === "string";Output:
var s = Symbol("s");
assert.ok((typeof s === "undefined" ? "undefined" : babelHelpers.typeof(s)) === "symbol");
assert.equal(typeof s === "undefined" ? "undefined" : babelHelpers.typeof(s), "symbol");
typeof s === "string";The main export is a Babel plugin factory function that returns a plugin configuration object.
/**
* Main plugin factory function for babel-plugin-transform-es2015-typeof-symbol
* @param {Object} babel - Babel instance with types utility
* @param {Object} babel.types - Babel types utility object (aliased as t)
* @returns {Object} Babel plugin configuration object with visitor methods
*/
export default function ({ types: t }) {
const IGNORE = Symbol();
return {
visitor: {
Scope({ scope }) { /* ... */ },
UnaryExpression(path) { /* ... */ }
}
};
}The returned plugin object contains visitor methods for AST transformation.
interface PluginConfig {
visitor: {
/**
* Handles Symbol identifier conflicts by renaming local Symbol bindings
* @param {Object} path - Babel path object with scope property
*/
Scope: (path: { scope: Object }) => void;
/**
* Transforms typeof expressions to handle symbols correctly
* @param {Object} path - Babel path object for UnaryExpression nodes
*/
UnaryExpression: (path: Object) => void;
};
}Handles Symbol identifier conflicts by renaming local Symbol bindings.
/**
* Processes scope to avoid Symbol identifier conflicts
* @param {Object} path - Babel path object with scope property
* @param {Object} path.scope - Babel scope object
*/
Scope({ scope }) {
if (!scope.getBinding("Symbol")) {
return;
}
scope.rename("Symbol");
}Transforms typeof expressions to handle symbols correctly.
/**
* Transforms typeof expressions to handle symbols properly
* @param {Object} path - Babel path object for UnaryExpression nodes
* @param {Object} path.node - The AST node being processed
* @param {Object} path.parent - The parent AST node
*/
UnaryExpression(path) {
const { node, parent } = path;
if (node[IGNORE]) return;
if (path.find((path) => path.node && !!path.node._generated)) return;
// Optimization: skip transformation for typeof comparisons with non-symbol/object literals
if (path.parentPath.isBinaryExpression() &&
t.EQUALITY_BINARY_OPERATORS.indexOf(parent.operator) >= 0) {
const opposite = path.getOpposite();
if (opposite.isLiteral() && opposite.node.value !== "symbol" &&
opposite.node.value !== "object") {
return;
}
}
if (node.operator === "typeof") {
const call = t.callExpression(this.addHelper("typeof"), [node.argument]);
if (path.get("argument").isIdentifier()) {
// Handle identifiers with conditional expression for undefined variables
const undefLiteral = t.stringLiteral("undefined");
const unary = t.unaryExpression("typeof", node.argument);
unary[IGNORE] = true;
path.replaceWith(t.conditionalExpression(
t.binaryExpression("===", unary, undefLiteral),
undefLiteral,
call
));
} else {
// Handle expressions directly with helper
path.replaceWith(call);
}
}
}Symbol bindings to avoid conflicts using scope.rename("Symbol")IGNORE Symbol to mark already processed nodes and avoid infinite recursion_generated flagtypeof unary expressions in the ASTtypeof foo === "string")this.addHelper("typeof") rather than generating inline codetypeof x === "undefined" ? "undefined" : helper(x)The plugin includes several optimizations:
t.EQUALITY_BINARY_OPERATORStypeof foo === "string" patterns by checking opposite.node.value_generated flag checksIGNORE Symbol to mark processed unary expressionsThis plugin integrates with the Babel transformation pipeline and:
this.addHelper("typeof") to inject the appropriate runtime helper functionScope and UnaryExpression visitorst) for creating conditional expressions, binary expressions, and call expressionsbabel-runtime for the actual typeof helper implementationThe plugin relies on Babel's helper system to provide the actual typeof implementation. The helper function generated by Babel typically looks like:
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol"
? function (obj) { return typeof obj; }
: function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype
? "symbol"
: typeof obj;
};This helper function: