1 | "use strict"
|
2 |
|
3 | const assert = require("assert")
|
4 | const acorn = require("acorn")
|
5 | const Parser = acorn.Parser.extend(require(".."))
|
6 |
|
7 | function test(text, expectedResult, additionalOptions) {
|
8 | it(text, function () {
|
9 | const result = Parser.parse(text, Object.assign({ ecmaVersion: 10 }, additionalOptions))
|
10 | if (expectedResult) {
|
11 | assert.deepStrictEqual(result.body[0], expectedResult)
|
12 | }
|
13 | })
|
14 | }
|
15 | function testFail(text, expectedResult, additionalOptions) {
|
16 | it(text, function () {
|
17 | let failed = false
|
18 | try {
|
19 | Parser.parse(text, Object.assign({ ecmaVersion: 10 }, additionalOptions))
|
20 | } catch (e) {
|
21 | assert.strictEqual(e.message, expectedResult)
|
22 | failed = true
|
23 | }
|
24 | assert(failed)
|
25 | })
|
26 | }
|
27 | const newNode = (start, props) => Object.assign(new acorn.Node({options: {}}, start), props)
|
28 |
|
29 | describe("acorn-static-class-features", function () {
|
30 | test(`class CustomDate {
|
31 | // ...
|
32 | static epoch = new CustomDate(0);
|
33 | }`)
|
34 |
|
35 | testFail("class A { static #a; f() { delete A.#a } }", "Private elements may not be deleted (1:27)")
|
36 | testFail("class A { static #a; static #a }", "Duplicate private element (1:21)")
|
37 | testFail("class A { static a = A.#a }", "Usage of undeclared private name (1:23)")
|
38 | testFail("class A { static a = () => arguments }", "A static class field initializer may not contain arguments (1:27)")
|
39 | testFail("class A { static a = () => super() }", "super() call outside constructor of a subclass (1:27)")
|
40 | testFail("class A { static # a }", "Unexpected token (1:19)")
|
41 | testFail("class A { static #a; a() { A.# a } }", "Unexpected token (1:31)")
|
42 | test(`class C {
|
43 | static async * #method() {
|
44 | }
|
45 | }`)
|
46 | test(`class C {
|
47 | static a = () => {
|
48 | function p () {
|
49 | console.log(arguments);
|
50 | }
|
51 | }
|
52 | }`)
|
53 |
|
54 | const classes = [
|
55 | { text: "class A { %s }", ast: getBody => {
|
56 | const body = getBody(10)
|
57 | return newNode(0, {
|
58 | type: "ClassDeclaration",
|
59 | end: body.end + 2,
|
60 | id: newNode(6, {
|
61 | type: "Identifier",
|
62 | end: 7,
|
63 | name: "A"
|
64 | }),
|
65 | superClass: null,
|
66 | body: newNode(8, {
|
67 | type: "ClassBody",
|
68 | end: body.end + 2,
|
69 | body: [body]
|
70 | })
|
71 | })
|
72 | } },
|
73 | { text: "class A { %s; }", ast: getBody => {
|
74 | const body = getBody(10)
|
75 | return newNode(0, {
|
76 | type: "ClassDeclaration",
|
77 | end: body.end + 3,
|
78 | id: newNode(6, {
|
79 | type: "Identifier",
|
80 | end: 7,
|
81 | name: "A"
|
82 | }),
|
83 | superClass: null,
|
84 | body: newNode(8, {
|
85 | type: "ClassBody",
|
86 | end: body.end + 3,
|
87 | body: [body]
|
88 | })
|
89 | })
|
90 | } },
|
91 | { text: "class A { %s; static #y }", ast: getBody => {
|
92 | const body = getBody(10)
|
93 | return newNode(0, {
|
94 | type: "ClassDeclaration",
|
95 | end: body.end + 13,
|
96 | id: newNode(6, {
|
97 | type: "Identifier",
|
98 | end: 7,
|
99 | name: "A"
|
100 | }),
|
101 | superClass: null,
|
102 | body: newNode(8, {
|
103 | type: "ClassBody",
|
104 | end: body.end + 13,
|
105 | body: [body, newNode(body.end + 2, {
|
106 | type: "PropertyDefinition",
|
107 | end: body.end + 11,
|
108 | key: newNode(body.end + 9, {
|
109 | type: "PrivateIdentifier",
|
110 | end: body.end + 11,
|
111 | name: "y"
|
112 | }),
|
113 | value: null,
|
114 | computed: false,
|
115 | static: true
|
116 | }) ]
|
117 | })
|
118 | })
|
119 | } },
|
120 | { text: "class A { %s;a() {} }", ast: getBody => {
|
121 | const body = getBody(10)
|
122 | return newNode(0, {
|
123 | type: "ClassDeclaration",
|
124 | end: body.end + 9,
|
125 | id: newNode(6, {
|
126 | type: "Identifier",
|
127 | end: 7,
|
128 | name: "A"
|
129 | }),
|
130 | superClass: null,
|
131 | body: newNode(8, {
|
132 | type: "ClassBody",
|
133 | end: body.end + 9,
|
134 | body: [ body, newNode(body.end + 1, {
|
135 | type: "MethodDefinition",
|
136 | end: body.end + 7,
|
137 | kind: "method",
|
138 | static: false,
|
139 | computed: false,
|
140 | key: newNode(body.end + 1, {
|
141 | type: "Identifier",
|
142 | end: body.end + 2,
|
143 | name: "a"
|
144 | }),
|
145 | value: newNode(body.end + 2, {
|
146 | type: "FunctionExpression",
|
147 | end: body.end + 7,
|
148 | id: null,
|
149 | generator: false,
|
150 | expression: false,
|
151 | async: false,
|
152 | params: [],
|
153 | body: newNode(body.end + 5, {
|
154 | type: "BlockStatement",
|
155 | end: body.end + 7,
|
156 | body: []
|
157 | })
|
158 | })
|
159 | }) ]
|
160 | })
|
161 | })
|
162 | } },
|
163 | { text: "class A { %s\na() {} }", ast: getBody => {
|
164 | const body = getBody(10)
|
165 | return newNode(0, {
|
166 | type: "ClassDeclaration",
|
167 | end: body.end + 9,
|
168 | id: newNode(6, {
|
169 | type: "Identifier",
|
170 | end: 7,
|
171 | name: "A"
|
172 | }),
|
173 | superClass: null,
|
174 | body: newNode(8, {
|
175 | type: "ClassBody",
|
176 | end: body.end + 9,
|
177 | body: [
|
178 | body,
|
179 | newNode(body.end + 1, {
|
180 | type: "MethodDefinition",
|
181 | end: body.end + 7,
|
182 | kind: "method",
|
183 | static: false,
|
184 | computed: false,
|
185 | key: newNode(body.end + 1, {
|
186 | type: "Identifier",
|
187 | end: body.end + 2,
|
188 | name: "a"
|
189 | }),
|
190 | value: newNode(body.end + 2, {
|
191 | type: "FunctionExpression",
|
192 | end: body.end + 7,
|
193 | id: null,
|
194 | generator: false,
|
195 | expression: false,
|
196 | async: false,
|
197 | params: [],
|
198 | body: newNode(body.end + 5, {
|
199 | type: "BlockStatement",
|
200 | end: body.end + 7,
|
201 | body: []
|
202 | })
|
203 | })
|
204 | })
|
205 | ]
|
206 | })
|
207 | })
|
208 | } },
|
209 | ]
|
210 |
|
211 | ;[
|
212 | { body: "static x", passes: true, ast: start => newNode(start, {
|
213 | type: "PropertyDefinition",
|
214 | end: start + 8,
|
215 | key: newNode(start + 7, {
|
216 | type: "Identifier",
|
217 | end: start + 8,
|
218 | name: "x"
|
219 | }),
|
220 | value: null,
|
221 | computed: false,
|
222 | static: true
|
223 | }) },
|
224 | { body: "static x = 0", passes: true, ast: start => newNode(start, {
|
225 | type: "PropertyDefinition",
|
226 | end: start + 12,
|
227 | key: newNode(start + 7, {
|
228 | type: "Identifier",
|
229 | end: start + 8,
|
230 | name: "x"
|
231 | }),
|
232 | value: newNode(start + 11, {
|
233 | type: "Literal",
|
234 | end: start + 12,
|
235 | value: 0,
|
236 | raw: "0"
|
237 | }),
|
238 | computed: false,
|
239 | static: true
|
240 | }) },
|
241 | { body: "static [x]", passes: true, ast: start => newNode(start, {
|
242 | type: "PropertyDefinition",
|
243 | end: start + 10,
|
244 | computed: true,
|
245 | key: newNode(start + 8, {
|
246 | type: "Identifier",
|
247 | end: start + 9,
|
248 | name: "x"
|
249 | }),
|
250 | value: null,
|
251 | static: true
|
252 | }) },
|
253 | { body: "static [x] = 0", passes: true, ast: start => newNode(start, {
|
254 | type: "PropertyDefinition",
|
255 | end: start + 14,
|
256 | computed: true,
|
257 | key: newNode(start + 8, {
|
258 | type: "Identifier",
|
259 | end: start + 9,
|
260 | name: "x"
|
261 | }),
|
262 | value: newNode(start + 13, {
|
263 | type: "Literal",
|
264 | end: start + 14,
|
265 | value: 0,
|
266 | raw: "0"
|
267 | }),
|
268 | static: true
|
269 | }) },
|
270 | { body: "static #x", passes: true, ast: start => newNode(start, {
|
271 | type: "PropertyDefinition",
|
272 | end: start + 9,
|
273 | computed: false,
|
274 | key: newNode(start + 7, {
|
275 | type: "PrivateIdentifier",
|
276 | end: start + 9,
|
277 | name: "x"
|
278 | }),
|
279 | value: null,
|
280 | static: true
|
281 | }) },
|
282 | { body: "static #x = 0", passes: true, ast: start => newNode(start, {
|
283 | type: "PropertyDefinition",
|
284 | end: start + 13,
|
285 | computed: false,
|
286 | key: newNode(start + 7, {
|
287 | type: "PrivateIdentifier",
|
288 | end: start + 9,
|
289 | name: "x"
|
290 | }),
|
291 | value: newNode(start + 12, {
|
292 | type: "Literal",
|
293 | end: start + 13,
|
294 | value: 0,
|
295 | raw: "0"
|
296 | }),
|
297 | static: true
|
298 | }) },
|
299 |
|
300 | { body: "static async", passes: true, ast: start => newNode(start, {
|
301 | type: "PropertyDefinition",
|
302 | end: start + 12,
|
303 | key: newNode(start + 7, {
|
304 | type: "Identifier",
|
305 | end: start + 12,
|
306 | name: "async"
|
307 | }),
|
308 | value: null,
|
309 | computed: false,
|
310 | static: true
|
311 | }) },
|
312 |
|
313 | { body: "static async = 5", passes: true, ast: start => newNode(start, {
|
314 | type: "PropertyDefinition",
|
315 | end: start + 16,
|
316 | key: newNode(start + 7, {
|
317 | type: "Identifier",
|
318 | end: start + 12,
|
319 | name: "async"
|
320 | }),
|
321 | value: newNode(start + 15, {
|
322 | type: "Literal",
|
323 | end: start + 16,
|
324 | raw: "5",
|
325 | value: 5
|
326 | }),
|
327 | computed: false,
|
328 | static: true
|
329 | }) },
|
330 | ].forEach(bodyInput => {
|
331 | const body = bodyInput.body, passes = bodyInput.passes, bodyAst = bodyInput.ast
|
332 | classes.forEach(input => {
|
333 | const text = input.text, options = input.options || {}, ast = input.ast
|
334 | ;(passes ? test : testFail)(text.replace("%s", body), ast(bodyAst), options)
|
335 | })
|
336 | })
|
337 |
|
338 |
|
339 | test("class A { a() { A.#a }; static #a() {} }")
|
340 |
|
341 | testFail("class A { static #a() {}; f() { delete A.#a } }", "Private elements may not be deleted (1:32)")
|
342 | testFail("class A { static #a() {}; static #a() {} }", "Duplicate private element (1:26)")
|
343 | test("class A { static get #a() {}; static set #a(newA) {} }")
|
344 | testFail("class A { a() { A.#a } }", "Usage of undeclared private name (1:18)")
|
345 | testFail("class A { a() { A.#a } b() { A.#b } }", "Usage of undeclared private name (1:18)")
|
346 | testFail("class A { static #constructor() {} }", "Classes may not have a private element named constructor (1:17)")
|
347 | testFail("class A { static #[ab]() {} }", "Unexpected token (1:18)")
|
348 | testFail("a = { static #ab() {} }", "Unexpected token (1:13)")
|
349 | testFail("class A { static [{#ab() {}}]() {} }", "Unexpected token (1:19)")
|
350 | testFail("class A{ static # a() {}}", "Unexpected token (1:18)")
|
351 | testFail("class C{ static #method() { super(); } };", "super() call outside constructor of a subclass (1:28)")
|
352 | test("class C{ static #method() { super.y(); } };")
|
353 |
|
354 | ;[
|
355 | { body: "static #x() {}", passes: true, ast: start => newNode(start, {
|
356 | type: "MethodDefinition",
|
357 | end: start + 14,
|
358 | computed: false,
|
359 | key: newNode(start + 7, {
|
360 | type: "PrivateIdentifier",
|
361 | end: start + 9,
|
362 | name: "x"
|
363 | }),
|
364 | kind: "method",
|
365 | static: true,
|
366 | value: newNode(start + 9, {
|
367 | type: "FunctionExpression",
|
368 | end: start + 14,
|
369 | async: false,
|
370 | body: newNode(start + 12, {
|
371 | type: "BlockStatement",
|
372 | body: [],
|
373 | end: start + 14,
|
374 | }),
|
375 | expression: false,
|
376 | generator: false,
|
377 | id: null,
|
378 | params: [],
|
379 | })
|
380 | }) },
|
381 | { body: "static get #x() {}", passes: true, ast: start => newNode(start, {
|
382 | type: "MethodDefinition",
|
383 | end: start + 18,
|
384 | computed: false,
|
385 | key: newNode(start + 11, {
|
386 | type: "PrivateIdentifier",
|
387 | end: start + 13,
|
388 | name: "x"
|
389 | }),
|
390 | kind: "get",
|
391 | static: true,
|
392 | value: newNode(start + 13, {
|
393 | type: "FunctionExpression",
|
394 | end: start + 18,
|
395 | async: false,
|
396 | body: newNode(start + 16, {
|
397 | body: [],
|
398 | end: start + 18,
|
399 | type: "BlockStatement"
|
400 | }),
|
401 | expression: false,
|
402 | generator: false,
|
403 | id: null,
|
404 | params: [],
|
405 | })
|
406 | }) },
|
407 |
|
408 | ].forEach(bodyInput => {
|
409 | const body = bodyInput.body, passes = bodyInput.passes, bodyAst = bodyInput.ast
|
410 | classes.forEach(input => {
|
411 | const text = input.text, options = input.options || {}, ast = input.ast
|
412 | ;(passes ? test : testFail)(text.replace("%s", body), ast(bodyAst), options)
|
413 | })
|
414 | })
|
415 | test("class A extends B { constructor() { super() } }")
|
416 | test(`class C {
|
417 | static f = 'test262';
|
418 | static 'g';
|
419 | static 0 = 'bar';
|
420 | static [computed];
|
421 | }`)
|
422 | test("class X{static delete}")
|
423 | test(`class Foo extends Bar {
|
424 | static field = super.field + 1;
|
425 | }`)
|
426 | })
|