Skip to content

Commit 51482e4

Browse files
authored
Merge pull request github#8295 from erik-krogh/ts46
JS: Add support for TypeScript 4.6
2 parents 7522a2d + 4c58f97 commit 51482e4

File tree

10 files changed

+877
-35
lines changed

10 files changed

+877
-35
lines changed

docs/codeql/support/reusables/versions-compilers.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
JavaScript,ECMAScript 2021 or lower,Not applicable,"``.js``, ``.jsx``, ``.mjs``, ``.es``, ``.es6``, ``.htm``, ``.html``, ``.xhtm``, ``.xhtml``, ``.vue``, ``.hbs``, ``.ejs``, ``.njk``, ``.json``, ``.yaml``, ``.yml``, ``.raml``, ``.xml`` [6]_"
2424
Python,"2.7, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10",Not applicable,``.py``
2525
Ruby [7]_,"up to 3.0.2",Not applicable,"``.rb``, ``.erb``, ``.gemspec``, ``Gemfile``"
26-
TypeScript [8]_,"2.6-4.5",Standard TypeScript compiler,"``.ts``, ``.tsx``"
26+
TypeScript [8]_,"2.6-4.6",Standard TypeScript compiler,"``.ts``, ``.tsx``"
2727

2828
.. container:: footnote-group
2929

