@travetto/transformer 7.0.0-rc.0 → 7.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/importer.ts +55 -55
- package/src/manager.ts +9 -4
- package/src/register.ts +62 -62
- package/src/resolver/builder.ts +57 -49
- package/src/resolver/coerce.ts +6 -6
- package/src/resolver/service.ts +10 -9
- package/src/state.ts +74 -74
- package/src/types/shared.ts +3 -3
- package/src/types/visitor.ts +3 -3
- package/src/util/core.ts +21 -19
- package/src/util/declaration.ts +24 -24
- package/src/util/decorator.ts +16 -16
- package/src/util/doc.ts +9 -9
- package/src/util/import.ts +11 -11
- package/src/util/literal.ts +84 -84
- package/src/util/log.ts +10 -10
- package/src/visitor.ts +18 -18
package/src/resolver/builder.ts
CHANGED
|
@@ -16,6 +16,14 @@ const UNDEFINED = Symbol();
|
|
|
16
16
|
|
|
17
17
|
const MAPPED_TYPE_SET = new Set(['Omit', 'Pick', 'Required', 'Partial']);
|
|
18
18
|
const isMappedType = (type: string | undefined): type is MappedType['operation'] => MAPPED_TYPE_SET.has(type!);
|
|
19
|
+
const getMappedFields = (type: ts.Type): string[] | undefined => {
|
|
20
|
+
if (type.isStringLiteral()) {
|
|
21
|
+
return [type.value];
|
|
22
|
+
} else if (type.isUnion() && type.types.every(subType => subType.isStringLiteral())) {
|
|
23
|
+
return type.types.map(subType => subType.value);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
19
27
|
/**
|
|
20
28
|
* List of global types that can be parameterized
|
|
21
29
|
*/
|
|
@@ -74,7 +82,7 @@ export function TypeCategorize(resolver: TransformResolver, type: ts.Type): { ca
|
|
|
74
82
|
try {
|
|
75
83
|
const source = DeclarationUtil.getPrimaryDeclarationNode(type).getSourceFile();
|
|
76
84
|
const sourceFile = source.fileName;
|
|
77
|
-
if (sourceFile && ManifestModuleUtil.
|
|
85
|
+
if (sourceFile && ManifestModuleUtil.TYPINGS_EXT_REGEX.test(sourceFile) && !resolver.isKnownFile(sourceFile)) {
|
|
78
86
|
return { category: 'foreign', type };
|
|
79
87
|
}
|
|
80
88
|
} catch { }
|
|
@@ -93,7 +101,7 @@ export function TypeCategorize(resolver: TransformResolver, type: ts.Type): { ca
|
|
|
93
101
|
const sourceFile = source.fileName;
|
|
94
102
|
if (sourceFile?.includes('typescript/lib')) {
|
|
95
103
|
return { category: 'literal', type };
|
|
96
|
-
} else if (sourceFile && ManifestModuleUtil.
|
|
104
|
+
} else if (sourceFile && ManifestModuleUtil.TYPINGS_EXT_REGEX.test(sourceFile) && !resolver.isKnownFile(sourceFile)) {
|
|
97
105
|
return { category: 'foreign', type: resolvedType };
|
|
98
106
|
} else if (!resolvedType.isClass()) { // Not a real type
|
|
99
107
|
return { category: 'shape', type: resolvedType };
|
|
@@ -105,7 +113,7 @@ export function TypeCategorize(resolver: TransformResolver, type: ts.Type): { ca
|
|
|
105
113
|
} else if (type.isLiteral()) {
|
|
106
114
|
return { category: 'shape', type };
|
|
107
115
|
} else if (objectFlags & ts.ObjectFlags.Mapped) { // Mapped types
|
|
108
|
-
if (type.getProperties().some(
|
|
116
|
+
if (type.getProperties().some(property => property.declarations || property.valueDeclaration)) {
|
|
109
117
|
return { category: 'mapped', type };
|
|
110
118
|
}
|
|
111
119
|
}
|
|
@@ -117,7 +125,7 @@ export function TypeCategorize(resolver: TransformResolver, type: ts.Type): { ca
|
|
|
117
125
|
*/
|
|
118
126
|
export const TypeBuilder: {
|
|
119
127
|
[K in Category]: {
|
|
120
|
-
build(resolver: TransformResolver, type: ts.Type, alias?: ts.Symbol): AnyType | undefined;
|
|
128
|
+
build(resolver: TransformResolver, type: ts.Type, context: { alias?: ts.Symbol, node?: ts.Node }): AnyType | undefined;
|
|
121
129
|
finalize?(type: Type<K>): AnyType;
|
|
122
130
|
}
|
|
123
131
|
} = {
|
|
@@ -152,7 +160,7 @@ export const TypeBuilder: {
|
|
|
152
160
|
}
|
|
153
161
|
}
|
|
154
162
|
if (values.length > 0) {
|
|
155
|
-
return ({ key: 'template', template: {
|
|
163
|
+
return ({ key: 'template', template: { operation: 'and', values }, ctor: String });
|
|
156
164
|
}
|
|
157
165
|
}
|
|
158
166
|
}
|
|
@@ -215,11 +223,11 @@ export const TypeBuilder: {
|
|
|
215
223
|
let undefinable = false;
|
|
216
224
|
let nullable = false;
|
|
217
225
|
const remainder = uType.types.filter(ut => {
|
|
218
|
-
const
|
|
219
|
-
const
|
|
220
|
-
undefinable ||=
|
|
221
|
-
nullable ||=
|
|
222
|
-
return !(
|
|
226
|
+
const isUndefined = (ut.getFlags() & (ts.TypeFlags.Undefined)) > 0;
|
|
227
|
+
const isNull = (ut.getFlags() & (ts.TypeFlags.Null)) > 0;
|
|
228
|
+
undefinable ||= isUndefined;
|
|
229
|
+
nullable ||= isNull;
|
|
230
|
+
return !(isUndefined || isNull);
|
|
223
231
|
});
|
|
224
232
|
const name = CoreUtil.getSymbol(uType)?.getName();
|
|
225
233
|
return { key: 'composition', name, undefinable, nullable, tsSubTypes: remainder, subTypes: [], operation: uType.isUnion() ? 'or' : 'and' };
|
|
@@ -239,77 +247,76 @@ export const TypeBuilder: {
|
|
|
239
247
|
ctor: String,
|
|
240
248
|
nullable: type.nullable,
|
|
241
249
|
undefinable: type.undefinable,
|
|
242
|
-
template: {
|
|
250
|
+
template: { operation: 'or', values: subTypes.map(subType => transformCast<TemplateType>(subType).template!) }
|
|
243
251
|
};
|
|
244
252
|
} else if (subTypes.length === 1) {
|
|
245
253
|
return { undefinable, nullable, ...first };
|
|
246
|
-
} else if (first.key === 'literal' && subTypes.every(
|
|
254
|
+
} else if (first.key === 'literal' && subTypes.every(item => item.name === first.name)) { // We have a common
|
|
247
255
|
type.commonType = first;
|
|
248
|
-
} else if (type.operation === 'and' && first.key === 'shape' && subTypes.every(
|
|
249
|
-
return {
|
|
256
|
+
} else if (type.operation === 'and' && first.key === 'shape' && subTypes.every(item => item.key === 'shape')) { // All shapes
|
|
257
|
+
return {
|
|
258
|
+
importName: first.importName,
|
|
259
|
+
name: first.name,
|
|
260
|
+
key: 'shape',
|
|
261
|
+
fieldTypes: subTypes.reduce((map, subType) => ({ ...map, ...subType.fieldTypes }), {})
|
|
262
|
+
};
|
|
250
263
|
}
|
|
251
264
|
return type;
|
|
252
265
|
}
|
|
253
266
|
},
|
|
254
267
|
mapped: {
|
|
255
|
-
build: (resolver, type,
|
|
268
|
+
build: (resolver, type, context) => {
|
|
256
269
|
let mainType: ts.Type | undefined;
|
|
257
270
|
let fields: string[] | undefined;
|
|
258
271
|
let operation: string | undefined;
|
|
272
|
+
let name: string | undefined;
|
|
259
273
|
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
} else {
|
|
277
|
-
mainType = type.aliasTypeArguments?.[0]!;
|
|
278
|
-
operation = type.aliasSymbol?.escapedName.toString();
|
|
279
|
-
fields = type.getApparentProperties().map(p => p.getName());
|
|
274
|
+
const declarations = DeclarationUtil.getDeclarations(type).filter(declaration => ts.isTypeAliasDeclaration(declaration));
|
|
275
|
+
const ref = declarations[0]?.type;
|
|
276
|
+
|
|
277
|
+
if (ref && ts.isTypeReferenceNode(ref) && ref.typeArguments && ref.typeArguments.length > 0) {
|
|
278
|
+
const [first, second] = ref.typeArguments;
|
|
279
|
+
mainType = resolver.getType(first);
|
|
280
|
+
operation = ref.typeName.getText();
|
|
281
|
+
name = resolver.getTypeAsString(type)!;
|
|
282
|
+
fields = !second ? [] : getMappedFields(resolver.getType(second));
|
|
283
|
+
} else if (type.aliasTypeArguments && type.aliasSymbol) {
|
|
284
|
+
mainType = type.aliasTypeArguments[0];
|
|
285
|
+
operation = type.aliasSymbol.escapedName.toString();
|
|
286
|
+
fields = (type.aliasTypeArguments.length > 1) ? getMappedFields(type.aliasTypeArguments[1]) : [];
|
|
287
|
+
name = `${resolver.getTypeAsString(mainType)!}_${operation}_${fields?.join('_')}`;
|
|
280
288
|
}
|
|
281
289
|
|
|
282
290
|
if (!isMappedType(operation) || fields === undefined || !mainType || !mainType.isClass()) {
|
|
283
|
-
return TypeBuilder.shape.build(resolver, type,
|
|
291
|
+
return TypeBuilder.shape.build(resolver, type, context);
|
|
284
292
|
}
|
|
285
293
|
|
|
286
294
|
const importName = resolver.getTypeImportName(mainType) ?? '<unknown>';
|
|
287
295
|
const mappedClassName = resolver.getTypeAsString(mainType)!;
|
|
288
|
-
const name = resolver.getTypeAsString(type)!;
|
|
289
296
|
|
|
290
297
|
return { key: 'mapped', name, original: mainType, operation, importName, mappedClassName, fields };
|
|
291
298
|
}
|
|
292
299
|
},
|
|
293
300
|
shape: {
|
|
294
|
-
build: (resolver, type,
|
|
301
|
+
build: (resolver, type, context) => {
|
|
295
302
|
const tsFieldTypes: Record<string, ts.Type> = {};
|
|
296
|
-
const name = CoreUtil.getSymbol(alias ?? type)?.getName();
|
|
303
|
+
const name = CoreUtil.getSymbol(context?.alias ?? type)?.getName();
|
|
297
304
|
const importName = resolver.getTypeImportName(type) ?? '<unknown>';
|
|
298
305
|
const tsTypeArguments = resolver.getAllTypeArguments(type);
|
|
299
|
-
const
|
|
300
|
-
if (
|
|
306
|
+
const properties = resolver.getPropertiesOfType(type);
|
|
307
|
+
if (properties.length === 0) {
|
|
301
308
|
return { key: 'literal', name: 'Object', ctor: Object, importName };
|
|
302
309
|
}
|
|
303
310
|
|
|
304
|
-
for (const member of
|
|
305
|
-
const
|
|
306
|
-
if (DeclarationUtil.isPublic(
|
|
307
|
-
const memberType = resolver.getType(
|
|
311
|
+
for (const member of properties) {
|
|
312
|
+
const decorator = DeclarationUtil.getPrimaryDeclarationNode(member);
|
|
313
|
+
if (DeclarationUtil.isPublic(decorator)) { // If public
|
|
314
|
+
const memberType = resolver.getType(decorator);
|
|
308
315
|
if (
|
|
309
316
|
!member.getName().includes('@') && // if not a symbol
|
|
310
317
|
!memberType.getCallSignatures().length // if not a function
|
|
311
318
|
) {
|
|
312
|
-
if ((ts.isPropertySignature(
|
|
319
|
+
if ((ts.isPropertySignature(decorator) || ts.isPropertyDeclaration(decorator)) && !!decorator.questionToken) {
|
|
313
320
|
Object.defineProperty(memberType, UNDEFINED, { value: true });
|
|
314
321
|
}
|
|
315
322
|
tsFieldTypes[member.getName()] = memberType;
|
|
@@ -331,7 +338,7 @@ export const TypeBuilder: {
|
|
|
331
338
|
// Resolving relative to source file
|
|
332
339
|
if (!importName || importName.startsWith('.')) {
|
|
333
340
|
const rawSourceFile: string = DeclarationUtil.getDeclarations(type)
|
|
334
|
-
?.find(
|
|
341
|
+
?.find(declaration => ts.getAllJSDocTags(declaration, (node): node is ts.JSDocTag => node.tagName.getText() === 'concrete').length)
|
|
335
342
|
?.getSourceFile().fileName ?? '';
|
|
336
343
|
|
|
337
344
|
if (!importName || importName === '.') {
|
|
@@ -344,8 +351,9 @@ export const TypeBuilder: {
|
|
|
344
351
|
|
|
345
352
|
// Convert name to $Concrete suffix if not provided
|
|
346
353
|
if (!name) {
|
|
347
|
-
const [
|
|
348
|
-
|
|
354
|
+
const [primaryDeclaration] = DeclarationUtil.getDeclarations(type)
|
|
355
|
+
.filter(declaration => ts.isInterfaceDeclaration(declaration) || ts.isTypeAliasDeclaration(declaration));
|
|
356
|
+
name = `${primaryDeclaration.name.text}$Concrete`;
|
|
349
357
|
}
|
|
350
358
|
|
|
351
359
|
return { key: 'managed', name, importName };
|
package/src/resolver/coerce.ts
CHANGED
|
@@ -4,12 +4,12 @@ export class CoerceUtil {
|
|
|
4
4
|
/**
|
|
5
5
|
* Is a value a plain JS object, created using {}
|
|
6
6
|
*/
|
|
7
|
-
static #isPlainObject(
|
|
8
|
-
return typeof
|
|
9
|
-
&&
|
|
10
|
-
&&
|
|
11
|
-
&&
|
|
12
|
-
&& Object.prototype.toString.call(
|
|
7
|
+
static #isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
8
|
+
return typeof value === 'object' // separate from primitives
|
|
9
|
+
&& value !== undefined
|
|
10
|
+
&& value !== null // is obvious
|
|
11
|
+
&& value.constructor === Object // separate instances (Array, DOM, ...)
|
|
12
|
+
&& Object.prototype.toString.call(value) === '[object Object]'; // separate build-in like Math
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
package/src/resolver/service.ts
CHANGED
|
@@ -73,10 +73,10 @@ export class SimpleResolver implements TransformResolver {
|
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
75
|
* Get type from element
|
|
76
|
-
* @param
|
|
76
|
+
* @param value
|
|
77
77
|
*/
|
|
78
|
-
getType(
|
|
79
|
-
return 'getSourceFile' in
|
|
78
|
+
getType(value: ts.Type | ts.Node): ts.Type {
|
|
79
|
+
return 'getSourceFile' in value ? this.#tsChecker.getTypeAtLocation(value) : value;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
@@ -106,7 +106,8 @@ export class SimpleResolver implements TransformResolver {
|
|
|
106
106
|
* Get list of properties
|
|
107
107
|
*/
|
|
108
108
|
getPropertiesOfType(type: ts.Type): ts.Symbol[] {
|
|
109
|
-
return this.#tsChecker.getPropertiesOfType(type)
|
|
109
|
+
return this.#tsChecker.getPropertiesOfType(type)
|
|
110
|
+
.filter(property => property.getName() !== '__proto__' && property.getName() !== 'prototype');
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
/**
|
|
@@ -123,7 +124,7 @@ export class SimpleResolver implements TransformResolver {
|
|
|
123
124
|
const { category, type } = TypeCategorize(this, resType);
|
|
124
125
|
const { build, finalize } = TypeBuilder[category];
|
|
125
126
|
|
|
126
|
-
let result = build(this, type, alias);
|
|
127
|
+
let result = build(this, type, { alias, node: node && 'kind' in node ? node : undefined });
|
|
127
128
|
|
|
128
129
|
// Convert via cache if needed
|
|
129
130
|
result = visited.getOrSet(type, result);
|
|
@@ -161,11 +162,11 @@ export class SimpleResolver implements TransformResolver {
|
|
|
161
162
|
|
|
162
163
|
try {
|
|
163
164
|
return resolve(this.getType(node));
|
|
164
|
-
} catch (
|
|
165
|
-
if (!(
|
|
166
|
-
throw
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (!(error instanceof Error)) {
|
|
167
|
+
throw error;
|
|
167
168
|
}
|
|
168
|
-
console.error(`Unable to resolve type in ${importName}`,
|
|
169
|
+
console.error(`Unable to resolve type in ${importName}`, error.stack);
|
|
169
170
|
return { key: 'literal', ctor: Object, name: 'object' };
|
|
170
171
|
}
|
|
171
172
|
}
|
package/src/state.ts
CHANGED
|
@@ -15,25 +15,27 @@ import { CoreUtil } from './util/core.ts';
|
|
|
15
15
|
import { LiteralUtil } from './util/literal.ts';
|
|
16
16
|
import { SystemUtil } from './util/system.ts';
|
|
17
17
|
|
|
18
|
-
function hasOriginal(
|
|
19
|
-
return !!
|
|
18
|
+
function hasOriginal(node: ts.Node): node is ts.Node & { original: ts.Node } {
|
|
19
|
+
return !!node && !node.parent && 'original' in node && !!node.original;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
function hasEscapedName(
|
|
23
|
-
return !!
|
|
22
|
+
function hasEscapedName(node: ts.Node): node is ts.Node & { name: { escapedText: string } } {
|
|
23
|
+
return !!node && 'name' in node && typeof node.name === 'object' && !!node.name && 'escapedText' in node.name && !!node.name.escapedText;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function isRedefinableDeclaration(
|
|
27
|
-
return ts.isFunctionDeclaration(
|
|
26
|
+
function isRedefinableDeclaration(node: ts.Node): node is ts.InterfaceDeclaration | ts.ClassDeclaration | ts.FunctionDeclaration {
|
|
27
|
+
return ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
const FOREIGN_TYPE_REGISTRY_FILE = '@travetto/runtime/src/function';
|
|
31
|
+
|
|
30
32
|
/**
|
|
31
33
|
* Transformer runtime state
|
|
32
34
|
*/
|
|
33
35
|
export class TransformerState implements State {
|
|
34
36
|
#resolver: SimpleResolver;
|
|
35
37
|
#imports: ImportManager;
|
|
36
|
-
#
|
|
38
|
+
#moduleIdentifier: ts.Identifier;
|
|
37
39
|
#manifestIndex: ManifestIndex;
|
|
38
40
|
#syntheticIdentifiers = new Map<string, ts.Identifier>();
|
|
39
41
|
#decorators = new Map<string, ts.PropertyAccessExpression>();
|
|
@@ -91,8 +93,8 @@ export class TransformerState implements State {
|
|
|
91
93
|
const resolved = this.resolveType(node);
|
|
92
94
|
if (resolved.key !== 'managed') {
|
|
93
95
|
const file = node.getSourceFile().fileName;
|
|
94
|
-
const
|
|
95
|
-
throw new Error(`Unable to import non-external type: ${node.getText()} ${resolved.key}: ${
|
|
96
|
+
const source = this.#resolver.getFileImportName(file);
|
|
97
|
+
throw new Error(`Unable to import non-external type: ${node.getText()} ${resolved.key}: ${source}`);
|
|
96
98
|
}
|
|
97
99
|
return resolved;
|
|
98
100
|
}
|
|
@@ -134,9 +136,9 @@ export class TransformerState implements State {
|
|
|
134
136
|
*/
|
|
135
137
|
readDocTagList(node: ts.Declaration, name: string): string[] {
|
|
136
138
|
return this.readDocTag(node, name)
|
|
137
|
-
.flatMap(
|
|
138
|
-
.map(
|
|
139
|
-
.filter(
|
|
139
|
+
.flatMap(tag => tag.split(/\s*,\s*/g))
|
|
140
|
+
.map(tag => tag.replace(/`/g, ''))
|
|
141
|
+
.filter(tag => !!tag);
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
/**
|
|
@@ -145,8 +147,8 @@ export class TransformerState implements State {
|
|
|
145
147
|
importDecorator(pth: string, name: string): ts.PropertyAccessExpression | undefined {
|
|
146
148
|
if (!this.#decorators.has(`${pth}:${name}`)) {
|
|
147
149
|
const ref = this.#imports.importFile(pth);
|
|
148
|
-
const
|
|
149
|
-
this.#decorators.set(name, this.factory.createPropertyAccessExpression(ref.
|
|
150
|
+
const identifier = this.factory.createIdentifier(name);
|
|
151
|
+
this.#decorators.set(name, this.factory.createPropertyAccessExpression(ref.identifier, identifier));
|
|
150
152
|
}
|
|
151
153
|
return this.#decorators.get(name);
|
|
152
154
|
}
|
|
@@ -162,22 +164,22 @@ export class TransformerState implements State {
|
|
|
162
164
|
/**
|
|
163
165
|
* Read a decorator's metadata
|
|
164
166
|
*/
|
|
165
|
-
getDecoratorMeta(
|
|
166
|
-
const
|
|
167
|
-
const type = this.#resolver.getType(
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
const mod =
|
|
167
|
+
getDecoratorMeta(decorator: ts.Decorator): DecoratorMeta | undefined {
|
|
168
|
+
const identifier = DecoratorUtil.getDecoratorIdentifier(decorator);
|
|
169
|
+
const type = this.#resolver.getType(identifier);
|
|
170
|
+
const declaration = DeclarationUtil.getOptionalPrimaryDeclarationNode(type);
|
|
171
|
+
const source = declaration?.getSourceFile().fileName;
|
|
172
|
+
const mod = source ? this.#resolver.getFileImportName(source, true) : undefined;
|
|
171
173
|
const file = this.#manifestIndex.getFromImport(mod ?? '')?.outputFile;
|
|
172
174
|
const targets = DocUtil.readAugments(type);
|
|
173
175
|
const example = DocUtil.readExample(type);
|
|
174
176
|
const module = file ? mod : undefined;
|
|
175
|
-
const name =
|
|
176
|
-
|
|
177
|
+
const name = identifier ?
|
|
178
|
+
identifier.escapedText?.toString()! :
|
|
177
179
|
undefined;
|
|
178
180
|
|
|
179
|
-
if (
|
|
180
|
-
return {
|
|
181
|
+
if (identifier && name) {
|
|
182
|
+
return { decorator, identifier, file, module, targets, name, options: example };
|
|
181
183
|
}
|
|
182
184
|
}
|
|
183
185
|
|
|
@@ -186,8 +188,8 @@ export class TransformerState implements State {
|
|
|
186
188
|
*/
|
|
187
189
|
getDecoratorList(node: ts.Node): DecoratorMeta[] {
|
|
188
190
|
return ts.canHaveDecorators(node) ? (ts.getDecorators(node) ?? [])
|
|
189
|
-
.map(
|
|
190
|
-
.filter(
|
|
191
|
+
.map(decorator => this.getDecoratorMeta(decorator))
|
|
192
|
+
.filter(meta => !!meta) : [];
|
|
191
193
|
}
|
|
192
194
|
|
|
193
195
|
/**
|
|
@@ -199,26 +201,26 @@ export class TransformerState implements State {
|
|
|
199
201
|
|
|
200
202
|
/**
|
|
201
203
|
* Register statement for inclusion in final output
|
|
202
|
-
* @param
|
|
204
|
+
* @param added
|
|
203
205
|
* @param before
|
|
204
206
|
*/
|
|
205
207
|
addStatements(added: ts.Statement[], before?: ts.Node | number): void {
|
|
206
|
-
const
|
|
207
|
-
let idx =
|
|
208
|
+
const statements = this.source.statements.slice(0);
|
|
209
|
+
let idx = statements.length + 1000;
|
|
208
210
|
|
|
209
211
|
if (before && typeof before !== 'number') {
|
|
210
|
-
let
|
|
211
|
-
if (hasOriginal(
|
|
212
|
-
|
|
212
|
+
let node = before;
|
|
213
|
+
if (hasOriginal(node)) {
|
|
214
|
+
node = node.original;
|
|
213
215
|
}
|
|
214
|
-
while (
|
|
215
|
-
|
|
216
|
+
while (node && !ts.isSourceFile(node.parent) && node !== node.parent) {
|
|
217
|
+
node = node.parent;
|
|
216
218
|
}
|
|
217
|
-
if (!ts.isStatement(
|
|
219
|
+
if (!ts.isStatement(node)) {
|
|
218
220
|
throw new Error('Unable to find statement at top level');
|
|
219
221
|
}
|
|
220
|
-
if (
|
|
221
|
-
idx =
|
|
222
|
+
if (node && ts.isSourceFile(node.parent) && statements.indexOf(node) >= 0) {
|
|
223
|
+
idx = statements.indexOf(node) - 1;
|
|
222
224
|
}
|
|
223
225
|
} else if (before !== undefined) {
|
|
224
226
|
idx = before;
|
|
@@ -239,21 +241,21 @@ export class TransformerState implements State {
|
|
|
239
241
|
/**
|
|
240
242
|
* From literal
|
|
241
243
|
*/
|
|
242
|
-
fromLiteral<T extends ts.Expression>(
|
|
243
|
-
fromLiteral(
|
|
244
|
-
fromLiteral(
|
|
245
|
-
fromLiteral(
|
|
246
|
-
fromLiteral(
|
|
247
|
-
fromLiteral(
|
|
248
|
-
fromLiteral(
|
|
249
|
-
return LiteralUtil.fromLiteral(this.factory,
|
|
244
|
+
fromLiteral<T extends ts.Expression>(value: T): T;
|
|
245
|
+
fromLiteral(value: undefined): ts.Identifier;
|
|
246
|
+
fromLiteral(value: null): ts.NullLiteral;
|
|
247
|
+
fromLiteral(value: object): ts.ObjectLiteralExpression;
|
|
248
|
+
fromLiteral(value: unknown[]): ts.ArrayLiteralExpression;
|
|
249
|
+
fromLiteral(value: string | boolean | number): ts.LiteralExpression;
|
|
250
|
+
fromLiteral(value: unknown): ts.Node {
|
|
251
|
+
return LiteralUtil.fromLiteral(this.factory, value!);
|
|
250
252
|
}
|
|
251
253
|
|
|
252
254
|
/**
|
|
253
255
|
* Extend
|
|
254
256
|
*/
|
|
255
|
-
extendObjectLiteral(
|
|
256
|
-
return LiteralUtil.extendObjectLiteral(this.factory,
|
|
257
|
+
extendObjectLiteral(source: object | ts.Expression, ...rest: (object | ts.Expression)[]): ts.ObjectLiteralExpression {
|
|
258
|
+
return LiteralUtil.extendObjectLiteral(this.factory, source, ...rest);
|
|
257
259
|
}
|
|
258
260
|
|
|
259
261
|
/**
|
|
@@ -266,8 +268,8 @@ export class TransformerState implements State {
|
|
|
266
268
|
/**
|
|
267
269
|
* Create a static field for a class
|
|
268
270
|
*/
|
|
269
|
-
createStaticField(name: string,
|
|
270
|
-
return CoreUtil.createStaticField(this.factory, name,
|
|
271
|
+
createStaticField(name: string, value: ts.Expression): ts.PropertyDeclaration {
|
|
272
|
+
return CoreUtil.createStaticField(this.factory, name, value);
|
|
271
273
|
}
|
|
272
274
|
|
|
273
275
|
/**
|
|
@@ -282,17 +284,17 @@ export class TransformerState implements State {
|
|
|
282
284
|
* Get filename identifier, regardless of module system
|
|
283
285
|
*/
|
|
284
286
|
getModuleIdentifier(): ts.Expression {
|
|
285
|
-
if (this.#
|
|
286
|
-
this.#
|
|
287
|
+
if (this.#moduleIdentifier === undefined) {
|
|
288
|
+
this.#moduleIdentifier = this.factory.createUniqueName('mod');
|
|
287
289
|
const entry = this.#resolver.getFileImport(this.source.fileName);
|
|
288
|
-
const
|
|
290
|
+
const declaration = this.factory.createVariableDeclaration(this.#moduleIdentifier, undefined, undefined,
|
|
289
291
|
this.fromLiteral([entry?.module, entry?.relativeFile ?? ''])
|
|
290
292
|
);
|
|
291
293
|
this.addStatements([
|
|
292
|
-
this.factory.createVariableStatement([], this.factory.createVariableDeclarationList([
|
|
294
|
+
this.factory.createVariableStatement([], this.factory.createVariableDeclarationList([declaration]))
|
|
293
295
|
], -1);
|
|
294
296
|
}
|
|
295
|
-
return this.#
|
|
297
|
+
return this.#moduleIdentifier;
|
|
296
298
|
}
|
|
297
299
|
|
|
298
300
|
/**
|
|
@@ -307,7 +309,7 @@ export class TransformerState implements State {
|
|
|
307
309
|
mod = typeof mod === 'string' ? mod : mod[ModuleNameSymbol]!;
|
|
308
310
|
const target = `${mod}:${name}`;
|
|
309
311
|
const list = this.getDecoratorList(node);
|
|
310
|
-
return list.find(
|
|
312
|
+
return list.find(meta => meta.targets?.includes(target) && (!module || meta.name === name && meta.module === module))?.decorator;
|
|
311
313
|
}
|
|
312
314
|
|
|
313
315
|
/**
|
|
@@ -377,14 +379,14 @@ export class TransformerState implements State {
|
|
|
377
379
|
findMethodByName(cls: ts.ClassLikeDeclaration | ts.Type, method: string): ts.MethodDeclaration | undefined {
|
|
378
380
|
if ('getSourceFile' in cls) {
|
|
379
381
|
return cls.members.find(
|
|
380
|
-
(
|
|
382
|
+
(value): value is ts.MethodDeclaration => ts.isMethodDeclaration(value) && ts.isIdentifier(value.name) && value.name.escapedText === method
|
|
381
383
|
);
|
|
382
384
|
} else {
|
|
383
|
-
const
|
|
384
|
-
for (const
|
|
385
|
-
const
|
|
386
|
-
if (
|
|
387
|
-
return
|
|
385
|
+
const properties = this.#resolver.getPropertiesOfType(cls);
|
|
386
|
+
for (const property of properties) {
|
|
387
|
+
const declaration = property.declarations?.[0];
|
|
388
|
+
if (declaration && property.escapedName === method && ts.isMethodDeclaration(declaration)) {
|
|
389
|
+
return declaration;
|
|
388
390
|
}
|
|
389
391
|
}
|
|
390
392
|
}
|
|
@@ -401,15 +403,13 @@ export class TransformerState implements State {
|
|
|
401
403
|
/**
|
|
402
404
|
* Produce a foreign target type
|
|
403
405
|
*/
|
|
404
|
-
getForeignTarget(
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
this.fromLiteral(`${ret.source.split('node_modules/')[1]}+${ret.name}`)
|
|
412
|
-
)
|
|
406
|
+
getForeignTarget(type: ForeignType): ts.Expression {
|
|
407
|
+
const file = this.importFile(FOREIGN_TYPE_REGISTRY_FILE);
|
|
408
|
+
return this.factory.createCallExpression(this.createAccess(
|
|
409
|
+
file.identifier,
|
|
410
|
+
this.factory.createIdentifier('foreignType'),
|
|
411
|
+
), [], [
|
|
412
|
+
this.fromLiteral(`${type.source.split('node_modules/')[1]}+${type.name}`)
|
|
413
413
|
]);
|
|
414
414
|
}
|
|
415
415
|
|
|
@@ -425,8 +425,8 @@ export class TransformerState implements State {
|
|
|
425
425
|
return this.getForeignTarget(type);
|
|
426
426
|
} else {
|
|
427
427
|
const file = node.getSourceFile().fileName;
|
|
428
|
-
const
|
|
429
|
-
throw new Error(`Unable to import non-external type: ${node.getText()} ${type.key}: ${
|
|
428
|
+
const source = this.getFileImportName(file);
|
|
429
|
+
throw new Error(`Unable to import non-external type: ${node.getText()} ${type.key}: ${source}`);
|
|
430
430
|
}
|
|
431
431
|
}
|
|
432
432
|
|
|
@@ -435,7 +435,7 @@ export class TransformerState implements State {
|
|
|
435
435
|
*/
|
|
436
436
|
getApparentTypeOfField(value: ts.Type, field: string): AnyType | undefined {
|
|
437
437
|
const checker = this.#resolver.getChecker();
|
|
438
|
-
const
|
|
439
|
-
return
|
|
438
|
+
const properties = checker.getApparentType(value).getApparentProperties().find(property => property.escapedName === field);
|
|
439
|
+
return properties ? this.resolveType(checker.getTypeOfSymbol(properties)) : undefined;
|
|
440
440
|
}
|
|
441
441
|
}
|
package/src/types/shared.ts
CHANGED
|
@@ -22,13 +22,13 @@ export interface DeclDocumentation {
|
|
|
22
22
|
*/
|
|
23
23
|
export type Import = {
|
|
24
24
|
path: string;
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
identifier: ts.Identifier;
|
|
26
|
+
statement?: ts.ImportDeclaration;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
/** Template Literal Types */
|
|
30
30
|
export type TemplateLiteralPart = string | NumberConstructor | StringConstructor | BooleanConstructor;
|
|
31
|
-
export type TemplateLiteral = {
|
|
31
|
+
export type TemplateLiteral = { operation: 'and' | 'or', values: (TemplateLiteralPart | TemplateLiteral)[] };
|
|
32
32
|
|
|
33
33
|
export function transformCast<T>(input: unknown): T {
|
|
34
34
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
package/src/types/visitor.ts
CHANGED
|
@@ -4,8 +4,8 @@ import ts from 'typescript';
|
|
|
4
4
|
* Decorator metadata
|
|
5
5
|
*/
|
|
6
6
|
export type DecoratorMeta = {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
decorator: ts.Decorator;
|
|
8
|
+
identifier: ts.Identifier;
|
|
9
9
|
module?: string;
|
|
10
10
|
file?: string;
|
|
11
11
|
targets?: string[];
|
|
@@ -18,7 +18,7 @@ export type State = {
|
|
|
18
18
|
importName: string;
|
|
19
19
|
added: Map<number, ts.Statement[]>;
|
|
20
20
|
getDecoratorList(node: ts.Node): DecoratorMeta[];
|
|
21
|
-
finalize(
|
|
21
|
+
finalize(source: ts.SourceFile): ts.SourceFile;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
export type TransformPhase = 'before' | 'after';
|