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

Mark static block as FunctionParent #13832

Merged
merged 5 commits into from Oct 11, 2021
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
44 changes: 22 additions & 22 deletions packages/babel-traverse/src/path/generated/validators.ts
Expand Up @@ -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>;
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;
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;
isFlow(opts?: object): this is NodePath<t.Flow>;
isReferenced(opts?: object): boolean;
isReferencedIdentifier(
opts?: object,
): this is NodePath<VirtualTypeAliases["ReferencedIdentifier"]>;
isReferencedMemberExpression(
opts?: object,
): this is NodePath<VirtualTypeAliases["ReferencedMemberExpression"]>;
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"]>;
}
24 changes: 12 additions & 12 deletions packages/babel-traverse/src/path/generated/virtual-types.ts
Expand Up @@ -5,22 +5,22 @@
import * as t from "@babel/types";

export interface VirtualTypeAliases {
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;
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;
Flow: t.Flow | t.ImportDeclaration | t.ExportDeclaration | t.ImportSpecifier;
Referenced: t.Node;
ReferencedIdentifier: t.Identifier | t.JSXIdentifier;
ReferencedMemberExpression: t.MemberExpression;
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;
}
167 changes: 167 additions & 0 deletions packages/babel-traverse/test/scope.js
Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old behavior was that var foo was registered as an own binding of the program scope? If so, we might want to also test that it's not registered in the global scope.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the old behavior is to register in upper functionParent scope / program scope.

we might want to also test that it's not registered in the global scope.

Well we can add a general test that the same binding should not be registered twice in different scope, is that true?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, testing that a binding is in one scope is effectively already testing that it's not in the other one.

});
});
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);
});
});
});
});
2 changes: 1 addition & 1 deletion packages/babel-types/scripts/generators/docs.js
Expand Up @@ -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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should rename FunctionParent to VarHoistScope?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am good with renaming and adding alias for backward compatibility. The FunctionParent is named v.s. BlockParent. If we pick VarHoistScope for FunctionParent, then we should also pick one for BlockParent: Maybe LexicalScope?

We also have Scope#getFunctionParent and Scope#getBlockParent which returns the scope's path's someParent's scope. They will also have to be renamed.

Related: By definition I would like to add Program to FunctionParent too. The Program is a FunctionParent in Babel 6 but we mark it as BlockParent since Babel 7: An AST node could be both BlockParent and FunctionParent, though: Precisely FunctionParent is a subset of BlockParent.

"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:
Expand Down
12 changes: 6 additions & 6 deletions packages/babel-types/src/asserts/generated/index.ts
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
13 changes: 7 additions & 6 deletions packages/babel-types/src/ast-types/generated/index.ts
Expand Up @@ -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";
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -2232,7 +2232,8 @@ export type FunctionParent =
| ObjectMethod
| ArrowFunctionExpression
| ClassMethod
| ClassPrivateMethod;
| ClassPrivateMethod
| StaticBlock;
export type Pureish =
| FunctionDeclaration
| FunctionExpression
Expand Down
6 changes: 3 additions & 3 deletions packages/babel-types/src/builders/generated/index.ts
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/babel-types/src/builders/generated/uppercase.js
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions packages/babel-types/src/definitions/core.ts
Expand Up @@ -2196,3 +2196,16 @@ defineType("PrivateName", {
},
},
});

defineType("StaticBlock", {
visitor: ["body"],
fields: {
body: {
validate: chain(
assertValueType("array"),
assertEach(assertNodeType("Statement")),
),
},
},
aliases: ["Scopable", "BlockParent", "FunctionParent"],
});