@as-pect/transform 8.0.1 → 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 +148 -176
- package/lib/createGenericTypeParameter.js +10 -10
- package/lib/createInheritedIgnoreListExpression.js +11 -0
- package/lib/createInterfaceReflectionMembers.js +25 -0
- package/lib/createStrictEqualsMember.js +143 -187
- package/lib/emptyTransformer.js +8 -8
- package/lib/hash.js +15 -15
- package/lib/index.js +36 -37
- 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 +9 -5
|
@@ -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,22 +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
|
+
],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
}
|
|
22
26
|
}
|