@as-pect/transform 8.1.0 → 9.0.0
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.
- package/README.md +58 -7
- package/lib/ClassReflectionTransform.js +134 -0
- package/lib/appendGeneratedClassReflectionMembers.js +30 -0
- package/lib/astHelpers.js +21 -0
- package/lib/createAddReflectedValueKeyValuePairsMember.js +16 -44
- package/lib/createInheritedIgnoreListExpression.js +11 -0
- package/lib/createInterfaceReflectionMembers.js +25 -0
- package/lib/createStrictEqualsMember.js +28 -72
- package/lib/index.js +7 -8
- package/lib/src/createAddReflectedValueKeyValuePairsMember.js +176 -0
- package/lib/src/createGenericTypeParameter.js +10 -0
- package/lib/src/createStrictEqualsMember.js +187 -0
- package/lib/src/emptyTransformer.js +8 -0
- package/lib/src/hash.js +15 -0
- package/lib/src/index.js +37 -0
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -1,11 +1,62 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@as-pect/transform`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
AssemblyScript transform for `as-pect` reflection support.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This package implements the Class reflection transform. It injects the generated class reflection shape that the runtime helpers in `@as-pect/assembly` depend on when tests compare class instances or print reflected object values.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
const transform = require('transform');
|
|
7
|
+
## Generated shape
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
For each class declaration found in source files and namespaces, the transform appends two public instance methods:
|
|
10
|
+
|
|
11
|
+
- `__aspectStrictEquals(rawRef: Object, stack: usize[], cache: usize[], ignore: StaticArray<i64>): bool`
|
|
12
|
+
- `__aspectAddReflectedValueKeyValuePairs(reflectedValue: i32, seen: Map<usize, i32>, ignore: StaticArray<i64>): void`
|
|
13
|
+
|
|
14
|
+
These method names are compatibility-sensitive. `packages/assembly/assembly/internal/Reflect.ts` calls them directly when it cannot use a built-in comparison or reflected-value path.
|
|
15
|
+
|
|
16
|
+
## ClassReflectionTransform
|
|
17
|
+
|
|
18
|
+
`ClassReflectionTransform` is the module that owns the shared Class-member plan for generated class reflection behavior. It inspects each class once and records the instance members that should participate in both strict equality and reflected key/value generation.
|
|
19
|
+
|
|
20
|
+
The member plan includes, in source order:
|
|
21
|
+
|
|
22
|
+
- instance fields
|
|
23
|
+
- instance getters
|
|
24
|
+
|
|
25
|
+
The member plan excludes:
|
|
26
|
+
|
|
27
|
+
- static members
|
|
28
|
+
- regular instance methods
|
|
29
|
+
- inherited members, which are handled by generated `super` calls
|
|
30
|
+
|
|
31
|
+
Each planned member carries its name, source range, getter/field kind, and `djb2` name hash. Generated methods pass these hashes through `ignore: StaticArray<i64>` when calling `super` so inherited generated methods do not duplicate members overridden by a child class.
|
|
32
|
+
|
|
33
|
+
Hash values intentionally remain the compatibility seam with existing generated code. If two inherited member names ever collide under `djb2`, the generated ignore list treats them as the same inherited member and suppresses the parent entry deterministically; the transform does not rename members or attempt a runtime fallback.
|
|
34
|
+
|
|
35
|
+
## Generation responsibilities
|
|
36
|
+
|
|
37
|
+
Class reflection generation is split across a few narrow responsibilities:
|
|
38
|
+
|
|
39
|
+
- `index.ts` walks parsed sources and namespaces, then delegates class and interface declarations to the generation module.
|
|
40
|
+
- `appendGeneratedClassReflectionMembers.ts` orchestrates which generated members or interface contracts should be appended, including idempotence checks and user-authored collision errors.
|
|
41
|
+
- `ClassReflectionTransform.ts` owns compatibility-sensitive generated member names, generated-member marking, class/interface collision checks, local equality-operator detection, and the Class-member plan.
|
|
42
|
+
- `createStrictEqualsMember.ts`, `createAddReflectedValueKeyValuePairsMember.ts`, and `createInterfaceReflectionMembers.ts` build the generated AssemblyScript AST for each runtime method shape.
|
|
43
|
+
|
|
44
|
+
This keeps traversal, planning, collision policy, and AST construction separate enough that a change to one generated method does not require re-learning every transform rule.
|
|
45
|
+
|
|
46
|
+
## Runtime relationship
|
|
47
|
+
|
|
48
|
+
`@as-pect/assembly` uses the generated methods in two places:
|
|
49
|
+
|
|
50
|
+
1. `Reflect.equals()` calls `__aspectStrictEquals` to compare class instances structurally.
|
|
51
|
+
2. `Reflect.toReflectedValue()` calls `__aspectAddReflectedValueKeyValuePairs` to add reflected object keys and values.
|
|
52
|
+
|
|
53
|
+
The transform and runtime therefore share a seam: the transform owns the generated method shape, and the runtime assumes that shape exists after compilation with `@as-pect/transform`.
|
|
54
|
+
|
|
55
|
+
## Compatibility expectations
|
|
56
|
+
|
|
57
|
+
- Keep both generated method names unchanged unless a coordinated runtime migration is planned.
|
|
58
|
+
- Keep the `ignore` hash behavior stable so inherited and overridden members are not reported twice.
|
|
59
|
+
- Keep generated behavior dependency-free; this package should remain a small local transform.
|
|
60
|
+
- Preserve source order when adding reflected/equality-relevant members so reporter output remains stable.
|
|
61
|
+
- Keep transform generation idempotent for the same parsed source; repeated passes should not append duplicate generated members.
|
|
62
|
+
- Reject user-authored `__aspectStrictEquals` or `__aspectAddReflectedValueKeyValuePairs` members with a clear transform error instead of silently overwriting them.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { djb2Hash } from "./hash.js";
|
|
2
|
+
/** The generated method runtime reflection calls for structural equality. */
|
|
3
|
+
export const STRICT_EQUALS_MEMBER_NAME = "__aspectStrictEquals";
|
|
4
|
+
/** The generated method runtime reflection calls to enumerate reflected object members. */
|
|
5
|
+
export const ADD_REFLECTED_VALUE_KEY_VALUE_PAIRS_MEMBER_NAME = "__aspectAddReflectedValueKeyValuePairs";
|
|
6
|
+
/** The generated marker inherited by classes that should defer strict equality to @operator("=="). */
|
|
7
|
+
export const HAS_EQ_OPERATOR_MEMBER_NAME = "__aspectHasEqOperator";
|
|
8
|
+
const generatedClassReflectionMembers = new WeakSet();
|
|
9
|
+
/** Mark a method declaration as generated by this transform pass. */
|
|
10
|
+
export function markGeneratedClassReflectionMember(methodDeclaration) {
|
|
11
|
+
generatedClassReflectionMembers.add(methodDeclaration);
|
|
12
|
+
return methodDeclaration;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Decide whether a generated class reflection member can be appended.
|
|
16
|
+
*
|
|
17
|
+
* The same parsed source can be passed through the transform more than once by
|
|
18
|
+
* tests, tooling, or recovery flows. Members that were generated by this module
|
|
19
|
+
* are skipped on later passes; user-authored collisions are rejected so runtime
|
|
20
|
+
* reflection does not silently call a method with an incompatible signature.
|
|
21
|
+
*/
|
|
22
|
+
export function shouldGenerateClassReflectionMember(classDeclaration, memberName) {
|
|
23
|
+
return shouldGenerateReflectionMember(classDeclaration, "class", memberName);
|
|
24
|
+
}
|
|
25
|
+
/** Decide whether a generated interface reflection contract can be appended. */
|
|
26
|
+
export function shouldGenerateInterfaceReflectionMember(interfaceDeclaration, memberName) {
|
|
27
|
+
return shouldGenerateReflectionMember(interfaceDeclaration, "interface", memberName);
|
|
28
|
+
}
|
|
29
|
+
function shouldGenerateReflectionMember(declaration, declarationKind, memberName) {
|
|
30
|
+
const existingMember = findMethod(declaration, memberName);
|
|
31
|
+
if (!existingMember)
|
|
32
|
+
return true;
|
|
33
|
+
if (generatedClassReflectionMembers.has(existingMember))
|
|
34
|
+
return false;
|
|
35
|
+
throw new Error(`Cannot generate ${memberName} for ${declarationKind} ${declaration.name.text} because that member already exists.`);
|
|
36
|
+
}
|
|
37
|
+
function findMethod(declaration, memberName) {
|
|
38
|
+
for (const member of declaration.members) {
|
|
39
|
+
if (member.kind !== 59 /* NodeKind.MethodDeclaration */)
|
|
40
|
+
continue;
|
|
41
|
+
const methodDeclaration = member;
|
|
42
|
+
if (methodDeclaration.name.text === memberName)
|
|
43
|
+
return methodDeclaration;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
/** The kind of class member included in the class reflection member plan. */
|
|
48
|
+
export var ClassReflectionMemberKind;
|
|
49
|
+
(function (ClassReflectionMemberKind) {
|
|
50
|
+
ClassReflectionMemberKind["Field"] = "field";
|
|
51
|
+
ClassReflectionMemberKind["Getter"] = "getter";
|
|
52
|
+
})(ClassReflectionMemberKind || (ClassReflectionMemberKind = {}));
|
|
53
|
+
/** Return true when the class declares a local AssemblyScript equality operator overload. */
|
|
54
|
+
export function hasLocalEqualsOperator(classDeclaration) {
|
|
55
|
+
for (const member of classDeclaration.members) {
|
|
56
|
+
if (member.kind !== 59 /* NodeKind.MethodDeclaration */)
|
|
57
|
+
continue;
|
|
58
|
+
const methodDeclaration = member;
|
|
59
|
+
if (!methodDeclaration.decorators)
|
|
60
|
+
continue;
|
|
61
|
+
for (const decorator of methodDeclaration.decorators) {
|
|
62
|
+
if (!decorator.args || decorator.args.length === 0)
|
|
63
|
+
continue;
|
|
64
|
+
if (!isOperatorDecoratorName(decorator.name))
|
|
65
|
+
continue;
|
|
66
|
+
const operatorName = decorator.args[0];
|
|
67
|
+
if (!operatorName)
|
|
68
|
+
continue;
|
|
69
|
+
if (operatorName.kind !== 17 /* NodeKind.Literal */)
|
|
70
|
+
continue;
|
|
71
|
+
if (operatorName.literalKind !== 2 /* LiteralKind.String */)
|
|
72
|
+
continue;
|
|
73
|
+
if (operatorName.value === "==")
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
function isOperatorDecoratorName(name) {
|
|
80
|
+
if (name.kind === 7 /* NodeKind.Identifier */)
|
|
81
|
+
return name.text === "operator";
|
|
82
|
+
if (name.kind === 22 /* NodeKind.PropertyAccess */) {
|
|
83
|
+
const propertyAccess = name;
|
|
84
|
+
return propertyAccess.expression.kind === 7 /* NodeKind.Identifier */ && propertyAccess.expression.text === "operator";
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Create the shared member plan for class reflection generation.
|
|
90
|
+
*
|
|
91
|
+
* Instance fields and instance getters are included. Other methods, static
|
|
92
|
+
* members, and inherited members are intentionally left out; generated super
|
|
93
|
+
* calls handle inherited members while passing these hashes as the ignore list.
|
|
94
|
+
*
|
|
95
|
+
* @param classDeclaration - The class declaration to inspect.
|
|
96
|
+
*/
|
|
97
|
+
export function createClassReflectionMemberPlan(classDeclaration) {
|
|
98
|
+
const members = new Array();
|
|
99
|
+
for (const member of classDeclaration.members) {
|
|
100
|
+
if (!member.is(262144 /* CommonFlags.Instance */))
|
|
101
|
+
continue;
|
|
102
|
+
switch (member.kind) {
|
|
103
|
+
case 55 /* NodeKind.FieldDeclaration */: {
|
|
104
|
+
const fieldDeclaration = member;
|
|
105
|
+
const name = fieldDeclaration.name.text;
|
|
106
|
+
members.push({
|
|
107
|
+
name,
|
|
108
|
+
hash: djb2Hash(name),
|
|
109
|
+
range: fieldDeclaration.range,
|
|
110
|
+
kind: ClassReflectionMemberKind.Field,
|
|
111
|
+
});
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case 59 /* NodeKind.MethodDeclaration */: {
|
|
115
|
+
if (!member.is(2048 /* CommonFlags.Get */))
|
|
116
|
+
break;
|
|
117
|
+
const methodDeclaration = member;
|
|
118
|
+
const name = methodDeclaration.name.text;
|
|
119
|
+
members.push({
|
|
120
|
+
name,
|
|
121
|
+
hash: djb2Hash(name),
|
|
122
|
+
range: methodDeclaration.name.range,
|
|
123
|
+
kind: ClassReflectionMemberKind.Getter,
|
|
124
|
+
});
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
classDeclaration,
|
|
131
|
+
members,
|
|
132
|
+
hashes: members.map((member) => member.hash),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ADD_REFLECTED_VALUE_KEY_VALUE_PAIRS_MEMBER_NAME, HAS_EQ_OPERATOR_MEMBER_NAME, STRICT_EQUALS_MEMBER_NAME, hasLocalEqualsOperator, markGeneratedClassReflectionMember, shouldGenerateClassReflectionMember, shouldGenerateInterfaceReflectionMember, } from "./ClassReflectionTransform.js";
|
|
2
|
+
import { createAddReflectedValueKeyValuePairsMember } from "./createAddReflectedValueKeyValuePairsMember.js";
|
|
3
|
+
import { createInterfaceAddReflectedValueKeyValuePairsMember, createInterfaceStrictEqualsMember, } from "./createInterfaceReflectionMembers.js";
|
|
4
|
+
import { createHasEqualsOperatorMember, createStrictEqualsMember } from "./createStrictEqualsMember.js";
|
|
5
|
+
/** Append all generated reflection members for a class declaration. */
|
|
6
|
+
export function appendGeneratedClassReflectionMembers(classDeclaration) {
|
|
7
|
+
const shouldGenerateStrictEquals = shouldGenerateClassReflectionMember(classDeclaration, STRICT_EQUALS_MEMBER_NAME);
|
|
8
|
+
const shouldGenerateReflectedPairs = shouldGenerateClassReflectionMember(classDeclaration, ADD_REFLECTED_VALUE_KEY_VALUE_PAIRS_MEMBER_NAME);
|
|
9
|
+
const shouldGenerateEqualsMarker = shouldGenerateClassReflectionMember(classDeclaration, HAS_EQ_OPERATOR_MEMBER_NAME);
|
|
10
|
+
if (hasLocalEqualsOperator(classDeclaration) && shouldGenerateEqualsMarker) {
|
|
11
|
+
classDeclaration.members.push(markGeneratedClassReflectionMember(createHasEqualsOperatorMember(classDeclaration)));
|
|
12
|
+
}
|
|
13
|
+
if (shouldGenerateStrictEquals) {
|
|
14
|
+
classDeclaration.members.push(markGeneratedClassReflectionMember(createStrictEqualsMember(classDeclaration)));
|
|
15
|
+
}
|
|
16
|
+
if (shouldGenerateReflectedPairs) {
|
|
17
|
+
classDeclaration.members.push(markGeneratedClassReflectionMember(createAddReflectedValueKeyValuePairsMember(classDeclaration)));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Append all generated reflection contracts for an interface declaration. */
|
|
21
|
+
export function appendGeneratedInterfaceReflectionMembers(interfaceDeclaration) {
|
|
22
|
+
const shouldGenerateStrictEquals = shouldGenerateInterfaceReflectionMember(interfaceDeclaration, STRICT_EQUALS_MEMBER_NAME);
|
|
23
|
+
const shouldGenerateReflectedPairs = shouldGenerateInterfaceReflectionMember(interfaceDeclaration, ADD_REFLECTED_VALUE_KEY_VALUE_PAIRS_MEMBER_NAME);
|
|
24
|
+
if (shouldGenerateStrictEquals) {
|
|
25
|
+
interfaceDeclaration.members.push(markGeneratedClassReflectionMember(createInterfaceStrictEqualsMember(interfaceDeclaration)));
|
|
26
|
+
}
|
|
27
|
+
if (shouldGenerateReflectedPairs) {
|
|
28
|
+
interfaceDeclaration.members.push(markGeneratedClassReflectionMember(createInterfaceAddReflectedValueKeyValuePairsMember(interfaceDeclaration)));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { TypeNode } from "assemblyscript/dist/assemblyscript.js";
|
|
2
|
+
/** Create a named type without type arguments. */
|
|
3
|
+
export function createSimpleNamedType(name, range) {
|
|
4
|
+
return TypeNode.createNamedType(TypeNode.createSimpleTypeName(name, range), null, false, range);
|
|
5
|
+
}
|
|
6
|
+
/** Create an Array<T> type for generated reflection members. */
|
|
7
|
+
export function createArrayType(elementName, range) {
|
|
8
|
+
return TypeNode.createNamedType(TypeNode.createSimpleTypeName("Array", range), [createSimpleNamedType(elementName, range)], false, range);
|
|
9
|
+
}
|
|
10
|
+
/** Create a StaticArray<T> type for generated reflection members. */
|
|
11
|
+
export function createStaticArrayType(elementName, range) {
|
|
12
|
+
return TypeNode.createNamedType(TypeNode.createSimpleTypeName("StaticArray", range), [createSimpleNamedType(elementName, range)], false, range);
|
|
13
|
+
}
|
|
14
|
+
/** Create a Map<K, V> type for generated reflection members. */
|
|
15
|
+
export function createMapType(keyName, valueName, range) {
|
|
16
|
+
return TypeNode.createNamedType(TypeNode.createSimpleTypeName("Map", range), [createSimpleNamedType(keyName, range), createSimpleNamedType(valueName, range)], false, range);
|
|
17
|
+
}
|
|
18
|
+
/** Create a default parameter with the generated member range. */
|
|
19
|
+
export function createDefaultParameter(name, type, range) {
|
|
20
|
+
return TypeNode.createParameter(0 /* ParameterKind.Default */, TypeNode.createIdentifierExpression(name, range), type, null, range);
|
|
21
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import { ADD_REFLECTED_VALUE_KEY_VALUE_PAIRS_MEMBER_NAME, createClassReflectionMemberPlan, } from "./ClassReflectionTransform.js";
|
|
2
2
|
import { TypeNode, } from "assemblyscript/dist/assemblyscript.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { createDefaultParameter, createMapType, createSimpleNamedType, createStaticArrayType } from "./astHelpers.js";
|
|
4
|
+
import { createInheritedIgnoreListExpression } from "./createInheritedIgnoreListExpression.js";
|
|
5
5
|
// const TypeNode = TypeNode;
|
|
6
6
|
// const {
|
|
7
7
|
// BlockStatement,
|
|
@@ -26,18 +26,16 @@ import { djb2Hash } from "./hash.js";
|
|
|
26
26
|
export function createAddReflectedValueKeyValuePairsMember(classDeclaration) {
|
|
27
27
|
const range = classDeclaration.name.range;
|
|
28
28
|
// __aspectAddReflectedValueKeyValuePairs(reflectedValue: i32, seen: Map<usize, i32>, ignore: StaticArray<i64>): void
|
|
29
|
-
return TypeNode.createMethodDeclaration(TypeNode.createIdentifierExpression(
|
|
29
|
+
return TypeNode.createMethodDeclaration(TypeNode.createIdentifierExpression(ADD_REFLECTED_VALUE_KEY_VALUE_PAIRS_MEMBER_NAME, range), null, 256 /* CommonFlags.Public */ | 262144 /* CommonFlags.Instance */ | (classDeclaration.isGeneric ? 131072 /* CommonFlags.GenericContext */ : 0), null, TypeNode.createFunctionType([
|
|
30
30
|
// reflectedValue: i32
|
|
31
|
-
|
|
31
|
+
createDefaultParameter("reflectedValue", createSimpleNamedType("i32", range), range),
|
|
32
32
|
// seen: Map<usize, i32>
|
|
33
|
-
|
|
33
|
+
createDefaultParameter("seen", createMapType("usize", "i32", range), range),
|
|
34
34
|
// ignore: i64[]
|
|
35
|
-
|
|
36
|
-
// Array<i64> -> i64[]
|
|
37
|
-
TypeNode.createNamedType(TypeNode.createSimpleTypeName("StaticArray", range), [createGenericTypeParameter("i64", range)], false, range), null, range),
|
|
35
|
+
createDefaultParameter("ignore", createStaticArrayType("i64", range), range),
|
|
38
36
|
],
|
|
39
37
|
// : void
|
|
40
|
-
|
|
38
|
+
createSimpleNamedType("void", range), null, false, range), createAddReflectedValueKeyValuePairsFunctionBody(classDeclaration), range);
|
|
41
39
|
}
|
|
42
40
|
/**
|
|
43
41
|
* Iterate over a given ClassDeclaration and return a block statement that contains the
|
|
@@ -48,35 +46,13 @@ export function createAddReflectedValueKeyValuePairsMember(classDeclaration) {
|
|
|
48
46
|
function createAddReflectedValueKeyValuePairsFunctionBody(classDeclaration) {
|
|
49
47
|
const body = new Array();
|
|
50
48
|
const range = classDeclaration.name.range;
|
|
51
|
-
const
|
|
52
|
-
// for each
|
|
53
|
-
for (const member of
|
|
54
|
-
|
|
55
|
-
if (member.is(262144 /* CommonFlags.Instance */)) {
|
|
56
|
-
switch (member.kind) {
|
|
57
|
-
// field declarations automatically get added
|
|
58
|
-
case 54 /* NodeKind.FieldDeclaration */: {
|
|
59
|
-
const fieldDeclaration = member;
|
|
60
|
-
const hashValue = djb2Hash(member.name.text);
|
|
61
|
-
pushKeyValueIfStatement(body, member.name.text, hashValue, fieldDeclaration.range);
|
|
62
|
-
nameHashes.push(hashValue);
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
// function declarations can be getters, check the get flag
|
|
66
|
-
case 58 /* NodeKind.MethodDeclaration */: {
|
|
67
|
-
if (member.is(2048 /* CommonFlags.Get */)) {
|
|
68
|
-
const methodDeclaration = member;
|
|
69
|
-
const hashValue = djb2Hash(member.name.text);
|
|
70
|
-
pushKeyValueIfStatement(body, member.name.text, hashValue, methodDeclaration.range);
|
|
71
|
-
nameHashes.push(hashValue);
|
|
72
|
-
}
|
|
73
|
-
break;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
49
|
+
const memberPlan = createClassReflectionMemberPlan(classDeclaration);
|
|
50
|
+
// Add key/value reflection for each reflected/equality-relevant class member.
|
|
51
|
+
for (const member of memberPlan.members) {
|
|
52
|
+
pushKeyValueIfStatement(body, member.name, member.hash, member.range);
|
|
77
53
|
}
|
|
78
54
|
// call into super first after all the property checks have been added
|
|
79
|
-
body.unshift(createIsDefinedIfStatement(
|
|
55
|
+
body.unshift(createIsDefinedIfStatement(memberPlan.hashes, range));
|
|
80
56
|
return TypeNode.createBlockStatement(body, range);
|
|
81
57
|
}
|
|
82
58
|
/**
|
|
@@ -93,21 +69,17 @@ function createIsDefinedIfStatement(nameHashes, range) {
|
|
|
93
69
|
// isDefined(super.__aspectAddReflectedValueKeyValuePairs)
|
|
94
70
|
TypeNode.createCallExpression(TypeNode.createIdentifierExpression("isDefined", range), null, [
|
|
95
71
|
// super.__aspectAddReflectedValueKeyValuePairs
|
|
96
|
-
TypeNode.createPropertyAccessExpression(TypeNode.createSuperExpression(range), TypeNode.createIdentifierExpression(
|
|
72
|
+
TypeNode.createPropertyAccessExpression(TypeNode.createSuperExpression(range), TypeNode.createIdentifierExpression(ADD_REFLECTED_VALUE_KEY_VALUE_PAIRS_MEMBER_NAME, range), range),
|
|
97
73
|
], range), TypeNode.createBlockStatement([
|
|
98
74
|
TypeNode.createExpressionStatement(
|
|
99
75
|
// super.__aspectAddReflectedValueKeyValuePairs(reflectedValue, seen, StaticArray.concat(ignore, [...] as StaticArray<i64>))
|
|
100
|
-
TypeNode.createCallExpression(TypeNode.createPropertyAccessExpression(TypeNode.createSuperExpression(range), TypeNode.createIdentifierExpression(
|
|
76
|
+
TypeNode.createCallExpression(TypeNode.createPropertyAccessExpression(TypeNode.createSuperExpression(range), TypeNode.createIdentifierExpression(ADD_REFLECTED_VALUE_KEY_VALUE_PAIRS_MEMBER_NAME, range), range), null, [
|
|
101
77
|
// reflectedValue,
|
|
102
78
|
TypeNode.createIdentifierExpression("reflectedValue", range),
|
|
103
79
|
// seen,
|
|
104
80
|
TypeNode.createIdentifierExpression("seen", range),
|
|
105
81
|
// StaticArray.concat(ignore, [...])
|
|
106
|
-
|
|
107
|
-
TypeNode.createIdentifierExpression("ignore", range),
|
|
108
|
-
// [...propNames]
|
|
109
|
-
TypeNode.createAssertionExpression(1 /* AssertionKind.As */, TypeNode.createArrayLiteralExpression(nameHashes.map((e) => TypeNode.createIntegerLiteralExpression(f64_as_i64(e), range)), range), TypeNode.createNamedType(TypeNode.createSimpleTypeName("StaticArray", range), [TypeNode.createNamedType(TypeNode.createSimpleTypeName("i64", range), null, false, range)], false, range), range),
|
|
110
|
-
], range),
|
|
82
|
+
createInheritedIgnoreListExpression(nameHashes, range),
|
|
111
83
|
], range)),
|
|
112
84
|
], range), null, range);
|
|
113
85
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TypeNode } from "assemblyscript/dist/assemblyscript.js";
|
|
2
|
+
import { createStaticArrayType } from "./astHelpers.js";
|
|
3
|
+
/**
|
|
4
|
+
* Create `StaticArray.concat(ignore, [...hashes] as StaticArray<i64>)` for inherited member suppression.
|
|
5
|
+
*/
|
|
6
|
+
export function createInheritedIgnoreListExpression(nameHashes, range) {
|
|
7
|
+
return TypeNode.createCallExpression(TypeNode.createPropertyAccessExpression(TypeNode.createIdentifierExpression("StaticArray", range), TypeNode.createIdentifierExpression("concat", range), range), null, [
|
|
8
|
+
TypeNode.createIdentifierExpression("ignore", range),
|
|
9
|
+
TypeNode.createAssertionExpression(1 /* AssertionKind.As */, TypeNode.createArrayLiteralExpression(nameHashes.map((hash) => TypeNode.createIntegerLiteralExpression(f64_as_i64(hash), range)), range), createStaticArrayType("i64", range), range),
|
|
10
|
+
], range);
|
|
11
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { TypeNode } from "assemblyscript/dist/assemblyscript.js";
|
|
2
|
+
import { ADD_REFLECTED_VALUE_KEY_VALUE_PAIRS_MEMBER_NAME, STRICT_EQUALS_MEMBER_NAME, } from "./ClassReflectionTransform.js";
|
|
3
|
+
import { createArrayType, createDefaultParameter, createMapType, createSimpleNamedType, createStaticArrayType, } from "./astHelpers.js";
|
|
4
|
+
/** Create the interface contract consumed by Reflect.equals for structural equality. */
|
|
5
|
+
export function createInterfaceStrictEqualsMember(interfaceDeclaration) {
|
|
6
|
+
const range = interfaceDeclaration.name.range;
|
|
7
|
+
return TypeNode.createMethodDeclaration(TypeNode.createIdentifierExpression(STRICT_EQUALS_MEMBER_NAME, range), null, createInterfaceMemberFlags(interfaceDeclaration), null, TypeNode.createFunctionType([
|
|
8
|
+
createDefaultParameter("rawRef", createSimpleNamedType("Object", range), range),
|
|
9
|
+
createDefaultParameter("stack", createArrayType("usize", range), range),
|
|
10
|
+
createDefaultParameter("cache", createArrayType("usize", range), range),
|
|
11
|
+
createDefaultParameter("ignore", createStaticArrayType("i64", range), range),
|
|
12
|
+
], createSimpleNamedType("bool", range), null, false, range), null, range);
|
|
13
|
+
}
|
|
14
|
+
/** Create the interface contract consumed by Reflect.toReflectedValue for member reporting. */
|
|
15
|
+
export function createInterfaceAddReflectedValueKeyValuePairsMember(interfaceDeclaration) {
|
|
16
|
+
const range = interfaceDeclaration.name.range;
|
|
17
|
+
return TypeNode.createMethodDeclaration(TypeNode.createIdentifierExpression(ADD_REFLECTED_VALUE_KEY_VALUE_PAIRS_MEMBER_NAME, range), null, createInterfaceMemberFlags(interfaceDeclaration), null, TypeNode.createFunctionType([
|
|
18
|
+
createDefaultParameter("reflectedValue", createSimpleNamedType("i32", range), range),
|
|
19
|
+
createDefaultParameter("seen", createMapType("usize", "i32", range), range),
|
|
20
|
+
createDefaultParameter("ignore", createStaticArrayType("i64", range), range),
|
|
21
|
+
], createSimpleNamedType("void", range), null, false, range), null, range);
|
|
22
|
+
}
|
|
23
|
+
function createInterfaceMemberFlags(interfaceDeclaration) {
|
|
24
|
+
return 256 /* CommonFlags.Public */ | 262144 /* CommonFlags.Instance */ | (interfaceDeclaration.isGeneric ? 131072 /* CommonFlags.GenericContext */ : 0);
|
|
25
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HAS_EQ_OPERATOR_MEMBER_NAME, STRICT_EQUALS_MEMBER_NAME, createClassReflectionMemberPlan, } from "./ClassReflectionTransform.js";
|
|
2
2
|
import { TypeNode, } from "assemblyscript/dist/assemblyscript.js";
|
|
3
|
+
import { createArrayType, createDefaultParameter, createSimpleNamedType, createStaticArrayType } from "./astHelpers.js";
|
|
4
|
+
import { createInheritedIgnoreListExpression } from "./createInheritedIgnoreListExpression.js";
|
|
3
5
|
/**
|
|
4
6
|
* This method creates a single FunctionDeclaration that allows Reflect.equals
|
|
5
7
|
* to validate normal class member values.
|
|
@@ -9,35 +11,24 @@ import { TypeNode, } from "assemblyscript/dist/assemblyscript.js";
|
|
|
9
11
|
export function createStrictEqualsMember(classDeclaration) {
|
|
10
12
|
const range = classDeclaration.name.range;
|
|
11
13
|
// __aspectStrictEquals(rawRef: Object, stackA: usize[], stackB: usize[], ignore: StaticArray<i64>): bool
|
|
12
|
-
return TypeNode.createMethodDeclaration(TypeNode.createIdentifierExpression(
|
|
14
|
+
return TypeNode.createMethodDeclaration(TypeNode.createIdentifierExpression(STRICT_EQUALS_MEMBER_NAME, range), null, 256 /* CommonFlags.Public */ | 262144 /* CommonFlags.Instance */ | (classDeclaration.isGeneric ? 131072 /* CommonFlags.GenericContext */ : 0), null, TypeNode.createFunctionType([
|
|
13
15
|
// rawRef: Object,
|
|
14
|
-
createDefaultParameter("rawRef",
|
|
16
|
+
createDefaultParameter("rawRef", createSimpleNamedType("Object", range), range),
|
|
15
17
|
// stack: usize[]
|
|
16
18
|
createDefaultParameter("stack", createArrayType("usize", range), range),
|
|
17
19
|
// cache: usize[]
|
|
18
20
|
createDefaultParameter("cache", createArrayType("usize", range), range),
|
|
19
21
|
// ignore: StaticArray<i64>
|
|
20
|
-
createDefaultParameter("ignore",
|
|
22
|
+
createDefaultParameter("ignore", createStaticArrayType("i64", range), range),
|
|
21
23
|
],
|
|
22
24
|
// : bool
|
|
23
25
|
createSimpleNamedType("bool", range), null, false, range), createStrictEqualsFunctionBody(classDeclaration), range);
|
|
24
26
|
}
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
*/
|
|
31
|
-
function createSimpleNamedType(name, range) {
|
|
32
|
-
return TypeNode.createNamedType(TypeNode.createSimpleTypeName(name, range), null, false, range);
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* This method creates an Array<name> type with the given range.
|
|
36
|
-
*
|
|
37
|
-
* @param {Range} range - The source range.
|
|
38
|
-
*/
|
|
39
|
-
function createArrayType(name, range) {
|
|
40
|
-
return TypeNode.createNamedType(TypeNode.createSimpleTypeName("Array", range), [TypeNode.createNamedType(TypeNode.createSimpleTypeName(name, range), null, false, range)], false, range);
|
|
27
|
+
/** Create the inherited marker used to tell strict equality to defer to @operator("=="). */
|
|
28
|
+
export function createHasEqualsOperatorMember(classDeclaration) {
|
|
29
|
+
const range = classDeclaration.name.range;
|
|
30
|
+
// protected __aspectHasEqOperator(): bool { return true; }
|
|
31
|
+
return TypeNode.createMethodDeclaration(TypeNode.createIdentifierExpression(HAS_EQ_OPERATOR_MEMBER_NAME, range), null, 1024 /* CommonFlags.Protected */ | 262144 /* CommonFlags.Instance */ | (classDeclaration.isGeneric ? 131072 /* CommonFlags.GenericContext */ : 0), null, TypeNode.createFunctionType([], createSimpleNamedType("bool", range), null, false, range), TypeNode.createBlockStatement([TypeNode.createReturnStatement(TypeNode.createTrueExpression(range), range)], range), range);
|
|
41
32
|
}
|
|
42
33
|
/**
|
|
43
34
|
* This method creates the entire function body for __aspectStrictEquals.
|
|
@@ -47,7 +38,7 @@ function createArrayType(name, range) {
|
|
|
47
38
|
function createStrictEqualsFunctionBody(classDeclaration) {
|
|
48
39
|
const body = new Array();
|
|
49
40
|
const range = classDeclaration.name.range;
|
|
50
|
-
const
|
|
41
|
+
const memberPlan = createClassReflectionMemberPlan(classDeclaration);
|
|
51
42
|
const rawRef = TypeNode.createIdentifierExpression("rawRef", range);
|
|
52
43
|
const classType = TypeNode.createNamedType(TypeNode.createSimpleTypeName(classDeclaration.name.text, range), classDeclaration.isGeneric
|
|
53
44
|
? classDeclaration.typeParameters.map((node) => TypeNode.createNamedType(TypeNode.createSimpleTypeName(node.name.text, range), null, false, range))
|
|
@@ -56,47 +47,26 @@ function createStrictEqualsFunctionBody(classDeclaration) {
|
|
|
56
47
|
body.push(TypeNode.createIfStatement(TypeNode.createUnaryPrefixExpression(95 /* Token.Exclamation */, TypeNode.createInstanceOfExpression(rawRef, classType, range), range), TypeNode.createReturnStatement(TypeNode.createFalseExpression(range), range), null, range));
|
|
57
48
|
// Cast rawRef into an instance of the class
|
|
58
49
|
body.push(TypeNode.createVariableStatement(null, [
|
|
59
|
-
TypeNode.createVariableDeclaration(TypeNode.createIdentifierExpression("ref", range), null, 8 /* CommonFlags.Const */, classType, TypeNode.createAssertionExpression(1 /* AssertionKind.As */, rawRef, classType, range), range)
|
|
50
|
+
TypeNode.createVariableDeclaration(TypeNode.createIdentifierExpression("ref", range), null, 8 /* CommonFlags.Const */, classType, TypeNode.createAssertionExpression(1 /* AssertionKind.As */, rawRef, classType, range), range),
|
|
60
51
|
], range));
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// field declarations automatically get added
|
|
67
|
-
case 54 /* NodeKind.FieldDeclaration */: {
|
|
68
|
-
const fieldDeclaration = member;
|
|
69
|
-
const hashValue = djb2Hash(member.name.text);
|
|
70
|
-
body.push(createStrictEqualsIfCheck(member.name.text, hashValue, fieldDeclaration.range));
|
|
71
|
-
nameHashes.push(hashValue);
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
// function declarations can be getters, check the get flag
|
|
75
|
-
case 58 /* NodeKind.MethodDeclaration */: {
|
|
76
|
-
if (member.is(2048 /* CommonFlags.Get */)) {
|
|
77
|
-
const methodDeclaration = member;
|
|
78
|
-
const hashValue = djb2Hash(member.name.text);
|
|
79
|
-
body.push(createStrictEqualsIfCheck(methodDeclaration.name.text, hashValue, methodDeclaration.name.range));
|
|
80
|
-
nameHashes.push(hashValue);
|
|
81
|
-
}
|
|
82
|
-
break;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
52
|
+
// If this class, or a parent class, defines @operator("=="), strict equality uses that operator as authoritative.
|
|
53
|
+
body.push(createEqualsOperatorDelegationStatement(range));
|
|
54
|
+
// Generate a check for each reflected/equality-relevant class member.
|
|
55
|
+
for (const member of memberPlan.members) {
|
|
56
|
+
body.push(createStrictEqualsIfCheck(member.name, member.hash, member.range));
|
|
86
57
|
}
|
|
87
58
|
// if (isDefined(...)) super.__aspectStrictEquals(ref, stack, cache, ignore.concat([...props]));
|
|
88
|
-
body.push(createSuperCallStatement(classDeclaration,
|
|
59
|
+
body.push(createSuperCallStatement(classDeclaration, memberPlan.hashes));
|
|
89
60
|
// return true;
|
|
90
61
|
body.push(TypeNode.createReturnStatement(TypeNode.createTrueExpression(range), range));
|
|
91
62
|
return TypeNode.createBlockStatement(body, range);
|
|
92
63
|
}
|
|
93
|
-
/**
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
*/
|
|
64
|
+
/** Create the optional fast path that lets user-defined equality decide strict equality. */
|
|
65
|
+
function createEqualsOperatorDelegationStatement(range) {
|
|
66
|
+
return TypeNode.createIfStatement(TypeNode.createCallExpression(TypeNode.createIdentifierExpression("isDefined", range), null, [
|
|
67
|
+
TypeNode.createPropertyAccessExpression(TypeNode.createThisExpression(range), TypeNode.createIdentifierExpression(HAS_EQ_OPERATOR_MEMBER_NAME, range), range),
|
|
68
|
+
], range), TypeNode.createReturnStatement(TypeNode.createBinaryExpression(76 /* Token.Equals_Equals */, TypeNode.createThisExpression(range), TypeNode.createIdentifierExpression("ref", range), range), range), null, range);
|
|
69
|
+
}
|
|
100
70
|
function createStrictEqualsIfCheck(name, hashValue, range) {
|
|
101
71
|
const equalsCheck = TypeNode.createBinaryExpression(76 /* Token.Equals_Equals */,
|
|
102
72
|
// Reflect.equals(this.prop, ref.prop, stack, cache)
|
|
@@ -129,16 +99,6 @@ function createStrictEqualsIfCheck(name, hashValue, range) {
|
|
|
129
99
|
// return false;
|
|
130
100
|
TypeNode.createReturnStatement(TypeNode.createFalseExpression(range), range), null, range);
|
|
131
101
|
}
|
|
132
|
-
/**
|
|
133
|
-
* Create a simple default parameter with a name and a type.
|
|
134
|
-
*
|
|
135
|
-
* @param {string} name - The name of the parameter.
|
|
136
|
-
* @param {TypeNode} typeNode - The type of the parameter.
|
|
137
|
-
* @param {Range} range - The source range of the parameter.
|
|
138
|
-
*/
|
|
139
|
-
function createDefaultParameter(name, typeNode, range) {
|
|
140
|
-
return TypeNode.createParameter(0 /* ParameterKind.Default */, TypeNode.createIdentifierExpression(name, range), typeNode, null, range);
|
|
141
|
-
}
|
|
142
102
|
/**
|
|
143
103
|
* This method creates a single property access and passes the given range to the AST.
|
|
144
104
|
*
|
|
@@ -160,7 +120,7 @@ function createPropertyAccess(root, property, range) {
|
|
|
160
120
|
function createSuperCallStatement(classDeclaration, nameHashes) {
|
|
161
121
|
const range = classDeclaration.name.range;
|
|
162
122
|
const ifStatement = TypeNode.createIfStatement(TypeNode.createCallExpression(TypeNode.createIdentifierExpression("isDefined", range), null, [
|
|
163
|
-
TypeNode.createPropertyAccessExpression(TypeNode.createSuperExpression(range), TypeNode.createIdentifierExpression(
|
|
123
|
+
TypeNode.createPropertyAccessExpression(TypeNode.createSuperExpression(range), TypeNode.createIdentifierExpression(STRICT_EQUALS_MEMBER_NAME, range), range),
|
|
164
124
|
], range), TypeNode.createBlockStatement([
|
|
165
125
|
TypeNode.createIfStatement(TypeNode.createUnaryPrefixExpression(95 /* Token.Exclamation */, createSuperCallExpression(nameHashes, range), range), TypeNode.createReturnStatement(TypeNode.createFalseExpression(range), range), null, range),
|
|
166
126
|
], range), null, range);
|
|
@@ -173,15 +133,11 @@ function createSuperCallStatement(classDeclaration, nameHashes) {
|
|
|
173
133
|
* @param {Range} range - The super call expression range
|
|
174
134
|
*/
|
|
175
135
|
function createSuperCallExpression(hashValues, range) {
|
|
176
|
-
return TypeNode.createCallExpression(TypeNode.createPropertyAccessExpression(TypeNode.createSuperExpression(range), TypeNode.createIdentifierExpression(
|
|
136
|
+
return TypeNode.createCallExpression(TypeNode.createPropertyAccessExpression(TypeNode.createSuperExpression(range), TypeNode.createIdentifierExpression(STRICT_EQUALS_MEMBER_NAME, range), range), null, [
|
|
177
137
|
TypeNode.createIdentifierExpression("ref", range),
|
|
178
138
|
TypeNode.createIdentifierExpression("stack", range),
|
|
179
139
|
TypeNode.createIdentifierExpression("cache", range),
|
|
180
140
|
// StaticArray.concat(ignore, [... props] as StaticArray<i64>)
|
|
181
|
-
|
|
182
|
-
TypeNode.createIdentifierExpression("ignore", range),
|
|
183
|
-
// [...] as StaticArray<i64>
|
|
184
|
-
TypeNode.createAssertionExpression(1 /* AssertionKind.As */, TypeNode.createArrayLiteralExpression(hashValues.map((e) => TypeNode.createIntegerLiteralExpression(f64_as_i64(e), range)), range), TypeNode.createNamedType(TypeNode.createSimpleTypeName("StaticArray", range), [TypeNode.createNamedType(TypeNode.createSimpleTypeName("i64", range), null, false, range)], false, range), range),
|
|
185
|
-
], range),
|
|
141
|
+
createInheritedIgnoreListExpression(hashValues, range),
|
|
186
142
|
], range);
|
|
187
143
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Transform } from "assemblyscript/dist/transform.js";
|
|
2
|
-
import {
|
|
3
|
-
import { createAddReflectedValueKeyValuePairsMember } from "./createAddReflectedValueKeyValuePairsMember.js";
|
|
2
|
+
import { appendGeneratedClassReflectionMembers, appendGeneratedInterfaceReflectionMembers, } from "./appendGeneratedClassReflectionMembers.js";
|
|
4
3
|
// @ts-ignore
|
|
5
4
|
export default class AspectTransform extends Transform {
|
|
6
5
|
/**
|
|
@@ -23,13 +22,13 @@ function traverseStatements(statements) {
|
|
|
23
22
|
// for each statement in the source
|
|
24
23
|
for (const statement of statements) {
|
|
25
24
|
// find each class declaration
|
|
26
|
-
if (statement.kind ===
|
|
27
|
-
|
|
28
|
-
const classDeclaration = statement;
|
|
29
|
-
classDeclaration.members.push(createStrictEqualsMember(classDeclaration));
|
|
30
|
-
classDeclaration.members.push(createAddReflectedValueKeyValuePairsMember(classDeclaration));
|
|
25
|
+
if (statement.kind === 52 /* NodeKind.ClassDeclaration */) {
|
|
26
|
+
appendGeneratedClassReflectionMembers(statement);
|
|
31
27
|
}
|
|
32
|
-
else if (statement.kind ===
|
|
28
|
+
else if (statement.kind === 58 /* NodeKind.InterfaceDeclaration */) {
|
|
29
|
+
appendGeneratedInterfaceReflectionMembers(statement);
|
|
30
|
+
}
|
|
31
|
+
else if (statement.kind === 60 /* NodeKind.NamespaceDeclaration */) {
|
|
33
32
|
const namespaceDeclaration = statement;
|
|
34
33
|
traverseStatements(namespaceDeclaration.members);
|
|
35
34
|
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// import { } from "@as-pect/;
|
|
2
|
+
import { TypeNode, } from "assemblyscript/dist/assemblyscript.js";
|
|
3
|
+
import { createGenericTypeParameter } from "./createGenericTypeParameter.js";
|
|
4
|
+
import { djb2Hash } from "./hash.js";
|
|
5
|
+
// const TypeNode = TypeNode;
|
|
6
|
+
// const {
|
|
7
|
+
// BlockStatement,
|
|
8
|
+
// ClassDeclaration,
|
|
9
|
+
// FieldDeclaration,
|
|
10
|
+
// MethodDeclaration,
|
|
11
|
+
// Range,
|
|
12
|
+
// Statement
|
|
13
|
+
// } =
|
|
14
|
+
// type BlockStatement = InstanceType<typeof BlockStatement>;
|
|
15
|
+
// type ClassDeclaration = InstanceType<typeof ClassDeclaration>;
|
|
16
|
+
// type FieldDeclaration = InstanceType<typeof FieldDeclaration>;
|
|
17
|
+
// type MethodDeclaration = InstanceType<typeof MethodDeclaration>;
|
|
18
|
+
// type Range = InstanceType<typeof Range>;
|
|
19
|
+
// type Statement = InstanceType<typeof Statement>;
|
|
20
|
+
/**
|
|
21
|
+
* Create a prototype method called __aspectAddReflectedValueKeyValuePairs on a given
|
|
22
|
+
* ClassDeclaration dynamically.
|
|
23
|
+
*
|
|
24
|
+
* @param {ClassDeclaration} classDeclaration - The target classDeclaration
|
|
25
|
+
*/
|
|
26
|
+
export function createAddReflectedValueKeyValuePairsMember(classDeclaration) {
|
|
27
|
+
const range = classDeclaration.name.range;
|
|
28
|
+
// __aspectAddReflectedValueKeyValuePairs(reflectedValue: i32, seen: Map<usize, i32>, ignore: StaticArray<i64>): void
|
|
29
|
+
return TypeNode.createMethodDeclaration(TypeNode.createIdentifierExpression("__aspectAddReflectedValueKeyValuePairs", range), null, 256 /* CommonFlags.Public */ | 262144 /* CommonFlags.Instance */ | (classDeclaration.isGeneric ? 131072 /* CommonFlags.GenericContext */ : 0), null, TypeNode.createFunctionType([
|
|
30
|
+
// reflectedValue: i32
|
|
31
|
+
TypeNode.createParameter(0 /* ParameterKind.Default */, TypeNode.createIdentifierExpression("reflectedValue", range), createGenericTypeParameter("i32", range), null, range),
|
|
32
|
+
// seen: Map<usize, i32>
|
|
33
|
+
TypeNode.createParameter(0 /* ParameterKind.Default */, TypeNode.createIdentifierExpression("seen", range), TypeNode.createNamedType(TypeNode.createSimpleTypeName("Map", range), [createGenericTypeParameter("usize", range), createGenericTypeParameter("i32", range)], false, range), null, range),
|
|
34
|
+
// ignore: i64[]
|
|
35
|
+
TypeNode.createParameter(0 /* ParameterKind.Default */, TypeNode.createIdentifierExpression("ignore", range),
|
|
36
|
+
// Array<i64> -> i64[]
|
|
37
|
+
TypeNode.createNamedType(TypeNode.createSimpleTypeName("StaticArray", range), [createGenericTypeParameter("i64", range)], false, range), null, range),
|
|
38
|
+
],
|
|
39
|
+
// : void
|
|
40
|
+
TypeNode.createNamedType(TypeNode.createSimpleTypeName("void", range), [], false, range), null, false, range), createAddReflectedValueKeyValuePairsFunctionBody(classDeclaration), range);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Iterate over a given ClassDeclaration and return a block statement that contains the
|
|
44
|
+
* body of a supposed function that reports the key value pairs of a given class.
|
|
45
|
+
*
|
|
46
|
+
* @param {ClassDeclaration} classDeclaration - The class declaration to be reported
|
|
47
|
+
*/
|
|
48
|
+
function createAddReflectedValueKeyValuePairsFunctionBody(classDeclaration) {
|
|
49
|
+
const body = new Array();
|
|
50
|
+
const range = classDeclaration.name.range;
|
|
51
|
+
const nameHashes = new Array();
|
|
52
|
+
// for each field declaration, generate a check
|
|
53
|
+
for (const member of classDeclaration.members) {
|
|
54
|
+
// if it's an instance member, regardless of access modifier
|
|
55
|
+
if (member.is(262144 /* CommonFlags.Instance */)) {
|
|
56
|
+
switch (member.kind) {
|
|
57
|
+
// field declarations automatically get added
|
|
58
|
+
case 55 /* NodeKind.FieldDeclaration */: {
|
|
59
|
+
const fieldDeclaration = member;
|
|
60
|
+
const hashValue = djb2Hash(member.name.text);
|
|
61
|
+
pushKeyValueIfStatement(body, member.name.text, hashValue, fieldDeclaration.range);
|
|
62
|
+
nameHashes.push(hashValue);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
// function declarations can be getters, check the get flag
|
|
66
|
+
case 59 /* NodeKind.MethodDeclaration */: {
|
|
67
|
+
if (member.is(2048 /* CommonFlags.Get */)) {
|
|
68
|
+
const methodDeclaration = member;
|
|
69
|
+
const hashValue = djb2Hash(member.name.text);
|
|
70
|
+
pushKeyValueIfStatement(body, member.name.text, hashValue, methodDeclaration.range);
|
|
71
|
+
nameHashes.push(hashValue);
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// call into super first after all the property checks have been added
|
|
79
|
+
body.unshift(createIsDefinedIfStatement(nameHashes, range));
|
|
80
|
+
return TypeNode.createBlockStatement(body, range);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Create an isDefined() function call with an if statement to prevent calls to
|
|
84
|
+
* super where they should not be made.
|
|
85
|
+
*
|
|
86
|
+
* @param {number[]} nameHashes - The array of property names to ignore in the children
|
|
87
|
+
* @param {Range} range - The reporting range of this statement
|
|
88
|
+
*/
|
|
89
|
+
function createIsDefinedIfStatement(nameHashes, range) {
|
|
90
|
+
// if (isDefined(super.__aspectAddReflectedValueKeyValuePairs))
|
|
91
|
+
// super.__aspectAddReflectedValueKeyValuePairs(reflectedValue, seen, StaticArray.concat(ignore, [...] as StaticArray<i64>))
|
|
92
|
+
return TypeNode.createIfStatement(
|
|
93
|
+
// isDefined(super.__aspectAddReflectedValueKeyValuePairs)
|
|
94
|
+
TypeNode.createCallExpression(TypeNode.createIdentifierExpression("isDefined", range), null, [
|
|
95
|
+
// super.__aspectAddReflectedValueKeyValuePairs
|
|
96
|
+
TypeNode.createPropertyAccessExpression(TypeNode.createSuperExpression(range), TypeNode.createIdentifierExpression("__aspectAddReflectedValueKeyValuePairs", range), range),
|
|
97
|
+
], range), TypeNode.createBlockStatement([
|
|
98
|
+
TypeNode.createExpressionStatement(
|
|
99
|
+
// super.__aspectAddReflectedValueKeyValuePairs(reflectedValue, seen, StaticArray.concat(ignore, [...] as StaticArray<i64>))
|
|
100
|
+
TypeNode.createCallExpression(TypeNode.createPropertyAccessExpression(TypeNode.createSuperExpression(range), TypeNode.createIdentifierExpression("__aspectAddReflectedValueKeyValuePairs", range), range), null, [
|
|
101
|
+
// reflectedValue,
|
|
102
|
+
TypeNode.createIdentifierExpression("reflectedValue", range),
|
|
103
|
+
// seen,
|
|
104
|
+
TypeNode.createIdentifierExpression("seen", range),
|
|
105
|
+
// StaticArray.concat(ignore, [...])
|
|
106
|
+
TypeNode.createCallExpression(TypeNode.createPropertyAccessExpression(TypeNode.createIdentifierExpression("StaticArray", range), TypeNode.createIdentifierExpression("concat", range), range), null, [
|
|
107
|
+
TypeNode.createIdentifierExpression("ignore", range),
|
|
108
|
+
// [...propNames]
|
|
109
|
+
TypeNode.createAssertionExpression(1 /* AssertionKind.As */, TypeNode.createArrayLiteralExpression(nameHashes.map((e) => TypeNode.createIntegerLiteralExpression(f64_as_i64(e), range)), range), TypeNode.createNamedType(TypeNode.createSimpleTypeName("StaticArray", range), [TypeNode.createNamedType(TypeNode.createSimpleTypeName("i64", range), null, false, range)], false, range), range),
|
|
110
|
+
], range),
|
|
111
|
+
], range)),
|
|
112
|
+
], range), null, range);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* For each key-value pair, we need to perform a runtime check to make sure that this property
|
|
116
|
+
* was not overridden in the parent of a given class.
|
|
117
|
+
*
|
|
118
|
+
* @param {Statement[]} body - The collection of statements for the function body
|
|
119
|
+
* @param {string} name - The name of the property
|
|
120
|
+
* @param {Range} range - The range for these statements
|
|
121
|
+
*/
|
|
122
|
+
function pushKeyValueIfStatement(body, name, hashValue, range) {
|
|
123
|
+
body.push(
|
|
124
|
+
// if (!ignore.includes("propName")) { ... }
|
|
125
|
+
TypeNode.createIfStatement(TypeNode.createUnaryPrefixExpression(95 /* Token.Exclamation */,
|
|
126
|
+
// ignore.includes("propName")
|
|
127
|
+
TypeNode.createCallExpression(TypeNode.createPropertyAccessExpression(TypeNode.createIdentifierExpression("ignore", range), TypeNode.createIdentifierExpression("includes", range), range), null, [
|
|
128
|
+
// hashValue
|
|
129
|
+
TypeNode.createIntegerLiteralExpression(f64_as_i64(hashValue), range),
|
|
130
|
+
], range), range), TypeNode.createBlockStatement([createPushReflectedObjectKeyStatement(name, range), createPushReflectedObjectValueStatement(name, range)], range), null, range));
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Create a function call to __aspectPushReflectedObjectKey to add a key to a given
|
|
134
|
+
* reflected value.
|
|
135
|
+
*
|
|
136
|
+
* @param {string} name - The name of the property
|
|
137
|
+
* @param {Range} range - The reange for this function call
|
|
138
|
+
*/
|
|
139
|
+
function createPushReflectedObjectKeyStatement(name, range) {
|
|
140
|
+
// __aspectPushReflectedObjectKey(reflectedValue, Reflect.toReflectedValue("propertyName", seen));
|
|
141
|
+
return TypeNode.createExpressionStatement(TypeNode.createCallExpression(TypeNode.createIdentifierExpression("__aspectPushReflectedObjectKey", range), null, [
|
|
142
|
+
// reflectedValue
|
|
143
|
+
TypeNode.createIdentifierExpression("reflectedValue", range),
|
|
144
|
+
// Reflect.toReflectedValue("propertyName", seen)
|
|
145
|
+
TypeNode.createCallExpression(
|
|
146
|
+
// Reflect.toReflectedValue
|
|
147
|
+
TypeNode.createPropertyAccessExpression(TypeNode.createIdentifierExpression("Reflect", range), TypeNode.createIdentifierExpression("toReflectedValue", range), range), null, [TypeNode.createStringLiteralExpression(name, range), TypeNode.createIdentifierExpression("seen", range)], range),
|
|
148
|
+
], range));
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Create a function call to __aspectPushReflectedObjectValue to add a key to a given
|
|
152
|
+
* reflected value.
|
|
153
|
+
*
|
|
154
|
+
* @param {string} name - The name of the property
|
|
155
|
+
* @param {Range} range - The reange for this function call
|
|
156
|
+
*/
|
|
157
|
+
function createPushReflectedObjectValueStatement(name, range) {
|
|
158
|
+
// __aspectPushReflectedObjectValue(reflectedValue, Reflect.toReflectedValue(this.propertyName, seen, ignore.concat([...])));
|
|
159
|
+
return TypeNode.createExpressionStatement(
|
|
160
|
+
// __aspectPushReflectedObjectValue(reflectedValue, Reflect.toReflectedValue(this.propertyName, seen, ignore.concat([...])))
|
|
161
|
+
TypeNode.createCallExpression(
|
|
162
|
+
// __aspectPushReflectedObjectValue
|
|
163
|
+
TypeNode.createIdentifierExpression("__aspectPushReflectedObjectValue", range), null, [
|
|
164
|
+
// reflectedValue
|
|
165
|
+
TypeNode.createIdentifierExpression("reflectedValue", range),
|
|
166
|
+
// Reflect.toReflectedValue(this.propertyName, seen))
|
|
167
|
+
TypeNode.createCallExpression(
|
|
168
|
+
// Reflect.toReflectedValue
|
|
169
|
+
TypeNode.createPropertyAccessExpression(TypeNode.createIdentifierExpression("Reflect", range), TypeNode.createIdentifierExpression("toReflectedValue", range), range), null, [
|
|
170
|
+
//this.propertyName
|
|
171
|
+
TypeNode.createPropertyAccessExpression(TypeNode.createThisExpression(range), TypeNode.createIdentifierExpression(name, range), range),
|
|
172
|
+
// seen
|
|
173
|
+
TypeNode.createIdentifierExpression("seen", range),
|
|
174
|
+
], range),
|
|
175
|
+
], range));
|
|
176
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TypeNode } from "assemblyscript/dist/assemblyscript.js";
|
|
2
|
+
/**
|
|
3
|
+
* This method makes a generic named parameter.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} name - The name of the type.
|
|
6
|
+
* @param {Range} range - The range given for the type parameter.
|
|
7
|
+
*/
|
|
8
|
+
export function createGenericTypeParameter(name, range) {
|
|
9
|
+
return TypeNode.createNamedType(TypeNode.createSimpleTypeName(name, range), null, false, range);
|
|
10
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { djb2Hash } from "./hash.js";
|
|
2
|
+
import { TypeNode, } from "assemblyscript/dist/assemblyscript.js";
|
|
3
|
+
/**
|
|
4
|
+
* This method creates a single FunctionDeclaration that allows Reflect.equals
|
|
5
|
+
* to validate normal class member values.
|
|
6
|
+
*
|
|
7
|
+
* @param {ClassDeclaration} classDeclaration - The class that requires a new function.
|
|
8
|
+
*/
|
|
9
|
+
export function createStrictEqualsMember(classDeclaration) {
|
|
10
|
+
const range = classDeclaration.name.range;
|
|
11
|
+
// __aspectStrictEquals(rawRef: Object, stackA: usize[], stackB: usize[], ignore: StaticArray<i64>): bool
|
|
12
|
+
return TypeNode.createMethodDeclaration(TypeNode.createIdentifierExpression("__aspectStrictEquals", range), null, 256 /* CommonFlags.Public */ | 262144 /* CommonFlags.Instance */ | (classDeclaration.isGeneric ? 131072 /* CommonFlags.GenericContext */ : 0), null, TypeNode.createFunctionType([
|
|
13
|
+
// rawRef: Object,
|
|
14
|
+
createDefaultParameter("rawRef", TypeNode.createNamedType(TypeNode.createSimpleTypeName("Object", range), null, false, range), range),
|
|
15
|
+
// stack: usize[]
|
|
16
|
+
createDefaultParameter("stack", createArrayType("usize", range), range),
|
|
17
|
+
// cache: usize[]
|
|
18
|
+
createDefaultParameter("cache", createArrayType("usize", range), range),
|
|
19
|
+
// ignore: StaticArray<i64>
|
|
20
|
+
createDefaultParameter("ignore", TypeNode.createNamedType(TypeNode.createSimpleTypeName("StaticArray", range), [TypeNode.createNamedType(TypeNode.createSimpleTypeName("i64", range), null, false, range)], false, range), range),
|
|
21
|
+
],
|
|
22
|
+
// : bool
|
|
23
|
+
createSimpleNamedType("bool", range), null, false, range), createStrictEqualsFunctionBody(classDeclaration), range);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* This method creates a simple name type with the given name and source range.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} name - The name of the type.
|
|
29
|
+
* @param {Range} range - The given source range.
|
|
30
|
+
*/
|
|
31
|
+
function createSimpleNamedType(name, range) {
|
|
32
|
+
return TypeNode.createNamedType(TypeNode.createSimpleTypeName(name, range), null, false, range);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* This method creates an Array<name> type with the given range.
|
|
36
|
+
*
|
|
37
|
+
* @param {Range} range - The source range.
|
|
38
|
+
*/
|
|
39
|
+
function createArrayType(name, range) {
|
|
40
|
+
return TypeNode.createNamedType(TypeNode.createSimpleTypeName("Array", range), [TypeNode.createNamedType(TypeNode.createSimpleTypeName(name, range), null, false, range)], false, range);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* This method creates the entire function body for __aspectStrictEquals.
|
|
44
|
+
*
|
|
45
|
+
* @param {ClassDeclaration} classDeclaration - The class declaration.
|
|
46
|
+
*/
|
|
47
|
+
function createStrictEqualsFunctionBody(classDeclaration) {
|
|
48
|
+
const body = new Array();
|
|
49
|
+
const range = classDeclaration.name.range;
|
|
50
|
+
const nameHashes = new Array();
|
|
51
|
+
const rawRef = TypeNode.createIdentifierExpression("rawRef", range);
|
|
52
|
+
const classType = TypeNode.createNamedType(TypeNode.createSimpleTypeName(classDeclaration.name.text, range), classDeclaration.isGeneric
|
|
53
|
+
? classDeclaration.typeParameters.map((node) => TypeNode.createNamedType(TypeNode.createSimpleTypeName(node.name.text, range), null, false, range))
|
|
54
|
+
: null, false, range);
|
|
55
|
+
// Check if the parameter is an instance of the class; return otherwise
|
|
56
|
+
body.push(TypeNode.createIfStatement(TypeNode.createUnaryPrefixExpression(95 /* Token.Exclamation */, TypeNode.createInstanceOfExpression(rawRef, classType, range), range), TypeNode.createReturnStatement(TypeNode.createFalseExpression(range), range), null, range));
|
|
57
|
+
// Cast rawRef into an instance of the class
|
|
58
|
+
body.push(TypeNode.createVariableStatement(null, [
|
|
59
|
+
TypeNode.createVariableDeclaration(TypeNode.createIdentifierExpression("ref", range), null, 8 /* CommonFlags.Const */, classType, TypeNode.createAssertionExpression(1 /* AssertionKind.As */, rawRef, classType, range), range)
|
|
60
|
+
], range));
|
|
61
|
+
// for each field declaration, generate a check
|
|
62
|
+
for (const member of classDeclaration.members) {
|
|
63
|
+
// if it's an instance member, regardless of access modifier
|
|
64
|
+
if (member.is(262144 /* CommonFlags.Instance */)) {
|
|
65
|
+
switch (member.kind) {
|
|
66
|
+
// field declarations automatically get added
|
|
67
|
+
case 55 /* NodeKind.FieldDeclaration */: {
|
|
68
|
+
const fieldDeclaration = member;
|
|
69
|
+
const hashValue = djb2Hash(member.name.text);
|
|
70
|
+
body.push(createStrictEqualsIfCheck(member.name.text, hashValue, fieldDeclaration.range));
|
|
71
|
+
nameHashes.push(hashValue);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
// function declarations can be getters, check the get flag
|
|
75
|
+
case 59 /* NodeKind.MethodDeclaration */: {
|
|
76
|
+
if (member.is(2048 /* CommonFlags.Get */)) {
|
|
77
|
+
const methodDeclaration = member;
|
|
78
|
+
const hashValue = djb2Hash(member.name.text);
|
|
79
|
+
body.push(createStrictEqualsIfCheck(methodDeclaration.name.text, hashValue, methodDeclaration.name.range));
|
|
80
|
+
nameHashes.push(hashValue);
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// if (isDefined(...)) super.__aspectStrictEquals(ref, stack, cache, ignore.concat([...props]));
|
|
88
|
+
body.push(createSuperCallStatement(classDeclaration, nameHashes));
|
|
89
|
+
// return true;
|
|
90
|
+
body.push(TypeNode.createReturnStatement(TypeNode.createTrueExpression(range), range));
|
|
91
|
+
return TypeNode.createBlockStatement(body, range);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* This function generates a single IfStatement with a nested ReturnStatement
|
|
95
|
+
* to validate a nested property on a given class.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} name - The name of the property.
|
|
98
|
+
* @param {Range} range - The source range for the given property.
|
|
99
|
+
*/
|
|
100
|
+
function createStrictEqualsIfCheck(name, hashValue, range) {
|
|
101
|
+
const equalsCheck = TypeNode.createBinaryExpression(76 /* Token.Equals_Equals */,
|
|
102
|
+
// Reflect.equals(this.prop, ref.prop, stack, cache)
|
|
103
|
+
TypeNode.createCallExpression(
|
|
104
|
+
// Reflect.equals
|
|
105
|
+
createPropertyAccess("Reflect", "equals", range), null, // types can be inferred by the compiler!
|
|
106
|
+
// arguments
|
|
107
|
+
[
|
|
108
|
+
// this.prop
|
|
109
|
+
TypeNode.createPropertyAccessExpression(TypeNode.createThisExpression(range), TypeNode.createIdentifierExpression(name, range), range),
|
|
110
|
+
// ref.prop
|
|
111
|
+
createPropertyAccess("ref", name, range),
|
|
112
|
+
// stack
|
|
113
|
+
TypeNode.createIdentifierExpression("stack", range),
|
|
114
|
+
// cache
|
|
115
|
+
TypeNode.createIdentifierExpression("cache", range),
|
|
116
|
+
], range), createPropertyAccess("Reflect", "FAILED_MATCH", range), range);
|
|
117
|
+
// !ignore.includes("prop")
|
|
118
|
+
const includesCheck = TypeNode.createUnaryPrefixExpression(95 /* Token.Exclamation */,
|
|
119
|
+
// ignore.includes("prop")
|
|
120
|
+
TypeNode.createCallExpression(
|
|
121
|
+
// ignore.includes
|
|
122
|
+
TypeNode.createPropertyAccessExpression(TypeNode.createIdentifierExpression("ignore", range), TypeNode.createIdentifierExpression("includes", range), range), null,
|
|
123
|
+
// (nameHash)
|
|
124
|
+
[TypeNode.createIntegerLiteralExpression(f64_as_i64(hashValue), range)], range), range);
|
|
125
|
+
// if (Reflect.equals(this.prop, ref.prop, stack, cache) === Reflect.FAILED_MATCH) return false;
|
|
126
|
+
return TypeNode.createIfStatement(
|
|
127
|
+
// Reflect.equals(this.prop, ref.prop, stack, cache) === Reflect.FAILED_MATCH
|
|
128
|
+
TypeNode.createBinaryExpression(97 /* Token.Ampersand_Ampersand */, includesCheck, equalsCheck, range),
|
|
129
|
+
// return false;
|
|
130
|
+
TypeNode.createReturnStatement(TypeNode.createFalseExpression(range), range), null, range);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Create a simple default parameter with a name and a type.
|
|
134
|
+
*
|
|
135
|
+
* @param {string} name - The name of the parameter.
|
|
136
|
+
* @param {TypeNode} typeNode - The type of the parameter.
|
|
137
|
+
* @param {Range} range - The source range of the parameter.
|
|
138
|
+
*/
|
|
139
|
+
function createDefaultParameter(name, typeNode, range) {
|
|
140
|
+
return TypeNode.createParameter(0 /* ParameterKind.Default */, TypeNode.createIdentifierExpression(name, range), typeNode, null, range);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* This method creates a single property access and passes the given range to the AST.
|
|
144
|
+
*
|
|
145
|
+
* @param {string} root - The name of the identifier representing the root.
|
|
146
|
+
* @param {string} property - The name of the identifier representing the property.
|
|
147
|
+
* @param {Range} range - The range of the property access.
|
|
148
|
+
*/
|
|
149
|
+
function createPropertyAccess(root, property, range) {
|
|
150
|
+
// root.property
|
|
151
|
+
return TypeNode.createPropertyAccessExpression(TypeNode.createIdentifierExpression(root, range), TypeNode.createIdentifierExpression(property, range), range);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* This method creates the function call into super.__aspectStrictEquals,
|
|
155
|
+
* wrapping it in a check to make sure the super function is defined first.
|
|
156
|
+
*
|
|
157
|
+
* @param {ClassDeclaration} classDeclaration - The given class declaration.
|
|
158
|
+
* @param {number[]} nameHashes - A collection of hash values of the comparing class properties.
|
|
159
|
+
*/
|
|
160
|
+
function createSuperCallStatement(classDeclaration, nameHashes) {
|
|
161
|
+
const range = classDeclaration.name.range;
|
|
162
|
+
const ifStatement = TypeNode.createIfStatement(TypeNode.createCallExpression(TypeNode.createIdentifierExpression("isDefined", range), null, [
|
|
163
|
+
TypeNode.createPropertyAccessExpression(TypeNode.createSuperExpression(range), TypeNode.createIdentifierExpression("__aspectStrictEquals", range), range),
|
|
164
|
+
], range), TypeNode.createBlockStatement([
|
|
165
|
+
TypeNode.createIfStatement(TypeNode.createUnaryPrefixExpression(95 /* Token.Exclamation */, createSuperCallExpression(nameHashes, range), range), TypeNode.createReturnStatement(TypeNode.createFalseExpression(range), range), null, range),
|
|
166
|
+
], range), null, range);
|
|
167
|
+
return ifStatement;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* This method actually creates the super.__aspectStrictEquals function call.
|
|
171
|
+
*
|
|
172
|
+
* @param {number[]} hashValues - The collection of hashed property name values
|
|
173
|
+
* @param {Range} range - The super call expression range
|
|
174
|
+
*/
|
|
175
|
+
function createSuperCallExpression(hashValues, range) {
|
|
176
|
+
return TypeNode.createCallExpression(TypeNode.createPropertyAccessExpression(TypeNode.createSuperExpression(range), TypeNode.createIdentifierExpression("__aspectStrictEquals", range), range), null, [
|
|
177
|
+
TypeNode.createIdentifierExpression("ref", range),
|
|
178
|
+
TypeNode.createIdentifierExpression("stack", range),
|
|
179
|
+
TypeNode.createIdentifierExpression("cache", range),
|
|
180
|
+
// StaticArray.concat(ignore, [... props] as StaticArray<i64>)
|
|
181
|
+
TypeNode.createCallExpression(TypeNode.createPropertyAccessExpression(TypeNode.createIdentifierExpression("StaticArray", range), TypeNode.createIdentifierExpression("concat", range), range), null, [
|
|
182
|
+
TypeNode.createIdentifierExpression("ignore", range),
|
|
183
|
+
// [...] as StaticArray<i64>
|
|
184
|
+
TypeNode.createAssertionExpression(1 /* AssertionKind.As */, TypeNode.createArrayLiteralExpression(hashValues.map((e) => TypeNode.createIntegerLiteralExpression(f64_as_i64(e), range)), range), TypeNode.createNamedType(TypeNode.createSimpleTypeName("StaticArray", range), [TypeNode.createNamedType(TypeNode.createSimpleTypeName("i64", range), null, false, range)], false, range), range),
|
|
185
|
+
], range),
|
|
186
|
+
], range);
|
|
187
|
+
}
|
package/lib/src/hash.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A simple djb2hash that returns a hash of a given string. See http://www.cse.yorku.ca/~oz/hash.html
|
|
3
|
+
* for implementation details.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} str - The string to be hashed
|
|
6
|
+
* @returns {number} The hash of the string
|
|
7
|
+
*/
|
|
8
|
+
export function djb2Hash(str) {
|
|
9
|
+
const points = Array.from(str);
|
|
10
|
+
let h = 5381;
|
|
11
|
+
for (let p = 0; p < points.length; p++)
|
|
12
|
+
// h = h * 33 + c;
|
|
13
|
+
h = (h << 5) + h + points[p].codePointAt(0);
|
|
14
|
+
return h;
|
|
15
|
+
}
|
package/lib/src/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Transform } from "assemblyscript/dist/transform.js";
|
|
2
|
+
import { createStrictEqualsMember } from "./createStrictEqualsMember.js";
|
|
3
|
+
import { createAddReflectedValueKeyValuePairsMember } from "./createAddReflectedValueKeyValuePairsMember.js";
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
export default class AspectTransform extends Transform {
|
|
6
|
+
/**
|
|
7
|
+
* This method results in a pure AST transform that inserts a strictEquals member
|
|
8
|
+
* into each ClassDeclaration.
|
|
9
|
+
*
|
|
10
|
+
* @param {Parser} parser - The AssemblyScript parser.
|
|
11
|
+
*/
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
afterParse(parser) {
|
|
14
|
+
// For backwards compatibility
|
|
15
|
+
let sources = parser.program ? parser.program.sources : parser.sources;
|
|
16
|
+
// for each program source
|
|
17
|
+
for (const source of sources) {
|
|
18
|
+
traverseStatements(source.statements);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function traverseStatements(statements) {
|
|
23
|
+
// for each statement in the source
|
|
24
|
+
for (const statement of statements) {
|
|
25
|
+
// find each class declaration
|
|
26
|
+
if (statement.kind === 52 /* NodeKind.ClassDeclaration */) {
|
|
27
|
+
// cast and create a strictEquals function
|
|
28
|
+
const classDeclaration = statement;
|
|
29
|
+
classDeclaration.members.push(createStrictEqualsMember(classDeclaration));
|
|
30
|
+
classDeclaration.members.push(createAddReflectedValueKeyValuePairsMember(classDeclaration));
|
|
31
|
+
}
|
|
32
|
+
else if (statement.kind === 60 /* NodeKind.NamespaceDeclaration */) {
|
|
33
|
+
const namespaceDeclaration = statement;
|
|
34
|
+
traverseStatements(namespaceDeclaration.members);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
package/package.json
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@as-pect/transform",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "The transform used by the as-pect core and assembly packages to enable strict equality.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"tsc:all": "tsc"
|
|
8
|
+
"tsc:all": "tsc",
|
|
9
|
+
"test": "npm run tsc:all && node --test __tests__/*.test.js"
|
|
9
10
|
},
|
|
10
11
|
"author": "Joshua Tenner <tenner.joshua@gmail.com>",
|
|
11
12
|
"license": "MIT",
|
|
12
13
|
"dependencies": {
|
|
13
|
-
"assemblyscript": "^0.
|
|
14
|
+
"assemblyscript": "^0.28.19"
|
|
14
15
|
},
|
|
15
16
|
"devDependencies": {
|
|
16
|
-
"typescript": "^
|
|
17
|
+
"typescript": "^6.0.3"
|
|
17
18
|
},
|
|
18
19
|
"files": [
|
|
19
20
|
"lib/",
|
|
20
21
|
"init/"
|
|
21
22
|
],
|
|
22
|
-
"
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
}
|
|
23
26
|
}
|