Mark static block as FunctionParent (#13832)

* refactor: move StaticBlock definition to core

* update generated types

* fix: mark StaticBlock as FunctionParent

* revise testcase

* update babel-types docs
This commit is contained in:
Huáng Jùnliàng 2021-10-10 20:22:27 -04:00 committed by GitHub
parent 00f8ee3484
commit 49a0d65be1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 253 additions and 85 deletions

View File

@ -418,38 +418,38 @@ export interface NodePathValidators {
isWhileStatement(opts?: object): this is NodePath<t.WhileStatement>;
isWithStatement(opts?: object): this is NodePath<t.WithStatement>;
isYieldExpression(opts?: object): this is NodePath<t.YieldExpression>;
isBindingIdentifier(
opts?: object,
): this is NodePath<VirtualTypeAliases["BindingIdentifier"]>;
isBlockScoped(opts?: object): boolean;
isExistentialTypeParam(
opts?: object,
): this is NodePath<VirtualTypeAliases["ExistentialTypeParam"]>;
isExpression(opts?: object): this is NodePath<t.Expression>;
isFlow(opts?: object): this is NodePath<t.Flow>;
isForAwaitStatement(
opts?: object,
): this is NodePath<VirtualTypeAliases["ForAwaitStatement"]>;
isGenerated(opts?: object): boolean;
isNumericLiteralTypeAnnotation(
opts?: object,
): this is NodePath<VirtualTypeAliases["NumericLiteralTypeAnnotation"]>;
isPure(opts?: object): boolean;
isReferenced(opts?: object): boolean;
isReferencedIdentifier(
opts?: object,
): this is NodePath<VirtualTypeAliases["ReferencedIdentifier"]>;
isReferencedMemberExpression(
opts?: object,
): this is NodePath<VirtualTypeAliases["ReferencedMemberExpression"]>;
isBindingIdentifier(
opts?: object,
): this is NodePath<VirtualTypeAliases["BindingIdentifier"]>;
isStatement(opts?: object): this is NodePath<t.Statement>;
isExpression(opts?: object): this is NodePath<t.Expression>;
isScope(opts?: object): this is NodePath<VirtualTypeAliases["Scope"]>;
isReferenced(opts?: object): boolean;
isBlockScoped(opts?: object): boolean;
isVar(opts?: object): this is NodePath<VirtualTypeAliases["Var"]>;
isUser(opts?: object): boolean;
isGenerated(opts?: object): boolean;
isPure(opts?: object): boolean;
isFlow(opts?: object): this is NodePath<t.Flow>;
isRestProperty(
opts?: object,
): this is NodePath<VirtualTypeAliases["RestProperty"]>;
isScope(opts?: object): this is NodePath<VirtualTypeAliases["Scope"]>;
isSpreadProperty(
opts?: object,
): this is NodePath<VirtualTypeAliases["SpreadProperty"]>;
isExistentialTypeParam(
opts?: object,
): this is NodePath<VirtualTypeAliases["ExistentialTypeParam"]>;
isNumericLiteralTypeAnnotation(
opts?: object,
): this is NodePath<VirtualTypeAliases["NumericLiteralTypeAnnotation"]>;
isForAwaitStatement(
opts?: object,
): this is NodePath<VirtualTypeAliases["ForAwaitStatement"]>;
isStatement(opts?: object): this is NodePath<t.Statement>;
isUser(opts?: object): boolean;
isVar(opts?: object): this is NodePath<VirtualTypeAliases["Var"]>;
}

View File

@ -5,22 +5,22 @@
import * as t from "@babel/types";
export interface VirtualTypeAliases {
BindingIdentifier: t.Identifier;
BlockScoped: t.Node;
ExistentialTypeParam: t.ExistsTypeAnnotation;
Expression: t.Expression;
Flow: t.Flow | t.ImportDeclaration | t.ExportDeclaration | t.ImportSpecifier;
ForAwaitStatement: t.ForOfStatement;
Generated: t.Node;
NumericLiteralTypeAnnotation: t.NumberLiteralTypeAnnotation;
Pure: t.Node;
Referenced: t.Node;
ReferencedIdentifier: t.Identifier | t.JSXIdentifier;
ReferencedMemberExpression: t.MemberExpression;
BindingIdentifier: t.Identifier;
Statement: t.Statement;
Expression: t.Expression;
Scope: t.Scopable | t.Pattern;
Referenced: t.Node;
BlockScoped: t.Node;
Var: t.VariableDeclaration;
User: t.Node;
Generated: t.Node;
Pure: t.Node;
Flow: t.Flow | t.ImportDeclaration | t.ExportDeclaration | t.ImportSpecifier;
RestProperty: t.RestElement;
Scope: t.Scopable | t.Pattern;
SpreadProperty: t.RestElement;
ExistentialTypeParam: t.ExistsTypeAnnotation;
NumericLiteralTypeAnnotation: t.NumberLiteralTypeAnnotation;
ForAwaitStatement: t.ForOfStatement;
Statement: t.Statement;
User: t.Node;
Var: t.VariableDeclaration;
}

View File

@ -702,4 +702,171 @@ describe("scope", () => {
}
});
});
describe("own bindings", () => {
// Var declarations should be declared in the nearest FunctionParent ancestry
describe("var declarations should be registered", () => {
it("in program", () => {
const program = getPath("var foo;");
expect(program.scope.hasOwnBinding("foo")).toBe(true);
});
it("in function declaration", () => {
const functionDeclaration = getPath("function f() { var foo; }").get(
"body.0.expression",
);
expect(functionDeclaration.scope.hasOwnBinding("foo")).toBe(true);
});
it("in function expression", () => {
const functionExpression = getPath("(function () { var foo; })").get(
"body.0.expression",
);
expect(functionExpression.scope.hasOwnBinding("foo")).toBe(true);
});
it("in arrow expression", () => {
const arrowExpression =
getPath("() => { var foo; }").get("body.0.expression");
expect(arrowExpression.scope.hasOwnBinding("foo")).toBe(true);
});
it("in object method", () => {
const objectMethod = getPath("({ method() { var foo; } })").get(
"body.0.expression.properties.0",
);
expect(objectMethod.scope.hasOwnBinding("foo")).toBe(true);
});
it("in class method", () => {
const classMethod = getPath("(class { method() { var foo; } })").get(
"body.0.expression.body.body.0",
);
expect(classMethod.scope.hasOwnBinding("foo")).toBe(true);
});
it("in class private method", () => {
const classMethod = getPath("(class { #method() { var foo; } })").get(
"body.0.expression.body.body.0",
);
expect(classMethod.scope.hasOwnBinding("foo")).toBe(true);
});
it("in static block", () => {
const staticBlock = getPath("(class { static { var foo; } })", {
plugins: ["classStaticBlock"],
}).get("body.0.expression.body.body.0");
expect(staticBlock.scope.hasOwnBinding("foo")).toBe(true);
});
});
describe("var declarations should not be registered", () => {
it("in block statement", () => {
const blockStatement = getPath("{ var foo; }").get("body.0");
expect(blockStatement.scope.hasOwnBinding("foo")).toBe(false);
});
it("in catch clause", () => {
const catchClause = getPath("try {} catch { var foo; }").get(
"body.0.handler",
);
expect(catchClause.scope.hasOwnBinding("foo")).toBe(false);
});
it("in for-init statement", () => {
const forStatement = getPath("for (var foo;;);").get("body.0");
expect(forStatement.scope.hasOwnBinding("foo")).toBe(false);
});
it("in for-in statement", () => {
const forStatement = getPath("for (var foo in x);").get("body.0");
expect(forStatement.scope.hasOwnBinding("foo")).toBe(false);
});
it("in for-of statement", () => {
const forStatement = getPath("for (var foo of x);").get("body.0");
expect(forStatement.scope.hasOwnBinding("foo")).toBe(false);
});
it("in switch statement", () => {
const switchStatement = getPath("switch (0) { case 0: var foo; }").get(
"body.0",
);
expect(switchStatement.scope.hasOwnBinding("foo")).toBe(false);
});
it("in while statement", () => {
const whileStatement = getPath("while (0) \n var foo;").get("body.0");
expect(whileStatement.scope.hasOwnBinding("foo")).toBe(false);
});
it("in do-while statement", () => {
const doWhileStatement = getPath("do \n var foo \n while(0);").get(
"body.0",
);
expect(doWhileStatement.scope.hasOwnBinding("foo")).toBe(false);
});
});
// Lexical declarations should be registered in the nearest BlockParent ancestry
describe("let declarations should be registered", () => {
it("in program", () => {
const program = getPath("let foo;");
expect(program.scope.hasOwnBinding("foo")).toBe(true);
});
it("in function declaration", () => {
const functionDeclaration = getPath("function f() { let foo; }").get(
"body.0.expression",
);
expect(functionDeclaration.scope.hasOwnBinding("foo")).toBe(true);
});
it("in function expression", () => {
const functionExpression = getPath("(function () { let foo; })").get(
"body.0.expression",
);
expect(functionExpression.scope.hasOwnBinding("foo")).toBe(true);
});
it("in arrow expression", () => {
const arrowExpression =
getPath("() => { let foo; }").get("body.0.expression");
expect(arrowExpression.scope.hasOwnBinding("foo")).toBe(true);
});
it("in object method", () => {
const objectMethod = getPath("({ method() { let foo; } })").get(
"body.0.expression.properties.0",
);
expect(objectMethod.scope.hasOwnBinding("foo")).toBe(true);
});
it("in class method", () => {
const classMethod = getPath("(class { method() { let foo; } })").get(
"body.0.expression.body.body.0",
);
expect(classMethod.scope.hasOwnBinding("foo")).toBe(true);
});
it("in class private method", () => {
const classMethod = getPath("(class { #method() { let foo; } })").get(
"body.0.expression.body.body.0",
);
expect(classMethod.scope.hasOwnBinding("foo")).toBe(true);
});
it("in static block", () => {
const staticBlock = getPath("(class { static { let foo; } })", {
plugins: ["classStaticBlock"],
}).get("body.0.expression.body.body.0");
expect(staticBlock.scope.hasOwnBinding("foo")).toBe(true);
});
it("in block statement", () => {
const blockStatement = getPath("{ let foo; }").get("body.0");
expect(blockStatement.scope.hasOwnBinding("foo")).toBe(true);
});
it("in catch clause", () => {
const catchClause = getPath("try {} catch { let foo; }").get(
"body.0.handler",
);
expect(catchClause.scope.hasOwnBinding("foo")).toBe(true);
});
it("in for-init statement", () => {
const forStatement = getPath("for (let foo;;);").get("body.0");
expect(forStatement.scope.hasOwnBinding("foo")).toBe(true);
});
it("in for-in statement", () => {
const forStatement = getPath("for (let foo in x);").get("body.0");
expect(forStatement.scope.hasOwnBinding("foo")).toBe(true);
});
it("in for-of statement", () => {
const forStatement = getPath("for (let foo of x);").get("body.0");
expect(forStatement.scope.hasOwnBinding("foo")).toBe(true);
});
it("in switch statement", () => {
const switchStatement = getPath("switch (0) { case 0: let foo; }").get(
"body.0",
);
expect(switchStatement.scope.hasOwnBinding("foo")).toBe(true);
});
});
});
});

