Skip to content

Commit

Permalink
Implement early errors for super use
Browse files Browse the repository at this point in the history
  • Loading branch information
TimothyGu authored and marijnh committed Oct 6, 2018
1 parent 71ea246 commit 94e8ad6
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 270 deletions.
14 changes: 8 additions & 6 deletions acorn/src/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {types as tt} from "./tokentype"
import {Parser} from "./state"
import {DestructuringErrors} from "./parseutil"
import {lineBreak} from "./whitespace"
import {functionFlags, SCOPE_ARROW, BIND_OUTSIDE, BIND_VAR} from "./scopeflags"
import {functionFlags, SCOPE_ARROW, SCOPE_SUPER, SCOPE_DIRECT_SUPER, BIND_OUTSIDE, BIND_VAR} from "./scopeflags"

const pp = Parser.prototype

Expand Down Expand Up @@ -115,7 +115,7 @@ pp.parseMaybeAssign = function(noIn, refDestructuringErrors, afterLeftParse) {
if (refDestructuringErrors) {
oldParenAssign = refDestructuringErrors.parenthesizedAssign
oldTrailingComma = refDestructuringErrors.trailingComma
oldShorthandAssign = refDestructuringErrors.shorthandAssign;
oldShorthandAssign = refDestructuringErrors.shorthandAssign
refDestructuringErrors.parenthesizedAssign = refDestructuringErrors.trailingComma = refDestructuringErrors.shorthandAssign = -1
} else {
refDestructuringErrors = new DestructuringErrors
Expand Down Expand Up @@ -307,10 +307,12 @@ pp.parseExprAtom = function(refDestructuringErrors) {
let node, canBeArrow = this.potentialArrowAt === this.start
switch (this.type) {
case tt._super:
if (!this.inFunction)
this.raise(this.start, "'super' outside of function or class")
if (!this.allowSuper)
this.raise(this.start, "'super' keyword outside a method")
node = this.startNode()
this.next()
if (this.type === tt.parenL && !this.allowDirectSuper)
this.raise(node.start, "super() call outside constructor of a subclass")
// The `super` keyword can appear at below:
// SuperProperty:
// super [ Expression ]
Expand Down Expand Up @@ -696,7 +698,7 @@ pp.initFunction = function(node) {

// Parse object or class method.

pp.parseMethod = function(isGenerator, isAsync) {
pp.parseMethod = function(isGenerator, isAsync, allowDirectSuper) {
let node = this.startNode(), oldYieldPos = this.yieldPos, oldAwaitPos = this.awaitPos

this.initFunction(node)
Expand All @@ -707,7 +709,7 @@ pp.parseMethod = function(isGenerator, isAsync) {

this.yieldPos = 0
this.awaitPos = 0
this.enterScope(functionFlags(isAsync, node.generator))
this.enterScope(functionFlags(isAsync, node.generator) | SCOPE_SUPER | (allowDirectSuper ? SCOPE_DIRECT_SUPER : 0))

this.expect(tt.parenL)
node.params = this.parseBindingList(tt.parenR, false, this.options.ecmaVersion >= 8)
Expand Down
12 changes: 7 additions & 5 deletions acorn/src/scope.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Parser} from "./state"
import {SCOPE_VAR, SCOPE_FUNCTION, SCOPE_ARROW, SCOPE_SIMPLE_CATCH, BIND_LEXICAL, BIND_SIMPLE_CATCH, BIND_FUNCTION} from "./scopeflags"
import {SCOPE_VAR, SCOPE_ARROW, SCOPE_SIMPLE_CATCH, BIND_LEXICAL, BIND_SIMPLE_CATCH, BIND_FUNCTION} from "./scopeflags"

const pp = Parser.prototype

Expand Down Expand Up @@ -58,8 +58,10 @@ pp.currentVarScope = function() {
}
}

pp.inNonArrowFunction = function() {
for (let i = this.scopeStack.length - 1; i >= 0; i--)
if (this.scopeStack[i].flags & SCOPE_FUNCTION && !(this.scopeStack[i].flags & SCOPE_ARROW)) return true
return false
// Could be useful for `this`, `new.target`, `super()`, `super.property`, and `super[property]`.
pp.currentThisScope = function() {
for (let i = this.scopeStack.length - 1;; i--) {
let scope = this.scopeStack[i]
if (scope.flags & SCOPE_VAR && !(scope.flags & SCOPE_ARROW)) return scope
}
}
4 changes: 3 additions & 1 deletion acorn/src/scopeflags.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ export const
SCOPE_ASYNC = 4,
SCOPE_GENERATOR = 8,
SCOPE_ARROW = 16,
SCOPE_SIMPLE_CATCH = 32
SCOPE_SIMPLE_CATCH = 32,
SCOPE_SUPER = 64,
SCOPE_DIRECT_SUPER = 128

export function functionFlags(async, generator) {
return SCOPE_FUNCTION | (async ? SCOPE_ASYNC : 0) | (generator ? SCOPE_GENERATOR : 0)
Expand Down
7 changes: 6 additions & 1 deletion acorn/src/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {reservedWords, keywords} from "./identifier"
import {types as tt} from "./tokentype"
import {lineBreak} from "./whitespace"
import {getOptions} from "./options"
import {SCOPE_TOP, SCOPE_FUNCTION, SCOPE_ASYNC, SCOPE_GENERATOR} from "./scopeflags"
import {SCOPE_TOP, SCOPE_FUNCTION, SCOPE_ASYNC, SCOPE_GENERATOR, SCOPE_SUPER, SCOPE_DIRECT_SUPER} from "./scopeflags"

function keywordRegexp(words) {
return new RegExp("^(?:" + words.replace(/ /g, "|") + ")$")
Expand Down Expand Up @@ -96,6 +96,11 @@ export class Parser {
get inFunction() { return (this.currentVarScope().flags & SCOPE_FUNCTION) > 0 }
get inGenerator() { return (this.currentVarScope().flags & SCOPE_GENERATOR) > 0 }
get inAsync() { return (this.currentVarScope().flags & SCOPE_ASYNC) > 0 }
get allowSuper() { return (this.currentThisScope().flags & SCOPE_SUPER) > 0 }
get allowDirectSuper() { return (this.currentThisScope().flags & SCOPE_DIRECT_SUPER) > 0 }

// Switch to a getter for 7.0.0.
inNonArrowFunction() { return (this.currentThisScope().flags & SCOPE_FUNCTION) > 0 }

static extend(...plugins) {
let cls = this
Expand Down
12 changes: 7 additions & 5 deletions acorn/src/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ pp.parseClass = function(node, isStatement) {
classBody.body = []
this.expect(tt.braceL)
while (!this.eat(tt.braceR)) {
const element = this.parseClassElement()
const element = this.parseClassElement(node.superClass !== null)
if (element) {
classBody.body.push(element)
if (element.type === "MethodDefinition" && element.kind === "constructor") {
Expand All @@ -548,7 +548,7 @@ pp.parseClass = function(node, isStatement) {
return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression")
}

pp.parseClassElement = function() {
pp.parseClassElement = function(constructorAllowsSuper) {
if (this.eat(tt.semi)) return null

let method = this.startNode()
Expand Down Expand Up @@ -580,16 +580,18 @@ pp.parseClassElement = function() {
}
if (!method.key) this.parsePropertyName(method)
let {key} = method
let allowsDirectSuper = false
if (!method.computed && !method.static && (key.type === "Identifier" && key.name === "constructor" ||
key.type === "Literal" && key.value === "constructor")) {
if (method.kind !== "method") this.raise(key.start, "Constructor can't have get/set modifier")
if (isGenerator) this.raise(key.start, "Constructor can't be a generator")
if (isAsync) this.raise(key.start, "Constructor can't be an async method")
method.kind = "constructor"
allowsDirectSuper = constructorAllowsSuper
} else if (method.static && key.type === "Identifier" && key.name === "prototype") {
this.raise(key.start, "Classes may not have a static property named prototype")
}
this.parseClassMethod(method, isGenerator, isAsync)
this.parseClassMethod(method, isGenerator, isAsync, allowsDirectSuper)
if (method.kind === "get" && method.value.params.length !== 0)
this.raiseRecoverable(method.value.start, "getter should have no params")
if (method.kind === "set" && method.value.params.length !== 1)
Expand All @@ -599,8 +601,8 @@ pp.parseClassElement = function() {
return method
}

pp.parseClassMethod = function(method, isGenerator, isAsync) {
method.value = this.parseMethod(isGenerator, isAsync)
pp.parseClassMethod = function(method, isGenerator, isAsync, allowsDirectSuper) {
method.value = this.parseMethod(isGenerator, isAsync, allowsDirectSuper)
return this.finishNode(method, "MethodDefinition")
}

Expand Down
Loading

0 comments on commit 94e8ad6

Please sign in to comment.