forked from babel/babel
/
class-scope.js
110 lines (88 loc) 路 3.13 KB
/
class-scope.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// @flow
import {
CLASS_ELEMENT_KIND_ACCESSOR,
CLASS_ELEMENT_FLAG_STATIC,
type ClassElementTypes,
} from "./scopeflags";
import { Errors } from "../parser/error";
export class ClassScope {
// A list of private named declared in the current class
privateNames: Set<string> = new Set();
// A list of private getters of setters without their counterpart
loneAccessors: Map<string, ClassElementTypes> = new Map();
// A list of private names used before being defined, mapping to
// their position.
undefinedPrivateNames: Map<string, number> = new Map();
}
type raiseFunction = (number, string, ...any) => void;
export default class ClassScopeHandler {
stack: Array<ClassScope> = [];
raise: raiseFunction;
undefinedPrivateNames: Map<string, number> = new Map();
constructor(raise: raiseFunction) {
this.raise = raise;
}
current(): ClassScope {
return this.stack[this.stack.length - 1];
}
enter() {
this.stack.push(new ClassScope());
}
exit() {
const oldClassScope = this.stack.pop();
// Migrate the usage of not yet defined private names to the outer
// class scope, or raise an error if we reached the top-level scope.
const current = this.current();
// Array.from is needed because this is compiled to an array-like for loop
for (const [name, pos] of Array.from(oldClassScope.undefinedPrivateNames)) {
if (current) {
if (!current.undefinedPrivateNames.has(name)) {
current.undefinedPrivateNames.set(name, pos);
}
} else {
this.raise(pos, Errors.InvalidPrivateFieldResolution, name);
}
}
}
declarePrivateName(
name: string,
elementType: ClassElementTypes,
pos: number,
) {
const classScope = this.current();
let redefined = classScope.privateNames.has(name);
if (elementType & CLASS_ELEMENT_KIND_ACCESSOR) {
const accessor = redefined && classScope.loneAccessors.get(name);
if (accessor) {
const oldStatic = accessor & CLASS_ELEMENT_FLAG_STATIC;
const newStatic = elementType & CLASS_ELEMENT_FLAG_STATIC;
const oldKind = accessor & CLASS_ELEMENT_KIND_ACCESSOR;
const newKind = elementType & CLASS_ELEMENT_KIND_ACCESSOR;
// The private name can be duplicated only if it is used by
// two accessors with different kind (get and set), and if
// they have the same placement (static or not).
redefined = oldKind === newKind || oldStatic !== newStatic;
if (!redefined) classScope.loneAccessors.delete(name);
} else if (!redefined) {
classScope.loneAccessors.set(name, elementType);
}
}
if (redefined) {
this.raise(pos, Errors.PrivateNameRedeclaration, name);
}
classScope.privateNames.add(name);
classScope.undefinedPrivateNames.delete(name);
}
usePrivateName(name: string, pos: number) {
let classScope;
for (classScope of this.stack) {
if (classScope.privateNames.has(name)) return;
}
if (classScope) {
classScope.undefinedPrivateNames.set(name, pos);
} else {
// top-level
this.raise(pos, Errors.InvalidPrivateFieldResolution, name);
}
}
}