[parser] Disallow duplicate and undeclared private names (#10456)

* [parser] Add private names tracking to Scope

- Disallow duplicate private names
- Disallow undeclared private names

* Update tests

* Test all possible duplications

* Test undeclared private names

* Better error message for top-level private names

* Fix flow

* Update test262 whitelist

* Update fixtures

* Update flow whitelist

* Remove old output.json

* Move ClassScopeHandler to a separate class

* Make the code readable
This commit is contained in:
Nicolò Ribaudo
2020-01-10 02:22:05 +01:00
committed by GitHub
parent 9f148a1603
commit 771c730fda
223 changed files with 16948 additions and 259 deletions

View File

@@ -0,0 +1,113 @@
// @flow
import {
CLASS_ELEMENT_KIND_ACCESSOR,
CLASS_ELEMENT_FLAG_STATIC,
type ClassElementTypes,
} from "./scopeflags";
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) => 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.raiseUndeclaredPrivateName(name, pos);
}
}
}
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, `Duplicate private name #${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.raiseUndeclaredPrivateName(name, pos);
}
}
raiseUndeclaredPrivateName(name: string, pos: number) {
this.raise(pos, `Private name #${name} is not defined`);
}
}

View File

@@ -43,6 +43,7 @@ export default class ScopeHandler<IScope: Scope = Scope> {
raise: raiseFunction;
inModule: boolean;
undefinedExports: Map<string, number> = new Map();
undefinedPrivateNames: Map<string, number> = new Map();
constructor(raise: raiseFunction, inModule: boolean) {
this.raise = raise;

View File

@@ -84,3 +84,23 @@ export type BindingTypes =
| typeof BIND_TS_ENUM
| typeof BIND_TS_AMBIENT
| typeof BIND_TS_NAMESPACE;
// prettier-ignore
export const CLASS_ELEMENT_FLAG_STATIC = 0b1_00,
CLASS_ELEMENT_KIND_GETTER = 0b0_10,
CLASS_ELEMENT_KIND_SETTER = 0b0_01,
CLASS_ELEMENT_KIND_ACCESSOR = CLASS_ELEMENT_KIND_GETTER | CLASS_ELEMENT_KIND_SETTER;
// prettier-ignore
export const CLASS_ELEMENT_STATIC_GETTER = CLASS_ELEMENT_KIND_GETTER | CLASS_ELEMENT_FLAG_STATIC,
CLASS_ELEMENT_STATIC_SETTER = CLASS_ELEMENT_KIND_SETTER | CLASS_ELEMENT_FLAG_STATIC,
CLASS_ELEMENT_INSTANCE_GETTER = CLASS_ELEMENT_KIND_GETTER,
CLASS_ELEMENT_INSTANCE_SETTER = CLASS_ELEMENT_KIND_SETTER,
CLASS_ELEMENT_OTHER = 0;
export type ClassElementTypes =
| typeof CLASS_ELEMENT_STATIC_GETTER
| typeof CLASS_ELEMENT_STATIC_SETTER
| typeof CLASS_ELEMENT_INSTANCE_GETTER
| typeof CLASS_ELEMENT_INSTANCE_SETTER
| typeof CLASS_ELEMENT_OTHER;