Skip to content

Commit 646f13e

Browse files
committed
Add comments on this_fields.ts
1 parent 03079b3 commit 646f13e

File tree

2 files changed

+90
-1
lines changed

2 files changed

+90
-1
lines changed

src/analysis/pre.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// This file contains analysis paths for class heads.
2+
13
import type { NodePath } from "@babel/core";
24
import type { BlockStatement, ClassDeclaration, Identifier, Program, TSInterfaceBody, TSMethodSignature, TSPropertySignature, TSType, TSTypeParameterDeclaration } from "@babel/types";
35
import { memberName, nonNullPath } from "../utils.js";

src/analysis/this_fields.ts

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,72 @@
1+
// This file contains analysis for class fields (`this.foo` and `C.foo`) where `C` is the class,
2+
// regardless of whether this is a special one (`this.props`) or a user-defined one (`this.foo`).
3+
//
4+
// Both the declarations and the usages are collected.
5+
16
import type { NodePath } from "@babel/core";
27
import type { AssignmentExpression, CallExpression, ClassAccessorProperty, ClassDeclaration, ClassMethod, ClassPrivateMethod, ClassPrivateProperty, ClassProperty, Expression, ExpressionStatement, MemberExpression, ThisExpression, TSDeclareMethod, TSType } from "@babel/types";
38
import { getOr, isClassAccessorProperty, isClassMethodLike, isClassMethodOrDecl, isClassPropertyLike, isNamedClassElement, isStaticBlock, memberName, memberRefName, nonNullPath } from "../utils.js";
49
import { AnalysisError } from "./error.js";
510

11+
/**
12+
* Aggregated result of class field analysis.
13+
*/
614
export type ThisFields = {
15+
/** Access to instance fields (`this.foo`), indexed by their names. */
716
thisFields: Map<string, ThisFieldSite[]>;
17+
/** Access to static fields (`C.foo`, where `C` is the class), indexed by their names. */
818
staticFields: Map<string, StaticFieldSite[]>;
919
};
1020

21+
/**
22+
* A place where the instance field is declared or used.
23+
*/
1124
export type ThisFieldSite = {
1225
type: "class_field";
26+
/**
27+
* Declaration. One of:
28+
*
29+
* - Class element (methods, fields, etc.)
30+
* - Assignment to `this` in the constructor
31+
*/
1332
path: NodePath<ClassProperty | ClassPrivateProperty | ClassMethod | ClassPrivateMethod | ClassAccessorProperty | TSDeclareMethod | AssignmentExpression>;
33+
/**
34+
* Type annotation, if any.
35+
*
36+
* Param/return annotations attached to function-like implementations are ignored.
37+
*/
1438
typing: FieldTyping | undefined;
39+
/**
40+
* Initializing expression, if any.
41+
*/
1542
init: FieldInit | undefined;
1643
hasWrite: undefined;
44+
/**
45+
* true if the initializer has a side effect.
46+
*/
1747
hasSideEffect: boolean;
1848
} | {
1949
type: "expr";
50+
/**
51+
* The node that accesses the field (both read and write)
52+
*/
2053
path: NodePath<MemberExpression>;
2154
typing: undefined;
2255
init: undefined;
56+
/**
57+
* true if it involves writing. This includes:
58+
*
59+
* - Assignment `this.foo = 42`
60+
* - Compound assignment `this.foo += 42`
61+
* - Delete `delete this.foo`
62+
*/
2363
hasWrite: boolean;
2464
hasSideEffect: undefined;
2565
};
2666