javascript/extractor/lib/typescript/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "typescript-parser-wrapper",
33
"private": true,
44
"dependencies": {
5-
"typescript": "4.5.2"
5+
"typescript": "4.6.2"
66
},
77
"scripts": {
88
"build": "tsc --project tsconfig.json",

javascript/extractor/lib/typescript/yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
version "12.7.11"
77
resolved node-12.7.11.tgz#be879b52031cfb5d295b047f5462d8ef1a716446
88

9-
typescript@4.5.2:
10-
version "4.5.2"
11-
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998"
12-
integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==
9+
typescript@4.6.2:
10+
version "4.6.2"
11+
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4"
12+
integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==

javascript/extractor/src/com/semmle/js/extractor/Main.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public class Main {
4343
* A version identifier that should be updated every time the extractor changes in such a way that
4444
* it may produce different tuples for the same file under the same {@link ExtractorConfig}.
4545
*/
46-
public static final String EXTRACTOR_VERSION = "2021-12-17";
46+
public static final String EXTRACTOR_VERSION = "2022-02-22";
4747

4848
public static final Pattern NEWLINE = Pattern.compile("\n");
4949

File renamed without changes.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: majorAnalysis
3+
---
4+
* Added support for TypeScript 4.6.

javascript/ql/test/library-tests/TypeScript/Types/printAst.expected

Lines changed: 652 additions & 26 deletions
Large diffs are not rendered by default.

javascript/ql/test/library-tests/TypeScript/Types/tests.expected

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,91 @@ getExprType
301301
| tst.ts:238:11:238:14 | Foo3 | { foo: string; } |
302302
| tst.ts:238:11:238:18 | Foo3.foo | string |
303303
| tst.ts:238:16:238:18 | foo | string |
304+
| tst.ts:240:8:240:11 | TS46 | typeof TS46 in library-tests/TypeScript/Types/tst.ts |
305+
| tst.ts:241:9:241:12 | Base | Base |
306+
| tst.ts:243:9:243:15 | Derived | Derived |
307+
| tst.ts:243:25:243:28 | Base | Base |
308+
| tst.ts:244:5:244:10 | myProp | boolean |
309+
| tst.ts:244:14:244:17 | true | true |
310+
| tst.ts:246:5:249:5 | constru ... ;\\n } | any |
311+
| tst.ts:247:7:247:13 | console | Console |
312+
| tst.ts:247:7:247:17 | console.log | (...data: any[]) => void |
313+
| tst.ts:247:7:247:51 | console ... per()") | void |
314+
| tst.ts:247:15:247:17 | log | (...data: any[]) => void |
315+
| tst.ts:247:19:247:50 | "Doing ... uper()" | "Doing something before super()" |
316+
| tst.ts:248:7:248:13 | super() | void |
317+
| tst.ts:253:9:253:12 | kind | "NumberContents" |
318+
| tst.ts:253:33:253:39 | payload | number |
319+
| tst.ts:254:9:254:12 | kind | "StringContents" |
320+
| tst.ts:254:33:254:39 | payload | string |
321+
| tst.ts:256:12:256:24 | processAction | (action: Action) => void |
322+
| tst.ts:256:26:256:31 | action | Action |
323+
| tst.ts:257:13:257:16 | kind | "NumberContents" \| "StringContents" |
324+
| tst.ts:257:13:257:16 | kind | "NumberContents" \| "StringContents" |
325+
| tst.ts:257:19:257:25 | payload | string \| number |
326+
| tst.ts:257:19:257:25 | payload | string \| number |
327+
| tst.ts:257:31:257:36 | action | Action |
328+
| tst.ts:258:9:258:12 | kind | "NumberContents" \| "StringContents" |
329+
| tst.ts:258:9:258:33 | kind == ... ntents" | boolean |
330+
| tst.ts:258:18:258:33 | "NumberContents" | "NumberContents" |
331+
| tst.ts:259:7:259:13 | console | Console |
332+
| tst.ts:259:7:259:17 | console.log | (...data: any[]) => void |
333+
| tst.ts:259:7:259:36 | console ... ixed()) | void |
334+
| tst.ts:259:15:259:17 | log | (...data: any[]) => void |
335+
| tst.ts:259:19:259:25 | payload | number |
336+
| tst.ts:259:19:259:33 | payload.toFixed | (fractionDigits?: number) => string |
337+
| tst.ts:259:19:259:35 | payload.toFixed() | string |
338+
| tst.ts:259:27:259:33 | toFixed | (fractionDigits?: number) => string |
339+
| tst.ts:260:16:260:19 | kind | "StringContents" |
340+
| tst.ts:260:16:260:40 | kind == ... ntents" | boolean |
341+
| tst.ts:260:25:260:40 | "StringContents" | "StringContents" |
342+
| tst.ts:261:7:261:13 | console | Console |
343+
| tst.ts:261:7:261:17 | console.log | (...data: any[]) => void |
344+
| tst.ts:261:7:261:40 | console ... Case()) | void |
345+
| tst.ts:261:15:261:17 | log | (...data: any[]) => void |
346+
| tst.ts:261:19:261:25 | payload | string |
347+
| tst.ts:261:19:261:37 | payload.toLowerCase | () => string |
348+
| tst.ts:261:19:261:39 | payload ... rCase() | string |
349+
| tst.ts:261:27:261:37 | toLowerCase | () => string |
350+
| tst.ts:266:5:266:10 | number | number |
351+
| tst.ts:267:5:267:10 | string | string |
352+
| tst.ts:268:5:268:11 | boolean | boolean |
353+
| tst.ts:273:7:273:10 | kind | K |
354+
| tst.ts:278:12:278:24 | processRecord | <K extends keyof TypeMap>(record: UnionRecord<K... |
355+
| tst.ts:278:51:278:56 | record | UnionRecord<K> |
356+
| tst.ts:279:5:279:10 | record | UnionRecord<K> |
357+
| tst.ts:279:5:279:22 | record.f(record.v) | void |
358+
| tst.ts:279:14:279:19 | record | UnionRecord<K> |
359+
| tst.ts:279:14:279:21 | record.v | any |
360+
| tst.ts:279:21:279:21 | v | any |
361+
| tst.ts:282:3:282:15 | processRecord | <K extends keyof TypeMap>(record: UnionRecord<K... |
362+
| tst.ts:282:3:287:4 | process ... },\\n }) | void |
363+
| tst.ts:282:17:287:3 | {\\n k ... },\\n } | { kind: "string"; f: (p: string) => void; } |
364+
| tst.ts:283:5:283:8 | kind | "string" |
365+
| tst.ts:283:11:283:18 | "string" | "string" |
366+
| tst.ts:284:5:284:5 | f | (val: string) => void |
367+
| tst.ts:284:8:286:5 | (val) = ... g\\n } | (val: string) => void |
368+
| tst.ts:284:9:284:11 | val | string |
369+
| tst.ts:285:7:285:13 | console | Console |
370+
| tst.ts:285:7:285:17 | console.log | (...data: any[]) => void |
371+
| tst.ts:285:7:285:36 | console ... Case()) | void |
372+
| tst.ts:285:15:285:17 | log | (...data: any[]) => void |
373+
| tst.ts:285:19:285:21 | val | string |
374+
| tst.ts:285:19:285:33 | val.toUpperCase | () => string |
375+
| tst.ts:285:19:285:35 | val.toUpperCase() | string |
376+
| tst.ts:285:23:285:33 | toUpperCase | () => string |
377+
| tst.ts:289:19:289:22 | args | ["a", number] \| ["b", string] |
378+
| tst.ts:291:9:291:10 | f1 | Func |
379+
| tst.ts:291:20:295:3 | (kind, ... }\\n } | (kind: "a" \| "b", payload: string \| number) => ... |
380+
| tst.ts:291:21:291:24 | kind | "a" \| "b" |
381+
| tst.ts:291:27:291:33 | payload | string \| number |
382+
| tst.ts:292:9:292:12 | kind | "a" \| "b" |
383+
| tst.ts:292:9:292:20 | kind === "a" | boolean |
384+
| tst.ts:292:18:292:20 | "a" | "a" |
385+
| tst.ts:293:7:293:13 | payload | number |
386+
| tst.ts:293:7:293:21 | payload.toFixed | (fractionDigits?: number) => string |
387+
| tst.ts:293:7:293:23 | payload.toFixed() | string |
388+
| tst.ts:293:15:293:21 | toFixed | (fractionDigits?: number) => string |
304389
| type_alias.ts:3:5:3:5 | b | boolean |
305390
| type_alias.ts:7:5:7:5 | c | ValueOrArray<number> |
306391
| type_alias.ts:14:9:14:32 | [proper ... ]: Json | any |
@@ -370,6 +455,12 @@ getTypeDefinitionType
370455
| tst.ts:205:10:208:3 | interfa ... ng;\\n } | Success |
371456
| tst.ts:210:10:213:3 | interfa ... ng;\\n } | Error |
372457
| tst.ts:222:3:234:3 | class P ... }\\n } | Person |
458+
| tst.ts:241:3:241:15 | class Base {} | Base |
459+
| tst.ts:243:3:250:3 | class D ... }\\n } | Derived |
460+
| tst.ts:252:3:254:50 | type Ac ... ring }; | Action |
461+
| tst.ts:265:3:269:3 | interfa ... an;\\n } | TypeMap |
462+
| tst.ts:271:3:276:7 | type Un ... }[P]; | UnionRecord<P> |
463+
| tst.ts:289:3:289:63 | type Fu ... > void; | Func |
373464
| type_alias.ts:1:1:1:17 | type B = boolean; | boolean |
374465
| type_alias.ts:5:1:5:50 | type Va ... ay<T>>; | ValueOrArray<T> |
375466
| type_alias.ts:9:1:15:13 | type Js ... Json[]; | Json |
@@ -572,6 +663,46 @@ getTypeExprType
572663
| tst.ts:223:12:223:17 | string | string |
573664
| tst.ts:224:23:224:28 | string | string |
574665
| tst.ts:228:19:228:25 | unknown | unknown |
666+
| tst.ts:252:8:252:13 | Action | Action |
667+
| tst.ts:253:5:254:49 | \| { kin ... tring } | { kind: "NumberContents"; payload: number; } \| ... |
668+
| tst.ts:253:7:253:49 | { kind: ... umber } | { kind: "NumberContents"; payload: number; } |
669+
| tst.ts:253:15:253:30 | "NumberContents" | "NumberContents" |
670+
| tst.ts:253:42:253:47 | number | number |
671+
| tst.ts:254:7:254:49 | { kind: ... tring } | { kind: "StringContents"; payload: string; } |
672+
| tst.ts:254:15:254:30 | "StringContents" | "StringContents" |
673+
| tst.ts:254:42:254:47 | string | string |
674+
| tst.ts:256:34:256:39 | Action | Action |
675+
| tst.ts:265:13:265:19 | TypeMap | TypeMap |
676+
| tst.ts:266:13:266:18 | number | number |
677+
| tst.ts:267:13:267:18 | string | string |
678+
| tst.ts:268:14:268:20 | boolean | boolean |
679+
| tst.ts:271:8:271:18 | UnionRecord | UnionRecord<P> |
680+
| tst.ts:271:20:271:20 | P | P |
681+
| tst.ts:271:30:271:42 | keyof TypeMap | keyof TypeMap |
682+
| tst.ts:271:36:271:42 | TypeMap | TypeMap |
683+
| tst.ts:272:6:272:6 | K | K |
684+
| tst.ts:272:11:272:11 | P | P |
685+
| tst.ts:273:13:273:13 | K | K |
686+
| tst.ts:274:14:274:20 | TypeMap | TypeMap |
687+
| tst.ts:274:22:274:22 | K | K |
688+
| tst.ts:274:29:274:32 | void | void |
689+
| tst.ts:276:5:276:5 | P | P |
690+
| tst.ts:278:26:278:26 | K | K |
691+
| tst.ts:278:36:278:48 | keyof TypeMap | keyof TypeMap |
692+
| tst.ts:278:42:278:48 | TypeMap | TypeMap |
693+
| tst.ts:278:59:278:69 | UnionRecord | UnionRecord<P> |
694+
| tst.ts:278:59:278:72 | UnionRecord<K> | UnionRecord<K> |
695+
| tst.ts:278:71:278:71 | K | K |
696+
| tst.ts:289:8:289:11 | Func | Func |
697+
| tst.ts:289:25:289:37 | ["a", number] | ["a", number] |
698+
| tst.ts:289:25:289:53 | ["a", n ... string] | ["a", number] \| ["b", string] |
699+
| tst.ts:289:26:289:28 | "a" | "a" |
700+
| tst.ts:289:31:289:36 | number | number |
701+
| tst.ts:289:41:289:53 | ["b", string] | ["b", string] |
702+
| tst.ts:289:42:289:44 | "b" | "b" |
703+
| tst.ts:289:47:289:52 | string | string |
704+
| tst.ts:289:59:289:62 | void | void |
705+
| tst.ts:291:13:291:16 | Func | Func |
575706
| type_alias.ts:1:6:1:6 | B | boolean |
576707
| type_alias.ts:1:10:1:16 | boolean | boolean |
577708
| type_alias.ts:3:8:3:8 | B | boolean |
@@ -633,8 +764,11 @@ getTypeExprType
633764
| type_definitions.ts:22:32:22:37 | number | number |
634765
missingToString
635766
referenceDefinition
767+
| Action | tst.ts:252:3:254:50 | type Ac ... ring }; |
636768
| Alias<T> | type_definitions.ts:21:1:21:20 | type Alias<T> = T[]; |
637769
| Alias<number> | type_definitions.ts:21:1:21:20 | type Alias<T> = T[]; |
770+
| Base | tst.ts:241:3:241:15 | class Base {} |
771+
| Base | tst.ts:241:3:241:15 | class Base {} |
638772
| C | tst.ts:203:3:203:46 | type C ... mber>>; |
639773
| C | type_definition_objects.ts:3:8:3:17 | class C {} |
640774
| C<T> | type_definitions.ts:8:1:10:1 | class C ... x: T\\n} |
@@ -645,12 +779,14 @@ referenceDefinition
645779
| Color.red | type_definitions.ts:14:3:14:5 | red |
646780
| Colors | tst.ts:152:5:156:5 | interfa ... ;\\n } |
647781
| Data | tst.ts:171:5:173:5 | interfa ... ;\\n } |
782+
| Derived | tst.ts:243:3:250:3 | class D ... }\\n } |
648783
| E | type_definition_objects.ts:6:8:6:16 | enum E {} |
649784
| EnumWithOneMember | type_definitions.ts:18:26:18:31 | member |
650785
| Error | tst.ts:210:10:213:3 | interfa ... ng;\\n } |
651786
| Foo | tst.ts:116:3:129:3 | class F ... }\\n } |
652787
| Foo | tst.ts:165:5:167:5 | interfa ... ;\\n } |
653788
| Foo | tst.ts:179:3:192:3 | class F ... \\n } |
789+
| Func | tst.ts:289:3:289:63 | type Fu ... > void; |
654790
| HasArea | tst.ts:58:1:60:1 | interfa ... mber;\\n} |
655791
| I<S> | type_definitions.ts:3:1:5:1 | interfa ... x: S;\\n} |
656792
| I<number> | type_definitions.ts:3:1:5:1 | interfa ... x: S;\\n} |
@@ -666,6 +802,9 @@ referenceDefinition
666802
| Super | tst.ts:91:3:95:3 | class S ... }\\n } |
667803
| Thing | tst.ts:78:10:88:3 | class T ... }\\n } |
668804
| ThingI | tst.ts:73:3:76:3 | interfa ... n);\\n } |
805+
| TypeMap | tst.ts:265:3:269:3 | interfa ... an;\\n } |
806+
| UnionRecord<K> | tst.ts:271:3:276:7 | type Un ... }[P]; |
807+
| UnionRecord<P> | tst.ts:271:3:276:7 | type Un ... }[P]; |
669808
| ValueOrArray<T> | type_alias.ts:5:1:5:50 | type Va ... ay<T>>; |
670809
| ValueOrArray<number> | type_alias.ts:5:1:5:50 | type Va ... ay<T>>; |
671810
| VirtualNode | type_alias.ts:19:1:21:57 | type Vi ... ode[]]; |
@@ -713,14 +852,22 @@ abstractSignature
713852
unionIndex
714853
| 1 | 0 | 1 \| 2 |
715854
| 2 | 1 | 1 \| 2 |
855+
| "NumberContents" | 0 | "NumberContents" \| "StringContents" |
856+
| "StringContents" | 1 | "NumberContents" \| "StringContents" |
857+
| "a" | 0 | "a" \| "b" |
858+
| "a" | 1 | number \| "a" |
859+
| "b" | 1 | "a" \| "b" |
716860
| "bigint" | 2 | "string" \| "number" \| "bigint" \| "boolean" \| "s... |
861+
| "boolean" | 2 | keyof TypeMap |
717862
| "boolean" | 3 | "string" \| "number" \| "bigint" \| "boolean" \| "s... |
718863
| "circle" | 0 | "circle" \| "square" |
719864
| "function" | 7 | "string" \| "number" \| "bigint" \| "boolean" \| "s... |
720865
| "number" | 1 | "string" \| "number" \| "bigint" \| "boolean" \| "s... |
866+
| "number" | 1 | keyof TypeMap |
721867
| "object" | 6 | "string" \| "number" \| "bigint" \| "boolean" \| "s... |
722868
| "square" | 1 | "circle" \| "square" |
723869
| "string" | 0 | "string" \| "number" \| "bigint" \| "boolean" \| "s... |
870+
| "string" | 0 | keyof TypeMap |
724871
| "symbol" | 4 | "string" \| "number" \| "bigint" \| "boolean" \| "s... |
725872
| "undefined" | 5 | "string" \| "number" \| "bigint" \| "boolean" \| "s... |
726873
| Error | 1 | Success \| Error |
@@ -736,13 +883,16 @@ unionIndex
736883
| TResult2 | 1 | TResult1 \| TResult2 |
737884
| ValueOrArray<T>[] | 1 | T \| ValueOrArray<T>[] |
738885
| ValueOrArray<number>[] | 1 | number \| ValueOrArray<number>[] |
886+
| ["a", number] | 0 | ["a", number] \| ["b", string] |
887+
| ["b", string] | 1 | ["a", number] \| ["b", string] |
739888
| [string, { [key: string]: any; }, ...VirtualNod... | 1 | VirtualNode \| { [key: string]: any; } |
740889
| [string, { [key: string]: any; }, ...VirtualNod... | 1 | string \| [string, { [key: string]: any; }, ...V... |
741890
| false | 0 | boolean |
742891
| false | 0 | boolean \| Promise<number> |
743892
| false | 1 | number \| boolean |
744893
| false | 2 | string \| number \| boolean |
745894
| false | 2 | string \| number \| boolean \| { [property: string... |
895+
| number | 0 | number \| "a" |
746896
| number | 0 | number \| ValueOrArray<number>[] |
747897
| number | 0 | number \| boolean |
748898
| number | 1 | string \| number |
@@ -767,6 +917,8 @@ unionIndex
767917
| { [key: string]: any; } | 1 | string \| { [key: string]: any; } |
768918
| { [key: string]: any; } | 2 | VirtualNode \| { [key: string]: any; } |
769919
| { [property: string]: Json; } | 4 | string \| number \| boolean \| { [property: string... |
920+
| { kind: "NumberContents"; payload: number; } | 0 | { kind: "NumberContents"; payload: number; } \| ... |
921+
| { kind: "StringContents"; payload: string; } | 1 | { kind: "NumberContents"; payload: number; } \| ... |
770922
| { kind: "circle"; radius: number; } | 0 | { kind: "circle"; radius: number; } \| { kind: "... |
771923
| { kind: "square"; sideLength: number; } | 1 | { kind: "circle"; radius: number; } \| { kind: "... |
772924
| { myUnion: true; } | 0 | MyUnion \| { yetAnotherType: true; } |
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{
22
"compilerOptions": {
3-
"lib": ["es2015"],
3+
"module": "esnext",
4+
"target": "esnext",
5+
"lib": ["dom", "esnext"],
46
"resolveJsonModule": true
57
}
68
}

javascript/ql/test/library-tests/TypeScript/Types/tst.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,62 @@ module TS45 {
235235
}
236236

237237
import * as Foo3 from "./something.json" assert { type: "json" };
238-
var foo = Foo3.foo;
238+
var foo = Foo3.foo;
239+
240+
module TS46 {
241+
class Base {}
242+
243+
class Derived extends Base {
244+
myProp = true;
245+
246+
constructor() {
247+
console.log("Doing something before super()");
248+
super();
249+
}
250+
}
251+
252+
type Action =
253+
| { kind: "NumberContents"; payload: number }
254+
| { kind: "StringContents"; payload: string };
255+
256+
function processAction(action: Action) {
257+
const { kind, payload } = action;
258+
if (kind === "NumberContents") {
259+
console.log(payload.toFixed()); // <- number
260+
} else if (kind === "StringContents") {
261+
console.log(payload.toLowerCase()); // <- string
262+
}
263+
}
264+
265+
interface TypeMap {
266+
number: number;
267+
string: string;
268+
boolean: boolean;
269+
}
270+
271+
type UnionRecord<P extends keyof TypeMap> = {
272+
[K in P]: {
273+
kind: K;
274+
f: (p: TypeMap[K]) => void;
275+
};
276+
}[P];
277+
278+
function processRecord<K extends keyof TypeMap>(record: UnionRecord<K>) {
279+
record.f(record.v);
280+
}
281+
282+
processRecord({
283+
kind: "string",
284+
f: (val) => {
285+
console.log(val.toUpperCase()); // <- string
286+
},
287+
});
288+
289+
type Func = (...args: ["a", number] | ["b", string]) => void;
290+
291+
const f1: Func = (kind, payload) => {
292+
if (kind === "a") {
293+
payload.toFixed(); // <- number
294+
}
295+
};
296+
}

0 commit comments

Comments
 (0)