View File

@ -206,7 +206,7 @@ const aliasDescriptions = {
ForXStatement:
"A cover of [ForInStatements and ForOfStatements](https://tc39.es/ecma262/#sec-for-in-and-for-of-statements).",
Function:
"A cover of functions and [method](#method)s, the must have `body` and `params`. Note: `Function` is different to `FunctionParent`.",
"A cover of functions and [method](#method)s, the must have `body` and `params`. Note: `Function` is different to `FunctionParent`. For example, a `StaticBlock` is a `FunctionParent` but not `Function`.",
FunctionParent:
"A cover of AST nodes that start an execution context with new [VariableEnvironment](https://tc39.es/ecma262/#table-additional-state-components-for-ecmascript-code-execution-contexts). In other words, they define the scope of `var` declarations. FunctionParent did not include `Program` since Babel 7.",
Immutable:

View File

@ -524,6 +524,12 @@ export function assertPrivateName(
): asserts node is t.PrivateName {
assert("PrivateName", node, opts);
}
export function assertStaticBlock(
node: object | null | undefined,
opts?: object | null,
): asserts node is t.StaticBlock {
assert("StaticBlock", node, opts);
}
export function assertAnyTypeAnnotation(
node: object | null | undefined,
opts?: object | null,
@ -1076,12 +1082,6 @@ export function assertDecimalLiteral(
): asserts node is t.DecimalLiteral {
assert("DecimalLiteral", node, opts);
}
export function assertStaticBlock(
node: object | null | undefined,
opts?: object | null,
): asserts node is t.StaticBlock {
assert("StaticBlock", node, opts);
}
export function assertModuleExpression(
node: object | null | undefined,
opts?: object | null,

View File

@ -1073,6 +1073,11 @@ export interface PrivateName extends BaseNode {
id: Identifier;
}
export interface StaticBlock extends BaseNode {
type: "StaticBlock";
body: Array<Statement>;
}
export interface AnyTypeAnnotation extends BaseNode {
type: "AnyTypeAnnotation";
}
@ -1633,11 +1638,6 @@ export interface DecimalLiteral extends BaseNode {
value: string;
}
export interface StaticBlock extends BaseNode {
type: "StaticBlock";
body: Array<Statement>;
}
export interface ModuleExpression extends BaseNode {
type: "ModuleExpression";
body: Program;
@ -2232,7 +2232,8 @@ export type FunctionParent =
| ObjectMethod
| ArrowFunctionExpression
| ClassMethod
| ClassPrivateMethod;
| ClassPrivateMethod
| StaticBlock;
export type Pureish =
| FunctionDeclaration
| FunctionExpression

View File

@ -547,6 +547,9 @@ export function classPrivateMethod(
export function privateName(id: t.Identifier): t.PrivateName {
return builder("PrivateName", ...arguments);
}
export function staticBlock(body: Array<t.Statement>): t.StaticBlock {
return builder("StaticBlock", ...arguments);
}
export function anyTypeAnnotation(): t.AnyTypeAnnotation {
return builder("AnyTypeAnnotation", ...arguments);
}
@ -1057,9 +1060,6 @@ export function tupleExpression(
export function decimalLiteral(value: string): t.DecimalLiteral {
return builder("DecimalLiteral", ...arguments);
}
export function staticBlock(body: Array<t.Statement>): t.StaticBlock {
return builder("StaticBlock", ...arguments);
}
export function moduleExpression(body: t.Program): t.ModuleExpression {
return builder("ModuleExpression", ...arguments);
}

View File

@ -94,6 +94,7 @@ export {
classPrivateProperty as ClassPrivateProperty,
classPrivateMethod as ClassPrivateMethod,
privateName as PrivateName,
staticBlock as StaticBlock,
anyTypeAnnotation as AnyTypeAnnotation,
arrayTypeAnnotation as ArrayTypeAnnotation,
booleanTypeAnnotation as BooleanTypeAnnotation,
@ -186,7 +187,6 @@ export {
recordExpression as RecordExpression,
tupleExpression as TupleExpression,
decimalLiteral as DecimalLiteral,
staticBlock as StaticBlock,
moduleExpression as ModuleExpression,
topicReference as TopicReference,
pipelineTopicExpression as PipelineTopicExpression,

View File

@ -2196,3 +2196,16 @@ defineType("PrivateName", {
},
},
});
defineType("StaticBlock", {
visitor: ["body"],
fields: {
body: {
validate: chain(
assertValueType("array"),
assertEach(assertNodeType("Statement")),
),
},
},
aliases: ["Scopable", "BlockParent", "FunctionParent"],
});

View File

@ -116,20 +116,6 @@ defineType("DecimalLiteral", {
aliases: ["Expression", "Pureish", "Literal", "Immutable"],
});
// https://github.com/tc39/proposal-class-static-block
defineType("StaticBlock", {
visitor: ["body"],
fields: {
body: {
validate: chain(
assertValueType("array"),
assertEach(assertNodeType("Statement")),
),
},
},
aliases: ["Scopable", "BlockParent"],
});
// https://github.com/tc39/proposal-js-module-blocks
defineType("ModuleExpression", {
visitor: ["body"],

View File

@ -1450,6 +1450,23 @@ export function isPrivateName(
return false;
}
export function isStaticBlock(
node: object | null | undefined,
opts?: object | null,
): node is t.StaticBlock {
if (!node) return false;
const nodeType = (node as t.Node).type;
if (nodeType === "StaticBlock") {
if (typeof opts === "undefined") {
return true;
} else {
return shallowEqual(node, opts);
}
}
return false;
}
export function isAnyTypeAnnotation(
node: object | null | undefined,
opts?: object | null,
@ -3014,23 +3031,6 @@ export function isDecimalLiteral(
return false;
}
export function isStaticBlock(
node: object | null | undefined,
opts?: object | null,
): node is t.StaticBlock {
if (!node) return false;
const nodeType = (node as t.Node).type;
if (nodeType === "StaticBlock") {
if (typeof opts === "undefined") {
return true;
} else {
return shallowEqual(node, opts);
}
}
return false;
}
export function isModuleExpression(
node: object | null | undefined,
opts?: object | null,
@ -4642,7 +4642,8 @@ export function isFunctionParent(
"ObjectMethod" === nodeType ||
"ArrowFunctionExpression" === nodeType ||
"ClassMethod" === nodeType ||
"ClassPrivateMethod" === nodeType
"ClassPrivateMethod" === nodeType ||
"StaticBlock" === nodeType
) {
if (typeof opts === "undefined") {
return true;