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