Skip to content

Commit b5dfa00

Browse files
committed
Enable class fields support.
1 parent b3b830b commit b5dfa00

File tree

8 files changed

+255
-7
lines changed

8 files changed

+255
-7
lines changed

build.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ var buble = require('buble')
55

66
var HEADER = '/* Generated by `npm run build`, do not edit! */\n\n'
77

8-
function compile (name, output) { // eslint-disable-line no-unused-vars
8+
function compile (name, output, fix) {
99
console.log(name, '→', output)
1010
mkdirp.sync(path.dirname(path.join(__dirname, output)))
1111
var source = fs.readFileSync(require.resolve(name), 'utf8')
12+
if (fix) source = fix(source)
1213
var result = buble.transform(source, {
1314
transforms: {
1415
dangerousForOf: true
@@ -19,3 +20,8 @@ function compile (name, output) { // eslint-disable-line no-unused-vars
1920

2021
compile('acorn-bigint', './lib/bigint/index.js')
2122
compile('acorn-import-meta', './lib/import-meta/index.js')
23+
compile('acorn-class-fields', './lib/class-fields/index.js')
24+
compile('acorn-static-class-features', './lib/static-class-features/index.js')
25+
compile('acorn-private-class-elements', './lib/node_modules/acorn-private-class-elements/index.js', function (str) {
26+
return str.replace('class extends Parser', 'class Parser_ extends Parser')
27+
})

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ var xtend = require('xtend')
33

44
var CJSParser = acorn.Parser
55
.extend(require('./lib/bigint'))
6+
.extend(require('./lib/class-fields'))
7+
.extend(require('./lib/static-class-features'))
68
.extend(require('acorn-dynamic-import').default)
79
var ESModulesParser = CJSParser
810
.extend(require('./lib/import-meta'))

lib/bigint/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ var tt = acorn.tokTypes
77
var isIdentifierStart = acorn.isIdentifierStart
88

99
module.exports = function(Parser) {
10-
return (function (Parser) {
10+
return /*@__PURE__*/(function (Parser) {
1111
function anonymous () {
1212
Parser.apply(this, arguments);
1313
}
@@ -55,7 +55,7 @@ module.exports = function(Parser) {
5555
}
5656

5757
var str = this.input.slice(start, this.pos)
58-
var val = typeof BigInt !== "undefined" && BigInt.parseInt ? BigInt.parseInt(str) : null
58+
var val = typeof BigInt !== "undefined" ? BigInt(str) : null
5959
++this.pos
6060
return this.finishToken(tt.num, val)
6161
};

lib/class-fields/index.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/* Generated by `npm run build`, do not edit! */
2+
3+
"use strict"
4+
5+
var acorn = require("acorn")
6+
var tt = acorn.tokTypes
7+
var privateClassElements = require("acorn-private-class-elements")
8+
9+
function maybeParseFieldValue(field) {
10+
if (this.eat(tt.eq)) {
11+
var oldInFieldValue = this._inFieldValue
12+
this._inFieldValue = true
13+
field.value = this.parseExpression()
14+
this._inFieldValue = oldInFieldValue
15+
} else { field.value = null }
16+
}
17+
18+
module.exports = function(Parser) {
19+
Parser = privateClassElements(Parser)
20+
return /*@__PURE__*/(function (Parser) {
21+
function anonymous () {
22+
Parser.apply(this, arguments);
23+
}
24+
25+
if ( Parser ) anonymous.__proto__ = Parser;
26+
anonymous.prototype = Object.create( Parser && Parser.prototype );
27+
anonymous.prototype.constructor = anonymous;
28+
29+
anonymous.prototype.parseClassElement = function parseClassElement (_constructorAllowsSuper) {
30+
if (this.options.ecmaVersion >= 8 && (this.type == tt.name || this.type == this.privateNameToken || this.type == tt.bracketL || this.type == tt.string)) {
31+
var branch = this._branch()
32+
if (branch.type == tt.bracketL) {
33+
var count = 0
34+
do {
35+
if (branch.eat(tt.bracketL)) { ++count }
36+
else if (branch.eat(tt.bracketR)) { --count }
37+
else { branch.next() }
38+
} while (count > 0)
39+
} else { branch.next() }
40+
if (branch.type == tt.eq || branch.canInsertSemicolon() || branch.type == tt.semi) {
41+
var node = this.startNode()
42+
if (this.type == this.privateNameToken) {
43+
this.parsePrivateClassElementName(node)
44+
} else {
45+
this.parsePropertyName(node)
46+
}
47+
if ((node.key.type === "Identifier" && node.key.name === "constructor") ||
48+
(node.key.type === "Literal" && node.key.value === "constructor")) {
49+
this.raise(node.key.start, "Classes may not have a field called constructor")
50+
}
51+
maybeParseFieldValue.call(this, node)
52+
this.finishNode(node, "FieldDefinition")
53+
this.semicolon()
54+
return node
55+
}
56+
}
57+
58+
return Parser.prototype.parseClassElement.apply(this, arguments)
59+
};
60+
61+
// Prohibit arguments in class field initializers
62+
anonymous.prototype.parseIdent = function parseIdent (liberal, isBinding) {
63+
var ident = Parser.prototype.parseIdent.call(this, liberal, isBinding)
64+
if (this._inFieldValue && ident.name == "arguments") { this.raise(ident.start, "A class field initializer may not contain arguments") }
65+
return ident
66+
};
67+
68+
return anonymous;
69+
}(Parser))
70+
}

lib/import-meta/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ var nextTokenIsDot = function (parser) {
1414
}
1515

1616
module.exports = function(Parser) {
17-
return (function (Parser) {
17+
return /*@__PURE__*/(function (Parser) {
1818
function anonymous () {
1919
Parser.apply(this, arguments);
2020
}

lib/static-class-features/index.js

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/* Generated by `npm run build`, do not edit! */
2+
3+
"use strict"
4+
5+
var skipWhiteSpace = /(?:\s|\/\/.*|\/\*[^]*?\*\/)*/g
6+
7+
var acorn = require("acorn")
8+
var tt = acorn.tokTypes
9+
10+
function maybeParseFieldValue(field) {
11+
if (this.eat(tt.eq)) {
12+
var oldInFieldValue = this._inStaticFieldValue
13+
this._inStaticFieldValue = true
14+
field.value = this.parseExpression()
15+
this._inStaticFieldValue = oldInFieldValue
16+
} else { field.value = null }
17+
}
18+
19+
var privateClassElements = require("acorn-private-class-elements")
20+
21+
module.exports = function(Parser) {
22+
var ExtendedParser = privateClassElements(Parser)
23+
24+
return /*@__PURE__*/(function (ExtendedParser) {
25+
function anonymous () {
26+
ExtendedParser.apply(this, arguments);
27+
}
28+
29+
if ( ExtendedParser ) anonymous.__proto__ = ExtendedParser;
30+
anonymous.prototype = Object.create( ExtendedParser && ExtendedParser.prototype );
31+
anonymous.prototype.constructor = anonymous;
32+
33+
anonymous.prototype.parseClassElement = function parseClassElement (_constructorAllowsSuper) {
34+
var this$1 = this;
35+
36+
if (this.eat(tt.semi)) { return null }
37+
38+
var node = this.startNode()
39+
40+
var tryContextual = function (k, noLineBreak) {
41+
if (typeof noLineBreak == "undefined") { noLineBreak = false }
42+
var start = this$1.start, startLoc = this$1.startLoc
43+
if (!this$1.eatContextual(k)) { return false }
44+
if (this$1.type !== tt.parenL && (!noLineBreak || !this$1.canInsertSemicolon())) { return true }
45+
if (node.key) { this$1.unexpected() }
46+
node.computed = false
47+
node.key = this$1.startNodeAt(start, startLoc)
48+
node.key.name = k
49+
this$1.finishNode(node.key, "Identifier")
50+
return false
51+
}
52+
53+
node.static = tryContextual("static")
54+
if (!node.static) { return ExtendedParser.prototype.parseClassElement.apply(this, arguments) }
55+
56+
var isGenerator = this.eat(tt.star)
57+
var isAsync = false
58+
if (!isGenerator) {
59+
// Special-case for `async`, since `parseClassMember` currently looks
60+
// for `(` to determine whether `async` is a method name
61+
if (this.options.ecmaVersion >= 8 && this.isContextual("async")) {
62+
skipWhiteSpace.lastIndex = this.pos
63+
var skip = skipWhiteSpace.exec(this.input)
64+
var next = this.input.charAt(this.pos + skip[0].length)
65+
if (next === ";" || next === "=") {
66+
node.key = this.parseIdent(true)
67+
node.computed = false
68+
maybeParseFieldValue.call(this, node)
69+
this.finishNode(node, "FieldDefinition")
70+
this.semicolon()
71+
return node
72+
} else if (this.options.ecmaVersion >= 8 && tryContextual("async", true)) {
73+
isAsync = true
74+
isGenerator = this.options.ecmaVersion >= 9 && this.eat(tt.star)
75+
}
76+
} else if (tryContextual("get")) {
77+
node.kind = "get"
78+
} else if (tryContextual("set")) {
79+
node.kind = "set"
80+
}
81+
}
82+
if (this.type === this.privateNameToken) {
83+
this.parsePrivateClassElementName(node)
84+
if (this.type !== tt.parenL) {
85+
if (node.key.name === "prototype") {
86+
this.raise(node.key.start, "Classes may not have a private static property named prototype")
87+
}
88+
maybeParseFieldValue.call(this, node)
89+
this.finishNode(node, "FieldDefinition")
90+
this.semicolon()
91+
return node
92+
}
93+
} else if (!node.key) {
94+
this.parsePropertyName(node)
95+
if ((node.key.name || node.key.value) === "prototype" && !node.computed) {
96+
this.raise(node.key.start, "Classes may not have a static property named prototype")
97+
}
98+
}
99+
if (!node.kind) { node.kind = "method" }
100+
this.parseClassMethod(node, isGenerator, isAsync)
101+
if (!node.kind && (node.key.name || node.key.value) === "constructor" && !node.computed) {
102+
this.raise(node.key.start, "Classes may not have a static field named constructor")
103+
}
104+
if (node.kind === "get" && node.value.params.length !== 0) {
105+
this.raiseRecoverable(node.value.start, "getter should have no params")
106+
}
107+
if (node.kind === "set" && node.value.params.length !== 1) {
108+
this.raiseRecoverable(node.value.start, "setter should have exactly one param")
109+
}
110+
if (node.kind === "set" && node.value.params[0].type === "RestElement") {
111+
this.raiseRecoverable(node.value.params[0].start, "Setter cannot use rest params")
112+
}
113+
114+
return node
115+
116+
};
117+
118+
// Parse public static fields
119+
anonymous.prototype.parseClassMethod = function parseClassMethod (method, isGenerator, isAsync, _allowsDirectSuper) {
120+
if (isGenerator || isAsync || method.kind != "method" || !method.static || this.options.ecmaVersion < 8 || this.type == tt.parenL) {
121+
return ExtendedParser.prototype.parseClassMethod.apply(this, arguments)
122+
}
123+
maybeParseFieldValue.call(this, method)
124+
delete method.kind
125+
method = this.finishNode(method, "FieldDefinition")
126+
this.semicolon()
127+
return method
128+
};
129+
130+
// Prohibit arguments in class field initializers
131+
anonymous.prototype.parseIdent = function parseIdent (liberal, isBinding) {
132+
var ident = ExtendedParser.prototype.parseIdent.call(this, liberal, isBinding)
133+
if (this._inStaticFieldValue && ident.name == "arguments") { this.raise(ident.start, "A static class field initializer may not contain arguments") }
134+
return ident
135+
};
136+
137+
return anonymous;
138+
}(ExtendedParser))
139+
}

package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,18 @@
77
"url": "https://github.com/browserify/acorn-node/issues"
88
},
99
"dependencies": {
10-
"acorn": "^6.0.2",
10+
"acorn": "^6.1.1",
1111
"acorn-dynamic-import": "^4.0.0",
12-
"acorn-walk": "^6.1.0",
12+
"acorn-walk": "^6.1.1",
1313
"xtend": "^4.0.1"
1414
},
1515
"devDependencies": {
1616
"acorn-bigint": "^0.3.1",
17+
"acorn-class-fields": "^0.3.1",
1718
"acorn-import-meta": "^0.3.0",
18-
"buble": "^0.19.6",
19+
"acorn-private-class-elements": "^0.1.1",
20+
"acorn-static-class-features": "^0.2.0",
21+
"buble": "^0.19.7",
1922
"mkdirp": "^0.5.1",
2023
"standard": "^11.0.1",
2124
"tape": "^4.9.1"

test/index.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,34 @@ test('supports dynamic import() with sourceType: script', function (t) {
9494
t.end()
9595
})
9696

97+
test('supports class instance properties', function (t) {
98+
t.doesNotThrow(function () {
99+
acorn.parse('class X { x = y }', { sourceType: 'script' })
100+
})
101+
t.end()
102+
})
103+
104+
test('supports private class instance properties', function (t) {
105+
t.doesNotThrow(function () {
106+
acorn.parse('class X { #x = y }', { sourceType: 'script' })
107+
})
108+
t.end()
109+
})
110+
111+
test('supports class static properties', function (t) {
112+
t.doesNotThrow(function () {
113+
acorn.parse('class X { static x = y }', { sourceType: 'script' })
114+
})
115+
t.end()
116+
})
117+
118+
test('supports private class static properties', function (t) {
119+
t.doesNotThrow(function () {
120+
acorn.parse('class X { static #x = y }', { sourceType: 'script' })
121+
})
122+
t.end()
123+
})
124+
97125
test('walk supports plugin syntax', function (t) {
98126
var ast = acorn.parse(
99127
'async function* a() { try { await import(xyz); } catch { for await (x of null) {} } yield import.meta.url }',

0 commit comments

Comments
 (0)