Skip to content

Commit e818d9e

Browse files
committed
Refactor: add one more layer of ClassFieldAnalysis
1 parent 1192332 commit e818d9e

File tree

5 files changed

+57
-50
lines changed

5 files changed

+57
-50
lines changed

src/analysis.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,22 +51,22 @@ export function analyzeClass(
5151
const locals = new LocalManager(path);
5252
const { instanceFields: sites, staticFields } = analyzeClassFields(path);
5353

54-
const propsObjSites = getAndDelete(sites, "props") ?? [];
55-
const defaultPropsObjSites = getAndDelete(staticFields, "defaultProps") ?? [];
54+
const propsObjAnalysis = getAndDelete(sites, "props") ?? { sites: [] };
55+
const defaultPropsObjAnalysis = getAndDelete(staticFields, "defaultProps") ?? { sites: [] };
5656

57-
const stateObjSites = getAndDelete(sites, "state") ?? [];
58-
const setStateSites = getAndDelete(sites, "setState") ?? [];
59-
const states = analyzeState(stateObjSites, setStateSites, locals, preanalysis);
57+
const stateObjAnalysis = getAndDelete(sites, "state") ?? { sites: [] };
58+
const setStateAnalysis = getAndDelete(sites, "setState") ?? { sites: [] };
59+
const states = analyzeState(stateObjAnalysis, setStateAnalysis, locals, preanalysis);
6060

61-
const renderSites = getAndDelete(sites, "render") ?? [];
61+
const renderAnalysis = getAndDelete(sites, "render") ?? { sites: [] };
6262

6363
analyzeOuterCapturings(path, locals);
6464
let renderPath: NodePath<ClassMethod> | undefined = undefined;
6565
{
66-
if (renderSites.some((site) => site.type === "expr")) {
66+
if (renderAnalysis.sites.some((site) => site.type === "expr")) {
6767
throw new AnalysisError(`do not use this.render`);
6868
}
69-
const init = renderSites.find((site) => site.init);
69+
const init = renderAnalysis.sites.find((site) => site.init);
7070
if (init) {
7171
if (init.path.isClassMethod()) {
7272
renderPath = init.path;
@@ -84,7 +84,7 @@ export function analyzeClass(
8484
if (!renderPath) {
8585
throw new AnalysisError(`Missing render method`);
8686
}
87-
const props = analyzeProps(propsObjSites, defaultPropsObjSites, locals, preanalysis);
87+
const props = analyzeProps(propsObjAnalysis, defaultPropsObjAnalysis, locals, preanalysis);
8888
for (const [name, propAnalysis] of props.props) {
8989
if (propAnalysis.needsAlias) {
9090
propAnalysis.newAliasName = locals.newLocal(

src/analysis/class_fields.ts

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,16 @@ import { AnalysisError } from "./error.js";
1313
*/
1414
export type ClassFieldsAnalysis = {
1515
/** Access to instance fields (`this.foo`), indexed by their names. */
16-
instanceFields: Map<string, ClassFieldSite[]>;
16+
instanceFields: Map<string, ClassFieldAnalysis>;
1717
/** Access to static fields (`C.foo`, where `C` is the class), indexed by their names. */
18-
staticFields: Map<string, ClassFieldSite[]>;
18+
staticFields: Map<string, ClassFieldAnalysis>;
19+
};
20+
21+
/**
22+
* Result of class field analysis for each field name.
23+
*/
24+
export type ClassFieldAnalysis = {
25+
sites: ClassFieldSite[];
1926
};
2027

2128
/**
@@ -100,10 +107,10 @@ export type FieldInit = {
100107
* - Static fields ... `C.foo`, where `C` is the class
101108
*/
102109
export function analyzeClassFields(path: NodePath<ClassDeclaration>): ClassFieldsAnalysis {
103-
const instanceFields = new Map<string, ClassFieldSite[]>();
104-
const getInstanceField = (name: string) => getOr(instanceFields, name, () => []);
105-
const staticFields = new Map<string, ClassFieldSite[]>();
106-
const getStaticField = (name: string) => getOr(staticFields, name, () => []);
110+
const instanceFields = new Map<string, ClassFieldAnalysis>();
111+
const getInstanceField = (name: string) => getOr(instanceFields, name, () => ({ sites: [] }));
112+
const staticFields = new Map<string, ClassFieldAnalysis>();
113+
const getStaticField = (name: string) => getOr(staticFields, name, () => ({ sites: [] }));
107114
let constructor: NodePath<ClassMethod> | undefined = undefined;
108115
const bodies: NodePath[] = [];
109116
// 1st pass: look for class field definitions
@@ -123,7 +130,7 @@ export function analyzeClassFields(path: NodePath<ClassDeclaration>): ClassField
123130
const valuePath = nonNullPath<Expression>(itemPath.get("value"));
124131
const typeAnnotation = itemPath.get("typeAnnotation");
125132
const typeAnnotation_ = typeAnnotation.isTSTypeAnnotation() ? typeAnnotation : undefined;
126-
field.push({
133+
field.sites.push({
127134
type: "decl",
128135
path: itemPath,
129136
typing: typeAnnotation_
@@ -146,7 +153,7 @@ export function analyzeClassFields(path: NodePath<ClassDeclaration>): ClassField
146153
// - In TS, it may lack the implementation (i.e. TSDeclareMethod)
147154
const kind = itemPath.node.kind ?? "method";
148155
if (kind === "method") {
149-
field.push({
156+
field.sites.push({
150157
type: "decl",
151158
path: itemPath,
152159
// We put `typing` here only when it is type-only
@@ -252,7 +259,7 @@ export function analyzeClassFields(path: NodePath<ClassDeclaration>): ClassField
252259
// TODO: check for parameter/local variable reference
253260

254261
const field = getInstanceField(name)!;
255-
field.push({
262+
field.sites.push({
256263
type: "decl",
257264
path: exprPath,
258265
typing: undefined,
@@ -297,7 +304,7 @@ export function analyzeClassFields(path: NodePath<ClassDeclaration>): ClassField
297304
argument: thisMemberPath.node,
298305
})
299306

300-
field.push({
307+
field.sites.push({
301308
type: "expr",
302309
path: thisMemberPath,
303310
typing: undefined,
@@ -312,28 +319,28 @@ export function analyzeClassFields(path: NodePath<ClassDeclaration>): ClassField
312319
}
313320

314321
// Post validation
315-
for (const [name, fieldSites] of instanceFields) {
316-
if (fieldSites.length === 0) {
322+
for (const [name, field] of instanceFields) {
323+
if (field.sites.length === 0) {
317324
instanceFields.delete(name);
318325
}
319-
const numInits = fieldSites.reduce((n, site) => n + Number(!!site.init), 0);
326+
const numInits = field.sites.reduce((n, site) => n + Number(!!site.init), 0);
320327
if (numInits > 1) {
321328
throw new AnalysisError(`${name} is initialized more than once`);
322329
}
323-
const numTypes = fieldSites.reduce((n, site) => n + Number(!!site.typing), 0);
330+
const numTypes = field.sites.reduce((n, site) => n + Number(!!site.typing), 0);
324331
if (numTypes > 1) {
325332
throw new AnalysisError(`${name} is declared more than once`);
326333
}
327334
}
328-
for (const [name, fieldSites] of staticFields) {
329-
if (fieldSites.length === 0) {
335+
for (const [name, field] of staticFields) {
336+
if (field.sites.length === 0) {
330337
instanceFields.delete(name);
331338
}
332-
const numInits = fieldSites.reduce((n, site) => n + Number(!!site.init), 0);
339+
const numInits = field.sites.reduce((n, site) => n + Number(!!site.init), 0);
333340
if (numInits > 1) {
334341
throw new AnalysisError(`static ${name} is initialized more than once`);
335342
}
336-
const numTypes = fieldSites.reduce((n, site) => n + Number(!!site.typing), 0);
343+
const numTypes = field.sites.reduce((n, site) => n + Number(!!site.typing), 0);
337344
if (numTypes > 1) {
338345
throw new AnalysisError(`static ${name} is declared more than once`);
339346
}

src/analysis/prop.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { Expression, MemberExpression, TSMethodSignature, TSPropertySignatu
44
import { getOr, memberName } from "../utils.js";
55
import { AnalysisError } from "./error.js";
66
import type { LocalManager } from "./local.js";
7-
import { ClassFieldSite } from "./class_fields.js";
7+
import { ClassFieldAnalysis } from "./class_fields.js";
88
import { trackMember } from "./track_member.js";
99
import { PreAnalysisResult } from "./pre.js";
1010

@@ -55,12 +55,12 @@ export type PropAlias = {
5555
* ```
5656
*/
5757
export function analyzeProps(
58-
propsObjSites: ClassFieldSite[],
59-
defaultPropsObjSites: ClassFieldSite[],
58+
propsObjAnalysis: ClassFieldAnalysis,
59+
defaultPropsObjAnalysis: ClassFieldAnalysis,
6060
locals: LocalManager,
6161
preanalysis: PreAnalysisResult,
6262
): PropsObjAnalysis {
63-
const defaultProps = analyzeDefaultProps(defaultPropsObjSites);
63+
const defaultProps = analyzeDefaultProps(defaultPropsObjAnalysis);
6464
const newObjSites: PropsObjSite[] = [];
6565
const props = new Map<string, PropAnalysis>();
6666
const getProp = (name: string) => getOr(props, name, () => ({
@@ -69,7 +69,7 @@ export function analyzeProps(
6969
needsAlias: false,
7070
}));
7171

72-
for (const site of propsObjSites) {
72+
for (const site of propsObjAnalysis.sites) {
7373
if (site.type !== "expr" || site.hasWrite) {
7474
throw new AnalysisError(`Invalid use of this.props`);
7575
}
@@ -114,16 +114,16 @@ export function analyzeProps(
114114
}
115115

116116
function analyzeDefaultProps(
117-
defaultPropsSites: ClassFieldSite[],
117+
defaultPropsAnalysis: ClassFieldAnalysis,
118118
): Map<string, NodePath<Expression>> | undefined {
119-
for (const site of defaultPropsSites) {
119+
for (const site of defaultPropsAnalysis.sites) {
120120
if (!site.init) {
121121
throw new AnalysisError(`Invalid use of static defaultState`);
122122
}
123123
}
124124

125125
const defaultPropsFields = new Map<string, NodePath<Expression>>();
126-
const init = defaultPropsSites.find((site) => site.init);
126+
const init = defaultPropsAnalysis.sites.find((site) => site.init);
127127
if (!init) {
128128
return;
129129
}

src/analysis/state.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { getOr, memberName } from "../utils.js";
44
import { AnalysisError } from "./error.js";
55
import { PreAnalysisResult } from "./pre.js";
66
import type { LocalManager } from "./local.js";
7-
import type { ClassFieldSite } from "./class_fields.js";
7+
import type { ClassFieldAnalysis } from "./class_fields.js";
88
import { trackMember } from "./track_member.js";
99

1010
export type StateObjAnalysis = Map<string, StateAnalysis>;
@@ -44,8 +44,8 @@ export type StateTypeAnnotation = {
4444
};
4545

4646
export function analyzeState(
47-
stateObjSites: ClassFieldSite[],
48-
setStateSites: ClassFieldSite[],
47+
stateObjAnalysis: ClassFieldAnalysis,
48+
setStateAnalysis: ClassFieldAnalysis,
4949
locals: LocalManager,
5050
preanalysis: PreAnalysisResult,
5151
): StateObjAnalysis {
@@ -54,7 +54,7 @@ export function analyzeState(
5454
sites: [],
5555
}));
5656

57-
const init = stateObjSites.find((site) => site.init);
57+
const init = stateObjAnalysis.sites.find((site) => site.init);
5858
if (init) {
5959
const init_ = init.init!;
6060
if (init_.type !== "init_value") {
@@ -84,7 +84,7 @@ export function analyzeState(
8484
});
8585
}
8686
}
87-
for (const site of stateObjSites) {
87+
for (const site of stateObjAnalysis.sites) {
8888
if (site.init) {
8989
continue;
9090
}
@@ -115,7 +115,7 @@ export function analyzeState(
115115
throw new AnalysisError(`Non-analyzable this.state`);
116116
}
117117
}
118-
for (const site of setStateSites) {
118+
for (const site of setStateAnalysis.sites) {
119119
if (site.type !== "expr" || site.hasWrite) {
120120
throw new AnalysisError(`Invalid use of this.setState`);
121121
}

src/analysis/user_defined.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ArrowFunctionExpression, ClassMethod, ClassPrivateMethod, Expression, F
33
import { isClassMethodLike, nonNullPath } from "../utils.js";
44
import { AnalysisError } from "./error.js";
55
import { analyzeLibRef, isReactRef } from "./lib.js";
6-
import type { ClassFieldSite } from "./class_fields.js";
6+
import type { ClassFieldAnalysis, ClassFieldSite } from "./class_fields.js";
77

88
const SPECIAL_MEMBER_NAMES = new Set<string>([
99
// Special variables
@@ -75,10 +75,10 @@ export type FnInit = {
7575
};
7676

7777
export function analyzeUserDefined(
78-
instanceFields: Map<string, ClassFieldSite[]>
78+
instanceFields: Map<string, ClassFieldAnalysis>
7979
): UserDefinedAnalysis {
8080
const fields = new Map<string, UserDefined>();
81-
for (const [name, fieldSites] of instanceFields) {
81+
for (const [name, field] of instanceFields) {
8282
if (SPECIAL_MEMBER_NAMES.has(name)) {
8383
throw new AnalysisError(`Cannot transform ${name}`);
8484
}
@@ -88,7 +88,7 @@ export function analyzeUserDefined(
8888
let refInitType2: NodePath<TSType> | undefined = undefined;
8989
let valInit: NodePath<Expression> | undefined = undefined;
9090
let valInitType: NodePath<TSType> | undefined = undefined;
91-
const initSite = fieldSites.find((site) => site.init);
91+
const initSite = field.sites.find((site) => site.init);
9292
if (initSite) {
9393
const init = initSite.init!;
9494
if (isClassMethodLike(initSite.path)) {
@@ -128,7 +128,7 @@ export function analyzeUserDefined(
128128
valInit = initPath;
129129
}
130130
}
131-
const typeSite = fieldSites.find((site) => site.typing);
131+
const typeSite = field.sites.find((site) => site.typing);
132132
if (typeSite) {
133133
const typing = typeSite.typing!;
134134
if (typing.type === "type_value") {
@@ -156,26 +156,26 @@ export function analyzeUserDefined(
156156
valInitType = typing.valueTypePath;
157157
}
158158
}
159-
const hasWrite = fieldSites.some((site) => site.hasWrite);
159+
const hasWrite = field.sites.some((site) => site.hasWrite);
160160
if (fnInit && !hasWrite) {
161161
fields.set(name, {
162162
type: "user_defined_function",
163163
init: fnInit,
164164
typeAnnotation: valInitType,
165-
sites: fieldSites,
165+
sites: field.sites,
166166
});
167167
} else if (isRefInit && !hasWrite) {
168168
fields.set(name, {
169169
type: "user_defined_ref",
170170
typeAnnotation: refInitType1 ?? refInitType2,
171-
sites: fieldSites,
171+
sites: field.sites,
172172
});
173173
} else if (valInit) {
174174
fields.set(name, {
175175
type: "user_defined_direct_ref",
176176
init: valInit,
177177
typeAnnotation: valInitType,
178-
sites: fieldSites,
178+
sites: field.sites,
179179
});
180180
} else {
181181
throw new AnalysisError(`Cannot transform this.${name}`);

0 commit comments

Comments
 (0)