Skip to content

Commit

Permalink
Merge pull request #15 from kumavis/static-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
goto-bus-stop authored May 29, 2020
2 parents d61ed46 + a1dfef9 commit b88000f
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 98 deletions.
2 changes: 1 addition & 1 deletion build.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ function privateClassElements (str) {

compile('acorn-bigint', './lib/bigint/index.js')
compile('acorn-numeric-separator', './lib/numeric-separator/index.js')
compile('acorn-dynamic-import', './lib/dynamic-import/index.js')
compile('acorn-import-meta', './lib/import-meta/index.js')
compile('acorn-export-ns-from', './lib/export-ns-from/index.js')
compile('acorn-class-fields', './lib/class-fields/index.js', privateClassElements)
compile('acorn-static-class-features', './lib/static-class-features/index.js', privateClassElements)
compile('acorn-private-class-elements', './lib/private-class-elements/index.js', function (str) {
return str.replace('class extends Parser', 'class Parser_ extends Parser')
.replace('new Parser', 'new Parser_')
// it also works with v7
.replace('if (acorn.version.indexOf("6.") != 0 || acorn.version.indexOf("6.0.") == 0) {', 'if (false) {')
})
2 changes: 1 addition & 1 deletion lib/private-class-elements/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ module.exports = function(Parser) {
Parser_.prototype.constructor = Parser_;

Parser_.prototype._branch = function _branch () {
this.__branch = this.__branch || new Parser({ecmaVersion: this.options.ecmaVersion}, this.input)
this.__branch = this.__branch || new Parser_({ecmaVersion: this.options.ecmaVersion}, this.input)
this.__branch.end = this.end
this.__branch.pos = this.pos
this.__branch.type = this.type
Expand Down
148 changes: 52 additions & 96 deletions lib/static-class-features/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,14 @@

"use strict"

var skipWhiteSpace = /(?:\s|\/\/.*|\/\*[^]*?\*\/)*/g

var acorn = require("acorn")
var tt = acorn.tokTypes

function maybeParseFieldValue(field) {
if (this.eat(tt.eq)) {
var oldInFieldValue = this._inStaticFieldValue
this._inStaticFieldValue = true
field.value = this.parseExpression()
this._inStaticFieldValue = oldInFieldValue
} else { field.value = null }
}

var privateClassElements = require("../private-class-elements")

module.exports = function(Parser) {
var ExtendedParser = privateClassElements(Parser)

var acorn = Parser.acorn || require("acorn")
var tt = acorn.tokTypes

return /*@__PURE__*/(function (ExtendedParser) {
function anonymous () {
ExtendedParser.apply(this, arguments);
Expand All @@ -30,107 +19,74 @@ module.exports = function(Parser) {
anonymous.prototype = Object.create( ExtendedParser && ExtendedParser.prototype );
anonymous.prototype.constructor = anonymous;

anonymous.prototype.parseClassElement = function parseClassElement (_constructorAllowsSuper) {
var this$1 = this;

if (this.eat(tt.semi)) { return null }

var node = this.startNode()
anonymous.prototype._maybeParseFieldValue = function _maybeParseFieldValue (field) {
if (this.eat(tt.eq)) {
var oldInFieldValue = this._inStaticFieldScope
this._inStaticFieldScope = this.currentThisScope()
field.value = this.parseExpression()
this._inStaticFieldScope = oldInFieldValue
} else { field.value = null }
};

var tryContextual = function (k, noLineBreak) {
if (typeof noLineBreak == "undefined") { noLineBreak = false }
var start = this$1.start, startLoc = this$1.startLoc
if (!this$1.eatContextual(k)) { return false }
if (this$1.type !== tt.parenL && (!noLineBreak || !this$1.canInsertSemicolon())) { return true }
if (node.key) { this$1.unexpected() }
node.computed = false
node.key = this$1.startNodeAt(start, startLoc)
node.key.name = k
this$1.finishNode(node.key, "Identifier")
return false
// Parse fields
anonymous.prototype.parseClassElement = function parseClassElement (_constructorAllowsSuper) {
if (this.options.ecmaVersion < 8 || !this.isContextual("static")) {
return ExtendedParser.prototype.parseClassElement.apply(this, arguments)
}

node.static = tryContextual("static")
if (!node.static) { return ExtendedParser.prototype.parseClassElement.apply(this, arguments) }

var isGenerator = this.eat(tt.star)
var isAsync = false
if (!isGenerator) {
// Special-case for `async`, since `parseClassMember` currently looks
// for `(` to determine whether `async` is a method name
if (this.options.ecmaVersion >= 8 && this.isContextual("async")) {
skipWhiteSpace.lastIndex = this.pos
var skip = skipWhiteSpace.exec(this.input)
var next = this.input.charAt(this.pos + skip[0].length)
if (next === ";" || next === "=") {
node.key = this.parseIdent(true)
node.computed = false
maybeParseFieldValue.call(this, node)
this.finishNode(node, "FieldDefinition")
this.semicolon()
return node
} else if (this.options.ecmaVersion >= 8 && tryContextual("async", true)) {
isAsync = true
isGenerator = this.options.ecmaVersion >= 9 && this.eat(tt.star)
}
} else if (tryContextual("get")) {
node.kind = "get"
} else if (tryContextual("set")) {
node.kind = "set"
}
var branch = this._branch()
branch.next()
if ([tt.name, tt.bracketL, tt.string, tt.num, this.privateNameToken].indexOf(branch.type) == -1) {
return ExtendedParser.prototype.parseClassElement.apply(this, arguments)
}
if (branch.type == tt.bracketL) {
var count = 0
do {
if (branch.eat(tt.bracketL)) { ++count }
else if (branch.eat(tt.bracketR)) { --count }
else { branch.next() }
} while (count > 0)
} else { branch.next() }
if (branch.type != tt.eq && !branch.canInsertSemicolon() && branch.type != tt.semi) {
return ExtendedParser.prototype.parseClassElement.apply(this, arguments)
}
if (this.type === this.privateNameToken) {

var node = this.startNode()
node.static = this.eatContextual("static")
if (this.type == this.privateNameToken) {
this.parsePrivateClassElementName(node)
if (this.type !== tt.parenL) {
if (node.key.name === "prototype") {
this.raise(node.key.start, "Classes may not have a private static property named prototype")
}
maybeParseFieldValue.call(this, node)
this.finishNode(node, "FieldDefinition")
this.semicolon()
return node
}
} else if (!node.key) {
} else {
this.parsePropertyName(node)
if ((node.key.name || node.key.value) === "prototype" && !node.computed) {
this.raise(node.key.start, "Classes may not have a static property named prototype")
}
}
if (!node.kind) { node.kind = "method" }
this.parseClassMethod(node, isGenerator, isAsync)
if (!node.kind && (node.key.name || node.key.value) === "constructor" && !node.computed) {
this.raise(node.key.start, "Classes may not have a static field named constructor")
}
if (node.kind === "get" && node.value.params.length !== 0) {
this.raiseRecoverable(node.value.start, "getter should have no params")
if ((node.key.type === "Identifier" && node.key.name === "constructor") ||
(node.key.type === "Literal" && !node.computed && node.key.value === "constructor")) {
this.raise(node.key.start, "Classes may not have a field called constructor")
}
if (node.kind === "set" && node.value.params.length !== 1) {
this.raiseRecoverable(node.value.start, "setter should have exactly one param")
}
if (node.kind === "set" && node.value.params[0].type === "RestElement") {
this.raiseRecoverable(node.value.params[0].start, "Setter cannot use rest params")
if ((node.key.name || node.key.value) === "prototype" && !node.computed) {
this.raise(node.key.start, "Classes may not have a static property named prototype")
}

this._maybeParseFieldValue(node)
this.finishNode(node, "FieldDefinition")
this.semicolon()
return node

};

// Parse public static fields
anonymous.prototype.parseClassMethod = function parseClassMethod (method, isGenerator, isAsync, _allowsDirectSuper) {
if (isGenerator || isAsync || method.kind != "method" || !method.static || this.options.ecmaVersion < 8 || this.type == tt.parenL) {
return ExtendedParser.prototype.parseClassMethod.apply(this, arguments)
// Parse private static methods
anonymous.prototype.parsePropertyName = function parsePropertyName (prop) {
if (prop.static && this.type == this.privateNameToken) {
this.parsePrivateClassElementName(prop)
} else {
ExtendedParser.prototype.parsePropertyName.call(this, prop)
}
maybeParseFieldValue.call(this, method)
delete method.kind
method = this.finishNode(method, "FieldDefinition")
this.semicolon()
return method
};

// Prohibit arguments in class field initializers
anonymous.prototype.parseIdent = function parseIdent (liberal, isBinding) {
var ident = ExtendedParser.prototype.parseIdent.call(this, liberal, isBinding)
if (this._inStaticFieldValue && ident.name == "arguments") { this.raise(ident.start, "A static class field initializer may not contain arguments") }
if (this._inStaticFieldScope && this.currentThisScope() === this._inStaticFieldScope && ident.name == "arguments") {
this.raise(ident.start, "A static class field initializer may not contain arguments")
}
return ident
};

Expand Down
7 changes: 7 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ test('supports class static properties', function (t) {
t.end()
})

test('supports class function named static', function (t) {
t.doesNotThrow(function () {
acorn.parse('class X { static () {} }', { sourceType: 'script' })
})
t.end()
})

test('supports private class static properties', function (t) {
t.doesNotThrow(function () {
acorn.parse('class X { static #x = y }', { sourceType: 'script' })
Expand Down

0 comments on commit b88000f

Please sign in to comment.