as-test 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,661 @@
1
+ import { Transform } from "assemblyscript/dist/transform.js";
2
+ import {
3
+ Parser,
4
+ Source,
5
+ SourceKind,
6
+ Statement,
7
+ Token,
8
+
9
+ BinaryExpression,
10
+ CommaExpression,
11
+ ParenthesizedExpression,
12
+ ParameterNode,
13
+ BlockStatement,
14
+ ExpressionStatement,
15
+ FunctionDeclaration,
16
+ IfStatement,
17
+ MethodDeclaration,
18
+ ReturnStatement,
19
+ SwitchCase,
20
+ TernaryExpression,
21
+ NodeKind,
22
+ ArrowKind,
23
+ Node,
24
+ ObjectLiteralExpression,
25
+ Tokenizer
26
+ } from "assemblyscript/dist/assemblyscript.js";
27
+
28
+ import { BaseVisitor, SimpleParser } from "visitor-as/dist/index.js";
29
+ import { isStdlib } from "visitor-as/dist/utils.js";
30
+ import { RangeTransform } from "visitor-as/dist/transformRange.js";
31
+
32
+ let ENABLED = false;
33
+
34
+ enum CoverType {
35
+ Function,
36
+ Expression,
37
+ Block
38
+ }
39
+
40
+ class CoverPoint {
41
+ public file: string = "";
42
+ public hash: string = "";
43
+ public line: number = 0;
44
+ public column: number = 0;
45
+ public type!: CoverType;
46
+ public executed: boolean = false;
47
+ }
48
+
49
+ class CoverageTransform extends BaseVisitor {
50
+ public mustImport: boolean = false;
51
+ public points: Map<string, CoverPoint> = new Map<string, CoverPoint>();
52
+ public globalStatements: Statement[] = [];
53
+ visitBinaryExpression(node: BinaryExpression): void {
54
+ super.visitBinaryExpression(node);
55
+ // @ts-ignore
56
+ if (node.visited) return;
57
+ // @ts-ignore
58
+ node.visited = true;
59
+ const path = node.range.source.normalizedPath;
60
+
61
+ switch (node.operator) {
62
+ case Token.Bar_Bar:
63
+ case Token.Ampersand_Ampersand: {
64
+ const right = node.right;
65
+ const rightLc = getLineCol(node);
66
+
67
+ const point = new CoverPoint();
68
+ point.line = rightLc?.line!;
69
+ point.column = rightLc?.column!;
70
+ point.file = path;
71
+ point.type = CoverType.Expression;
72
+
73
+ point.hash = hash(point);
74
+
75
+
76
+ const replacer = new RangeTransform(node);
77
+ const registerStmt = SimpleParser.parseTopLevelStatement(
78
+ `__REGISTER({
79
+ file: "${point.file}",
80
+ hash: "${point.hash}",
81
+ line: ${point.line},
82
+ column: ${point.column},
83
+ type: "Expression",
84
+ executed: false
85
+ });`
86
+ );
87
+ replacer.visit(registerStmt);
88
+
89
+ let coverExpression = SimpleParser.parseExpression(
90
+ `(__COVER("${point.hash}"), $$REPLACE_ME)`
91
+ ) as ParenthesizedExpression;
92
+ replacer.visit(coverExpression);
93
+
94
+ (coverExpression.expression as CommaExpression).expressions[1] = right;
95
+
96
+ node.right = coverExpression;
97
+
98
+ this.globalStatements.push(registerStmt);
99
+
100
+ break;
101
+ }
102
+ }
103
+ }
104
+ visitMethodDeclaration(node: MethodDeclaration): void {
105
+ super.visitMethodDeclaration(node);
106
+ // @ts-ignore
107
+ if (node.visited) return;
108
+ // @ts-ignore
109
+ node.visited = true;
110
+ if (node.body) {
111
+ // @ts-ignore
112
+ if (node.body.visited) return;
113
+ // @ts-ignore
114
+ node.body.visited = true;
115
+ const path = node.range.source.normalizedPath;
116
+ const funcLc = getLineCol(node);
117
+
118
+ const point = new CoverPoint();
119
+ point.line = funcLc?.line!;
120
+ point.column = funcLc?.column!;
121
+ point.file = path;
122
+ point.type = CoverType.Function;
123
+
124
+ point.hash = hash(point);
125
+
126
+
127
+ const replacer = new RangeTransform(node);
128
+ const registerStmt = SimpleParser.parseTopLevelStatement(
129
+ `__REGISTER({
130
+ file: "${point.file}",
131
+ hash: "${point.hash}",
132
+ line: ${point.line},
133
+ column: ${point.column},
134
+ type: "Function",
135
+ executed: false
136
+ })`
137
+ );
138
+ replacer.visit(registerStmt);
139
+
140
+ const coverStmt = SimpleParser.parseStatement(
141
+ `__COVER("${point.hash}")`,
142
+ true
143
+ );
144
+ replacer.visit(coverStmt);
145
+
146
+ const bodyBlock = node.body as BlockStatement;
147
+ bodyBlock.statements.unshift(coverStmt);
148
+
149
+ this.globalStatements.push(registerStmt);
150
+ }
151
+ }
152
+ visitParameter(node: ParameterNode): void {
153
+ // @ts-ignore
154
+ if (node.visited) return;
155
+ // @ts-ignore
156
+ node.visited = true;
157
+ const path = node.range.source.normalizedPath;
158
+ if (node.initializer) {
159
+ // @ts-ignore
160
+ if (node.initializer.visited) return;
161
+ // @ts-ignore
162
+ node.initializer.visited = true;
163
+ super.visitParameter(node);
164
+ const paramLc = getLineCol(node.initializer);
165
+
166
+ const point = new CoverPoint();
167
+ point.line = paramLc?.line!;
168
+ point.column = paramLc?.column!;
169
+ point.file = path;
170
+ point.type = CoverType.Expression;
171
+
172
+ point.hash = hash(point);
173
+
174
+
175
+ const replacer = new RangeTransform(node);
176
+ const registerStmt = SimpleParser.parseTopLevelStatement(
177
+ `__REGISTER({
178
+ file: "${point.file}",
179
+ hash: "${point.hash}",
180
+ line: ${point.line},
181
+ column: ${point.column},
182
+ type: "Expression",
183
+ executed: false
184
+ })`
185
+ );
186
+ replacer.visit(registerStmt);
187
+
188
+ const coverExpression = SimpleParser.parseExpression(
189
+ `(__COVER("${point.hash}"), $$REPLACE_ME)`
190
+ ) as ParenthesizedExpression;
191
+ replacer.visit(coverExpression);
192
+
193
+ (coverExpression.expression as CommaExpression).expressions[1] = node.initializer;
194
+
195
+ node.initializer = coverExpression;
196
+
197
+ this.globalStatements.push(registerStmt);
198
+ }
199
+ }
200
+ visitFunctionDeclaration(node: FunctionDeclaration, isDefault?: boolean | undefined): void {
201
+ super.visitFunctionDeclaration(node, isDefault);
202
+ // @ts-ignore
203
+ if (node.visited) return;
204
+ // @ts-ignore
205
+ node.visited = true;
206
+ if (node.body) {
207
+ // @ts-ignore
208
+ if (node.body.visited) return;
209
+ // @ts-ignore
210
+ node.body.visited = true;
211
+
212
+ const path = node.range.source.normalizedPath;
213
+ const funcLc = getLineCol(node);
214
+ const point = new CoverPoint();
215
+ point.line = funcLc?.line!;
216
+ point.column = funcLc?.column!;
217
+ point.file = path;
218
+ point.type = CoverType.Function;
219
+
220
+ point.hash = hash(point);
221
+
222
+
223
+ const replacer = new RangeTransform(node);
224
+ const registerStmt = SimpleParser.parseTopLevelStatement(
225
+ `__REGISTER({
226
+ file: "${point.file}",
227
+ hash: "${point.hash}",
228
+ line: ${point.line},
229
+ column: ${point.column},
230
+ type: "Function",
231
+ executed: false
232
+ })`
233
+ );
234
+ replacer.visit(registerStmt);
235
+
236
+ this.globalStatements.push(registerStmt);
237
+
238
+ if (node.body.kind === NodeKind.Export) {
239
+ const coverStmt = SimpleParser.parseStatement(`{
240
+ __COVER("${point.hash}")
241
+ return $$REPLACE_ME
242
+ }`) as BlockStatement;
243
+ replacer.visit(coverStmt);
244
+
245
+ const bodyReturn = coverStmt.statements[1] as ReturnStatement;
246
+ const body = node.body as ExpressionStatement;
247
+ node.arrowKind = ArrowKind.Single;
248
+ bodyReturn.value = body.expression;
249
+ node.body = body;
250
+ } else {
251
+ const coverStmt = SimpleParser.parseStatement(
252
+ `__COVER("${point.hash}")`,
253
+ true
254
+ );
255
+ replacer.visit(coverStmt);
256
+
257
+ if (node.body instanceof BlockStatement) {
258
+ node.body.statements.unshift(coverStmt);
259
+ console.log(node)
260
+ } else if (node.body instanceof ExpressionStatement) {
261
+ const expression = (node.body as ExpressionStatement).expression;
262
+ node.body = Node.createBlockStatement([
263
+ Node.createReturnStatement(
264
+ expression,
265
+ expression.range
266
+ )],
267
+ expression.range
268
+ );
269
+
270
+ const bodyBlock = node.body as BlockStatement;
271
+ bodyBlock.statements.unshift(coverStmt);
272
+ }
273
+ }
274
+ }
275
+ }
276
+ visitIfStatement(node: IfStatement): void {
277
+ // @ts-ignore
278
+ if (node.visited) return;
279
+ // @ts-ignore
280
+ node.visited = true;
281
+ let visitIfTrue = false;
282
+ let visitIfFalse = false;
283
+
284
+ const ifTrue = node.ifTrue;
285
+ const ifFalse = node.ifFalse;
286
+
287
+ const path = node.range.source.normalizedPath;
288
+
289
+ if (ifTrue.kind !== NodeKind.Block) {
290
+ const trueLc = getLineCol(ifTrue);
291
+ const point = new CoverPoint();
292
+
293
+ point.line = trueLc?.line!;
294
+ point.column = trueLc?.column!;
295
+ point.file = path;
296
+ point.type = CoverType.Expression;
297
+
298
+ point.hash = hash(point);
299
+
300
+
301
+ const replacer = new RangeTransform(ifTrue);
302
+
303
+ const registerStmt = SimpleParser.parseTopLevelStatement(
304
+ `__REGISTER({
305
+ file: "${point.file}",
306
+ hash: "${point.hash}",
307
+ line: ${point.line},
308
+ column: ${point.column},
309
+ type: "Expression",
310
+ executed: false
311
+ })`
312
+ );
313
+ replacer.visit(registerStmt);
314
+
315
+ const coverStmt = SimpleParser.parseStatement(
316
+ `{__COVER("${point.hash}")};`,
317
+ true
318
+ ) as BlockStatement;
319
+ replacer.visit(coverStmt);
320
+
321
+ coverStmt.statements.push(ifTrue);
322
+ node.ifTrue = coverStmt;
323
+
324
+ this.globalStatements.push(registerStmt);
325
+
326
+ visitIfTrue = true;
327
+ visitIfFalse = !!ifFalse;
328
+ }
329
+
330
+ if (ifFalse && ifFalse.kind !== NodeKind.Block) {
331
+ const falseLc = getLineCol(ifFalse);
332
+ const point = new CoverPoint();
333
+
334
+ point.line = falseLc?.line!;
335
+ point.column = falseLc?.column!;
336
+ point.file = path;
337
+ point.type = CoverType.Expression;
338
+
339
+ point.hash = hash(point);
340
+
341
+
342
+ const replacer = new RangeTransform(ifTrue);
343
+
344
+ const registerStmt = SimpleParser.parseTopLevelStatement(
345
+ `__REGISTER({
346
+ file: "${point.file}",
347
+ hash: "${point.hash}",
348
+ line: ${point.line},
349
+ column: ${point.column},
350
+ type: "Expression",
351
+ executed: false
352
+ })`
353
+ );
354
+ replacer.visit(registerStmt);
355
+
356
+ const coverStmt = SimpleParser.parseStatement(
357
+ `{__COVER("${point.hash}")};`,
358
+ true
359
+ ) as BlockStatement;
360
+ replacer.visit(coverStmt);
361
+
362
+ coverStmt.statements.push(ifFalse);
363
+ node.ifFalse = coverStmt;
364
+
365
+ this.globalStatements.push(registerStmt);
366
+
367
+ visitIfTrue = true;
368
+ visitIfFalse = true;
369
+ }
370
+ if (visitIfTrue || visitIfFalse) {
371
+ if (visitIfTrue) {
372
+ // @ts-ignore
373
+ if (ifTrue.visited) return;
374
+ // @ts-ignore
375
+ ifTrue.visited = true;
376
+ this._visit(ifTrue);
377
+ }
378
+ if (visitIfFalse) {
379
+ // @ts-ignore
380
+ if (ifFalse.visited) return;
381
+ // @ts-ignore
382
+ ifFalse.visited = true;
383
+ this._visit(ifFalse!);
384
+ }
385
+ } else {
386
+ super.visitIfStatement(node);
387
+ }
388
+ }
389
+ visitTernaryExpression(node: TernaryExpression): void {
390
+ // @ts-ignore
391
+ if (node.visited) return;
392
+ // @ts-ignore
393
+ node.visited = true;
394
+ super.visitTernaryExpression(node);
395
+
396
+ const trueExpression = node.ifThen;
397
+ const falseExpression = node.ifElse;
398
+
399
+ const path = node.range.source.normalizedPath;
400
+ {
401
+ const trueLc = getLineCol(trueExpression);
402
+ const point = new CoverPoint();
403
+ point.line = trueLc?.line!;
404
+ point.column = trueLc?.column!;
405
+ point.file = path;
406
+ point.type = CoverType.Expression;
407
+
408
+ point.hash = hash(point);
409
+
410
+
411
+ const replacer = new RangeTransform(trueExpression);
412
+
413
+ const registerStmt = SimpleParser.parseTopLevelStatement(
414
+ `__REGISTER({
415
+ file: "${point.file}",
416
+ hash: "${point.hash}",
417
+ line: ${point.line},
418
+ column: ${point.column},
419
+ type: "Expression",
420
+ executed: false
421
+ })`
422
+ );
423
+ replacer.visit(registerStmt);
424
+
425
+ const coverExpression = SimpleParser.parseExpression(
426
+ `(__COVER("${point.hash}"), $$REPLACE_ME)`
427
+ ) as ParenthesizedExpression;
428
+ replacer.visit(coverExpression);
429
+
430
+ (coverExpression.expression as CommaExpression).expressions[1] = trueExpression;
431
+ node.ifThen = coverExpression;
432
+
433
+ this.globalStatements.push(registerStmt);
434
+ }
435
+ {
436
+ const falseLc = getLineCol(falseExpression);
437
+ const point = new CoverPoint();
438
+ point.line = falseLc?.line!;
439
+ point.column = falseLc?.column!;
440
+ point.file = path;
441
+ point.type = CoverType.Expression;
442
+
443
+ point.hash = hash(point);
444
+
445
+
446
+ const replacer = new RangeTransform(falseExpression);
447
+
448
+ const registerStmt = SimpleParser.parseTopLevelStatement(
449
+ `__REGISTER({
450
+ file: "${point.file}",
451
+ hash: "${point.hash}",
452
+ line: ${point.line},
453
+ column: ${point.column},
454
+ type: "Expression",
455
+ executed: false
456
+ })`
457
+ );
458
+ replacer.visit(registerStmt);
459
+
460
+ const coverExpression = SimpleParser.parseExpression(
461
+ `(__COVER("${point.hash}"), $$REPLACE_ME)`
462
+ ) as ParenthesizedExpression;
463
+ replacer.visit(coverExpression);
464
+
465
+ (coverExpression.expression as CommaExpression).expressions[1] = falseExpression;
466
+ node.ifElse = coverExpression;
467
+ this.globalStatements.push(registerStmt);
468
+ }
469
+ }
470
+ visitSwitchCase(node: SwitchCase): void {
471
+ // @ts-ignore
472
+ if (node.visited) return;
473
+ // @ts-ignore
474
+ node.visited = true;
475
+ const path = node.range.source.normalizedPath;
476
+ const caseLc = getLineCol(node);
477
+
478
+ const point = new CoverPoint();
479
+ point.line = caseLc?.line!;
480
+ point.column = caseLc?.column!;
481
+ point.file = path;
482
+ point.type = CoverType.Block;
483
+
484
+ point.hash = hash(point);
485
+
486
+
487
+ const replacer = new RangeTransform(node);
488
+
489
+ const registerStmt = SimpleParser.parseTopLevelStatement(
490
+ `__REGISTER({
491
+ file: "${point.file}",
492
+ hash: "${point.hash}",
493
+ line: ${point.line},
494
+ column: ${point.column},
495
+ type: "Block",
496
+ executed: false
497
+ })`
498
+ );
499
+ replacer.visit(registerStmt);
500
+
501
+ const coverStmt = SimpleParser.parseStatement(
502
+ `__COVER("${point.hash}")`
503
+ );
504
+ replacer.visit(coverStmt);
505
+
506
+ this.globalStatements.push(registerStmt);
507
+ super.visitSwitchCase(node);
508
+ node.statements.unshift(coverStmt);
509
+ }
510
+ visitBlockStatement(node: BlockStatement): void {
511
+ // @ts-ignore
512
+ if (node.visited) return;
513
+ // @ts-ignore
514
+ node.visited = true;
515
+ const path = node.range.source.normalizedPath;
516
+
517
+ const blockLc = getLineCol(node);
518
+
519
+ const point = new CoverPoint();
520
+ point.line = blockLc?.line!;
521
+ point.column = blockLc?.column!;
522
+ point.file = path;
523
+ point.type = CoverType.Block;
524
+
525
+ point.hash = hash(point);
526
+
527
+
528
+ const replacer = new RangeTransform(node);
529
+
530
+ const registerStmt = SimpleParser.parseTopLevelStatement(
531
+ `__REGISTER({
532
+ file: "${point.file}",
533
+ hash: "${point.hash}",
534
+ line: ${point.line},
535
+ column: ${point.column},
536
+ type: "Block",
537
+ executed: false
538
+ })`
539
+ );
540
+ replacer.visit(registerStmt);
541
+
542
+ const coverStmt = SimpleParser.parseStatement(
543
+ `__COVER("${point.hash}")`
544
+ );
545
+ replacer.visit(coverStmt);
546
+
547
+ this.globalStatements.push(registerStmt);
548
+ super.visitBlockStatement(node);
549
+ node.statements.unshift(coverStmt);
550
+ }
551
+ visitSource(node: Source): void {
552
+ super.visitSource(node);
553
+ }
554
+ }
555
+
556
+ class CoverageFinder extends BaseVisitor {
557
+ visitObjectLiteralExpression(node: ObjectLiteralExpression): void {
558
+ for (let i = 0; i < node.names.length; i++) {
559
+ const name = node.names[i]!;
560
+ if (name.text === "coverage") {
561
+ const v = node.values[i]!;
562
+ if (v.kind === NodeKind.True) {
563
+ ENABLED = true;
564
+ }
565
+ }
566
+ }
567
+ }
568
+ }
569
+
570
+ export default class Transformer extends Transform {
571
+ // Trigger the transform after parse.
572
+ afterParse(parser: Parser): void {
573
+ for (const source of parser.sources) {
574
+ if (source.sourceKind === SourceKind.UserEntry) {
575
+ const transform = new CoverageFinder();
576
+ transform.visit(source);
577
+ }
578
+ }
579
+ if (!ENABLED) return;
580
+
581
+ // Create new transform
582
+ const transformer = new CoverageTransform();
583
+
584
+ // Sort the sources so that user scripts are visited last
585
+ const sources = parser.sources
586
+ .filter((source) => !isStdlib(source))
587
+ .sort((_a, _b) => {
588
+ const a = _a.internalPath;
589
+ const b = _b.internalPath;
590
+ if (a[0] === "~" && b[0] !== "~") {
591
+ return -1;
592
+ } else if (a[0] !== "~" && b[0] === "~") {
593
+ return 1;
594
+ } else {
595
+ return 0;
596
+ }
597
+ });
598
+
599
+ // Loop over every source
600
+ for (const source of sources) {
601
+ if (source.isLibrary) continue;
602
+ if (source.simplePath === "coverage") continue;
603
+ // Ignore all lib and std. Visit everything else.
604
+ if (!isStdlib(source)) {
605
+ transformer.visit(source);
606
+ if (transformer.globalStatements.length) {
607
+ source.statements.unshift(...transformer.globalStatements);
608
+ const tokenizer = new Tokenizer(
609
+ new Source(
610
+ SourceKind.User,
611
+ source.normalizedPath,
612
+ "import { __REGISTER, __COVER } from \"as-test/assembly/coverage\";"
613
+ )
614
+ );
615
+ parser.currentSource = tokenizer.source;
616
+ source.statements.unshift(parser.parseTopLevelStatement(tokenizer)!);
617
+ parser.currentSource = source;
618
+ }
619
+ }
620
+ transformer.globalStatements = [];
621
+ }
622
+ }
623
+ }
624
+
625
+ /**
626
+ * A simple djb2hash that returns a hash of a given string. See http://www.cse.yorku.ca/~oz/hash.html
627
+ * for implementation details.
628
+ *
629
+ * @param {string} str - The string to be hashed
630
+ * @returns {number} The hash of the string
631
+ */
632
+ function djb2Hash(str: string): number {
633
+ const points = Array.from(str);
634
+ let h = 5381;
635
+ for (let p = 0; p < points.length; p++)
636
+ // h = (h * 31 + c) | 0;
637
+ h = ((h << 5) - h + points[p]!.codePointAt(0)!) | 0;
638
+ return h;
639
+ }
640
+
641
+ function hash(point: CoverPoint): string {
642
+ const hsh = djb2Hash(point.file + point.line.toString() + point.column.toString() + point.type.toString());
643
+ if (hsh < 0) {
644
+ const out = hsh.toString(16);
645
+ return "3" + out.slice(1);
646
+ } else {
647
+ return hsh.toString(16);
648
+ }
649
+ }
650
+
651
+ class LineColumn {
652
+ public line!: number;
653
+ public column!: number;
654
+ }
655
+
656
+ function getLineCol(node: Node): LineColumn {
657
+ return {
658
+ line: node.range.source.lineAt(node.range.start),
659
+ column: node.range.source.columnAt()
660
+ }
661
+ }