Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add placeholders support to @babel/types and @babel/generator #9542

Merged
merged 2 commits into from
Mar 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/babel-generator/src/generators/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,13 @@ export function DirectiveLiteral(node: Object) {
export function InterpreterDirective(node: Object) {
this.token(`#!${node.value}\n`);
}

export function Placeholder(node: Object) {
this.token("%%");
this.print(node.name);
this.token("%%");

if (node.expectedNode === "Statement") {
this.semicolon();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
var %%a%% = %%b%%

%%c%%

class %%d%% {}
class A %%e%%

function %%f%%(...%%g%%) %%h%%
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"plugins": ["placeholders"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
var %%a%% = %%b%%;
%%c%%;

class %%d%% {}

class A %%e%%

function %%f%%(...%%g%%) %%h%%
33 changes: 28 additions & 5 deletions packages/babel-types/scripts/generators/generateValidators.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,45 @@
"use strict";
const definitions = require("../../lib/definitions");

const has = Function.call.bind(Object.prototype.hasOwnProperty);

function joinComparisons(leftArr, right) {
return (
leftArr.map(JSON.stringify).join(` === ${right} || `) + ` === ${right}`
);
}

function addIsHelper(type, aliasKeys, deprecated) {
const targetType = JSON.stringify(type);
let aliasSource = "";
if (aliasKeys) {
aliasSource =
" || " +
aliasKeys.map(JSON.stringify).join(" === nodeType || ") +
" === nodeType";
aliasSource = " || " + joinComparisons(aliasKeys, "nodeType");
}

let placeholderSource = "";
const placeholderTypes = [];
if (
definitions.PLACEHOLDERS.includes(type) &&
has(definitions.FLIPPED_ALIAS_KEYS, type)
) {
placeholderTypes.push(type);
}
if (has(definitions.PLACEHOLDERS_FLIPPED_ALIAS, type)) {
placeholderTypes.push(...definitions.PLACEHOLDERS_FLIPPED_ALIAS[type]);
}
if (placeholderTypes.length > 0) {
placeholderSource =
' || nodeType === "Placeholder" && (' +
joinComparisons(placeholderTypes, "node.expectedNode") +
")";
}

return `export function is${type}(node: ?Object, opts?: Object): boolean {
${deprecated || ""}
if (!node) return false;

const nodeType = node.type;
if (nodeType === ${targetType}${aliasSource}) {
if (nodeType === ${targetType}${aliasSource}${placeholderSource}) {
if (typeof opts === "undefined") {
return true;
} else {
Expand Down
3 changes: 3 additions & 0 deletions packages/babel-types/src/asserts/generated/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,9 @@ export function assertJSXClosingFragment(
export function assertNoop(node: Object, opts?: Object = {}): void {
assert("Noop", node, opts);
}
export function assertPlaceholder(node: Object, opts?: Object = {}): void {
assert("Placeholder", node, opts);
}
export function assertArgumentPlaceholder(
node: Object,
opts?: Object = {},
Expand Down
4 changes: 4 additions & 0 deletions packages/babel-types/src/builders/generated/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,10 @@ export function Noop(...args: Array<any>): Object {
return builder("Noop", ...args);
}
export { Noop as noop };
export function Placeholder(...args: Array<any>): Object {
return builder("Placeholder", ...args);
}
export { Placeholder as placeholder };
export function ArgumentPlaceholder(...args: Array<any>): Object {
return builder("ArgumentPlaceholder", ...args);
}
Expand Down
11 changes: 11 additions & 0 deletions packages/babel-types/src/definitions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ import {
BUILDER_KEYS,
DEPRECATED_KEYS,
} from "./utils";
import {
PLACEHOLDERS,
PLACEHOLDERS_ALIAS,
PLACEHOLDERS_FLIPPED_ALIAS,
} from "./placeholders";

// We do this here, because at this point the visitor keys should be ready and setup
toFastProperties(VISITOR_KEYS);
Expand All @@ -24,6 +29,9 @@ toFastProperties(NODE_FIELDS);
toFastProperties(BUILDER_KEYS);
toFastProperties(DEPRECATED_KEYS);

toFastProperties(PLACEHOLDERS_ALIAS);
toFastProperties(PLACEHOLDERS_FLIPPED_ALIAS);

const TYPES: Array<string> = Object.keys(VISITOR_KEYS)
.concat(Object.keys(FLIPPED_ALIAS_KEYS))
.concat(Object.keys(DEPRECATED_KEYS));
Expand All @@ -35,5 +43,8 @@ export {
NODE_FIELDS,
BUILDER_KEYS,
DEPRECATED_KEYS,
PLACEHOLDERS,
PLACEHOLDERS_ALIAS,
PLACEHOLDERS_FLIPPED_ALIAS,
TYPES,
};
17 changes: 16 additions & 1 deletion packages/babel-types/src/definitions/misc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
// @flow
import defineType from "./utils";
import defineType, { assertNodeType, assertOneOf } from "./utils";
import { PLACEHOLDERS } from "./placeholders";

defineType("Noop", {
visitor: [],
});

defineType("Placeholder", {
visitor: [],
builder: ["expectedNode", "name"],
// aliases: [], defined in placeholders.js
fields: {
name: {
validate: assertNodeType("Identifier"),
},
expectedNode: {
validate: assertOneOf(...PLACEHOLDERS),
},
},
});
33 changes: 33 additions & 0 deletions packages/babel-types/src/definitions/placeholders.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ALIAS_KEYS } from "./utils";

export const PLACEHOLDERS = [
"Identifier",
"StringLiteral",
"Expression",
"Statement",
"Declaration",
"BlockStatement",
"ClassBody",
"Pattern",
];

export const PLACEHOLDERS_ALIAS: { [string]: Array<string> } = {
Declaration: ["Statement"],
Pattern: ["PatternLike", "LVal"],
};

for (const type of PLACEHOLDERS) {
const alias = ALIAS_KEYS[type];
if (alias && alias.length) PLACEHOLDERS_ALIAS[type] = alias;
}

export const PLACEHOLDERS_FLIPPED_ALIAS: { [string]: Array<string> } = {};

Object.keys(PLACEHOLDERS_ALIAS).forEach(type => {
PLACEHOLDERS_ALIAS[type].forEach(alias => {
if (!Object.hasOwnProperty.call(PLACEHOLDERS_FLIPPED_ALIAS, alias)) {
PLACEHOLDERS_FLIPPED_ALIAS[alias] = [];
}
PLACEHOLDERS_FLIPPED_ALIAS[alias].push(type);
});
});
1 change: 1 addition & 0 deletions packages/babel-types/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export { default as isImmutable } from "./validators/isImmutable";
export { default as isLet } from "./validators/isLet";
export { default as isNode } from "./validators/isNode";
export { default as isNodesEquivalent } from "./validators/isNodesEquivalent";
export { default as isPlaceholderType } from "./validators/isPlaceholderType";
export { default as isReferenced } from "./validators/isReferenced";
export { default as isScope } from "./validators/isScope";
export { default as isSpecifierDefault } from "./validators/isSpecifierDefault";
Expand Down
61 changes: 48 additions & 13 deletions packages/babel-types/src/validators/generated/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2093,6 +2093,20 @@ export function isNoop(node: ?Object, opts?: Object): boolean {

return false;
}
export function isPlaceholder(node: ?Object, opts?: Object): boolean {
if (!node) return false;

const nodeType = node.type;
if (nodeType === "Placeholder") {
if (typeof opts === "undefined") {
return true;
} else {
return shallowEqual(node, opts);
}
}

return false;
}
export function isArgumentPlaceholder(node: ?Object, opts?: Object): boolean {
if (!node) return false;

Expand Down Expand Up @@ -3280,7 +3294,11 @@ export function isExpression(node: ?Object, opts?: Object): boolean {
"BigIntLiteral" === nodeType ||
"TSAsExpression" === nodeType ||
"TSTypeAssertion" === nodeType ||
"TSNonNullExpression" === nodeType
"TSNonNullExpression" === nodeType ||
(nodeType === "Placeholder" &&
("Expression" === node.expectedNode ||
"Identifier" === node.expectedNode ||
"StringLiteral" === node.expectedNode))
) {
if (typeof opts === "undefined") {
return true;
Expand Down Expand Up @@ -3331,7 +3349,8 @@ export function isScopable(node: ?Object, opts?: Object): boolean {
"ClassExpression" === nodeType ||
"ForOfStatement" === nodeType ||
"ClassMethod" === nodeType ||
"ClassPrivateMethod" === nodeType
"ClassPrivateMethod" === nodeType ||
(nodeType === "Placeholder" && "BlockStatement" === node.expectedNode)
) {
if (typeof opts === "undefined") {
return true;
Expand Down Expand Up @@ -3362,7 +3381,8 @@ export function isBlockParent(node: ?Object, opts?: Object): boolean {
"ArrowFunctionExpression" === nodeType ||
"ForOfStatement" === nodeType ||
"ClassMethod" === nodeType ||
"ClassPrivateMethod" === nodeType
"ClassPrivateMethod" === nodeType ||
(nodeType === "Placeholder" && "BlockStatement" === node.expectedNode)
) {
if (typeof opts === "undefined") {
return true;
Expand All @@ -3380,7 +3400,8 @@ export function isBlock(node: ?Object, opts?: Object): boolean {
if (
nodeType === "Block" ||
"BlockStatement" === nodeType ||
"Program" === nodeType
"Program" === nodeType ||
(nodeType === "Placeholder" && "BlockStatement" === node.expectedNode)
) {
if (typeof opts === "undefined") {
return true;
Expand Down Expand Up @@ -3442,7 +3463,11 @@ export function isStatement(node: ?Object, opts?: Object): boolean {
"TSModuleDeclaration" === nodeType ||
"TSImportEqualsDeclaration" === nodeType ||
"TSExportAssignment" === nodeType ||
"TSNamespaceExportDeclaration" === nodeType
"TSNamespaceExportDeclaration" === nodeType ||
(nodeType === "Placeholder" &&
("Statement" === node.expectedNode ||
"Declaration" === node.expectedNode ||
"BlockStatement" === node.expectedNode))
) {
if (typeof opts === "undefined") {
return true;
Expand Down Expand Up @@ -3667,7 +3692,8 @@ export function isPureish(node: ?Object, opts?: Object): boolean {
"ArrowFunctionExpression" === nodeType ||
"ClassDeclaration" === nodeType ||
"ClassExpression" === nodeType ||
"BigIntLiteral" === nodeType
"BigIntLiteral" === nodeType ||
(nodeType === "Placeholder" && "StringLiteral" === node.expectedNode)
) {
if (typeof opts === "undefined") {
return true;
Expand Down Expand Up @@ -3708,7 +3734,8 @@ export function isDeclaration(node: ?Object, opts?: Object): boolean {
"TSInterfaceDeclaration" === nodeType ||
"TSTypeAliasDeclaration" === nodeType ||
"TSEnumDeclaration" === nodeType ||
"TSModuleDeclaration" === nodeType
"TSModuleDeclaration" === nodeType ||
(nodeType === "Placeholder" && "Declaration" === node.expectedNode)
) {
if (typeof opts === "undefined") {
return true;
Expand All @@ -3729,7 +3756,9 @@ export function isPatternLike(node: ?Object, opts?: Object): boolean {
"RestElement" === nodeType ||
"AssignmentPattern" === nodeType ||
"ArrayPattern" === nodeType ||
"ObjectPattern" === nodeType
"ObjectPattern" === nodeType ||
(nodeType === "Placeholder" &&
("Pattern" === node.expectedNode || "Identifier" === node.expectedNode))
) {
if (typeof opts === "undefined") {
return true;
Expand All @@ -3752,7 +3781,9 @@ export function isLVal(node: ?Object, opts?: Object): boolean {
"AssignmentPattern" === nodeType ||
"ArrayPattern" === nodeType ||
"ObjectPattern" === nodeType ||
"TSParameterProperty" === nodeType
"TSParameterProperty" === nodeType ||
(nodeType === "Placeholder" &&
("Pattern" === node.expectedNode || "Identifier" === node.expectedNode))
) {
if (typeof opts === "undefined") {
return true;
Expand All @@ -3770,7 +3801,8 @@ export function isTSEntityName(node: ?Object, opts?: Object): boolean {
if (
nodeType === "TSEntityName" ||
"Identifier" === nodeType ||
"TSQualifiedName" === nodeType
"TSQualifiedName" === nodeType ||
(nodeType === "Placeholder" && "Identifier" === node.expectedNode)
) {
if (typeof opts === "undefined") {
return true;
Expand All @@ -3793,7 +3825,8 @@ export function isLiteral(node: ?Object, opts?: Object): boolean {
"BooleanLiteral" === nodeType ||
"RegExpLiteral" === nodeType ||
"TemplateLiteral" === nodeType ||
"BigIntLiteral" === nodeType
"BigIntLiteral" === nodeType ||
(nodeType === "Placeholder" && "StringLiteral" === node.expectedNode)
) {
if (typeof opts === "undefined") {
return true;
Expand Down Expand Up @@ -3824,7 +3857,8 @@ export function isImmutable(node: ?Object, opts?: Object): boolean {
"JSXFragment" === nodeType ||
"JSXOpeningFragment" === nodeType ||
"JSXClosingFragment" === nodeType ||
"BigIntLiteral" === nodeType
"BigIntLiteral" === nodeType ||
(nodeType === "Placeholder" && "StringLiteral" === node.expectedNode)
) {
if (typeof opts === "undefined") {
return true;
Expand Down Expand Up @@ -3940,7 +3974,8 @@ export function isPattern(node: ?Object, opts?: Object): boolean {
nodeType === "Pattern" ||
"AssignmentPattern" === nodeType ||
"ArrayPattern" === nodeType ||
"ObjectPattern" === nodeType
"ObjectPattern" === nodeType ||
(nodeType === "Placeholder" && "Pattern" === node.expectedNode)
) {
if (typeof opts === "undefined") {
return true;
Expand Down
18 changes: 17 additions & 1 deletion packages/babel-types/src/validators/is.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// @flow
import shallowEqual from "../utils/shallowEqual";
import isType from "./isType";
import isPlaceholderType from "./isPlaceholderType";
import { FLIPPED_ALIAS_KEYS } from "../definitions";

/**
* Returns whether `node` is of given `type`.
Expand All @@ -11,7 +13,21 @@ export default function is(type: string, node: Object, opts?: Object): boolean {
if (!node) return false;

const matches = isType(node.type, type);
if (!matches) return false;
if (!matches) {
if (!opts && node.type === "Placeholder" && type in FLIPPED_ALIAS_KEYS) {
// We can only return true if the placeholder doesn't replace a real node,
// but it replaces a category of nodes (an alias).
//
// t.is("Identifier", node) gives some guarantees about node's shape, so we
// can't say that Placeholder(expectedNode: "Identifier") is an identifier
// because it doesn't have the same properties.
// On the other hand, t.is("Expression", node) doesn't say anything about
// the shape of node because Expression can be many different nodes: we can,
// and should, safely report expression placeholders as Expressions.
return isPlaceholderType(node.expectedNode, type);
}
return false;
}

if (typeof opts === "undefined") {
return true;
Expand Down