@runtime-type-inspector/transpiler 3.0.5 → 3.0.7
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/index.cjs +3005 -2968
- package/index.mjs +1420 -1391
- package/package.json +2 -2
package/index.mjs
CHANGED
|
@@ -2,223 +2,612 @@ import { parse } from '@babel/parser';
|
|
|
2
2
|
import ts from 'typescript';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
* For actual builds, we use expandType() anyway (which is based on TypeScript).
|
|
7
|
-
* But since TypeScript is a huge dependency, I'm looking into BabelFlow/BabelTypescript parser.
|
|
8
|
-
* Comparing AST's like this usually helps to find bugs or potential issues,
|
|
9
|
-
* while we can benchmark for best performance too.
|
|
10
|
-
* @example
|
|
11
|
-
* const tooComplex = 'Array<string|{chunks?: undefined|Array<{language: string|null, timestamp: Array<number|null>, text: string}>}>';
|
|
12
|
-
* console.log(expandTypeDepFree(tooComplex));
|
|
13
|
-
*/
|
|
14
|
-
/**
|
|
15
|
-
* @typedef {object} ExpandTypeReturnValue
|
|
16
|
-
* @property {'array' | 'union' | 'record' | 'tuple' | 'object' | 'promise' | 'typeof'} type - The type.
|
|
17
|
-
* @property {object | string} [elementType] - For Array.
|
|
18
|
-
* @property {object | string} [key] - For Record<key, val>
|
|
19
|
-
* @property {object | string} [val] - For Record<key, val>
|
|
20
|
-
* @property {(object | string)[]} [members] - For unions.
|
|
21
|
-
* @property {object | string} [properties] - For objects.
|
|
22
|
-
* @property {(object | string)[]} [elements] - For tuples.
|
|
23
|
-
* @property {object | string} [argument] - For typeof.
|
|
24
|
-
*/
|
|
25
|
-
/**
|
|
26
|
-
* 'DepFree' refers to the fact that this function has no dependencies,
|
|
27
|
-
* while `expandType` depends on TypeScript itself for maximum compatibility.
|
|
28
|
-
* @example
|
|
29
|
-
* expandTypeDepFree('(123) '); // Outputs: '123'
|
|
30
|
-
* expandTypeDepFree('Array<number> '); // Outputs: { type: 'array', elementType: 'number' }
|
|
31
|
-
* expandTypeDepFree('Array<(123) > '); // Outputs: { type: 'array', elementType: '123' }
|
|
32
|
-
* expandTypeDepFree(' ( ( 123 ) ) '); // Outputs: '123'
|
|
33
|
-
* expandTypeDepFree(' (string ) |(number ) '); // Outputs: { type: 'union', members: ['string', 'number'] }
|
|
34
|
-
* expandTypeDepFree(' (( Object ) ) '); // Outputs: { type: 'object', properties: {} }
|
|
35
|
-
* @param {string} type - The input type to expand.
|
|
36
|
-
* @returns {string | ExpandTypeReturnValue} Object containing parsed information from type string.
|
|
37
|
-
*/
|
|
38
|
-
function expandTypeDepFree(type) {
|
|
39
|
-
type = type.trim();
|
|
40
|
-
// '(123)' -> '123'
|
|
41
|
-
while (!type.includes('|') && type[0] === '(' && type[type.length - 1] === ')') {
|
|
42
|
-
type = type.slice(1, -1).trim();
|
|
43
|
-
}
|
|
44
|
-
// (1) Rest parameters like ...string
|
|
45
|
-
if (type[0] === '.' && type[1] === '.' && type[2] === '.') {
|
|
46
|
-
const elementType = type.slice(3);
|
|
47
|
-
return {
|
|
48
|
-
type: 'array',
|
|
49
|
-
elementType
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
// (2)
|
|
53
|
-
// Array<...>
|
|
54
|
-
if (type.startsWith("Array<") && type.endsWith('>')) {
|
|
55
|
-
const typeSlice = type.slice(6, -1);
|
|
56
|
-
const elementType = expandTypeDepFree(typeSlice);
|
|
57
|
-
return {
|
|
58
|
-
type: "array",
|
|
59
|
-
elementType
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
// Promise<...>
|
|
63
|
-
if (type.startsWith("Promise<") && type.endsWith('>')) {
|
|
64
|
-
const typeSlice = type.slice(8, -1);
|
|
65
|
-
const elementType = expandTypeDepFree(typeSlice);
|
|
66
|
-
return {
|
|
67
|
-
type: "promise",
|
|
68
|
-
elementType
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
// (3) Object<...> or Record<...>
|
|
72
|
-
if ((type.startsWith("Object<") || type.startsWith("Record<")) && type.endsWith('>')) {
|
|
73
|
-
const recordSlice = type.slice(7, -1);
|
|
74
|
-
const firstComma = recordSlice.indexOf(',');
|
|
75
|
-
if (firstComma === -1) {
|
|
76
|
-
console.warn("expandTypeDepFree> invalid Object/Record");
|
|
77
|
-
}
|
|
78
|
-
const key = recordSlice.slice(0, firstComma).trim();
|
|
79
|
-
const val = recordSlice.slice(firstComma + 1).trim();
|
|
80
|
-
return {
|
|
81
|
-
type: "record",
|
|
82
|
-
key: expandTypeDepFree(key),
|
|
83
|
-
val: expandTypeDepFree(val)
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
// (4) {...}
|
|
87
|
-
if (type[0] === '{' && type[type.length - 1] === '}') {
|
|
88
|
-
const propertiesArray = type.slice(1, -1).split(','); // ['entity: Entity', ' app: AppBase']
|
|
89
|
-
const properties = {};
|
|
90
|
-
propertiesArray.forEach(_ => {
|
|
91
|
-
const [propName, propType] = _.split(":").map(_ => _.trim());
|
|
92
|
-
if (!propName || !propType) {
|
|
93
|
-
console.warn('expandTypeDepFree> unexpected type format, fix');
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
properties[propName] = propType;
|
|
97
|
-
});
|
|
98
|
-
return {
|
|
99
|
-
type: 'object',
|
|
100
|
-
properties
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
// (5) expand unions
|
|
104
|
-
const members = type.split("|");
|
|
105
|
-
if (members.length >= 2) {
|
|
106
|
-
members.forEach((_, i) => members[i] = _.trim());
|
|
107
|
-
return {
|
|
108
|
-
type: 'union',
|
|
109
|
-
members: members.map(expandTypeDepFree)
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
// (6) expand [] Arrays
|
|
113
|
-
// Test arrays: new pc.Mat3().set([1, 2, 3, "asd"])
|
|
114
|
-
if (type.endsWith("[]")) {
|
|
115
|
-
const typeSlice = type.slice(0, -2);
|
|
116
|
-
return {
|
|
117
|
-
type: 'array',
|
|
118
|
-
elementType: expandTypeDepFree(typeSlice)
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
// (7) expand tuples
|
|
122
|
-
if (type[0] === '[' && type[type.length - 1] === ']') {
|
|
123
|
-
const elements = type.slice(1, -1).split(','); // ['null', ' Texture', ' Texture', ' Texture', ' Texture', ' Texture', ' Texture']
|
|
124
|
-
return {
|
|
125
|
-
type: 'tuple',
|
|
126
|
-
elements: elements.map(expandTypeDepFree)
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
// (8) expand typeof expressions
|
|
130
|
-
if (type.startsWith('typeof ')) {
|
|
131
|
-
const argument = expandTypeDepFree(type.substring(7));
|
|
132
|
-
return {
|
|
133
|
-
type: 'typeof',
|
|
134
|
-
argument
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
if (type === 'object' || type === 'Object') {
|
|
138
|
-
return {
|
|
139
|
-
type: 'object',
|
|
140
|
-
properties: {}
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
return type;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/** @typedef {import('@babel/types').Node} Node */
|
|
147
|
-
/** @typedef {import('@babel/types').Function} Function */
|
|
148
|
-
/**
|
|
149
|
-
* Checks if the provided node is a function-like structure.
|
|
5
|
+
* Transforms a type string into a structured type representation.
|
|
150
6
|
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
7
|
+
* This function parses a given type string and converts it into a TypeScript
|
|
8
|
+
* Abstract Syntax Tree (AST), then uses that AST to return a structured type
|
|
9
|
+
* representation that can be further utilized or interpreted.
|
|
10
|
+
*
|
|
11
|
+
* @todo Better handling of weird case: Array<>
|
|
12
|
+
* @example
|
|
13
|
+
* const {expandType} = await import("./src-transpiler/expandType.mjs");
|
|
14
|
+
* expandType('[string, Array|AnyTypedArray, number[]]|[ONNXTensor]');
|
|
15
|
+
* expandType('(123) '); // Outputs: '123'
|
|
16
|
+
* expandType(' ( ( 123 ) ) '); // Outputs: '123'
|
|
17
|
+
* expandType('Array<number> '); // Outputs: {type: 'array', elementType: 'number'}
|
|
18
|
+
* expandType('Array<(123) > '); // Outputs: {type: 'array', elementType: '123'}
|
|
19
|
+
* expandType('Array<"abc" | 123> '); // Outputs: {type: 'array', elementType: {type: 'union', members: ['"abc"', '123']}}
|
|
20
|
+
* expandType(' (string ) |(number ) '); // Outputs: {type: 'union', members: [ 'string', 'number']}
|
|
21
|
+
* expandType(' "apples" | ( "bananas") '); // Outputs: {type: 'union', members: [ '"apples"', '"bananas"']}
|
|
22
|
+
* expandType('123? '); // Outputs: {"type":"union","members":["123","null"]}
|
|
23
|
+
* expandType('123|null '); // Outputs: {"type":"union","members":["123","null"]}
|
|
24
|
+
* expandType('Map<string, any> '); // Outputs:
|
|
25
|
+
* expandType('typeof Number '); // Outputs:
|
|
26
|
+
* @param {string} type - The type string to be expanded into a structured representation.
|
|
27
|
+
* @todo Share type with expandTypeBabelTS and expandTypeDepFree
|
|
28
|
+
* @returns {string | {type: string, [key: string]: any} | undefined} The structured type
|
|
29
|
+
* representation obtained from parsing and converting the provided type string.
|
|
153
30
|
*/
|
|
154
|
-
function
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
case 'ClassMethod':
|
|
158
|
-
case 'ClassPrivateMethod':
|
|
159
|
-
case 'FunctionDeclaration':
|
|
160
|
-
case 'FunctionExpression':
|
|
161
|
-
case 'ObjectMethod':
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
164
|
-
return false;
|
|
31
|
+
function expandType(type) {
|
|
32
|
+
const ast = parseType(type);
|
|
33
|
+
return toSourceTS(ast);
|
|
165
34
|
}
|
|
166
|
-
|
|
167
35
|
/**
|
|
168
|
-
* @
|
|
169
|
-
*
|
|
36
|
+
* @todo I want to use for example: import('typescript').Node
|
|
37
|
+
* But the TS types make no sense to me so far ... need to investigate more.
|
|
38
|
+
* @typedef TypeScriptType
|
|
39
|
+
* @property {object[]|undefined} typeArguments - The type arguments.
|
|
40
|
+
* @property {import('typescript').Node} typeName - The type name.
|
|
41
|
+
* @property {number} kind - The kind for `ts.SyntaxKind[kind]`.
|
|
170
42
|
*/
|
|
171
43
|
/**
|
|
172
|
-
* @param {string
|
|
173
|
-
* @
|
|
174
|
-
* @returns {string | DocType} The simplified type.
|
|
44
|
+
* @param {string} str - The type string.
|
|
45
|
+
* @returns {TypeScriptType} - The node containing all the information about the input type string.
|
|
175
46
|
*/
|
|
176
|
-
function
|
|
177
|
-
//
|
|
178
|
-
if (
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
type = type.trim();
|
|
182
|
-
if (type !== 'object' && type !== 'object[]' && type !== 'union' && !optional) {
|
|
183
|
-
// console.log("simplify", type);
|
|
184
|
-
return type;
|
|
185
|
-
}
|
|
186
|
-
type = {
|
|
187
|
-
type,
|
|
188
|
-
optional
|
|
189
|
-
};
|
|
190
|
-
} else {
|
|
191
|
-
debugger;
|
|
192
|
-
console.warn("simplifyType> neither object nor string for type", type);
|
|
193
|
-
}
|
|
194
|
-
if (type.type === 'object' && type.properties && Object.keys(type.properties).length === 0) {
|
|
195
|
-
delete type.properties;
|
|
196
|
-
// console.log("delete empty", type);
|
|
47
|
+
function parseType(str) {
|
|
48
|
+
// TS doesn't like ... notation in this context
|
|
49
|
+
if (str.startsWith('...')) {
|
|
50
|
+
str = str.slice(3); // remove dots
|
|
51
|
+
str += '[]'; // turn into array
|
|
197
52
|
}
|
|
198
|
-
|
|
199
|
-
|
|
53
|
+
// type tmp = (...string) => 123; to have a function context
|
|
54
|
+
str = `type tmp = ${str};`;
|
|
55
|
+
const ast = ts.createSourceFile('repl.ts', str, ts.ScriptTarget.Latest, true /*setParentNodes*/);
|
|
56
|
+
return ast.statements[0].type;
|
|
200
57
|
}
|
|
201
|
-
|
|
58
|
+
/** @type {Record<string, 'missing'|'found'>} */
|
|
59
|
+
const requiredTypeofs = {};
|
|
202
60
|
/**
|
|
203
|
-
*
|
|
61
|
+
* Converts a TypeScript AST node to a source string representation or to an intermediate object describing the type.
|
|
204
62
|
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
63
|
+
* This function handles various TypeScript AST node types and converts them into a string
|
|
64
|
+
* or an object representing the type.
|
|
65
|
+
*
|
|
66
|
+
* @param {TypeScriptType} node - The TypeScript AST node to convert.
|
|
67
|
+
* @returns {string | number | boolean | {type: string, [key: string]: any} | undefined} The source string/number,
|
|
68
|
+
* or an object with type information based on the node, or `undefined` if the node kind is not handled.
|
|
208
69
|
*/
|
|
209
|
-
function
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
70
|
+
function toSourceTS(node) {
|
|
71
|
+
const {
|
|
72
|
+
typeArguments,
|
|
73
|
+
typeName
|
|
74
|
+
} = node;
|
|
75
|
+
const kind_ = ts.SyntaxKind[node.kind];
|
|
76
|
+
const {
|
|
77
|
+
AnyKeyword,
|
|
78
|
+
ArrayType,
|
|
79
|
+
BooleanKeyword,
|
|
80
|
+
FunctionType,
|
|
81
|
+
Identifier,
|
|
82
|
+
IntersectionType,
|
|
83
|
+
JSDocAllType,
|
|
84
|
+
LastTypeNode,
|
|
85
|
+
LiteralType,
|
|
86
|
+
NullKeyword,
|
|
87
|
+
NumberKeyword,
|
|
88
|
+
NumericLiteral,
|
|
89
|
+
ObjectKeyword,
|
|
90
|
+
Parameter,
|
|
91
|
+
ParenthesizedType,
|
|
92
|
+
PropertySignature,
|
|
93
|
+
StringKeyword,
|
|
94
|
+
StringLiteral,
|
|
95
|
+
ThisType,
|
|
96
|
+
TupleType,
|
|
97
|
+
TypeLiteral,
|
|
98
|
+
TypeReference,
|
|
99
|
+
UndefinedKeyword,
|
|
100
|
+
UnionType,
|
|
101
|
+
JSDocNullableType,
|
|
102
|
+
TrueKeyword,
|
|
103
|
+
FalseKeyword,
|
|
104
|
+
VoidKeyword,
|
|
105
|
+
UnknownKeyword,
|
|
106
|
+
NeverKeyword,
|
|
107
|
+
BigIntKeyword,
|
|
108
|
+
BigIntLiteral,
|
|
109
|
+
ConditionalType,
|
|
110
|
+
IndexedAccessType,
|
|
111
|
+
RestType,
|
|
112
|
+
TypeQuery,
|
|
113
|
+
// parseType('typeof Number')
|
|
114
|
+
TypeOperator,
|
|
115
|
+
// parseType('keyof typeof obj')
|
|
116
|
+
KeyOfKeyword,
|
|
117
|
+
// "operator" key in TypeOperator node
|
|
118
|
+
ConstructorType,
|
|
119
|
+
// parseType('new (...args: any[]) => any');
|
|
120
|
+
NamedTupleMember,
|
|
121
|
+
MappedType,
|
|
122
|
+
// parseType('{[K in TaskType]: InstanceType<typeof SUPPORTED_TASKS[K]["pipeline"]>}')
|
|
123
|
+
TypeParameter // Basically K and TaskType of MappedType
|
|
124
|
+
} = ts.SyntaxKind;
|
|
125
|
+
// console.log({typeArguments, typeName, kind_, node});
|
|
126
|
+
switch (node.kind) {
|
|
127
|
+
case BigIntKeyword:
|
|
128
|
+
return {
|
|
129
|
+
type: 'bigint'
|
|
130
|
+
};
|
|
131
|
+
case BigIntLiteral:
|
|
132
|
+
const literal = node.text.slice(0, -1); // Remove the "n"
|
|
133
|
+
return {
|
|
134
|
+
type: 'bigint',
|
|
135
|
+
literal
|
|
136
|
+
};
|
|
137
|
+
case ConditionalType:
|
|
138
|
+
// Keys on node:
|
|
139
|
+
// ['pos', 'end', 'flags', 'modifierFlagsCache', 'transformFlags', 'parent', 'kind', 'checkType',
|
|
140
|
+
// 'extendsType', 'trueType', 'falseType', 'locals', 'nextContainer']
|
|
141
|
+
const checkType = toSourceTS(node.checkType);
|
|
142
|
+
const extendsType = toSourceTS(node.extendsType);
|
|
143
|
+
const trueType = toSourceTS(node.trueType);
|
|
144
|
+
const falseType = toSourceTS(node.falseType);
|
|
145
|
+
return {
|
|
146
|
+
type: 'condition',
|
|
147
|
+
checkType,
|
|
148
|
+
extendsType,
|
|
149
|
+
trueType,
|
|
150
|
+
falseType
|
|
151
|
+
};
|
|
152
|
+
case ConstructorType:
|
|
153
|
+
{
|
|
154
|
+
const _parameters = node.parameters.map(toSourceTS);
|
|
155
|
+
const _ret = toSourceTS(node.type);
|
|
156
|
+
return {
|
|
157
|
+
type: 'new',
|
|
158
|
+
parameters: _parameters,
|
|
159
|
+
ret: _ret
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
case FunctionType:
|
|
163
|
+
const parameters = node.parameters.map(toSourceTS);
|
|
164
|
+
return {
|
|
165
|
+
type: 'function',
|
|
166
|
+
parameters
|
|
167
|
+
};
|
|
168
|
+
case IndexedAccessType:
|
|
169
|
+
const index = toSourceTS(node.indexType);
|
|
170
|
+
const object = toSourceTS(node.objectType);
|
|
171
|
+
return {
|
|
172
|
+
type: 'indexedAccess',
|
|
173
|
+
index,
|
|
174
|
+
object
|
|
175
|
+
};
|
|
176
|
+
case RestType:
|
|
177
|
+
const annotation = toSourceTS(node.type);
|
|
178
|
+
return {
|
|
179
|
+
type: 'rest',
|
|
180
|
+
annotation
|
|
181
|
+
};
|
|
182
|
+
case JSDocNullableType:
|
|
183
|
+
const t = toSourceTS(node.type);
|
|
184
|
+
return {
|
|
185
|
+
type: 'union',
|
|
186
|
+
members: [t, 'null']
|
|
187
|
+
};
|
|
188
|
+
case MappedType:
|
|
189
|
+
{
|
|
190
|
+
const result = toSourceTS(node.type);
|
|
191
|
+
const parameter = node.typeParameter;
|
|
192
|
+
if (parameter.kind === TypeParameter) {
|
|
193
|
+
// For example: {[K in TaskType]: InstanceType etc.
|
|
194
|
+
const iterable = toSourceTS(parameter.constraint); // TaskType
|
|
195
|
+
const element = toSourceTS(parameter.name); // K
|
|
196
|
+
return {
|
|
197
|
+
type: 'mapping',
|
|
198
|
+
iterable,
|
|
199
|
+
element,
|
|
200
|
+
result
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
console.warn("MappedType: expected TypeParameter");
|
|
204
|
+
return 'transpiler-error';
|
|
205
|
+
}
|
|
206
|
+
// todo work out more: const jsdoc = `(...a: ...number) => 123
|
|
207
|
+
// TS even thinks it's two parameters... just go for array/[]
|
|
208
|
+
case Parameter:
|
|
209
|
+
const type = node.type ? toSourceTS(node.type) : 'any';
|
|
210
|
+
const name = toSourceTS(node.name);
|
|
211
|
+
const ret = {
|
|
212
|
+
type,
|
|
213
|
+
name
|
|
214
|
+
};
|
|
215
|
+
if (node.dotDotDotToken) {
|
|
216
|
+
return {
|
|
217
|
+
type: 'array',
|
|
218
|
+
elementType: ret
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
return ret;
|
|
222
|
+
case TypeQuery:
|
|
223
|
+
const argument = toSourceTS(node.exprName);
|
|
224
|
+
// Notify Asserter class that we have to register variables with this name
|
|
225
|
+
if (!requiredTypeofs[argument]) {
|
|
226
|
+
requiredTypeofs[argument] = 'missing';
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
type: 'typeof',
|
|
230
|
+
argument
|
|
231
|
+
};
|
|
232
|
+
case TypeOperator:
|
|
233
|
+
if (node.operator === KeyOfKeyword) {
|
|
234
|
+
const _argument = toSourceTS(node.type);
|
|
235
|
+
return {
|
|
236
|
+
type: 'keyof',
|
|
237
|
+
argument: _argument
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
console.warn("unimplemented TypeOperator", node);
|
|
241
|
+
case TypeReference:
|
|
242
|
+
{
|
|
243
|
+
if ((typeName.text === 'Object' || typeName.text === 'Record') && (typeArguments == null ? void 0 : typeArguments.length) === 2) {
|
|
244
|
+
return {
|
|
245
|
+
type: 'record',
|
|
246
|
+
key: toSourceTS(typeArguments[0]),
|
|
247
|
+
val: toSourceTS(typeArguments[1])
|
|
248
|
+
};
|
|
249
|
+
} else if (typeName.text === 'Object' && (!typeArguments || (typeArguments == null ? void 0 : typeArguments.length) === 0)) {
|
|
250
|
+
return {
|
|
251
|
+
type: 'object',
|
|
252
|
+
properties: {}
|
|
253
|
+
};
|
|
254
|
+
} else if (typeName.text === 'Map' && (typeArguments == null ? void 0 : typeArguments.length) === 2) {
|
|
255
|
+
const key = toSourceTS(typeArguments[0]);
|
|
256
|
+
const val = toSourceTS(typeArguments[1]);
|
|
257
|
+
return {
|
|
258
|
+
type: 'map',
|
|
259
|
+
key,
|
|
260
|
+
val
|
|
261
|
+
};
|
|
262
|
+
} else if (typeName.text === 'Array' && (typeArguments == null ? void 0 : typeArguments.length) === 1) {
|
|
263
|
+
const elementType = toSourceTS(typeArguments[0]);
|
|
264
|
+
return {
|
|
265
|
+
type: 'array',
|
|
266
|
+
elementType
|
|
267
|
+
};
|
|
268
|
+
} else if (typeName.text === 'Promise' && (typeArguments == null ? void 0 : typeArguments.length) === 1) {
|
|
269
|
+
const elementType = toSourceTS(typeArguments[0]);
|
|
270
|
+
return {
|
|
271
|
+
type: 'promise',
|
|
272
|
+
elementType
|
|
273
|
+
};
|
|
274
|
+
} else if (typeName.text === 'Set' && (typeArguments == null ? void 0 : typeArguments.length) === 1) {
|
|
275
|
+
const elementType = toSourceTS(typeArguments[0]);
|
|
276
|
+
return {
|
|
277
|
+
type: 'set',
|
|
278
|
+
elementType
|
|
279
|
+
};
|
|
280
|
+
} else if (typeName.text === 'Class' && (typeArguments == null ? void 0 : typeArguments.length) === 1) {
|
|
281
|
+
const elementType = toSourceTS(typeArguments[0]);
|
|
282
|
+
return {
|
|
283
|
+
type: 'class',
|
|
284
|
+
elementType
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
if (!typeArguments) {
|
|
288
|
+
return typeName.getText();
|
|
289
|
+
}
|
|
290
|
+
const _name = typeName.text;
|
|
291
|
+
const args = typeArguments.map(toSourceTS);
|
|
292
|
+
return {
|
|
293
|
+
type: 'reference',
|
|
294
|
+
name: _name,
|
|
295
|
+
args
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
case StringKeyword:
|
|
299
|
+
return node.getText();
|
|
300
|
+
case NumberKeyword:
|
|
301
|
+
return node.getText();
|
|
302
|
+
case NamedTupleMember:
|
|
303
|
+
return toSourceTS(node.type);
|
|
304
|
+
case IntersectionType:
|
|
305
|
+
{
|
|
306
|
+
const _members = node.types.map(toSourceTS);
|
|
307
|
+
return {
|
|
308
|
+
type: 'intersection',
|
|
309
|
+
members: _members
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
case TupleType:
|
|
313
|
+
const elements = node.elements.map(toSourceTS);
|
|
314
|
+
return {
|
|
315
|
+
type: 'tuple',
|
|
316
|
+
elements
|
|
317
|
+
};
|
|
318
|
+
case UnionType:
|
|
319
|
+
const members = node.types.map(toSourceTS);
|
|
320
|
+
return {
|
|
321
|
+
type: 'union',
|
|
322
|
+
members
|
|
323
|
+
};
|
|
324
|
+
case TypeLiteral:
|
|
325
|
+
const properties = {};
|
|
326
|
+
node.members.forEach(member => {
|
|
327
|
+
const name = toSourceTS(member.name);
|
|
328
|
+
const type = toSourceTS(member.type);
|
|
329
|
+
properties[name] = type;
|
|
330
|
+
});
|
|
331
|
+
return {
|
|
332
|
+
type: 'object',
|
|
333
|
+
properties
|
|
334
|
+
};
|
|
335
|
+
case PropertySignature:
|
|
336
|
+
console.warn('toSourceTS> should not happen, handled by TypeLiteral directly');
|
|
337
|
+
return `${toSourceTS(node.name)}: ${toSourceTS(node.type)}`;
|
|
338
|
+
case Identifier:
|
|
339
|
+
return node.text;
|
|
340
|
+
case ArrayType:
|
|
341
|
+
{
|
|
342
|
+
const elementType = toSourceTS(node.elementType);
|
|
343
|
+
return {
|
|
344
|
+
type: 'array',
|
|
345
|
+
elementType
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
case LiteralType:
|
|
349
|
+
return toSourceTS(node.literal);
|
|
350
|
+
case AnyKeyword:
|
|
351
|
+
case BooleanKeyword:
|
|
352
|
+
// ts.SyntaxKind[parseType("*").kind] === 'JSDocAllType'
|
|
353
|
+
case JSDocAllType:
|
|
354
|
+
case NullKeyword:
|
|
355
|
+
case StringLiteral:
|
|
356
|
+
case ThisType:
|
|
357
|
+
case UndefinedKeyword:
|
|
358
|
+
case VoidKeyword:
|
|
359
|
+
case UnknownKeyword:
|
|
360
|
+
case NeverKeyword:
|
|
361
|
+
return node.getText();
|
|
362
|
+
case TrueKeyword:
|
|
363
|
+
return true;
|
|
364
|
+
case FalseKeyword:
|
|
365
|
+
return false;
|
|
366
|
+
case NumericLiteral:
|
|
367
|
+
return Number(node.getText());
|
|
368
|
+
case ObjectKeyword:
|
|
369
|
+
return {
|
|
370
|
+
type: 'object',
|
|
371
|
+
properties: {}
|
|
372
|
+
};
|
|
373
|
+
case ParenthesizedType:
|
|
374
|
+
// fall-through for parentheses
|
|
375
|
+
return toSourceTS(node.type);
|
|
376
|
+
case LastTypeNode:
|
|
377
|
+
return toSourceTS(node.qualifier);
|
|
378
|
+
default:
|
|
379
|
+
// const test = {};
|
|
380
|
+
// Object.entries(ts.SyntaxKind).forEach(([name, id]) => {
|
|
381
|
+
// test[id] = (test[id] || []);
|
|
382
|
+
// test[id].push(name);
|
|
383
|
+
// });
|
|
384
|
+
// console.log(test);
|
|
385
|
+
console.warn('toSourceTS> unhandled kind - make sure to understand you cannot reverse TS enums');
|
|
386
|
+
console.warn('if they contain range aliases, for example:');
|
|
387
|
+
console.warn('ts.SyntaxKind.NumericLiteral === ts.SyntaxKind.FirstLiteralToken');
|
|
388
|
+
console.warn('so this "kind" could be wrong, but requires handling anyway:', kind_, node);
|
|
389
|
+
debugger;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* @todo expandTypeDepFree doesn't support "complicated" types.
|
|
395
|
+
* For actual builds, we use expandType() anyway (which is based on TypeScript).
|
|
396
|
+
* But since TypeScript is a huge dependency, I'm looking into BabelFlow/BabelTypescript parser.
|
|
397
|
+
* Comparing AST's like this usually helps to find bugs or potential issues,
|
|
398
|
+
* while we can benchmark for best performance too.
|
|
399
|
+
* @example
|
|
400
|
+
* const tooComplex = 'Array<string|{chunks?: undefined|Array<{language: string|null, timestamp: Array<number|null>, text: string}>}>';
|
|
401
|
+
* console.log(expandTypeDepFree(tooComplex));
|
|
402
|
+
*/
|
|
403
|
+
/**
|
|
404
|
+
* @typedef {object} ExpandTypeReturnValue
|
|
405
|
+
* @property {'array' | 'union' | 'record' | 'tuple' | 'object' | 'promise' | 'typeof'} type - The type.
|
|
406
|
+
* @property {object | string} [elementType] - For Array.
|
|
407
|
+
* @property {object | string} [key] - For Record<key, val>
|
|
408
|
+
* @property {object | string} [val] - For Record<key, val>
|
|
409
|
+
* @property {(object | string)[]} [members] - For unions.
|
|
410
|
+
* @property {object | string} [properties] - For objects.
|
|
411
|
+
* @property {(object | string)[]} [elements] - For tuples.
|
|
412
|
+
* @property {object | string} [argument] - For typeof.
|
|
413
|
+
*/
|
|
414
|
+
/**
|
|
415
|
+
* 'DepFree' refers to the fact that this function has no dependencies,
|
|
416
|
+
* while `expandType` depends on TypeScript itself for maximum compatibility.
|
|
417
|
+
* @example
|
|
418
|
+
* expandTypeDepFree('(123) '); // Outputs: '123'
|
|
419
|
+
* expandTypeDepFree('Array<number> '); // Outputs: { type: 'array', elementType: 'number' }
|
|
420
|
+
* expandTypeDepFree('Array<(123) > '); // Outputs: { type: 'array', elementType: '123' }
|
|
421
|
+
* expandTypeDepFree(' ( ( 123 ) ) '); // Outputs: '123'
|
|
422
|
+
* expandTypeDepFree(' (string ) |(number ) '); // Outputs: { type: 'union', members: ['string', 'number'] }
|
|
423
|
+
* expandTypeDepFree(' (( Object ) ) '); // Outputs: { type: 'object', properties: {} }
|
|
424
|
+
* @param {string} type - The input type to expand.
|
|
425
|
+
* @returns {string | ExpandTypeReturnValue} Object containing parsed information from type string.
|
|
426
|
+
*/
|
|
427
|
+
function expandTypeDepFree(type) {
|
|
428
|
+
type = type.trim();
|
|
429
|
+
// '(123)' -> '123'
|
|
430
|
+
while (!type.includes('|') && type[0] === '(' && type[type.length - 1] === ')') {
|
|
431
|
+
type = type.slice(1, -1).trim();
|
|
432
|
+
}
|
|
433
|
+
// (1) Rest parameters like ...string
|
|
434
|
+
if (type[0] === '.' && type[1] === '.' && type[2] === '.') {
|
|
435
|
+
const elementType = type.slice(3);
|
|
436
|
+
return {
|
|
437
|
+
type: 'array',
|
|
438
|
+
elementType
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
// (2)
|
|
442
|
+
// Array<...>
|
|
443
|
+
if (type.startsWith("Array<") && type.endsWith('>')) {
|
|
444
|
+
const typeSlice = type.slice(6, -1);
|
|
445
|
+
const elementType = expandTypeDepFree(typeSlice);
|
|
446
|
+
return {
|
|
447
|
+
type: "array",
|
|
448
|
+
elementType
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
// Promise<...>
|
|
452
|
+
if (type.startsWith("Promise<") && type.endsWith('>')) {
|
|
453
|
+
const typeSlice = type.slice(8, -1);
|
|
454
|
+
const elementType = expandTypeDepFree(typeSlice);
|
|
455
|
+
return {
|
|
456
|
+
type: "promise",
|
|
457
|
+
elementType
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
// (3) Object<...> or Record<...>
|
|
461
|
+
if ((type.startsWith("Object<") || type.startsWith("Record<")) && type.endsWith('>')) {
|
|
462
|
+
const recordSlice = type.slice(7, -1);
|
|
463
|
+
const firstComma = recordSlice.indexOf(',');
|
|
464
|
+
if (firstComma === -1) {
|
|
465
|
+
console.warn("expandTypeDepFree> invalid Object/Record");
|
|
466
|
+
}
|
|
467
|
+
const key = recordSlice.slice(0, firstComma).trim();
|
|
468
|
+
const val = recordSlice.slice(firstComma + 1).trim();
|
|
469
|
+
return {
|
|
470
|
+
type: "record",
|
|
471
|
+
key: expandTypeDepFree(key),
|
|
472
|
+
val: expandTypeDepFree(val)
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
// (4) {...}
|
|
476
|
+
if (type[0] === '{' && type[type.length - 1] === '}') {
|
|
477
|
+
const propertiesArray = type.slice(1, -1).split(','); // ['entity: Entity', ' app: AppBase']
|
|
478
|
+
const properties = {};
|
|
479
|
+
propertiesArray.forEach(_ => {
|
|
480
|
+
const [propName, propType] = _.split(":").map(_ => _.trim());
|
|
481
|
+
if (!propName || !propType) {
|
|
482
|
+
console.warn('expandTypeDepFree> unexpected type format, fix');
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
properties[propName] = propType;
|
|
486
|
+
});
|
|
487
|
+
return {
|
|
488
|
+
type: 'object',
|
|
489
|
+
properties
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
// (5) expand unions
|
|
493
|
+
const members = type.split("|");
|
|
494
|
+
if (members.length >= 2) {
|
|
495
|
+
members.forEach((_, i) => members[i] = _.trim());
|
|
496
|
+
return {
|
|
497
|
+
type: 'union',
|
|
498
|
+
members: members.map(expandTypeDepFree)
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
// (6) expand [] Arrays
|
|
502
|
+
// Test arrays: new pc.Mat3().set([1, 2, 3, "asd"])
|
|
503
|
+
if (type.endsWith("[]")) {
|
|
504
|
+
const typeSlice = type.slice(0, -2);
|
|
505
|
+
return {
|
|
506
|
+
type: 'array',
|
|
507
|
+
elementType: expandTypeDepFree(typeSlice)
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
// (7) expand tuples
|
|
511
|
+
if (type[0] === '[' && type[type.length - 1] === ']') {
|
|
512
|
+
const elements = type.slice(1, -1).split(','); // ['null', ' Texture', ' Texture', ' Texture', ' Texture', ' Texture', ' Texture']
|
|
513
|
+
return {
|
|
514
|
+
type: 'tuple',
|
|
515
|
+
elements: elements.map(expandTypeDepFree)
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
// (8) expand typeof expressions
|
|
519
|
+
if (type.startsWith('typeof ')) {
|
|
520
|
+
const argument = expandTypeDepFree(type.substring(7));
|
|
521
|
+
return {
|
|
522
|
+
type: 'typeof',
|
|
523
|
+
argument
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
if (type === 'object' || type === 'Object') {
|
|
527
|
+
return {
|
|
528
|
+
type: 'object',
|
|
529
|
+
properties: {}
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
return type;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/** @typedef {import('@babel/types').Node} Node */
|
|
536
|
+
/** @typedef {import('@babel/types').Function} Function */
|
|
537
|
+
/**
|
|
538
|
+
* Checks if the provided node is a function-like structure.
|
|
539
|
+
*
|
|
540
|
+
* @param {Node} node - The Babel AST node to be tested.
|
|
541
|
+
* @returns {node is Function} - `true` if the node is a function-like structure, otherwise `false`.
|
|
542
|
+
*/
|
|
543
|
+
function nodeIsFunction(node) {
|
|
544
|
+
switch (node.type) {
|
|
545
|
+
case 'ArrowFunctionExpression':
|
|
546
|
+
case 'ClassMethod':
|
|
547
|
+
case 'ClassPrivateMethod':
|
|
548
|
+
case 'FunctionDeclaration':
|
|
549
|
+
case 'FunctionExpression':
|
|
550
|
+
case 'ObjectMethod':
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* @typedef DocType
|
|
558
|
+
* @property {boolean} optional - Type is optional.
|
|
559
|
+
*/
|
|
560
|
+
/**
|
|
561
|
+
* @param {string | DocType} type - The type.
|
|
562
|
+
* @param {boolean} optional - Optionality
|
|
563
|
+
* @returns {string | DocType} The simplified type.
|
|
564
|
+
*/
|
|
565
|
+
function simplifyType(type, optional) {
|
|
566
|
+
// If it's already an object, just set optionality.
|
|
567
|
+
if (type instanceof Object) {
|
|
568
|
+
type.optional = optional;
|
|
569
|
+
} else if (typeof type === 'string') {
|
|
570
|
+
type = type.trim();
|
|
571
|
+
if (type !== 'object' && type !== 'object[]' && type !== 'union' && !optional) {
|
|
572
|
+
// console.log("simplify", type);
|
|
573
|
+
return type;
|
|
574
|
+
}
|
|
575
|
+
type = {
|
|
576
|
+
type,
|
|
577
|
+
optional
|
|
578
|
+
};
|
|
579
|
+
} else {
|
|
580
|
+
debugger;
|
|
581
|
+
console.warn("simplifyType> neither object nor string for type", type);
|
|
582
|
+
}
|
|
583
|
+
if (type.type === 'object' && type.properties && Object.keys(type.properties).length === 0) {
|
|
584
|
+
delete type.properties;
|
|
585
|
+
// console.log("delete empty", type);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return type;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Parses JSDoc comments to extract parameter type information.
|
|
593
|
+
*
|
|
594
|
+
* @param {string} src - The JSDoc comment string to parse.
|
|
595
|
+
* @param {Function} [expandType] - An optional function to process the types found in the JSDoc.
|
|
596
|
+
* @returns {Record<string, any> | undefined} An object mapping parameter names to their parsed types, or undefined if no parameters are found.
|
|
597
|
+
*/
|
|
598
|
+
function parseJSDoc(src, expandType = expandTypeDepFree) {
|
|
599
|
+
// Parse something like: @param {Object} [kwargs={}] Optional arguments.
|
|
600
|
+
const regex = /@param \{(.*?)\} ([\[\]a-zA-Z0-9_$=\{\}\.'" ]+)/g;
|
|
601
|
+
const matches = [...src.matchAll(regex)];
|
|
602
|
+
/** @type {Record<string, any>} */
|
|
603
|
+
const params = Object.create(null);
|
|
604
|
+
matches.forEach(_ => {
|
|
605
|
+
const type = expandType(_[1].trim());
|
|
606
|
+
let name = _[2].trim();
|
|
607
|
+
let optional = false;
|
|
608
|
+
// Examples:
|
|
609
|
+
// name: [kwargs={}] The configuration parameters.
|
|
610
|
+
// name: [d = 1.0] Sample spacing
|
|
222
611
|
if (name[0] === '[') {
|
|
223
612
|
// Possible improvement: counting opening/closing brackets for perfect match
|
|
224
613
|
const closer = name.lastIndexOf(']');
|
|
@@ -2117,1284 +2506,924 @@ class Stringifier {
|
|
|
2117
2506
|
const b = this.toSource(local);
|
|
2118
2507
|
if (a !== b) {
|
|
2119
2508
|
return `${a} as ${b}`;
|
|
2120
|
-
}
|
|
2121
|
-
return a;
|
|
2122
|
-
}
|
|
2123
|
-
/**
|
|
2124
|
-
* @param {import("@babel/types").ImportDefaultSpecifier} node - The Babel AST node.
|
|
2125
|
-
* @returns {string} Stringification of the node.
|
|
2126
|
-
*/
|
|
2127
|
-
ImportDefaultSpecifier(node) {
|
|
2128
|
-
const {
|
|
2129
|
-
local
|
|
2130
|
-
} = node;
|
|
2131
|
-
return this.toSource(local);
|
|
2132
|
-
}
|
|
2133
|
-
/**
|
|
2134
|
-
* @param {import("@babel/types").ImportNamespaceSpecifier} node - The Babel AST node.
|
|
2135
|
-
* @returns {string} Stringification of the node.
|
|
2136
|
-
*/
|
|
2137
|
-
ImportNamespaceSpecifier(node) {
|
|
2138
|
-
const {
|
|
2139
|
-
local
|
|
2140
|
-
} = node;
|
|
2141
|
-
return `* as ${this.toSource(local)}`;
|
|
2142
|
-
}
|
|
2143
|
-
/**
|
|
2144
|
-
* @param {import("@babel/types").File} node - The Babel AST node.
|
|
2145
|
-
* @returns {string} Stringification of the node.
|
|
2146
|
-
*/
|
|
2147
|
-
File(node) {
|
|
2148
|
-
return this.toSource(node.program) + '\n';
|
|
2149
|
-
}
|
|
2150
|
-
/**
|
|
2151
|
-
* @param {import("@babel/types").Program} node - The Babel AST node.
|
|
2152
|
-
* @returns {string} Stringification of the node.
|
|
2153
|
-
*/
|
|
2154
|
-
Program(node) {
|
|
2155
|
-
const {
|
|
2156
|
-
/*sourceType, interpreter,*/body,
|
|
2157
|
-
directives
|
|
2158
|
-
} = node;
|
|
2159
|
-
let out = '';
|
|
2160
|
-
// @todo I would like to keep comments above and below,
|
|
2161
|
-
// but below one is currently dropped (does't matter for AST)
|
|
2162
|
-
// See: test/typechecking/directive.mjs
|
|
2163
|
-
out += this.mapToSource(directives);
|
|
2164
|
-
out += this.mapToSource(body).join('\n');
|
|
2165
|
-
return out;
|
|
2166
|
-
}
|
|
2167
|
-
/**
|
|
2168
|
-
* @param {import("@babel/types").StringLiteral} node - The Babel AST node.
|
|
2169
|
-
* @returns {string} Stringification of the node.
|
|
2170
|
-
*/
|
|
2171
|
-
StringLiteral(node) {
|
|
2172
|
-
const {
|
|
2173
|
-
extra
|
|
2174
|
-
} = node;
|
|
2175
|
-
// Never experienced this so far, but types are types...
|
|
2176
|
-
if (!extra) {
|
|
2177
|
-
debugger;
|
|
2178
|
-
return '';
|
|
2179
|
-
}
|
|
2180
|
-
if (typeof extra.raw !== 'string') {
|
|
2181
|
-
debugger;
|
|
2182
|
-
return '';
|
|
2183
|
-
}
|
|
2184
|
-
return extra.raw;
|
|
2185
|
-
}
|
|
2186
|
-
/**
|
|
2187
|
-
* @param {import("@babel/types").NumericLiteral} node - The Babel AST node.
|
|
2188
|
-
* @returns {string} Stringification of the node.
|
|
2189
|
-
*/
|
|
2190
|
-
NumericLiteral(node) {
|
|
2191
|
-
return node.extra.raw;
|
|
2192
|
-
}
|
|
2193
|
-
/**
|
|
2194
|
-
* @param {import("@babel/types").AssignmentPattern} node - The Babel AST node.
|
|
2195
|
-
* @returns {string} Stringification of the node.
|
|
2196
|
-
*/
|
|
2197
|
-
AssignmentPattern(node) {
|
|
2198
|
-
const {
|
|
2199
|
-
left,
|
|
2200
|
-
right
|
|
2201
|
-
} = node;
|
|
2202
|
-
return `${this.toSource(left)} = ${this.toSource(right)}`;
|
|
2203
|
-
}
|
|
2204
|
-
/**
|
|
2205
|
-
* @param {import("@babel/types").NullLiteral} node - The Babel AST node.
|
|
2206
|
-
* @returns {string} Stringification of the node.
|
|
2207
|
-
*/
|
|
2208
|
-
NullLiteral(node) {
|
|
2209
|
-
return 'null';
|
|
2210
|
-
}
|
|
2211
|
-
/**
|
|
2212
|
-
* @param {import("@babel/types").TaggedTemplateExpression} node - The Babel AST node.
|
|
2213
|
-
* @returns {string} Stringification of the node.
|
|
2214
|
-
*/
|
|
2215
|
-
TaggedTemplateExpression(node) {
|
|
2216
|
-
const {
|
|
2217
|
-
tag,
|
|
2218
|
-
quasi
|
|
2219
|
-
} = node;
|
|
2220
|
-
const t = this.toSource(tag);
|
|
2221
|
-
const q = this.toSource(quasi);
|
|
2222
|
-
return t + q;
|
|
2223
|
-
}
|
|
2224
|
-
/**
|
|
2225
|
-
* @param {import("@babel/types").YieldExpression} node - The Babel AST node.
|
|
2226
|
-
* @returns {string} Stringification of the node.
|
|
2227
|
-
*/
|
|
2228
|
-
YieldExpression(node) {
|
|
2229
|
-
const {
|
|
2230
|
-
delegate,
|
|
2231
|
-
argument
|
|
2232
|
-
} = node;
|
|
2233
|
-
let keyword = 'yield';
|
|
2234
|
-
if (delegate) {
|
|
2235
|
-
keyword += '*';
|
|
2236
|
-
}
|
|
2237
|
-
const a = this.toSource(argument);
|
|
2238
|
-
return `${keyword} ${a}`;
|
|
2239
|
-
}
|
|
2240
|
-
}
|
|
2241
|
-
|
|
2242
|
-
/** @typedef {import('@babel/types').Node } Node */
|
|
2243
|
-
/** @typedef {import("@babel/types").ClassMethod } ClassMethod */
|
|
2244
|
-
/** @typedef {import("@babel/types").ClassPrivateMethod} ClassPrivateMethod */
|
|
2245
|
-
/** @typedef {import('./stat.mjs').Stat } Stat */
|
|
2246
|
-
/**
|
|
2247
|
-
* @typedef {object} Options
|
|
2248
|
-
* @property {boolean} [forceCurly] - Determines whether curly braces are enforced in Stringifier.
|
|
2249
|
-
* @property {boolean} [validateDivision] - Indicates whether division operations should be validated.
|
|
2250
|
-
* @property {Function} [expandType] - A function that expands shorthand types into full descriptions.
|
|
2251
|
-
* @property {string} [filename] - The name of a file to which the instance pertains.
|
|
2252
|
-
* @property {boolean} [addHeader] - Whether to add import declarations headers. Defaults to true.
|
|
2253
|
-
* @property {string[]} [ignoreLocations] - Ignore these locations because they are known false-positives.
|
|
2254
|
-
*/
|
|
2255
|
-
class Asserter extends Stringifier {
|
|
2256
|
-
/**
|
|
2257
|
-
* @param {Options} [options] - The options.
|
|
2258
|
-
*/
|
|
2259
|
-
constructor({
|
|
2260
|
-
forceCurly = true,
|
|
2261
|
-
validateDivision = true,
|
|
2262
|
-
expandType = expandTypeDepFree,
|
|
2263
|
-
filename,
|
|
2264
|
-
addHeader = true,
|
|
2265
|
-
ignoreLocations = []
|
|
2266
|
-
} = {}) {
|
|
2267
|
-
super();
|
|
2268
|
-
/** @type {Record<string, Stat>} */
|
|
2269
|
-
this.stats = {
|
|
2270
|
-
'ArrowFunctionExpression': {
|
|
2271
|
-
checked: 0,
|
|
2272
|
-
unchecked: 0
|
|
2273
|
-
},
|
|
2274
|
-
'ClassMethod#constructor': {
|
|
2275
|
-
checked: 0,
|
|
2276
|
-
unchecked: 0
|
|
2277
|
-
},
|
|
2278
|
-
'ClassMethod#get': {
|
|
2279
|
-
checked: 0,
|
|
2280
|
-
unchecked: 0
|
|
2281
|
-
},
|
|
2282
|
-
'ClassMethod#method': {
|
|
2283
|
-
checked: 0,
|
|
2284
|
-
unchecked: 0
|
|
2285
|
-
},
|
|
2286
|
-
'ClassMethod#set': {
|
|
2287
|
-
checked: 0,
|
|
2288
|
-
unchecked: 0
|
|
2289
|
-
},
|
|
2290
|
-
'ClassPrivateMethod#get': {
|
|
2291
|
-
checked: 0,
|
|
2292
|
-
unchecked: 0
|
|
2293
|
-
},
|
|
2294
|
-
'ClassPrivateMethod#method': {
|
|
2295
|
-
checked: 0,
|
|
2296
|
-
unchecked: 0
|
|
2297
|
-
},
|
|
2298
|
-
'ClassPrivateMethod#set': {
|
|
2299
|
-
checked: 0,
|
|
2300
|
-
unchecked: 0
|
|
2301
|
-
},
|
|
2302
|
-
'FunctionDeclaration': {
|
|
2303
|
-
checked: 0,
|
|
2304
|
-
unchecked: 0
|
|
2305
|
-
},
|
|
2306
|
-
'FunctionExpression': {
|
|
2307
|
-
checked: 0,
|
|
2308
|
-
unchecked: 0
|
|
2309
|
-
},
|
|
2310
|
-
'ObjectMethod': {
|
|
2311
|
-
checked: 0,
|
|
2312
|
-
unchecked: 0
|
|
2313
|
-
}
|
|
2314
|
-
};
|
|
2315
|
-
/** @type {Record<string, object>} */
|
|
2316
|
-
this.typedefs = {};
|
|
2317
|
-
this.forceCurly = forceCurly;
|
|
2318
|
-
this.validateDivision = validateDivision;
|
|
2319
|
-
// @todo collect every type + manually validate as test set
|
|
2320
|
-
// + implement expandType using Babel Flow type parser aswell
|
|
2321
|
-
this.expandType = expandType;
|
|
2322
|
-
this.filename = filename;
|
|
2323
|
-
this.addHeader = addHeader;
|
|
2324
|
-
this.ignoreLocations = ignoreLocations;
|
|
2509
|
+
}
|
|
2510
|
+
return a;
|
|
2325
2511
|
}
|
|
2326
2512
|
/**
|
|
2327
|
-
*
|
|
2328
|
-
* @override
|
|
2329
|
-
* @param {import("@babel/types").ArrowFunctionExpression} node - The Babel AST node.
|
|
2513
|
+
* @param {import("@babel/types").ImportDefaultSpecifier} node - The Babel AST node.
|
|
2330
2514
|
* @returns {string} Stringification of the node.
|
|
2331
2515
|
*/
|
|
2332
|
-
|
|
2516
|
+
ImportDefaultSpecifier(node) {
|
|
2333
2517
|
const {
|
|
2334
|
-
|
|
2335
|
-
body,
|
|
2336
|
-
generator,
|
|
2337
|
-
params /*, extra*/
|
|
2518
|
+
local
|
|
2338
2519
|
} = node;
|
|
2339
|
-
|
|
2340
|
-
if (async) {
|
|
2341
|
-
out += 'async ';
|
|
2342
|
-
}
|
|
2343
|
-
if (generator) {
|
|
2344
|
-
out += ' * ';
|
|
2345
|
-
}
|
|
2346
|
-
out += this.FunctionDeclarationParams(params);
|
|
2347
|
-
out += ' =>';
|
|
2348
|
-
if (body.type === 'BlockStatement') {
|
|
2349
|
-
out += this.toSource(body);
|
|
2350
|
-
} else {
|
|
2351
|
-
out += ' {\n';
|
|
2352
|
-
out += this.generateTypeChecks(node);
|
|
2353
|
-
out += this.spaces + 'return ' + this.toSource(body) + ';\n';
|
|
2354
|
-
out += '}';
|
|
2355
|
-
}
|
|
2356
|
-
return out;
|
|
2520
|
+
return this.toSource(local);
|
|
2357
2521
|
}
|
|
2358
2522
|
/**
|
|
2359
|
-
* @
|
|
2360
|
-
* @param {import("@babel/types").ClassDeclaration} node - The Babel AST node.
|
|
2523
|
+
* @param {import("@babel/types").ImportNamespaceSpecifier} node - The Babel AST node.
|
|
2361
2524
|
* @returns {string} Stringification of the node.
|
|
2362
2525
|
*/
|
|
2363
|
-
|
|
2526
|
+
ImportNamespaceSpecifier(node) {
|
|
2364
2527
|
const {
|
|
2365
|
-
|
|
2528
|
+
local
|
|
2366
2529
|
} = node;
|
|
2367
|
-
|
|
2368
|
-
let out = super.ClassDeclaration(node);
|
|
2369
|
-
out += `${this.spaces}registerClass(${id_});`;
|
|
2370
|
-
return out;
|
|
2530
|
+
return `* as ${this.toSource(local)}`;
|
|
2371
2531
|
}
|
|
2372
2532
|
/**
|
|
2373
|
-
*
|
|
2374
|
-
*
|
|
2375
|
-
* @param {Node} node - The starting node to search from.
|
|
2376
|
-
* @param {T} type - Type name of the node to search for.
|
|
2377
|
-
* @template {Node['type']} T
|
|
2378
|
-
* @returns {Extract<Node, {type: T}>|undefined} The first ancestor node of the specified type, or undefined if none is found.
|
|
2533
|
+
* @param {import("@babel/types").File} node - The Babel AST node.
|
|
2534
|
+
* @returns {string} Stringification of the node.
|
|
2379
2535
|
*/
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
return this.parents.findLast((_, i) => {
|
|
2383
|
-
if (i > currentIndex) {
|
|
2384
|
-
return false;
|
|
2385
|
-
}
|
|
2386
|
-
return _.type === type;
|
|
2387
|
-
});
|
|
2536
|
+
File(node) {
|
|
2537
|
+
return this.toSource(node.program) + '\n';
|
|
2388
2538
|
}
|
|
2389
2539
|
/**
|
|
2390
|
-
*
|
|
2391
|
-
*
|
|
2392
|
-
* @todo
|
|
2393
|
-
* Once we went over every node, we can see if we really require registerTypef, registerClass etc.
|
|
2394
|
-
* @returns {string} The import declaration header for importing RTI.
|
|
2540
|
+
* @param {import("@babel/types").Program} node - The Babel AST node.
|
|
2541
|
+
* @returns {string} Stringification of the node.
|
|
2395
2542
|
*/
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
return
|
|
2543
|
+
Program(node) {
|
|
2544
|
+
const {
|
|
2545
|
+
/*sourceType, interpreter,*/body,
|
|
2546
|
+
directives
|
|
2547
|
+
} = node;
|
|
2548
|
+
let out = '';
|
|
2549
|
+
// @todo I would like to keep comments above and below,
|
|
2550
|
+
// but below one is currently dropped (does't matter for AST)
|
|
2551
|
+
// See: test/typechecking/directive.mjs
|
|
2552
|
+
out += this.mapToSource(directives);
|
|
2553
|
+
out += this.mapToSource(body).join('\n');
|
|
2554
|
+
return out;
|
|
2408
2555
|
}
|
|
2409
2556
|
/**
|
|
2410
|
-
*
|
|
2411
|
-
*
|
|
2412
|
-
* This method travels up the syntax tree from the given node to find a parent node with leading comments.
|
|
2413
|
-
* This search is bounded by function boundaries or a 'CallExpression' node, as per the logic defined within the loop.
|
|
2414
|
-
* @param {Node} node - The node representing the arrow function expression for which to find the leading comments node.
|
|
2415
|
-
* @returns {Node|undefined} The node that contains the leading comments, or `undefined`
|
|
2416
|
-
* if none is found before reaching a different function or 'CallExpression'.
|
|
2557
|
+
* @param {import("@babel/types").StringLiteral} node - The Babel AST node.
|
|
2558
|
+
* @returns {string} Stringification of the node.
|
|
2417
2559
|
*/
|
|
2418
|
-
|
|
2560
|
+
StringLiteral(node) {
|
|
2419
2561
|
const {
|
|
2420
|
-
|
|
2421
|
-
} =
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
return
|
|
2562
|
+
extra
|
|
2563
|
+
} = node;
|
|
2564
|
+
// Never experienced this so far, but types are types...
|
|
2565
|
+
if (!extra) {
|
|
2566
|
+
debugger;
|
|
2567
|
+
return '';
|
|
2426
2568
|
}
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
// contain leadingComments.
|
|
2431
|
-
i--;
|
|
2432
|
-
while (i >= 0) {
|
|
2433
|
-
parent = parents[i];
|
|
2434
|
-
//if (parent.type === 'CallExpression') {
|
|
2435
|
-
// break;
|
|
2436
|
-
//}
|
|
2437
|
-
if (nodeIsFunction(parent)) {
|
|
2438
|
-
break;
|
|
2439
|
-
}
|
|
2440
|
-
if (parent.leadingComments) {
|
|
2441
|
-
return parent;
|
|
2442
|
-
}
|
|
2443
|
-
i--;
|
|
2569
|
+
if (typeof extra.raw !== 'string') {
|
|
2570
|
+
debugger;
|
|
2571
|
+
return '';
|
|
2444
2572
|
}
|
|
2573
|
+
return extra.raw;
|
|
2445
2574
|
}
|
|
2446
2575
|
/**
|
|
2447
|
-
*
|
|
2448
|
-
* @
|
|
2576
|
+
* @param {import("@babel/types").NumericLiteral} node - The Babel AST node.
|
|
2577
|
+
* @returns {string} Stringification of the node.
|
|
2449
2578
|
*/
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
console.warn("[WARN]", this.filename);
|
|
2453
|
-
}
|
|
2454
|
-
console.warn(...args);
|
|
2579
|
+
NumericLiteral(node) {
|
|
2580
|
+
return node.extra.raw;
|
|
2455
2581
|
}
|
|
2456
|
-
|
|
2582
|
+
/**
|
|
2583
|
+
* @param {import("@babel/types").AssignmentPattern} node - The Babel AST node.
|
|
2584
|
+
* @returns {string} Stringification of the node.
|
|
2585
|
+
*/
|
|
2586
|
+
AssignmentPattern(node) {
|
|
2457
2587
|
const {
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
if (parent.leadingComments) {
|
|
2463
|
-
return parent;
|
|
2464
|
-
}
|
|
2465
|
-
// Skip now, if we find another function first,
|
|
2466
|
-
// there is no JSDoc for our function anymore.
|
|
2467
|
-
// Not interested in our start node if it didn't
|
|
2468
|
-
// contain leadingComments.
|
|
2469
|
-
i--;
|
|
2470
|
-
while (i >= 0) {
|
|
2471
|
-
parent = parents[i];
|
|
2472
|
-
//if (parent.type === 'CallExpression') {
|
|
2473
|
-
// break;
|
|
2474
|
-
//}
|
|
2475
|
-
if (nodeIsFunction(parent)) {
|
|
2476
|
-
break;
|
|
2477
|
-
}
|
|
2478
|
-
if (parent.leadingComments) {
|
|
2479
|
-
return parent;
|
|
2480
|
-
}
|
|
2481
|
-
i--;
|
|
2482
|
-
}
|
|
2483
|
-
/** @todo convert all files in test/typechecking/*.mjs into full unit tests */
|
|
2484
|
-
// Old way:
|
|
2485
|
-
// if (node.leadingComments) {
|
|
2486
|
-
// return node.leadingComments;
|
|
2487
|
-
// }
|
|
2488
|
-
// node = this.findParentOfType(node, 'ExpressionStatement');
|
|
2489
|
-
// if (!node) {
|
|
2490
|
-
// /**
|
|
2491
|
-
// * @todo Need more refactoring, see missing type-assertions in test/typechecking/good-old-es5.mjs
|
|
2492
|
-
// */
|
|
2493
|
-
// node = this.parents.findLast(_ => _.type === 'VariableDeclaration');
|
|
2494
|
-
// if (!node) {
|
|
2495
|
-
// return;
|
|
2496
|
-
// }
|
|
2497
|
-
// }
|
|
2588
|
+
left,
|
|
2589
|
+
right
|
|
2590
|
+
} = node;
|
|
2591
|
+
return `${this.toSource(left)} = ${this.toSource(right)}`;
|
|
2498
2592
|
}
|
|
2499
2593
|
/**
|
|
2500
|
-
* @param {
|
|
2501
|
-
* @returns {
|
|
2594
|
+
* @param {import("@babel/types").NullLiteral} node - The Babel AST node.
|
|
2595
|
+
* @returns {string} Stringification of the node.
|
|
2502
2596
|
*/
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
}
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
const exportNamedDeclaration = this.findParentOfType(node, 'ExportNamedDeclaration');
|
|
2519
|
-
leadingComments = exportNamedDeclaration == null ? void 0 : exportNamedDeclaration.leadingComments;
|
|
2520
|
-
}
|
|
2521
|
-
if (node.type === 'ArrowFunctionExpression') {
|
|
2522
|
-
const tmp = this.getLeadingCommentsNodeForArrowFunctionExpression(node);
|
|
2523
|
-
leadingComments = tmp == null ? void 0 : tmp.leadingComments;
|
|
2524
|
-
}
|
|
2525
|
-
}
|
|
2526
|
-
if (leadingComments && leadingComments.length) {
|
|
2527
|
-
const lastComment = leadingComments[leadingComments.length - 1];
|
|
2528
|
-
if (lastComment.type === "CommentBlock") {
|
|
2529
|
-
if (lastComment.value.includes('@event')) {
|
|
2530
|
-
return;
|
|
2531
|
-
}
|
|
2532
|
-
if (node.type === 'ClassMethod' && node.kind === 'set') {
|
|
2533
|
-
const paramName = this.getNameOfParam(node.params[0]);
|
|
2534
|
-
if (node.params.length !== 1) {
|
|
2535
|
-
this.warn("getJSDoc> setters require exactly one argument");
|
|
2536
|
-
}
|
|
2537
|
-
return {
|
|
2538
|
-
[paramName]: parseJSDocSetter(lastComment.value, this.expandType)
|
|
2539
|
-
};
|
|
2540
|
-
}
|
|
2541
|
-
if (lastComment.value.includes('@ignoreRTI')) {
|
|
2542
|
-
return;
|
|
2543
|
-
}
|
|
2544
|
-
return parseJSDoc(lastComment.value, this.expandType);
|
|
2545
|
-
}
|
|
2546
|
-
}
|
|
2597
|
+
NullLiteral(node) {
|
|
2598
|
+
return 'null';
|
|
2599
|
+
}
|
|
2600
|
+
/**
|
|
2601
|
+
* @param {import("@babel/types").TaggedTemplateExpression} node - The Babel AST node.
|
|
2602
|
+
* @returns {string} Stringification of the node.
|
|
2603
|
+
*/
|
|
2604
|
+
TaggedTemplateExpression(node) {
|
|
2605
|
+
const {
|
|
2606
|
+
tag,
|
|
2607
|
+
quasi
|
|
2608
|
+
} = node;
|
|
2609
|
+
const t = this.toSource(tag);
|
|
2610
|
+
const q = this.toSource(quasi);
|
|
2611
|
+
return t + q;
|
|
2547
2612
|
}
|
|
2548
2613
|
/**
|
|
2549
|
-
*
|
|
2550
|
-
*
|
|
2551
|
-
* This function expects a node representing a function parameter and attempts to extract
|
|
2552
|
-
* the parameter's name directly or from an AssignmentPattern.
|
|
2553
|
-
*
|
|
2554
|
-
* @param {Node} param - The AST node representing the function parameter from which to extract the name.
|
|
2555
|
-
* @returns {string} The name of the parameter as a string, or the parameter's source code if the extraction fails.
|
|
2614
|
+
* @param {import("@babel/types").YieldExpression} node - The Babel AST node.
|
|
2615
|
+
* @returns {string} Stringification of the node.
|
|
2556
2616
|
*/
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2617
|
+
YieldExpression(node) {
|
|
2618
|
+
const {
|
|
2619
|
+
delegate,
|
|
2620
|
+
argument
|
|
2621
|
+
} = node;
|
|
2622
|
+
let keyword = 'yield';
|
|
2623
|
+
if (delegate) {
|
|
2624
|
+
keyword += '*';
|
|
2564
2625
|
}
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
return this.toSource(param);
|
|
2568
|
-
}
|
|
2569
|
-
statsReset() {
|
|
2570
|
-
Object.values(this.stats).forEach(statReset);
|
|
2626
|
+
const a = this.toSource(argument);
|
|
2627
|
+
return `${keyword} ${a}`;
|
|
2571
2628
|
}
|
|
2572
|
-
|
|
2573
|
-
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
/** @typedef {import('@babel/types').Node } Node */
|
|
2632
|
+
/** @typedef {import("@babel/types").ClassMethod } ClassMethod */
|
|
2633
|
+
/** @typedef {import("@babel/types").ClassPrivateMethod} ClassPrivateMethod */
|
|
2634
|
+
/** @typedef {import('./stat.mjs').Stat } Stat */
|
|
2635
|
+
/**
|
|
2636
|
+
* @typedef {object} Options
|
|
2637
|
+
* @property {boolean} [forceCurly] - Determines whether curly braces are enforced in Stringifier.
|
|
2638
|
+
* @property {boolean} [validateDivision] - Indicates whether division operations should be validated.
|
|
2639
|
+
* @property {Function} [expandType] - A function that expands shorthand types into full descriptions.
|
|
2640
|
+
* @property {string} [filename] - The name of a file to which the instance pertains.
|
|
2641
|
+
* @property {boolean} [addHeader] - Whether to add import declarations headers. Defaults to true.
|
|
2642
|
+
* @property {string[]} [ignoreLocations] - Ignore these locations because they are known false-positives.
|
|
2643
|
+
*/
|
|
2644
|
+
class Asserter extends Stringifier {
|
|
2645
|
+
/**
|
|
2646
|
+
* @param {Options} [options] - The options.
|
|
2647
|
+
*/
|
|
2648
|
+
constructor({
|
|
2649
|
+
forceCurly = true,
|
|
2650
|
+
validateDivision = true,
|
|
2651
|
+
expandType = expandTypeDepFree,
|
|
2652
|
+
filename,
|
|
2653
|
+
addHeader = true,
|
|
2654
|
+
ignoreLocations = []
|
|
2655
|
+
} = {}) {
|
|
2656
|
+
super();
|
|
2657
|
+
/** @type {Record<string, Stat>} */
|
|
2658
|
+
this.stats = {
|
|
2659
|
+
'ArrowFunctionExpression': {
|
|
2660
|
+
checked: 0,
|
|
2661
|
+
unchecked: 0
|
|
2662
|
+
},
|
|
2663
|
+
'ClassMethod#constructor': {
|
|
2664
|
+
checked: 0,
|
|
2665
|
+
unchecked: 0
|
|
2666
|
+
},
|
|
2667
|
+
'ClassMethod#get': {
|
|
2668
|
+
checked: 0,
|
|
2669
|
+
unchecked: 0
|
|
2670
|
+
},
|
|
2671
|
+
'ClassMethod#method': {
|
|
2672
|
+
checked: 0,
|
|
2673
|
+
unchecked: 0
|
|
2674
|
+
},
|
|
2675
|
+
'ClassMethod#set': {
|
|
2676
|
+
checked: 0,
|
|
2677
|
+
unchecked: 0
|
|
2678
|
+
},
|
|
2679
|
+
'ClassPrivateMethod#get': {
|
|
2680
|
+
checked: 0,
|
|
2681
|
+
unchecked: 0
|
|
2682
|
+
},
|
|
2683
|
+
'ClassPrivateMethod#method': {
|
|
2684
|
+
checked: 0,
|
|
2685
|
+
unchecked: 0
|
|
2686
|
+
},
|
|
2687
|
+
'ClassPrivateMethod#set': {
|
|
2688
|
+
checked: 0,
|
|
2689
|
+
unchecked: 0
|
|
2690
|
+
},
|
|
2691
|
+
'FunctionDeclaration': {
|
|
2692
|
+
checked: 0,
|
|
2693
|
+
unchecked: 0
|
|
2694
|
+
},
|
|
2695
|
+
'FunctionExpression': {
|
|
2696
|
+
checked: 0,
|
|
2697
|
+
unchecked: 0
|
|
2698
|
+
},
|
|
2699
|
+
'ObjectMethod': {
|
|
2700
|
+
checked: 0,
|
|
2701
|
+
unchecked: 0
|
|
2702
|
+
}
|
|
2703
|
+
};
|
|
2704
|
+
/** @type {Record<string, object>} */
|
|
2705
|
+
this.typedefs = {};
|
|
2706
|
+
this.forceCurly = forceCurly;
|
|
2707
|
+
this.validateDivision = validateDivision;
|
|
2708
|
+
// @todo collect every type + manually validate as test set
|
|
2709
|
+
// + implement expandType using Babel Flow type parser aswell
|
|
2710
|
+
this.expandType = expandType;
|
|
2711
|
+
this.filename = filename;
|
|
2712
|
+
this.addHeader = addHeader;
|
|
2713
|
+
this.ignoreLocations = ignoreLocations;
|
|
2574
2714
|
}
|
|
2575
2715
|
/**
|
|
2576
|
-
*
|
|
2577
|
-
*
|
|
2578
|
-
* @param {
|
|
2579
|
-
* @returns {
|
|
2580
|
-
* node type is unhandled, defaults to returning a dummy object with 'checked' and 'unchecked'
|
|
2581
|
-
* properties both set to 0.
|
|
2716
|
+
* We expand type-asserted ArrowFunctionExpressions in order to add type assertions.
|
|
2717
|
+
* @override
|
|
2718
|
+
* @param {import("@babel/types").ArrowFunctionExpression} node - The Babel AST node.
|
|
2719
|
+
* @returns {string} Stringification of the node.
|
|
2582
2720
|
*/
|
|
2583
|
-
|
|
2721
|
+
ArrowFunctionExpression(node) {
|
|
2584
2722
|
const {
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
} = parent;
|
|
2594
|
-
return stats[`ClassMethod#${kind}`];
|
|
2595
|
-
} else if (type === 'ClassPrivateMethod') {
|
|
2596
|
-
const parent = /** @type {ClassPrivateMethod} */
|
|
2597
|
-
this.parent;
|
|
2598
|
-
const {
|
|
2599
|
-
kind
|
|
2600
|
-
} = parent;
|
|
2601
|
-
return stats[`ClassPrivateMethod#${kind}`];
|
|
2723
|
+
async,
|
|
2724
|
+
body,
|
|
2725
|
+
generator,
|
|
2726
|
+
params /*, extra*/
|
|
2727
|
+
} = node;
|
|
2728
|
+
let out = '';
|
|
2729
|
+
if (async) {
|
|
2730
|
+
out += 'async ';
|
|
2602
2731
|
}
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
this.warn("getStatsForNode> dummy, but unhandled... fix for node type", node);
|
|
2606
|
-
return {
|
|
2607
|
-
checked: 0,
|
|
2608
|
-
unchecked: 0
|
|
2609
|
-
};
|
|
2732
|
+
if (generator) {
|
|
2733
|
+
out += ' * ';
|
|
2610
2734
|
}
|
|
2611
|
-
|
|
2735
|
+
out += this.FunctionDeclarationParams(params);
|
|
2736
|
+
out += ' =>';
|
|
2737
|
+
if (body.type === 'BlockStatement') {
|
|
2738
|
+
out += this.toSource(body);
|
|
2739
|
+
} else {
|
|
2740
|
+
out += ' {\n';
|
|
2741
|
+
out += this.generateTypeChecks(node);
|
|
2742
|
+
out += this.spaces + 'return ' + this.toSource(body) + ';\n';
|
|
2743
|
+
out += '}';
|
|
2744
|
+
}
|
|
2745
|
+
return out;
|
|
2612
2746
|
}
|
|
2613
2747
|
/**
|
|
2614
|
-
*
|
|
2615
|
-
*
|
|
2616
|
-
*
|
|
2617
|
-
* one of them matches the provided name. Supports various parameter types such as Identifiers
|
|
2618
|
-
* and AssignmentPatterns.
|
|
2619
|
-
*
|
|
2620
|
-
* @param {Node} node - The Babel AST node to inspect. If it's a BlockStatement, the parent node is used instead.
|
|
2621
|
-
* @param {string} name - The name of the parameter to look for within the node's parameters.
|
|
2622
|
-
* @returns {boolean} True if the node has a parameter with the given name; false otherwise.
|
|
2748
|
+
* @override
|
|
2749
|
+
* @param {import("@babel/types").ClassDeclaration} node - The Babel AST node.
|
|
2750
|
+
* @returns {string} Stringification of the node.
|
|
2623
2751
|
*/
|
|
2624
|
-
|
|
2625
|
-
if (node.type === 'BlockStatement') {
|
|
2626
|
-
node = this.parent;
|
|
2627
|
-
}
|
|
2752
|
+
ClassDeclaration(node) {
|
|
2628
2753
|
const {
|
|
2629
|
-
|
|
2754
|
+
id
|
|
2630
2755
|
} = node;
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
} else if (type === 'Identifier') {
|
|
2649
|
-
return node.name === name;
|
|
2650
|
-
} else if (type === 'ArrayPattern' || type === 'ObjectPattern' || type === 'RestElement') {
|
|
2756
|
+
const id_ = this.toSource(id);
|
|
2757
|
+
let out = super.ClassDeclaration(node);
|
|
2758
|
+
out += `${this.spaces}registerClass(${id_});`;
|
|
2759
|
+
return out;
|
|
2760
|
+
}
|
|
2761
|
+
/**
|
|
2762
|
+
* Finds the closest ancestor of the given node that matches the specified type.
|
|
2763
|
+
*
|
|
2764
|
+
* @param {Node} node - The starting node to search from.
|
|
2765
|
+
* @param {T} type - Type name of the node to search for.
|
|
2766
|
+
* @template {Node['type']} T
|
|
2767
|
+
* @returns {Extract<Node, {type: T}>|undefined} The first ancestor node of the specified type, or undefined if none is found.
|
|
2768
|
+
*/
|
|
2769
|
+
findParentOfType(node, type) {
|
|
2770
|
+
const currentIndex = this.parents.findLastIndex(_ => _ === node);
|
|
2771
|
+
return this.parents.findLast((_, i) => {
|
|
2772
|
+
if (i > currentIndex) {
|
|
2651
2773
|
return false;
|
|
2652
2774
|
}
|
|
2653
|
-
|
|
2654
|
-
const code = _.toSource(node);
|
|
2655
|
-
console.log("Unknown type to test params for", type, code);
|
|
2656
|
-
return false;
|
|
2775
|
+
return _.type === type;
|
|
2657
2776
|
});
|
|
2658
2777
|
}
|
|
2659
2778
|
/**
|
|
2660
|
-
*
|
|
2661
|
-
*
|
|
2662
|
-
*
|
|
2663
|
-
*
|
|
2664
|
-
*
|
|
2665
|
-
* it returns an empty string.
|
|
2666
|
-
*
|
|
2667
|
-
* @override
|
|
2668
|
-
* @param {Node} node - The Babel AST node for which to generate type checks.
|
|
2669
|
-
* @returns {string} A string of code with type check assertions, based on the JSDoc comments associated with the given node.
|
|
2779
|
+
* Alternatively "import * as rti from ..." would also prevent "Unused external imports" warning...
|
|
2780
|
+
* or keeping log of every single call during RTI parsing.
|
|
2781
|
+
* @todo
|
|
2782
|
+
* Once we went over every node, we can see if we really require registerTypef, registerClass etc.
|
|
2783
|
+
* @returns {string} The import declaration header for importing RTI.
|
|
2670
2784
|
*/
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
parent
|
|
2674
|
-
} = this;
|
|
2675
|
-
if (node.type === 'BlockStatement' && !nodeIsFunction(parent)) {
|
|
2785
|
+
getHeader() {
|
|
2786
|
+
if (!this.addHeader) {
|
|
2676
2787
|
return '';
|
|
2677
2788
|
}
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
if (!jsdoc) {
|
|
2682
|
-
stat.unchecked++;
|
|
2683
|
-
return '';
|
|
2789
|
+
let header = "import {inspectType, youCanAddABreakpointHere, registerVariable";
|
|
2790
|
+
if (this.validateDivision) {
|
|
2791
|
+
header += ", validateDivision";
|
|
2684
2792
|
}
|
|
2685
|
-
|
|
2793
|
+
header += ", registerTypedef, registerClass} from '@runtime-type-inspector/runtime';\n";
|
|
2794
|
+
// Prevent tree-shaking in UMD build so we can always "add a breakpoint here".
|
|
2795
|
+
header += "export * from '@runtime-type-inspector/runtime';\n";
|
|
2796
|
+
return header;
|
|
2797
|
+
}
|
|
2798
|
+
/**
|
|
2799
|
+
* Retrieves the node associated with the leading comments for an arrow function expression.
|
|
2800
|
+
*
|
|
2801
|
+
* This method travels up the syntax tree from the given node to find a parent node with leading comments.
|
|
2802
|
+
* This search is bounded by function boundaries or a 'CallExpression' node, as per the logic defined within the loop.
|
|
2803
|
+
* @param {Node} node - The node representing the arrow function expression for which to find the leading comments node.
|
|
2804
|
+
* @returns {Node|undefined} The node that contains the leading comments, or `undefined`
|
|
2805
|
+
* if none is found before reaching a different function or 'CallExpression'.
|
|
2806
|
+
*/
|
|
2807
|
+
getLeadingCommentsNodeForArrowFunctionExpression(node) {
|
|
2686
2808
|
const {
|
|
2687
|
-
|
|
2809
|
+
parents
|
|
2688
2810
|
} = this;
|
|
2689
|
-
let
|
|
2690
|
-
let
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
return '// IGNORE RTI TYPE VALIDATIONS, KNOWN ISSUES\n';
|
|
2811
|
+
let i = parents.findLastIndex(_ => _ === node);
|
|
2812
|
+
let parent = parents[i];
|
|
2813
|
+
if (parent.leadingComments) {
|
|
2814
|
+
return parent;
|
|
2694
2815
|
}
|
|
2695
|
-
//
|
|
2696
|
-
//
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
if (param) {
|
|
2708
|
-
const isObjectPattern = param.type === 'ObjectPattern';
|
|
2709
|
-
const isArrayPattern = param.type === 'ArrayPattern';
|
|
2710
|
-
const isSupportedPattern = isObjectPattern || isArrayPattern;
|
|
2711
|
-
// There are four kinds of patterns:
|
|
2712
|
-
// ObjectPattern:
|
|
2713
|
-
// function test({x = 123}) {return x;} test({x: 456});
|
|
2714
|
-
// ArrayPattern:
|
|
2715
|
-
// function test([x = 123]) {return x;}; test([456]);
|
|
2716
|
-
// AssignmentPattern made up of ObjectPattern:
|
|
2717
|
-
// function test({x = 123} = {}) {return x;} test();
|
|
2718
|
-
// AssignmentPattern made up of ArrayPattern:
|
|
2719
|
-
// function test([x = 123] = []) {return x;} test();
|
|
2720
|
-
if (isSupportedPattern) {
|
|
2721
|
-
// The name doesn't matter any longer, because any pattern inherently
|
|
2722
|
-
// drops the identifier from the AST. But we can access it
|
|
2723
|
-
// via arguments[paramIndex] anyway.
|
|
2724
|
-
name = `arguments[${paramIndex}]`;
|
|
2725
|
-
} else if (param.type === 'AssignmentPattern') {
|
|
2726
|
-
const _loc = this.getName(node);
|
|
2727
|
-
if (param.left.type === 'ArrayPattern' && type.type === 'array') {
|
|
2728
|
-
// Add a type assertion for each element of the ArrayPattern
|
|
2729
|
-
for (const element of param.left.elements) {
|
|
2730
|
-
if (element.type !== 'Identifier') {
|
|
2731
|
-
this.warn('Only Identifier case handled right now');
|
|
2732
|
-
continue;
|
|
2733
|
-
}
|
|
2734
|
-
const _t = JSON.stringify(type.elementType, null, 2).replaceAll('\n', '\n' + spaces);
|
|
2735
|
-
out += `${spaces}if (!inspectType(${element.name}, ${_t}, '${_loc}', '${name}')) {\n`;
|
|
2736
|
-
out += `${spaces} youCanAddABreakpointHere();\n${spaces}}\n`;
|
|
2737
|
-
}
|
|
2738
|
-
continue;
|
|
2739
|
-
} else if (param.left.type === 'ObjectPattern' && type.type === 'object') {
|
|
2740
|
-
// Add a type assertion for each property of the ObjectPattern
|
|
2741
|
-
for (const property of param.left.properties) {
|
|
2742
|
-
if (property.key.type !== 'Identifier') {
|
|
2743
|
-
this.warn('ObjectPattern> Only Identifier case handled right now');
|
|
2744
|
-
continue;
|
|
2745
|
-
}
|
|
2746
|
-
const keyName = property.key.name;
|
|
2747
|
-
if (type.type !== 'object' || !type.properties) {
|
|
2748
|
-
this.warn("missing subtype information in JSDoc> in type", JSON.stringify(type, null, 2), "for ObjectPattern:", this.toSource(property).trim());
|
|
2749
|
-
continue;
|
|
2750
|
-
}
|
|
2751
|
-
const subType = type.properties[keyName];
|
|
2752
|
-
if (!subType) {
|
|
2753
|
-
this.warn("missing subtype information in JSDoc");
|
|
2754
|
-
continue;
|
|
2755
|
-
}
|
|
2756
|
-
const _t2 = JSON.stringify(subType, null, 2).replaceAll('\n', '\n' + spaces);
|
|
2757
|
-
out += `${spaces}if (!inspectType(${keyName}, ${_t2}, '${_loc}', '${name}')) {\n`;
|
|
2758
|
-
out += `${spaces} youCanAddABreakpointHere();\n${spaces}}\n`;
|
|
2759
|
-
}
|
|
2760
|
-
continue;
|
|
2761
|
-
}
|
|
2762
|
-
this.warn(`generateTypeChecks> ${_loc}> todo implement`, `AssignmentPattern for parameter ${name}`);
|
|
2763
|
-
continue;
|
|
2764
|
-
}
|
|
2765
|
-
} else {
|
|
2766
|
-
const _loc2 = this.getName(node);
|
|
2767
|
-
this.warn(`generateTypeChecks> ${_loc2}> Missing param: ${name}`);
|
|
2768
|
-
continue;
|
|
2769
|
-
}
|
|
2770
|
-
}
|
|
2771
|
-
let t = JSON.stringify(type, null, 2).replaceAll('\n', '\n' + spaces);
|
|
2772
|
-
if (type === 'this') {
|
|
2773
|
-
const classDecl = this.findParentOfType(node, 'ClassDeclaration');
|
|
2774
|
-
if (!(classDecl != null && classDecl.id)) {
|
|
2775
|
-
this.warn('generateTypeChecks> !classDecl?.id');
|
|
2776
|
-
}
|
|
2777
|
-
t = '"' + this.toSource(classDecl.id) + '"';
|
|
2778
|
-
}
|
|
2779
|
-
const _loc3 = this.getName(node);
|
|
2780
|
-
let prevCheck = '';
|
|
2781
|
-
// JSDoc doesn't support multiple function signatures yet, but this is
|
|
2782
|
-
// exactly what we would need to deal with ObjectPool'ing
|
|
2783
|
-
if (_loc3 === 'ContactPoint#constructor' || _loc3 === 'ContactResult#constructor' || _loc3 === 'SingleContactResult#constructor') {
|
|
2784
|
-
prevCheck = 'arguments.length !== 0 && ';
|
|
2816
|
+
// Skip now, if we find another function first,
|
|
2817
|
+
// there is no JSDoc for our function anymore.
|
|
2818
|
+
// Not interested in our start node if it didn't
|
|
2819
|
+
// contain leadingComments.
|
|
2820
|
+
i--;
|
|
2821
|
+
while (i >= 0) {
|
|
2822
|
+
parent = parents[i];
|
|
2823
|
+
//if (parent.type === 'CallExpression') {
|
|
2824
|
+
// break;
|
|
2825
|
+
//}
|
|
2826
|
+
if (nodeIsFunction(parent)) {
|
|
2827
|
+
break;
|
|
2785
2828
|
}
|
|
2786
|
-
if (
|
|
2787
|
-
|
|
2788
|
-
first = false;
|
|
2829
|
+
if (parent.leadingComments) {
|
|
2830
|
+
return parent;
|
|
2789
2831
|
}
|
|
2790
|
-
|
|
2791
|
-
out += `${spaces} youCanAddABreakpointHere();\n${spaces}}\n`;
|
|
2832
|
+
i--;
|
|
2792
2833
|
}
|
|
2793
|
-
return out;
|
|
2794
2834
|
}
|
|
2795
2835
|
/**
|
|
2796
|
-
*
|
|
2797
|
-
* @
|
|
2836
|
+
* Emits a warning message to the console, optionally prefixed with the instance's filename.
|
|
2837
|
+
* @param {...any} args - A list of arguments to be passed to the console.warn function.
|
|
2798
2838
|
*/
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
// See good-old-es5.mjs example for a test case
|
|
2803
|
-
// TODO: Make an even better name based on Object.assign(ScopeSpace.prototype
|
|
2804
|
-
// Ideally we would figure out the name: ScopeSpace#resolve
|
|
2805
|
-
// Currently we only find "resolve" (still better than 'unnamed function expression'...)
|
|
2806
|
-
return this.toSource(objectProperty.key);
|
|
2807
|
-
}
|
|
2808
|
-
const expressionStatement = this.findParentOfType(node, 'ExpressionStatement');
|
|
2809
|
-
if (expressionStatement) {
|
|
2810
|
-
// There are many kinds of expressions
|
|
2811
|
-
// type Expression = ArrayExpression | AssignmentExpression | BinaryExpression | CallExpression | ...
|
|
2812
|
-
const {
|
|
2813
|
-
left
|
|
2814
|
-
} = expressionStatement.expression;
|
|
2815
|
-
if (left) {
|
|
2816
|
-
return this.toSource(left);
|
|
2817
|
-
}
|
|
2818
|
-
console.warn("Asserter#getNameForFunctionExpression> expression without left");
|
|
2839
|
+
warn(...args) {
|
|
2840
|
+
if (this.filename) {
|
|
2841
|
+
console.warn("[WARN]", this.filename);
|
|
2819
2842
|
}
|
|
2820
|
-
|
|
2843
|
+
console.warn(...args);
|
|
2821
2844
|
}
|
|
2822
|
-
|
|
2823
|
-
* @param {Node} node - The Babel AST node.
|
|
2824
|
-
* @returns {string} Stringification of the node.
|
|
2825
|
-
*/
|
|
2826
|
-
getName(node) {
|
|
2827
|
-
const toSource = this.toSource.bind(this);
|
|
2828
|
-
if (node.type === 'BlockStatement') {
|
|
2829
|
-
node = this.parent;
|
|
2830
|
-
}
|
|
2845
|
+
getLeadingCommentsNodeForFunctionExpression(node) {
|
|
2831
2846
|
const {
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
return toSource(parent.id);
|
|
2857
|
-
}
|
|
2858
|
-
return 'getName> missing parent for ' + node.type;
|
|
2859
|
-
case 'ObjectMethod':
|
|
2860
|
-
return toSource(key);
|
|
2861
|
-
default:
|
|
2862
|
-
this.warn('getName> unhandled type', type, 'for', node);
|
|
2863
|
-
return '/*MISSING*/';
|
|
2847
|
+
parents
|
|
2848
|
+
} = this;
|
|
2849
|
+
let i = parents.findLastIndex(_ => _ === node);
|
|
2850
|
+
let parent = parents[i];
|
|
2851
|
+
if (parent.leadingComments) {
|
|
2852
|
+
return parent;
|
|
2853
|
+
}
|
|
2854
|
+
// Skip now, if we find another function first,
|
|
2855
|
+
// there is no JSDoc for our function anymore.
|
|
2856
|
+
// Not interested in our start node if it didn't
|
|
2857
|
+
// contain leadingComments.
|
|
2858
|
+
i--;
|
|
2859
|
+
while (i >= 0) {
|
|
2860
|
+
parent = parents[i];
|
|
2861
|
+
//if (parent.type === 'CallExpression') {
|
|
2862
|
+
// break;
|
|
2863
|
+
//}
|
|
2864
|
+
if (nodeIsFunction(parent)) {
|
|
2865
|
+
break;
|
|
2866
|
+
}
|
|
2867
|
+
if (parent.leadingComments) {
|
|
2868
|
+
return parent;
|
|
2869
|
+
}
|
|
2870
|
+
i--;
|
|
2864
2871
|
}
|
|
2872
|
+
/** @todo convert all files in test/typechecking/*.mjs into full unit tests */
|
|
2873
|
+
// Old way:
|
|
2874
|
+
// if (node.leadingComments) {
|
|
2875
|
+
// return node.leadingComments;
|
|
2876
|
+
// }
|
|
2877
|
+
// node = this.findParentOfType(node, 'ExpressionStatement');
|
|
2878
|
+
// if (!node) {
|
|
2879
|
+
// /**
|
|
2880
|
+
// * @todo Need more refactoring, see missing type-assertions in test/typechecking/good-old-es5.mjs
|
|
2881
|
+
// */
|
|
2882
|
+
// node = this.parents.findLast(_ => _.type === 'VariableDeclaration');
|
|
2883
|
+
// if (!node) {
|
|
2884
|
+
// return;
|
|
2885
|
+
// }
|
|
2886
|
+
// }
|
|
2865
2887
|
}
|
|
2866
2888
|
/**
|
|
2867
|
-
* @
|
|
2868
|
-
* @
|
|
2869
|
-
* @returns {string} Stringification of the node.
|
|
2889
|
+
* @param {Node} node - The Babel AST node.
|
|
2890
|
+
* @returns {undefined | {}} The return value of `parseJSDoc`.
|
|
2870
2891
|
*/
|
|
2871
|
-
|
|
2872
|
-
if (
|
|
2873
|
-
|
|
2892
|
+
getJSDoc(node) {
|
|
2893
|
+
if (node.type === 'BlockStatement') {
|
|
2894
|
+
node = this.parent;
|
|
2874
2895
|
}
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
operator,
|
|
2878
|
-
right
|
|
2896
|
+
let {
|
|
2897
|
+
leadingComments
|
|
2879
2898
|
} = node;
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2899
|
+
// Receive the leadingComments from the ExpressionStatement, not the FunctionExpression itself.
|
|
2900
|
+
if (node.type === 'FunctionExpression') {
|
|
2901
|
+
const tmp = this.getLeadingCommentsNodeForFunctionExpression(node);
|
|
2902
|
+
leadingComments = tmp == null ? void 0 : tmp.leadingComments;
|
|
2884
2903
|
}
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
const {
|
|
2895
|
-
/*errors,*/program,
|
|
2896
|
-
comments
|
|
2897
|
-
} = node;
|
|
2898
|
-
if (comments) {
|
|
2899
|
-
for (const comment of comments) {
|
|
2900
|
-
const warn = this.warn.bind(this);
|
|
2901
|
-
parseJSDocTypedef(this.typedefs, warn, comment, this.expandType);
|
|
2904
|
+
// Receive the leadingComments from ExportNamedDeclaration, if FunctionDeclaration has none
|
|
2905
|
+
if (!leadingComments) {
|
|
2906
|
+
if (node.type === 'FunctionDeclaration') {
|
|
2907
|
+
const exportNamedDeclaration = this.findParentOfType(node, 'ExportNamedDeclaration');
|
|
2908
|
+
leadingComments = exportNamedDeclaration == null ? void 0 : exportNamedDeclaration.leadingComments;
|
|
2909
|
+
}
|
|
2910
|
+
if (node.type === 'ArrowFunctionExpression') {
|
|
2911
|
+
const tmp = this.getLeadingCommentsNodeForArrowFunctionExpression(node);
|
|
2912
|
+
leadingComments = tmp == null ? void 0 : tmp.leadingComments;
|
|
2902
2913
|
}
|
|
2903
2914
|
}
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2915
|
+
if (leadingComments && leadingComments.length) {
|
|
2916
|
+
const lastComment = leadingComments[leadingComments.length - 1];
|
|
2917
|
+
if (lastComment.type === "CommentBlock") {
|
|
2918
|
+
if (lastComment.value.includes('@event')) {
|
|
2919
|
+
return;
|
|
2920
|
+
}
|
|
2921
|
+
if (node.type === 'ClassMethod' && node.kind === 'set') {
|
|
2922
|
+
const paramName = this.getNameOfParam(node.params[0]);
|
|
2923
|
+
if (node.params.length !== 1) {
|
|
2924
|
+
this.warn("getJSDoc> setters require exactly one argument");
|
|
2925
|
+
}
|
|
2926
|
+
return {
|
|
2927
|
+
[paramName]: parseJSDocSetter(lastComment.value, this.expandType)
|
|
2928
|
+
};
|
|
2929
|
+
}
|
|
2930
|
+
if (lastComment.value.includes('@ignoreRTI')) {
|
|
2931
|
+
return;
|
|
2932
|
+
}
|
|
2933
|
+
return parseJSDoc(lastComment.value, this.expandType);
|
|
2934
|
+
}
|
|
2910
2935
|
}
|
|
2911
|
-
const code = this.toSource(program) + '\n';
|
|
2912
|
-
out += code;
|
|
2913
|
-
return out;
|
|
2914
|
-
}
|
|
2915
|
-
}
|
|
2916
|
-
|
|
2917
|
-
/**
|
|
2918
|
-
* Simple facade which does all the processing. Processes the input
|
|
2919
|
-
* source string, adding runtime type checks based on JSDoc comments.
|
|
2920
|
-
*
|
|
2921
|
-
* This function takes JavaScript source code as input, parses it to an AST, traverses the
|
|
2922
|
-
* AST to find type annotations in JSDoc comments, and generates appropriate runtime type
|
|
2923
|
-
* assertions. These are then inserted into the source, producing a new version of the code
|
|
2924
|
-
* that includes runtime type checking based on the original JSDoc annotations.
|
|
2925
|
-
*
|
|
2926
|
-
* @param {string} src - The input source code containing JSDoc comments to be processed
|
|
2927
|
-
* for type checks.
|
|
2928
|
-
* @param {import('./Asserter.mjs').Options} [options] - Configuration options that dictate
|
|
2929
|
-
* how the processing is performed.
|
|
2930
|
-
* @returns {string} The transformed source code with inserted runtime type checks, or the
|
|
2931
|
-
* original source code commented with an error if processing fails.
|
|
2932
|
-
*/
|
|
2933
|
-
function addTypeChecks(src, options) {
|
|
2934
|
-
try {
|
|
2935
|
-
const asserter = new Asserter(options);
|
|
2936
|
-
const ast = parse(src, {
|
|
2937
|
-
sourceType: 'module',
|
|
2938
|
-
createParenthesizedExpressions: true
|
|
2939
|
-
});
|
|
2940
|
-
const out = asserter.getHeader() + asserter.toSource(ast);
|
|
2941
|
-
return out;
|
|
2942
|
-
} catch (e) {
|
|
2943
|
-
console.error(e);
|
|
2944
|
-
return `/*<addTypeChecks-error>*/${src}/*</addTypeChecks-error>*/`;
|
|
2945
2936
|
}
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
function
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
const drop = ['loc', 'start', 'end', 'leadingComments', 'trailingComments', 'innerComments', 'innerComments', 'comments'];
|
|
2963
|
-
/**
|
|
2964
|
-
* @example
|
|
2965
|
-
* setRight(ast2jsonForComparison(parseSync("/** *"))); // Close comment with / after last *
|
|
2966
|
-
* @param {object} ast - The Babel AST.
|
|
2967
|
-
* @returns {string} String representation in JSON format for debugging/inspecting the AST.
|
|
2968
|
-
*/
|
|
2969
|
-
function ast2jsonForComparison(ast) {
|
|
2970
|
-
return JSON.stringify(ast, function (name, val) {
|
|
2971
|
-
if (name === 'trailingComma' || name === 'parenStart') {
|
|
2972
|
-
return 'offset removed for better comparison';
|
|
2973
|
-
}
|
|
2974
|
-
if (drop.includes(name)) {
|
|
2975
|
-
return undefined; // remove
|
|
2937
|
+
/**
|
|
2938
|
+
* Retrieves the name of a parameter from a Babel AST node.
|
|
2939
|
+
*
|
|
2940
|
+
* This function expects a node representing a function parameter and attempts to extract
|
|
2941
|
+
* the parameter's name directly or from an AssignmentPattern.
|
|
2942
|
+
*
|
|
2943
|
+
* @param {Node} param - The AST node representing the function parameter from which to extract the name.
|
|
2944
|
+
* @returns {string} The name of the parameter as a string, or the parameter's source code if the extraction fails.
|
|
2945
|
+
*/
|
|
2946
|
+
getNameOfParam(param) {
|
|
2947
|
+
if (param.type === 'Identifier') {
|
|
2948
|
+
return param.name;
|
|
2949
|
+
} else if (param.type === 'AssignmentPattern') {
|
|
2950
|
+
if (param.left.type === 'Identifier') {
|
|
2951
|
+
return param.left.name;
|
|
2952
|
+
}
|
|
2976
2953
|
}
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
}
|
|
2981
|
-
|
|
2982
|
-
/**
|
|
2983
|
-
* A roundtrip between code -> AST -> code to validate Stringifier.
|
|
2984
|
-
* @param {string} code - The code.
|
|
2985
|
-
* @returns {string | undefined} The new and once parsed and stringified code.
|
|
2986
|
-
*/
|
|
2987
|
-
function code2ast2code(code) {
|
|
2988
|
-
const stringifier = new Stringifier();
|
|
2989
|
-
const ast = parse(code, {
|
|
2990
|
-
sourceType: 'module'
|
|
2991
|
-
});
|
|
2992
|
-
if (!ast) {
|
|
2993
|
-
return;
|
|
2994
|
-
}
|
|
2995
|
-
const out = stringifier.toSource(ast);
|
|
2996
|
-
return out;
|
|
2997
|
-
}
|
|
2998
|
-
|
|
2999
|
-
/**
|
|
3000
|
-
* @param {string} left - Left source code.
|
|
3001
|
-
* @param {string} right - Right source code.
|
|
3002
|
-
* @returns {boolean} Whether source codes are identical on the AST level.
|
|
3003
|
-
*/
|
|
3004
|
-
function compareAST(left, right) {
|
|
3005
|
-
const l = parse(left, {
|
|
3006
|
-
sourceType: 'module'
|
|
3007
|
-
});
|
|
3008
|
-
const r = parse(right, {
|
|
3009
|
-
sourceType: 'module'
|
|
3010
|
-
});
|
|
3011
|
-
const ljson = ast2jsonForComparison(l);
|
|
3012
|
-
const rjson = ast2jsonForComparison(r);
|
|
3013
|
-
const test = ljson === rjson;
|
|
3014
|
-
return test;
|
|
3015
|
-
}
|
|
3016
|
-
|
|
3017
|
-
/**
|
|
3018
|
-
* Transforms a type string into a structured type representation.
|
|
3019
|
-
*
|
|
3020
|
-
* This function parses a given type string and converts it into a TypeScript
|
|
3021
|
-
* Abstract Syntax Tree (AST), then uses that AST to return a structured type
|
|
3022
|
-
* representation that can be further utilized or interpreted.
|
|
3023
|
-
*
|
|
3024
|
-
* @todo Better handling of weird case: Array<>
|
|
3025
|
-
* @example
|
|
3026
|
-
* const {expandType} = await import("./src-transpiler/expandType.mjs");
|
|
3027
|
-
* expandType('[string, Array|AnyTypedArray, number[]]|[ONNXTensor]');
|
|
3028
|
-
* expandType('(123) '); // Outputs: '123'
|
|
3029
|
-
* expandType(' ( ( 123 ) ) '); // Outputs: '123'
|
|
3030
|
-
* expandType('Array<number> '); // Outputs: {type: 'array', elementType: 'number'}
|
|
3031
|
-
* expandType('Array<(123) > '); // Outputs: {type: 'array', elementType: '123'}
|
|
3032
|
-
* expandType('Array<"abc" | 123> '); // Outputs: {type: 'array', elementType: {type: 'union', members: ['"abc"', '123']}}
|
|
3033
|
-
* expandType(' (string ) |(number ) '); // Outputs: {type: 'union', members: [ 'string', 'number']}
|
|
3034
|
-
* expandType(' "apples" | ( "bananas") '); // Outputs: {type: 'union', members: [ '"apples"', '"bananas"']}
|
|
3035
|
-
* expandType('123? '); // Outputs: {"type":"union","members":["123","null"]}
|
|
3036
|
-
* expandType('123|null '); // Outputs: {"type":"union","members":["123","null"]}
|
|
3037
|
-
* expandType('Map<string, any> '); // Outputs:
|
|
3038
|
-
* expandType('typeof Number '); // Outputs:
|
|
3039
|
-
* @param {string} type - The type string to be expanded into a structured representation.
|
|
3040
|
-
* @todo Share type with expandTypeBabelTS and expandTypeDepFree
|
|
3041
|
-
* @returns {string | {type: string, [key: string]: any} | undefined} The structured type
|
|
3042
|
-
* representation obtained from parsing and converting the provided type string.
|
|
3043
|
-
*/
|
|
3044
|
-
function expandType(type) {
|
|
3045
|
-
const ast = parseType(type);
|
|
3046
|
-
return toSourceTS(ast);
|
|
3047
|
-
}
|
|
3048
|
-
/**
|
|
3049
|
-
* @todo I want to use for example: import('typescript').Node
|
|
3050
|
-
* But the TS types make no sense to me so far ... need to investigate more.
|
|
3051
|
-
* @typedef TypeScriptType
|
|
3052
|
-
* @property {object[]|undefined} typeArguments - The type arguments.
|
|
3053
|
-
* @property {import('typescript').Node} typeName - The type name.
|
|
3054
|
-
* @property {number} kind - The kind for `ts.SyntaxKind[kind]`.
|
|
3055
|
-
*/
|
|
3056
|
-
/**
|
|
3057
|
-
* @param {string} str - The type string.
|
|
3058
|
-
* @returns {TypeScriptType} - The node containing all the information about the input type string.
|
|
3059
|
-
*/
|
|
3060
|
-
function parseType(str) {
|
|
3061
|
-
// TS doesn't like ... notation in this context
|
|
3062
|
-
if (str.startsWith('...')) {
|
|
3063
|
-
str = str.slice(3); // remove dots
|
|
3064
|
-
str += '[]'; // turn into array
|
|
2954
|
+
debugger;
|
|
2955
|
+
this.warn("unable to extra name from param in specified way - may contain too much information");
|
|
2956
|
+
return this.toSource(param);
|
|
3065
2957
|
}
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
PropertySignature,
|
|
3104
|
-
StringKeyword,
|
|
3105
|
-
StringLiteral,
|
|
3106
|
-
ThisType,
|
|
3107
|
-
TupleType,
|
|
3108
|
-
TypeLiteral,
|
|
3109
|
-
TypeReference,
|
|
3110
|
-
UndefinedKeyword,
|
|
3111
|
-
UnionType,
|
|
3112
|
-
JSDocNullableType,
|
|
3113
|
-
TrueKeyword,
|
|
3114
|
-
FalseKeyword,
|
|
3115
|
-
VoidKeyword,
|
|
3116
|
-
UnknownKeyword,
|
|
3117
|
-
NeverKeyword,
|
|
3118
|
-
BigIntKeyword,
|
|
3119
|
-
BigIntLiteral,
|
|
3120
|
-
ConditionalType,
|
|
3121
|
-
IndexedAccessType,
|
|
3122
|
-
RestType,
|
|
3123
|
-
TypeQuery,
|
|
3124
|
-
// parseType('typeof Number')
|
|
3125
|
-
TypeOperator,
|
|
3126
|
-
// parseType('keyof typeof obj')
|
|
3127
|
-
KeyOfKeyword,
|
|
3128
|
-
// "operator" key in TypeOperator node
|
|
3129
|
-
ConstructorType,
|
|
3130
|
-
// parseType('new (...args: any[]) => any');
|
|
3131
|
-
NamedTupleMember,
|
|
3132
|
-
MappedType,
|
|
3133
|
-
// parseType('{[K in TaskType]: InstanceType<typeof SUPPORTED_TASKS[K]["pipeline"]>}')
|
|
3134
|
-
TypeParameter // Basically K and TaskType of MappedType
|
|
3135
|
-
} = ts.SyntaxKind;
|
|
3136
|
-
// console.log({typeArguments, typeName, kind_, node});
|
|
3137
|
-
switch (node.kind) {
|
|
3138
|
-
case BigIntKeyword:
|
|
3139
|
-
return {
|
|
3140
|
-
type: 'bigint'
|
|
3141
|
-
};
|
|
3142
|
-
case BigIntLiteral:
|
|
3143
|
-
const literal = node.text.slice(0, -1); // Remove the "n"
|
|
3144
|
-
return {
|
|
3145
|
-
type: 'bigint',
|
|
3146
|
-
literal
|
|
3147
|
-
};
|
|
3148
|
-
case ConditionalType:
|
|
3149
|
-
// Keys on node:
|
|
3150
|
-
// ['pos', 'end', 'flags', 'modifierFlagsCache', 'transformFlags', 'parent', 'kind', 'checkType',
|
|
3151
|
-
// 'extendsType', 'trueType', 'falseType', 'locals', 'nextContainer']
|
|
3152
|
-
const checkType = toSourceTS(node.checkType);
|
|
3153
|
-
const extendsType = toSourceTS(node.extendsType);
|
|
3154
|
-
const trueType = toSourceTS(node.trueType);
|
|
3155
|
-
const falseType = toSourceTS(node.falseType);
|
|
2958
|
+
statsReset() {
|
|
2959
|
+
Object.values(this.stats).forEach(statReset);
|
|
2960
|
+
}
|
|
2961
|
+
statsPrint() {
|
|
2962
|
+
console.table(this.stats);
|
|
2963
|
+
}
|
|
2964
|
+
/**
|
|
2965
|
+
* Retrieves statistical information for a given Babel AST node of this instance.
|
|
2966
|
+
*
|
|
2967
|
+
* @param {Node} node - The Babel AST node for which the statistical data is retrieved.
|
|
2968
|
+
* @returns {Stat} An object containing the statistical data for the specified node. If the
|
|
2969
|
+
* node type is unhandled, defaults to returning a dummy object with 'checked' and 'unchecked'
|
|
2970
|
+
* properties both set to 0.
|
|
2971
|
+
*/
|
|
2972
|
+
getStatsForNode(node) {
|
|
2973
|
+
const {
|
|
2974
|
+
stats
|
|
2975
|
+
} = this;
|
|
2976
|
+
const type = nodeIsFunction(node) ? node.type : this.parentType;
|
|
2977
|
+
if (type === 'ClassMethod') {
|
|
2978
|
+
const parent = /** @type {ClassMethod} */
|
|
2979
|
+
this.parent;
|
|
2980
|
+
const {
|
|
2981
|
+
kind
|
|
2982
|
+
} = parent;
|
|
2983
|
+
return stats[`ClassMethod#${kind}`];
|
|
2984
|
+
} else if (type === 'ClassPrivateMethod') {
|
|
2985
|
+
const parent = /** @type {ClassPrivateMethod} */
|
|
2986
|
+
this.parent;
|
|
2987
|
+
const {
|
|
2988
|
+
kind
|
|
2989
|
+
} = parent;
|
|
2990
|
+
return stats[`ClassPrivateMethod#${kind}`];
|
|
2991
|
+
}
|
|
2992
|
+
const stat = stats[type];
|
|
2993
|
+
if (!stat) {
|
|
2994
|
+
this.warn("getStatsForNode> dummy, but unhandled... fix for node type", node);
|
|
3156
2995
|
return {
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
extendsType,
|
|
3160
|
-
trueType,
|
|
3161
|
-
falseType
|
|
2996
|
+
checked: 0,
|
|
2997
|
+
unchecked: 0
|
|
3162
2998
|
};
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
2999
|
+
}
|
|
3000
|
+
return stat;
|
|
3001
|
+
}
|
|
3002
|
+
/**
|
|
3003
|
+
* Checks if a provided Babel AST node has a parameter with the given name.
|
|
3004
|
+
*
|
|
3005
|
+
* This function will look at the node's parameters if available and determine whether
|
|
3006
|
+
* one of them matches the provided name. Supports various parameter types such as Identifiers
|
|
3007
|
+
* and AssignmentPatterns.
|
|
3008
|
+
*
|
|
3009
|
+
* @param {Node} node - The Babel AST node to inspect. If it's a BlockStatement, the parent node is used instead.
|
|
3010
|
+
* @param {string} name - The name of the parameter to look for within the node's parameters.
|
|
3011
|
+
* @returns {boolean} True if the node has a parameter with the given name; false otherwise.
|
|
3012
|
+
*/
|
|
3013
|
+
nodeHasParamName(node, name) {
|
|
3014
|
+
if (node.type === 'BlockStatement') {
|
|
3015
|
+
node = this.parent;
|
|
3016
|
+
}
|
|
3017
|
+
const {
|
|
3018
|
+
params
|
|
3019
|
+
} = node;
|
|
3020
|
+
if (!params) {
|
|
3021
|
+
this.warn("nodeHasParamName> Expected params for", {
|
|
3022
|
+
node,
|
|
3023
|
+
name
|
|
3024
|
+
});
|
|
3025
|
+
return false;
|
|
3026
|
+
}
|
|
3027
|
+
return params.some(node => {
|
|
3028
|
+
const {
|
|
3029
|
+
type
|
|
3030
|
+
} = node;
|
|
3031
|
+
if (type === "AssignmentPattern") {
|
|
3032
|
+
const {
|
|
3033
|
+
left
|
|
3034
|
+
} = node;
|
|
3035
|
+
console.assert(left.type === 'Identifier' || left.type === 'ObjectPattern' || left.type === 'ArrayPattern' || left.type === 'AssignmentPattern', 'Expected Identifier or ObjectPattern');
|
|
3036
|
+
return left.name === name;
|
|
3037
|
+
} else if (type === 'Identifier') {
|
|
3038
|
+
return node.name === name;
|
|
3039
|
+
} else if (type === 'ArrayPattern' || type === 'ObjectPattern' || type === 'RestElement') {
|
|
3040
|
+
return false;
|
|
3041
|
+
}
|
|
3042
|
+
const _ = new Stringifier();
|
|
3043
|
+
const code = _.toSource(node);
|
|
3044
|
+
console.log("Unknown type to test params for", type, code);
|
|
3045
|
+
return false;
|
|
3046
|
+
});
|
|
3047
|
+
}
|
|
3048
|
+
/**
|
|
3049
|
+
* Generates a string containing type checks for a given Babel AST node based on associated JSDoc information.
|
|
3050
|
+
*
|
|
3051
|
+
* This function analyzes the node and its JSDoc annotations to construct runtime type
|
|
3052
|
+
* check expressions. It handles various parameter patterns and outputs code that performs
|
|
3053
|
+
* actual type assertions. If a node does not correspond to any known or supported pattern,
|
|
3054
|
+
* it returns an empty string.
|
|
3055
|
+
*
|
|
3056
|
+
* @override
|
|
3057
|
+
* @param {Node} node - The Babel AST node for which to generate type checks.
|
|
3058
|
+
* @returns {string} A string of code with type check assertions, based on the JSDoc comments associated with the given node.
|
|
3059
|
+
*/
|
|
3060
|
+
generateTypeChecks(node) {
|
|
3061
|
+
const {
|
|
3062
|
+
parent
|
|
3063
|
+
} = this;
|
|
3064
|
+
if (node.type === 'BlockStatement' && !nodeIsFunction(parent)) {
|
|
3065
|
+
return '';
|
|
3066
|
+
}
|
|
3067
|
+
const jsdoc = this.getJSDoc(node);
|
|
3068
|
+
// return '// ' + JSON.stringify(jsdoc) + '\n';
|
|
3069
|
+
const stat = this.getStatsForNode(node);
|
|
3070
|
+
if (!jsdoc) {
|
|
3071
|
+
stat.unchecked++;
|
|
3072
|
+
return '';
|
|
3073
|
+
}
|
|
3074
|
+
stat.checked++;
|
|
3075
|
+
const {
|
|
3076
|
+
spaces
|
|
3077
|
+
} = this;
|
|
3078
|
+
let out = '';
|
|
3079
|
+
let first = true;
|
|
3080
|
+
const loc = this.getName(node);
|
|
3081
|
+
if (this.ignoreLocations.includes(loc)) {
|
|
3082
|
+
return '// IGNORE RTI TYPE VALIDATIONS, KNOWN ISSUES\n';
|
|
3083
|
+
}
|
|
3084
|
+
//out += `${spaces}/*${spaces} node.type=${node.type}\n${spaces}
|
|
3085
|
+
// ${JSON.stringify(jsdoc)}\n${parent}\n${spaces}*/\n`;
|
|
3086
|
+
for (let name in jsdoc) {
|
|
3087
|
+
const type = jsdoc[name];
|
|
3088
|
+
const hasParam = this.nodeHasParamName(node, name);
|
|
3089
|
+
if (!hasParam) {
|
|
3090
|
+
let testNode = node;
|
|
3091
|
+
if (node.type === 'BlockStatement') {
|
|
3092
|
+
testNode = this.parent;
|
|
3093
|
+
}
|
|
3094
|
+
const paramIndex = Object.keys(jsdoc).findIndex(_ => _ === name);
|
|
3095
|
+
const param = testNode.params[paramIndex];
|
|
3096
|
+
if (param) {
|
|
3097
|
+
const isObjectPattern = param.type === 'ObjectPattern';
|
|
3098
|
+
const isArrayPattern = param.type === 'ArrayPattern';
|
|
3099
|
+
const isSupportedPattern = isObjectPattern || isArrayPattern;
|
|
3100
|
+
// There are four kinds of patterns:
|
|
3101
|
+
// ObjectPattern:
|
|
3102
|
+
// function test({x = 123}) {return x;} test({x: 456});
|
|
3103
|
+
// ArrayPattern:
|
|
3104
|
+
// function test([x = 123]) {return x;}; test([456]);
|
|
3105
|
+
// AssignmentPattern made up of ObjectPattern:
|
|
3106
|
+
// function test({x = 123} = {}) {return x;} test();
|
|
3107
|
+
// AssignmentPattern made up of ArrayPattern:
|
|
3108
|
+
// function test([x = 123] = []) {return x;} test();
|
|
3109
|
+
if (isSupportedPattern) {
|
|
3110
|
+
// The name doesn't matter any longer, because any pattern inherently
|
|
3111
|
+
// drops the identifier from the AST. But we can access it
|
|
3112
|
+
// via arguments[paramIndex] anyway.
|
|
3113
|
+
name = `arguments[${paramIndex}]`;
|
|
3114
|
+
} else if (param.type === 'AssignmentPattern') {
|
|
3115
|
+
const _loc = this.getName(node);
|
|
3116
|
+
if (param.left.type === 'ArrayPattern' && type.type === 'array') {
|
|
3117
|
+
// Add a type assertion for each element of the ArrayPattern
|
|
3118
|
+
for (const element of param.left.elements) {
|
|
3119
|
+
if (element.type !== 'Identifier') {
|
|
3120
|
+
this.warn('Only Identifier case handled right now');
|
|
3121
|
+
continue;
|
|
3122
|
+
}
|
|
3123
|
+
const _t = JSON.stringify(type.elementType, null, 2).replaceAll('\n', '\n' + spaces);
|
|
3124
|
+
out += `${spaces}if (!inspectType(${element.name}, ${_t}, '${_loc}', '${name}')) {\n`;
|
|
3125
|
+
out += `${spaces} youCanAddABreakpointHere();\n${spaces}}\n`;
|
|
3126
|
+
}
|
|
3127
|
+
continue;
|
|
3128
|
+
} else if (param.left.type === 'ObjectPattern' && type.type === 'object') {
|
|
3129
|
+
// Add a type assertion for each property of the ObjectPattern
|
|
3130
|
+
for (const property of param.left.properties) {
|
|
3131
|
+
if (property.key.type !== 'Identifier') {
|
|
3132
|
+
this.warn('ObjectPattern> Only Identifier case handled right now');
|
|
3133
|
+
continue;
|
|
3134
|
+
}
|
|
3135
|
+
const keyName = property.key.name;
|
|
3136
|
+
if (type.type !== 'object' || !type.properties) {
|
|
3137
|
+
this.warn("missing subtype information in JSDoc> in type", JSON.stringify(type, null, 2), "for ObjectPattern:", this.toSource(property).trim());
|
|
3138
|
+
continue;
|
|
3139
|
+
}
|
|
3140
|
+
const subType = type.properties[keyName];
|
|
3141
|
+
if (!subType) {
|
|
3142
|
+
this.warn("missing subtype information in JSDoc");
|
|
3143
|
+
continue;
|
|
3144
|
+
}
|
|
3145
|
+
const _t2 = JSON.stringify(subType, null, 2).replaceAll('\n', '\n' + spaces);
|
|
3146
|
+
out += `${spaces}if (!inspectType(${keyName}, ${_t2}, '${_loc}', '${name}')) {\n`;
|
|
3147
|
+
out += `${spaces} youCanAddABreakpointHere();\n${spaces}}\n`;
|
|
3148
|
+
}
|
|
3149
|
+
continue;
|
|
3150
|
+
}
|
|
3151
|
+
this.warn(`generateTypeChecks> ${_loc}> todo implement`, `AssignmentPattern for parameter ${name}`);
|
|
3152
|
+
continue;
|
|
3153
|
+
}
|
|
3154
|
+
} else {
|
|
3155
|
+
const _loc2 = this.getName(node);
|
|
3156
|
+
this.warn(`generateTypeChecks> ${_loc2}> Missing param: ${name}`);
|
|
3157
|
+
continue;
|
|
3158
|
+
}
|
|
3172
3159
|
}
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
};
|
|
3179
|
-
case IndexedAccessType:
|
|
3180
|
-
const index = toSourceTS(node.indexType);
|
|
3181
|
-
const object = toSourceTS(node.objectType);
|
|
3182
|
-
return {
|
|
3183
|
-
type: 'indexedAccess',
|
|
3184
|
-
index,
|
|
3185
|
-
object
|
|
3186
|
-
};
|
|
3187
|
-
case RestType:
|
|
3188
|
-
const annotation = toSourceTS(node.type);
|
|
3189
|
-
return {
|
|
3190
|
-
type: 'rest',
|
|
3191
|
-
annotation
|
|
3192
|
-
};
|
|
3193
|
-
case JSDocNullableType:
|
|
3194
|
-
const t = toSourceTS(node.type);
|
|
3195
|
-
return {
|
|
3196
|
-
type: 'union',
|
|
3197
|
-
members: [t, 'null']
|
|
3198
|
-
};
|
|
3199
|
-
case MappedType:
|
|
3200
|
-
{
|
|
3201
|
-
const result = toSourceTS(node.type);
|
|
3202
|
-
const parameter = node.typeParameter;
|
|
3203
|
-
if (parameter.kind === TypeParameter) {
|
|
3204
|
-
// For example: {[K in TaskType]: InstanceType etc.
|
|
3205
|
-
const iterable = toSourceTS(parameter.constraint); // TaskType
|
|
3206
|
-
const element = toSourceTS(parameter.name); // K
|
|
3207
|
-
return {
|
|
3208
|
-
type: 'mapping',
|
|
3209
|
-
iterable,
|
|
3210
|
-
element,
|
|
3211
|
-
result
|
|
3212
|
-
};
|
|
3160
|
+
let t = JSON.stringify(type, null, 2).replaceAll('\n', '\n' + spaces);
|
|
3161
|
+
if (type === 'this') {
|
|
3162
|
+
const classDecl = this.findParentOfType(node, 'ClassDeclaration');
|
|
3163
|
+
if (!(classDecl != null && classDecl.id)) {
|
|
3164
|
+
this.warn('generateTypeChecks> !classDecl?.id');
|
|
3213
3165
|
}
|
|
3214
|
-
|
|
3215
|
-
return 'transpiler-error';
|
|
3166
|
+
t = '"' + this.toSource(classDecl.id) + '"';
|
|
3216
3167
|
}
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
type,
|
|
3224
|
-
name
|
|
3225
|
-
};
|
|
3226
|
-
if (node.dotDotDotToken) {
|
|
3227
|
-
return {
|
|
3228
|
-
type: 'array',
|
|
3229
|
-
elementType: ret
|
|
3230
|
-
};
|
|
3168
|
+
const _loc3 = this.getName(node);
|
|
3169
|
+
let prevCheck = '';
|
|
3170
|
+
// JSDoc doesn't support multiple function signatures yet, but this is
|
|
3171
|
+
// exactly what we would need to deal with ObjectPool'ing
|
|
3172
|
+
if (_loc3 === 'ContactPoint#constructor' || _loc3 === 'ContactResult#constructor' || _loc3 === 'SingleContactResult#constructor') {
|
|
3173
|
+
prevCheck = 'arguments.length !== 0 && ';
|
|
3231
3174
|
}
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
return {
|
|
3236
|
-
type: 'typeof',
|
|
3237
|
-
argument
|
|
3238
|
-
};
|
|
3239
|
-
case TypeOperator:
|
|
3240
|
-
if (node.operator === KeyOfKeyword) {
|
|
3241
|
-
const _argument = toSourceTS(node.type);
|
|
3242
|
-
return {
|
|
3243
|
-
type: 'keyof',
|
|
3244
|
-
argument: _argument
|
|
3245
|
-
};
|
|
3175
|
+
if (first) {
|
|
3176
|
+
out += '\n';
|
|
3177
|
+
first = false;
|
|
3246
3178
|
}
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
};
|
|
3275
|
-
} else if (typeName.text === 'Promise' && (typeArguments == null ? void 0 : typeArguments.length) === 1) {
|
|
3276
|
-
const elementType = toSourceTS(typeArguments[0]);
|
|
3277
|
-
return {
|
|
3278
|
-
type: 'promise',
|
|
3279
|
-
elementType
|
|
3280
|
-
};
|
|
3281
|
-
} else if (typeName.text === 'Set' && (typeArguments == null ? void 0 : typeArguments.length) === 1) {
|
|
3282
|
-
const elementType = toSourceTS(typeArguments[0]);
|
|
3283
|
-
return {
|
|
3284
|
-
type: 'set',
|
|
3285
|
-
elementType
|
|
3286
|
-
};
|
|
3287
|
-
} else if (typeName.text === 'Class' && (typeArguments == null ? void 0 : typeArguments.length) === 1) {
|
|
3288
|
-
const elementType = toSourceTS(typeArguments[0]);
|
|
3289
|
-
return {
|
|
3290
|
-
type: 'class',
|
|
3291
|
-
elementType
|
|
3292
|
-
};
|
|
3293
|
-
}
|
|
3294
|
-
if (!typeArguments) {
|
|
3295
|
-
return typeName.getText();
|
|
3296
|
-
}
|
|
3297
|
-
const _name = typeName.text;
|
|
3298
|
-
const args = typeArguments.map(toSourceTS);
|
|
3299
|
-
return {
|
|
3300
|
-
type: 'reference',
|
|
3301
|
-
name: _name,
|
|
3302
|
-
args
|
|
3303
|
-
};
|
|
3179
|
+
out += `${spaces}if (${prevCheck}!inspectType(${name}, ${t}, '${_loc3}', '${name}')) {\n`;
|
|
3180
|
+
out += `${spaces} youCanAddABreakpointHere();\n${spaces}}\n`;
|
|
3181
|
+
}
|
|
3182
|
+
return out;
|
|
3183
|
+
}
|
|
3184
|
+
/**
|
|
3185
|
+
* @param {Node} node - The Babel AST node.
|
|
3186
|
+
* @returns {string} Best possible human-readable name of given node.
|
|
3187
|
+
*/
|
|
3188
|
+
getNameForFunctionExpression(node) {
|
|
3189
|
+
const objectProperty = this.findParentOfType(node, 'ObjectProperty');
|
|
3190
|
+
if (objectProperty) {
|
|
3191
|
+
// See good-old-es5.mjs example for a test case
|
|
3192
|
+
// TODO: Make an even better name based on Object.assign(ScopeSpace.prototype
|
|
3193
|
+
// Ideally we would figure out the name: ScopeSpace#resolve
|
|
3194
|
+
// Currently we only find "resolve" (still better than 'unnamed function expression'...)
|
|
3195
|
+
return this.toSource(objectProperty.key);
|
|
3196
|
+
}
|
|
3197
|
+
const expressionStatement = this.findParentOfType(node, 'ExpressionStatement');
|
|
3198
|
+
if (expressionStatement) {
|
|
3199
|
+
// There are many kinds of expressions
|
|
3200
|
+
// type Expression = ArrayExpression | AssignmentExpression | BinaryExpression | CallExpression | ...
|
|
3201
|
+
const {
|
|
3202
|
+
left
|
|
3203
|
+
} = expressionStatement.expression;
|
|
3204
|
+
if (left) {
|
|
3205
|
+
return this.toSource(left);
|
|
3304
3206
|
}
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3207
|
+
console.warn("Asserter#getNameForFunctionExpression> expression without left");
|
|
3208
|
+
}
|
|
3209
|
+
return 'unnamed function expression';
|
|
3210
|
+
}
|
|
3211
|
+
/**
|
|
3212
|
+
* @param {Node} node - The Babel AST node.
|
|
3213
|
+
* @returns {string} Stringification of the node.
|
|
3214
|
+
*/
|
|
3215
|
+
getName(node) {
|
|
3216
|
+
const toSource = this.toSource.bind(this);
|
|
3217
|
+
if (node.type === 'BlockStatement') {
|
|
3218
|
+
node = this.parent;
|
|
3219
|
+
}
|
|
3220
|
+
const {
|
|
3221
|
+
type,
|
|
3222
|
+
key,
|
|
3223
|
+
id
|
|
3224
|
+
} = node;
|
|
3225
|
+
switch (type) {
|
|
3226
|
+
case 'FunctionDeclaration':
|
|
3227
|
+
return toSource(id);
|
|
3228
|
+
case 'ClassMethod':
|
|
3229
|
+
case 'ClassPrivateMethod':
|
|
3230
|
+
const classDecl = this.parents.findLast(_ => _.type === 'ClassDeclaration');
|
|
3231
|
+
let out = '';
|
|
3232
|
+
if (classDecl) {
|
|
3233
|
+
out += toSource(classDecl.id) + '#';
|
|
3234
|
+
}
|
|
3235
|
+
out += toSource(key);
|
|
3236
|
+
return out;
|
|
3237
|
+
case 'FunctionExpression':
|
|
3238
|
+
if (id) {
|
|
3239
|
+
return toSource(id);
|
|
3240
|
+
}
|
|
3241
|
+
return this.getNameForFunctionExpression(node);
|
|
3242
|
+
case 'ArrowFunctionExpression':
|
|
3243
|
+
const parent = this.findParentOfType(node, 'VariableDeclarator');
|
|
3244
|
+
if (parent) {
|
|
3245
|
+
return toSource(parent.id);
|
|
3246
|
+
}
|
|
3247
|
+
return 'getName> missing parent for ' + node.type;
|
|
3248
|
+
case 'ObjectMethod':
|
|
3249
|
+
return toSource(key);
|
|
3250
|
+
default:
|
|
3251
|
+
this.warn('getName> unhandled type', type, 'for', node);
|
|
3252
|
+
return '/*MISSING*/';
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
/**
|
|
3256
|
+
* @override
|
|
3257
|
+
* @param {import("@babel/types").BinaryExpression} node - The Babel AST node.
|
|
3258
|
+
* @returns {string} Stringification of the node.
|
|
3259
|
+
*/
|
|
3260
|
+
BinaryExpression(node) {
|
|
3261
|
+
if (!this.validateDivision) {
|
|
3262
|
+
return super.BinaryExpression(node);
|
|
3263
|
+
}
|
|
3264
|
+
const {
|
|
3265
|
+
left,
|
|
3266
|
+
operator,
|
|
3267
|
+
right
|
|
3268
|
+
} = node;
|
|
3269
|
+
const left_ = this.toSource(left);
|
|
3270
|
+
const right_ = this.toSource(right);
|
|
3271
|
+
if (operator === '/') {
|
|
3272
|
+
return `validateDivision(${left_}, ${right_})`;
|
|
3273
|
+
}
|
|
3274
|
+
return `${left_} ${operator} ${right_}`;
|
|
3275
|
+
}
|
|
3276
|
+
/**
|
|
3277
|
+
* @override
|
|
3278
|
+
* @param {import("@babel/types").File} node - The Babel AST node.
|
|
3279
|
+
* @returns {string} Stringification of the node.
|
|
3280
|
+
*/
|
|
3281
|
+
File(node) {
|
|
3282
|
+
// @todo figure out why errors is in Babel node and not in @babel/types...
|
|
3283
|
+
const {
|
|
3284
|
+
/*errors,*/program,
|
|
3285
|
+
comments
|
|
3286
|
+
} = node;
|
|
3287
|
+
if (comments) {
|
|
3288
|
+
for (const comment of comments) {
|
|
3289
|
+
const warn = this.warn.bind(this);
|
|
3290
|
+
parseJSDocTypedef(this.typedefs, warn, comment, this.expandType);
|
|
3318
3291
|
}
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
{
|
|
3349
|
-
|
|
3350
|
-
return {
|
|
3351
|
-
type: 'array',
|
|
3352
|
-
elementType
|
|
3353
|
-
};
|
|
3292
|
+
}
|
|
3293
|
+
//console.log("this.typedefs", this.typedefs);
|
|
3294
|
+
let out = '';
|
|
3295
|
+
for (const name in this.typedefs) {
|
|
3296
|
+
const typedef = this.typedefs[name];
|
|
3297
|
+
const json = JSON.stringify(typedef, null, 2);
|
|
3298
|
+
out += `registerTypedef('${name}', ${json});\n`;
|
|
3299
|
+
}
|
|
3300
|
+
const code = this.toSource(program) + '\n';
|
|
3301
|
+
out += code;
|
|
3302
|
+
return out;
|
|
3303
|
+
}
|
|
3304
|
+
/**
|
|
3305
|
+
* @override
|
|
3306
|
+
* @param {import("@babel/types").VariableDeclaration} node - The Babel AST node.
|
|
3307
|
+
* @returns {string} Stringification of the node.
|
|
3308
|
+
*/
|
|
3309
|
+
VariableDeclaration(node) {
|
|
3310
|
+
const {
|
|
3311
|
+
declarations
|
|
3312
|
+
} = node;
|
|
3313
|
+
let ret = super.VariableDeclaration(node);
|
|
3314
|
+
for (const {
|
|
3315
|
+
id
|
|
3316
|
+
} of declarations) {
|
|
3317
|
+
const name = this.toSource(id);
|
|
3318
|
+
if (requiredTypeofs[name] === 'missing') {
|
|
3319
|
+
ret += `\n${this.spaces}registerVariable('${name}', ${name});\n`;
|
|
3320
|
+
requiredTypeofs[name] = 'found';
|
|
3321
|
+
} else if (requiredTypeofs[name] === 'found') {
|
|
3322
|
+
console.warn(`Already registered variable named ${name} for typeof validation`);
|
|
3354
3323
|
}
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3324
|
+
}
|
|
3325
|
+
return ret;
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
|
|
3329
|
+
/**
|
|
3330
|
+
* Simple facade which does all the processing. Processes the input
|
|
3331
|
+
* source string, adding runtime type checks based on JSDoc comments.
|
|
3332
|
+
*
|
|
3333
|
+
* This function takes JavaScript source code as input, parses it to an AST, traverses the
|
|
3334
|
+
* AST to find type annotations in JSDoc comments, and generates appropriate runtime type
|
|
3335
|
+
* assertions. These are then inserted into the source, producing a new version of the code
|
|
3336
|
+
* that includes runtime type checking based on the original JSDoc annotations.
|
|
3337
|
+
*
|
|
3338
|
+
* @param {string} src - The input source code containing JSDoc comments to be processed
|
|
3339
|
+
* for type checks.
|
|
3340
|
+
* @param {import('./Asserter.mjs').Options} [options] - Configuration options that dictate
|
|
3341
|
+
* how the processing is performed.
|
|
3342
|
+
* @returns {string} The transformed source code with inserted runtime type checks, or the
|
|
3343
|
+
* original source code commented with an error if processing fails.
|
|
3344
|
+
*/
|
|
3345
|
+
function addTypeChecks(src, options) {
|
|
3346
|
+
try {
|
|
3347
|
+
const asserter = new Asserter(options);
|
|
3348
|
+
const ast = parse(src, {
|
|
3349
|
+
sourceType: 'module',
|
|
3350
|
+
createParenthesizedExpressions: true
|
|
3351
|
+
});
|
|
3352
|
+
const out = asserter.getHeader() + asserter.toSource(ast);
|
|
3353
|
+
return out;
|
|
3354
|
+
} catch (e) {
|
|
3355
|
+
console.error(e);
|
|
3356
|
+
return `/*<addTypeChecks-error>*/${src}/*</addTypeChecks-error>*/`;
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
/**
|
|
3361
|
+
* @param {object} ast - The Babel AST.
|
|
3362
|
+
* @returns {string} String representation in JSON format for debugging/inspecting the AST.
|
|
3363
|
+
*/
|
|
3364
|
+
function ast2json(ast) {
|
|
3365
|
+
return JSON.stringify(ast, function (name, val) {
|
|
3366
|
+
if (name === "loc" || name === "start" || name === "end") {
|
|
3367
|
+
return; // remove
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
return val; // keep
|
|
3371
|
+
}, 2);
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3374
|
+
const drop = ['loc', 'start', 'end', 'leadingComments', 'trailingComments', 'innerComments', 'innerComments', 'comments'];
|
|
3375
|
+
/**
|
|
3376
|
+
* @example
|
|
3377
|
+
* setRight(ast2jsonForComparison(parseSync("/** *"))); // Close comment with / after last *
|
|
3378
|
+
* @param {object} ast - The Babel AST.
|
|
3379
|
+
* @returns {string} String representation in JSON format for debugging/inspecting the AST.
|
|
3380
|
+
*/
|
|
3381
|
+
function ast2jsonForComparison(ast) {
|
|
3382
|
+
return JSON.stringify(ast, function (name, val) {
|
|
3383
|
+
if (name === 'trailingComma' || name === 'parenStart') {
|
|
3384
|
+
return 'offset removed for better comparison';
|
|
3385
|
+
}
|
|
3386
|
+
if (drop.includes(name)) {
|
|
3387
|
+
return undefined; // remove
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
return val; // keep
|
|
3391
|
+
}, 2);
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
/**
|
|
3395
|
+
* A roundtrip between code -> AST -> code to validate Stringifier.
|
|
3396
|
+
* @param {string} code - The code.
|
|
3397
|
+
* @returns {string | undefined} The new and once parsed and stringified code.
|
|
3398
|
+
*/
|
|
3399
|
+
function code2ast2code(code) {
|
|
3400
|
+
const stringifier = new Stringifier();
|
|
3401
|
+
const ast = parse(code, {
|
|
3402
|
+
sourceType: 'module'
|
|
3403
|
+
});
|
|
3404
|
+
if (!ast) {
|
|
3405
|
+
return;
|
|
3397
3406
|
}
|
|
3407
|
+
const out = stringifier.toSource(ast);
|
|
3408
|
+
return out;
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
/**
|
|
3412
|
+
* @param {string} left - Left source code.
|
|
3413
|
+
* @param {string} right - Right source code.
|
|
3414
|
+
* @returns {boolean} Whether source codes are identical on the AST level.
|
|
3415
|
+
*/
|
|
3416
|
+
function compareAST(left, right) {
|
|
3417
|
+
const l = parse(left, {
|
|
3418
|
+
sourceType: 'module'
|
|
3419
|
+
});
|
|
3420
|
+
const r = parse(right, {
|
|
3421
|
+
sourceType: 'module'
|
|
3422
|
+
});
|
|
3423
|
+
const ljson = ast2jsonForComparison(l);
|
|
3424
|
+
const rjson = ast2jsonForComparison(r);
|
|
3425
|
+
const test = ljson === rjson;
|
|
3426
|
+
return test;
|
|
3398
3427
|
}
|
|
3399
3428
|
|
|
3400
3429
|
/**
|
|
@@ -3638,4 +3667,4 @@ function toSourceBabelTS(node) {
|
|
|
3638
3667
|
}
|
|
3639
3668
|
}
|
|
3640
3669
|
|
|
3641
|
-
export { Asserter, Stringifier, addTypeChecks, ast2json, ast2jsonForComparison, code2ast2code, compareAST, expandType, expandTypeBabelTS, expandTypeDepFree, extractCurlyContent, extractNameAndOptionality, nodeIsFunction, parseJSDoc, parseJSDocSetter, parseJSDocTypedef, parseType, parseTypeBabelTS, simplifyType, toSourceBabelTS, toSourceTS, trimEndSpaces };
|
|
3670
|
+
export { Asserter, Stringifier, addTypeChecks, ast2json, ast2jsonForComparison, code2ast2code, compareAST, expandType, expandTypeBabelTS, expandTypeDepFree, extractCurlyContent, extractNameAndOptionality, nodeIsFunction, parseJSDoc, parseJSDocSetter, parseJSDocTypedef, parseType, parseTypeBabelTS, requiredTypeofs, simplifyType, toSourceBabelTS, toSourceTS, trimEndSpaces };
|