67+
/**
68+
* Essentially a TSTypeAnnotation, but accounts for TSDeclareMethod as well.
69+
*/
2770
export type FieldTyping = {
2871
type: "type_value";
2972
valueTypePath: NodePath<TSType>;
@@ -32,6 +75,9 @@ export type FieldTyping = {
3275
methodDeclPath: NodePath<TSDeclareMethod>;
3376
}
3477

78+
/**
79+
* Essentially an Expression, but accounts for ClassMethod as well.
80+
*/
3581
export type FieldInit = {
3682
type: "init_value";
3783
valuePath: NodePath<Expression>
@@ -40,15 +86,41 @@ export type FieldInit = {
4086
methodPath: NodePath<ClassMethod | ClassPrivateMethod>;
4187
};
4288

89+
/**
90+
* A place where the static field is declared or used.
91+
*/
4392
export type StaticFieldSite = {
4493
type: "class_field";
94+
/**
95+
* Declaration. One of:
96+
*
97+
* - Class element (methods, fields, etc.)
98+
* - Assignment to `this` in a static initialization block
99+
*/
45100
path: NodePath<ClassProperty | ClassPrivateProperty | ClassMethod | ClassPrivateMethod | ClassAccessorProperty | TSDeclareMethod | AssignmentExpression>;
101+
/**
102+
* Type annotation, if any.
103+
*
104+
* Param/return annotations attached to function-like implementations are ignored.
105+
*/
46106
typing: FieldTyping | undefined;
107+
/**
108+
* Initializing expression, if any.
109+
*/
47110
init: FieldInit | undefined;
48111
hasWrite: undefined;
112+
/**
113+
* true if the initializer has a side effect.
114+
*/
49115
hasSideEffect: boolean;
50116
};
51117

118+
/**
119+
* Collect declarations and uses of the following:
120+
*
121+
* - Instance fields ... `this.foo`
122+
* - Static fields ... `C.foo`, where `C` is the class
123+
*/
52124
export function analyzeThisFields(path: NodePath<ClassDeclaration>): ThisFields {
53125
const thisFields = new Map<string, ThisFieldSite[]>();
54126
const getThisField = (name: string) => getOr(thisFields, name, () => []);
@@ -59,13 +131,17 @@ export function analyzeThisFields(path: NodePath<ClassDeclaration>): ThisFields
59131
// 1st pass: look for class field definitions
60132
for (const itemPath of path.get("body").get("body")) {
61133
if (isNamedClassElement(itemPath)) {
134+
// The element is a class method or a class field (in a general sense)
62135
const isStatic = itemPath.node.static;
63136
const name = memberName(itemPath.node);
64137
if (name == null) {
65138
throw new AnalysisError(`Unnamed class element`);
66139
}
67140
const field = isStatic ? getStaticField(name) : getThisField(name);
68141
if (isClassPropertyLike(itemPath)) {
142+
// Class field.
143+
// - May have an initializer: `foo = 42;` or not: `foo;`
144+
// - May have a type annotation: `foo: number;` or not: `foo;`
69145
const valuePath = nonNullPath<Expression>(itemPath.get("value"));
70146
const typeAnnotation = itemPath.get("typeAnnotation");
71147
const typeAnnotation_ = typeAnnotation.isTSTypeAnnotation() ? typeAnnotation : undefined;
@@ -83,14 +159,19 @@ export function analyzeThisFields(path: NodePath<ClassDeclaration>): ThisFields
83159
hasSideEffect: !!itemPath.node.value && estimateSideEffect(itemPath.node.value),
84160
});
85161
if (valuePath) {
162+
// Initializer should be analyzed in step 2 too (considered to be in the constructor)
86163
bodies.push(valuePath);
87164
}
88165
} else if (isClassMethodOrDecl(itemPath)) {
166+
// Class method, constructor, getter/setter, or an accessor (those that will be introduced in the decorator proposal).
167+
//
168+
// - In TS, it may lack the implementation (i.e. TSDeclareMethod)
89169
const kind = itemPath.node.kind ?? "method";
90170
if (kind === "method") {
91171
field.push({
92172
type: "class_field",
93173
path: itemPath,
174+
// We put `typing` here only when it is type-only
94175
typing: itemPath.isTSDeclareMethod()
95176
? {
96177
type: "type_method",
@@ -103,6 +184,7 @@ export function analyzeThisFields(path: NodePath<ClassDeclaration>): ThisFields
103184
hasWrite: undefined,
104185
hasSideEffect: false,
105186
});
187+
// Analysis for step 2
106188
if (isClassMethodLike(itemPath)) {
107189
for (const paramPath of itemPath.get("params")) {
108190
bodies.push(paramPath);
@@ -133,6 +215,8 @@ export function analyzeThisFields(path: NodePath<ClassDeclaration>): ThisFields
133215

134216
// 1st pass additional work: field initialization in constructor
135217
if (constructor) {
218+
// Only `constructor(props)` is allowed.
219+
// TODO: accept context as well
136220
if (constructor.node.params.length > 1) {
137221
throw new AnalysisError(`Constructor has too many parameters`);
138222
} else if (constructor.node.params.length < 1) {
@@ -146,6 +230,7 @@ export function analyzeThisFields(path: NodePath<ClassDeclaration>): ThisFields
146230
const stmts = constructor.get("body").get("body");
147231

148232
// Check super() call
233+
// Must be super(props) or super(props, context)
149234
const superCallIndex = stmts.findIndex((stmt) =>
150235
stmt.node.type === "ExpressionStatement"
151236
&& stmt.node.expression.type === "CallExpression"
@@ -169,6 +254,7 @@ export function analyzeThisFields(path: NodePath<ClassDeclaration>): ThisFields
169254
throw new AnalysisError(`Invalid argument for super()`);
170255
}
171256

257+
// Analyze init statements (must be in the form of `this.foo = expr;`)
172258
const initStmts = stmts.slice(superCallIndex + 1);
173259
for (const stmt of initStmts) {
174260
if (!(
@@ -247,6 +333,7 @@ export function analyzeThisFields(path: NodePath<ClassDeclaration>): ThisFields
247333
traverseItem(body);
248334
}
249335

336+
// Post validation
250337
for (const [name, fieldSites] of thisFields) {
251338
if (fieldSites.length === 0) {
252339
thisFields.delete(name);
@@ -312,7 +399,7 @@ function estimateSideEffect(expr: Expression): boolean {
312399
case "MemberExpression":
313400
// Assume `foo.bar` to be pure
314401
return estimateSideEffect(expr.object) || (expr.property.type !== "PrivateName" && estimateSideEffect(expr.property));
315-
402+
316403
case "UnaryExpression":
317404
switch (expr.operator) {
318405
case "void":

0 commit comments

Comments
 (0)