366 lines
9.6 KiB
JavaScript
366 lines
9.6 KiB
JavaScript
"use strict"
|
|
|
|
const assert = require("assert")
|
|
const acorn = require("acorn"), classFields = require("..")
|
|
const Parser = acorn.Parser.extend(classFields)
|
|
|
|
function test(text, expectedResult, additionalOptions) {
|
|
it(text, function () {
|
|
const result = Parser.parse(text, Object.assign({ ecmaVersion: 9 }, additionalOptions))
|
|
if (expectedResult) {
|
|
assert.deepStrictEqual(result.body[0], expectedResult)
|
|
}
|
|
})
|
|
}
|
|
function testFail(text, expectedResult, additionalOptions) {
|
|
it(text, function () {
|
|
let msg = null
|
|
try {
|
|
Parser.parse(text, Object.assign({ ecmaVersion: 9 }, additionalOptions))
|
|
} catch (e) {
|
|
msg = e.message
|
|
}
|
|
assert.strictEqual(msg, expectedResult)
|
|
})
|
|
}
|
|
const newNode = (start, props) => Object.assign(new acorn.Node({options: {}}, start), props)
|
|
|
|
describe("acorn-class-fields", function () {
|
|
test(`class P extends Q {
|
|
x = super.x
|
|
}`)
|
|
|
|
test(`class Counter extends HTMLElement {
|
|
x = 0;
|
|
|
|
clicked() {
|
|
this.x++;
|
|
}
|
|
|
|
render() {
|
|
return this.x.toString();
|
|
}
|
|
}`)
|
|
|
|
test(`
|
|
class AsyncIterPipe{
|
|
static get [ Symbol.species](){
|
|
return Promise
|
|
}
|
|
static get Closing(){
|
|
return Closing
|
|
}
|
|
static get controllerSignals(){ return controllerSignals}
|
|
static get listenerBinding(){ return listenerBinding}
|
|
|
|
// state
|
|
done= false
|
|
}
|
|
`)
|
|
|
|
test(`class Counter extends HTMLElement {
|
|
#x = 0;
|
|
|
|
clicked() {
|
|
this.#x++;
|
|
}
|
|
|
|
render() {
|
|
return this.#x.toString();
|
|
}
|
|
}`)
|
|
test("class A { a = this.#a; #a = 4 }")
|
|
|
|
test("class A { 5 = 5; #5 = 5 }")
|
|
test("class A { delete = 5; #delete = 5 }")
|
|
|
|
testFail("class A { #a; f() { delete this.#a } }", "Private elements may not be deleted (1:20)")
|
|
testFail("class A { #a; #a }", "Duplicate private element (1:14)")
|
|
testFail("class A { a = this.#a }", "Usage of undeclared private name (1:19)")
|
|
testFail("class A { a = this.#a; b = this.#b }", "Usage of undeclared private name (1:19)")
|
|
testFail("class A { constructor = 4 }", "Classes may not have a field called constructor (1:10)")
|
|
testFail("class A { #constructor = 4 }", "Classes may not have a private element named constructor (1:10)")
|
|
testFail("class A { a = () => arguments }", "A class field initializer may not contain arguments (1:20)")
|
|
testFail("class A { a = () => super() }", "super() call outside constructor of a subclass (1:20)")
|
|
testFail("class A { # a }", "Unexpected token (1:10)")
|
|
testFail("class A { #a; a() { this.# a } }", "Unexpected token (1:27)")
|
|
|
|
const classes = [
|
|
{ text: "class A { %s }", ast: getBody => {
|
|
const body = getBody(10)
|
|
return newNode(0, {
|
|
type: "ClassDeclaration",
|
|
end: body.end + 2,
|
|
id: newNode(6, {
|
|
type: "Identifier",
|
|
end: 7,
|
|
name: "A"
|
|
}),
|
|
superClass: null,
|
|
body: newNode(8, {
|
|
type: "ClassBody",
|
|
end: body.end + 2,
|
|
body: [body]
|
|
})
|
|
})
|
|
} },
|
|
{ text: "class A { %s; }", ast: getBody => {
|
|
const body = getBody(10)
|
|
return newNode(0, {
|
|
type: "ClassDeclaration",
|
|
end: body.end + 3,
|
|
id: newNode(6, {
|
|
type: "Identifier",
|
|
end: 7,
|
|
name: "A"
|
|
}),
|
|
superClass: null,
|
|
body: newNode(8, {
|
|
type: "ClassBody",
|
|
end: body.end + 3,
|
|
body: [body]
|
|
})
|
|
})
|
|
} },
|
|
{ text: "class A { %s; #y }", ast: getBody => {
|
|
const body = getBody(10)
|
|
return newNode(0, {
|
|
type: "ClassDeclaration",
|
|
end: body.end + 6,
|
|
id: newNode(6, {
|
|
type: "Identifier",
|
|
end: 7,
|
|
name: "A"
|
|
}),
|
|
superClass: null,
|
|
body: newNode(8, {
|
|
type: "ClassBody",
|
|
end: body.end + 6,
|
|
body: [body, newNode(body.end + 2, {
|
|
type: "FieldDefinition",
|
|
end: body.end + 4,
|
|
key: newNode(body.end + 2, {
|
|
type: "PrivateName",
|
|
end: body.end + 4,
|
|
name: "y"
|
|
}),
|
|
value: null,
|
|
computed: false
|
|
}) ]
|
|
})
|
|
})
|
|
} },
|
|
{ text: "class A { %s;a() {} }", ast: getBody => {
|
|
const body = getBody(10)
|
|
return newNode(0, {
|
|
type: "ClassDeclaration",
|
|
end: body.end + 9,
|
|
id: newNode(6, {
|
|
type: "Identifier",
|
|
end: 7,
|
|
name: "A"
|
|
}),
|
|
superClass: null,
|
|
body: newNode(8, {
|
|
type: "ClassBody",
|
|
end: body.end + 9,
|
|
body: [ body, newNode(body.end + 1, {
|
|
type: "MethodDefinition",
|
|
end: body.end + 7,
|
|
kind: "method",
|
|
static: false,
|
|
computed: false,
|
|
key: newNode(body.end + 1, {
|
|
type: "Identifier",
|
|
end: body.end + 2,
|
|
name: "a"
|
|
}),
|
|
value: newNode(body.end + 2, {
|
|
type: "FunctionExpression",
|
|
end: body.end + 7,
|
|
id: null,
|
|
generator: false,
|
|
expression: false,
|
|
async: false,
|
|
params: [],
|
|
body: newNode(body.end + 5, {
|
|
type: "BlockStatement",
|
|
end: body.end + 7,
|
|
body: []
|
|
})
|
|
})
|
|
}) ]
|
|
})
|
|
})
|
|
} },
|
|
{ text: "class A { %s\na() {} }", ast: getBody => {
|
|
const body = getBody(10)
|
|
return newNode(0, {
|
|
type: "ClassDeclaration",
|
|
end: body.end + 9,
|
|
id: newNode(6, {
|
|
type: "Identifier",
|
|
end: 7,
|
|
name: "A"
|
|
}),
|
|
superClass: null,
|
|
body: newNode(8, {
|
|
type: "ClassBody",
|
|
end: body.end + 9,
|
|
body: [
|
|
body,
|
|
newNode(body.end + 1, {
|
|
type: "MethodDefinition",
|
|
end: body.end + 7,
|
|
kind: "method",
|
|
static: false,
|
|
computed: false,
|
|
key: newNode(body.end + 1, {
|
|
type: "Identifier",
|
|
end: body.end + 2,
|
|
name: "a"
|
|
}),
|
|
value: newNode(body.end + 2, {
|
|
type: "FunctionExpression",
|
|
end: body.end + 7,
|
|
id: null,
|
|
generator: false,
|
|
expression: false,
|
|
async: false,
|
|
params: [],
|
|
body: newNode(body.end + 5, {
|
|
type: "BlockStatement",
|
|
end: body.end + 7,
|
|
body: []
|
|
})
|
|
})
|
|
})
|
|
]
|
|
})
|
|
})
|
|
} },
|
|
];
|
|
|
|
[
|
|
{ body: "x", passes: true, ast: start => newNode(start, {
|
|
type: "FieldDefinition",
|
|
end: start + 1,
|
|
key: newNode(start, {
|
|
type: "Identifier",
|
|
end: start + 1,
|
|
name: "x"
|
|
}),
|
|
value: null,
|
|
computed: false
|
|
}) },
|
|
{ body: "x = 0", passes: true, ast: start => newNode(start, {
|
|
type: "FieldDefinition",
|
|
end: start + 5,
|
|
key: newNode(start, {
|
|
type: "Identifier",
|
|
end: start + 1,
|
|
name: "x"
|
|
}),
|
|
value: newNode(start + 4, {
|
|
type: "Literal",
|
|
end: start + 5,
|
|
value: 0,
|
|
raw: "0"
|
|
}),
|
|
computed: false
|
|
}) },
|
|
{ body: "[x]", passes: true, ast: start => newNode(start, {
|
|
type: "FieldDefinition",
|
|
end: start + 3,
|
|
computed: true,
|
|
key: newNode(start + 1, {
|
|
type: "Identifier",
|
|
end: start + 2,
|
|
name: "x"
|
|
}),
|
|
value: null
|
|
}) },
|
|
{ body: "[x] = 0", passes: true, ast: start => newNode(start, {
|
|
type: "FieldDefinition",
|
|
end: start + 7,
|
|
computed: true,
|
|
key: newNode(start + 1, {
|
|
type: "Identifier",
|
|
end: start + 2,
|
|
name: "x"
|
|
}),
|
|
value: newNode(start + 6, {
|
|
type: "Literal",
|
|
end: start + 7,
|
|
value: 0,
|
|
raw: "0"
|
|
})
|
|
}) },
|
|
{ body: "#x", passes: true, ast: start => newNode(start, {
|
|
type: "FieldDefinition",
|
|
end: start + 2,
|
|
computed: false,
|
|
key: newNode(start, {
|
|
type: "PrivateName",
|
|
end: start + 2,
|
|
name: "x"
|
|
}),
|
|
value: null,
|
|
}) },
|
|
{ body: "#x = 0", passes: true, ast: start => newNode(start, {
|
|
type: "FieldDefinition",
|
|
end: start + 6,
|
|
computed: false,
|
|
key: newNode(start, {
|
|
type: "PrivateName",
|
|
end: start + 2,
|
|
name: "x"
|
|
}),
|
|
value: newNode(start + 5, {
|
|
type: "Literal",
|
|
end: start + 6,
|
|
value: 0,
|
|
raw: "0"
|
|
})
|
|
}) },
|
|
|
|
{ body: "async", passes: true, ast: start => newNode(start, {
|
|
type: "FieldDefinition",
|
|
end: start + 5,
|
|
key: newNode(start, {
|
|
type: "Identifier",
|
|
end: start + 5,
|
|
name: "async"
|
|
}),
|
|
value: null,
|
|
computed: false
|
|
}) },
|
|
|
|
{ body: "async = 5", passes: true, ast: start => newNode(start, {
|
|
type: "FieldDefinition",
|
|
end: start + 9,
|
|
key: newNode(start, {
|
|
type: "Identifier",
|
|
end: start + 5,
|
|
name: "async"
|
|
}),
|
|
value: newNode(start + 8, {
|
|
type: "Literal",
|
|
end: start + 9,
|
|
raw: "5",
|
|
value: 5
|
|
}),
|
|
computed: false
|
|
}) },
|
|
].forEach(bodyInput => {
|
|
const body = bodyInput.body, passes = bodyInput.passes, bodyAst = bodyInput.ast
|
|
classes.forEach(input => {
|
|
const text = input.text, options = input.options || {}, ast = input.ast;
|
|
(passes ? test : testFail)(text.replace("%s", body), ast(bodyAst), options)
|
|
})
|
|
})
|
|
|
|
testFail("class C { \\u0061sync m(){} };", "Unexpected token (1:21)")
|
|
test("class A extends B { constructor() { super() } }")
|
|
})
|