@travetto/transformer 3.0.0-rc.4 → 3.0.0-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -12
- package/__index__.ts +15 -0
- package/package.json +13 -6
- package/src/importer.ts +125 -35
- package/src/manager.ts +66 -0
- package/src/manifest-index.ts +29 -0
- package/src/register.ts +40 -11
- package/src/resolver/builder.ts +37 -37
- package/src/resolver/cache.ts +2 -2
- package/src/resolver/coerce.ts +103 -0
- package/src/resolver/service.ts +12 -5
- package/src/resolver/types.ts +6 -3
- package/src/state.ts +93 -61
- package/src/types/shared.ts +1 -1
- package/src/types/visitor.ts +7 -5
- package/src/util/core.ts +8 -6
- package/src/util/declaration.ts +1 -1
- package/src/util/decorator.ts +1 -1
- package/src/util/doc.ts +1 -1
- package/src/util/import.ts +17 -10
- package/src/util/literal.ts +3 -2
- package/src/util/log.ts +2 -4
- package/src/util/system.ts +31 -0
- package/src/visitor.ts +11 -30
- package/index.ts +0 -7
- package/src/util/index.ts +0 -6
- package/test-support/util.ts +0 -57
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!-- This file was generated by @travetto/doc and should not be modified directly -->
|
|
2
|
-
<!-- Please modify https://github.com/travetto/travetto/tree/main/module/transformer/
|
|
2
|
+
<!-- Please modify https://github.com/travetto/travetto/tree/main/module/transformer/DOC.ts and execute "npx trv doc" to rebuild -->
|
|
3
3
|
# Transformation
|
|
4
4
|
## Functionality for AST transformations, with transformer registration, and general utils
|
|
5
5
|
|
|
@@ -10,7 +10,7 @@ npm install @travetto/transformer
|
|
|
10
10
|
|
|
11
11
|
This module provides support for enhanced AST transformations, and declarative transformer registration, with common patterns to support all the transformers used throughout the framework. Transformations are located by `support/transformer.<name>.ts` as the filename.
|
|
12
12
|
|
|
13
|
-
The module is primarily aimed at extremely advanced usages for things that cannot be detected at runtime. The [Registry](https://github.com/travetto/travetto/tree/main/module/registry#readme "Patterns and utilities for handling registration of metadata and functionality for run-time use") module already has knowledge of all `class`es and `field`s, and is able to listen to changes there. Many of the modules build upon work by some of the foundational transformers defined in [Registry](https://github.com/travetto/travetto/tree/main/module/registry#readme "Patterns and utilities for handling registration of metadata and functionality for run-time use"), [Schema](https://github.com/travetto/travetto/tree/main/module/schema#readme "Data type registry for runtime validation, reflection and binding.
|
|
13
|
+
The module is primarily aimed at extremely advanced usages for things that cannot be detected at runtime. The [Registry](https://github.com/travetto/travetto/tree/main/module/registry#readme "Patterns and utilities for handling registration of metadata and functionality for run-time use") module already has knowledge of all `class`es and `field`s, and is able to listen to changes there. Many of the modules build upon work by some of the foundational transformers defined in [Registry](https://github.com/travetto/travetto/tree/main/module/registry#readme "Patterns and utilities for handling registration of metadata and functionality for run-time use"), [Schema](https://github.com/travetto/travetto/tree/main/module/schema#readme "Data type registry for runtime validation, reflection and binding.") and [Dependency Injection](https://github.com/travetto/travetto/tree/main/module/di#readme "Dependency registration/management and injection support."). These all center around defining a registry of classes, and associated type information.
|
|
14
14
|
|
|
15
15
|
Because working with the [Typescript](https://typescriptlang.org) API can be delicate (and open to breaking changes), creating new transformers should be done cautiously.
|
|
16
16
|
|
|
@@ -20,22 +20,19 @@ Below is an example of a transformer that upper cases all `class`, `method` and
|
|
|
20
20
|
|
|
21
21
|
**Code: Sample Transformer - Upper case all declarations**
|
|
22
22
|
```typescript
|
|
23
|
-
import
|
|
23
|
+
import ts from 'typescript';
|
|
24
24
|
|
|
25
|
-
import { OnProperty, TransformerState, OnMethod, OnClass
|
|
25
|
+
import { OnProperty, TransformerState, OnMethod, OnClass } from '@travetto/transformer';
|
|
26
26
|
|
|
27
27
|
export class MakeUpper {
|
|
28
28
|
|
|
29
|
-
static [TransformerId] = '@trv:transformer-test';
|
|
30
|
-
|
|
31
29
|
@OnProperty()
|
|
32
30
|
static handleProperty(state: TransformerState, node: ts.PropertyDeclaration): ts.PropertyDeclaration {
|
|
33
|
-
if (!state.
|
|
31
|
+
if (!state.file.includes('doc/src')) {
|
|
34
32
|
return node;
|
|
35
33
|
}
|
|
36
34
|
return state.factory.updatePropertyDeclaration(
|
|
37
35
|
node,
|
|
38
|
-
[],
|
|
39
36
|
node.modifiers,
|
|
40
37
|
node.name.getText().toUpperCase(),
|
|
41
38
|
undefined,
|
|
@@ -46,12 +43,11 @@ export class MakeUpper {
|
|
|
46
43
|
|
|
47
44
|
@OnClass()
|
|
48
45
|
static handleClass(state: TransformerState, node: ts.ClassDeclaration): ts.ClassDeclaration {
|
|
49
|
-
if (!state.
|
|
46
|
+
if (!state.file.includes('doc/src')) {
|
|
50
47
|
return node;
|
|
51
48
|
}
|
|
52
49
|
return state.factory.updateClassDeclaration(
|
|
53
50
|
node,
|
|
54
|
-
[],
|
|
55
51
|
node.modifiers,
|
|
56
52
|
state.createIdentifier(node.name!.getText().toUpperCase()),
|
|
57
53
|
node.typeParameters,
|
|
@@ -62,12 +58,11 @@ export class MakeUpper {
|
|
|
62
58
|
|
|
63
59
|
@OnMethod()
|
|
64
60
|
static handleMethod(state: TransformerState, node: ts.MethodDeclaration): ts.MethodDeclaration {
|
|
65
|
-
if (!state.
|
|
61
|
+
if (!state.file.includes('doc/src')) {
|
|
66
62
|
return node;
|
|
67
63
|
}
|
|
68
64
|
return state.factory.updateMethodDeclaration(
|
|
69
65
|
node,
|
|
70
|
-
[],
|
|
71
66
|
node.modifiers,
|
|
72
67
|
undefined,
|
|
73
68
|
state.createIdentifier(node.name.getText().toUpperCase()),
|
package/__index__.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from './src/state';
|
|
2
|
+
export * from './src/visitor';
|
|
3
|
+
export * from './src/register';
|
|
4
|
+
export * from './src/types/visitor';
|
|
5
|
+
export * from './src/types/shared';
|
|
6
|
+
export type { AnyType } from './src/resolver/types';
|
|
7
|
+
export * from './src/manager';
|
|
8
|
+
|
|
9
|
+
export * from './src/util/core';
|
|
10
|
+
export * from './src/util/declaration';
|
|
11
|
+
export * from './src/util/decorator';
|
|
12
|
+
export * from './src/util/doc';
|
|
13
|
+
export * from './src/util/literal';
|
|
14
|
+
export * from './src/util/log';
|
|
15
|
+
export * from './src/util/system';
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/transformer",
|
|
3
|
-
"
|
|
4
|
-
"version": "3.0.0-rc.4",
|
|
3
|
+
"version": "3.0.0-rc.6",
|
|
5
4
|
"description": "Functionality for AST transformations, with transformer registration, and general utils",
|
|
6
5
|
"keywords": [
|
|
7
6
|
"typescript",
|
|
@@ -15,17 +14,25 @@
|
|
|
15
14
|
"name": "Travetto Framework"
|
|
16
15
|
},
|
|
17
16
|
"files": [
|
|
18
|
-
"
|
|
17
|
+
"__index__.ts",
|
|
19
18
|
"src",
|
|
20
|
-
"
|
|
19
|
+
"support"
|
|
21
20
|
],
|
|
22
|
-
"main": "
|
|
21
|
+
"main": "__index__.ts",
|
|
23
22
|
"repository": {
|
|
24
23
|
"url": "https://github.com/travetto/travetto.git",
|
|
25
24
|
"directory": "module/transformer"
|
|
26
25
|
},
|
|
27
26
|
"dependencies": {
|
|
28
|
-
"@travetto/
|
|
27
|
+
"@travetto/manifest": "^3.0.0-rc.3",
|
|
28
|
+
"tslib": "^2.4.1",
|
|
29
|
+
"typescript": "^4.9.4"
|
|
30
|
+
},
|
|
31
|
+
"travetto": {
|
|
32
|
+
"displayName": "Transformation",
|
|
33
|
+
"profiles": [
|
|
34
|
+
"compile"
|
|
35
|
+
]
|
|
29
36
|
},
|
|
30
37
|
"publishConfig": {
|
|
31
38
|
"access": "public"
|
package/src/importer.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { basename, dirname, relative } from 'path';
|
|
1
|
+
import ts from 'typescript';
|
|
3
2
|
|
|
4
|
-
import {
|
|
5
|
-
import { ModuleUtil } from '@travetto/boot/src/internal/module-util';
|
|
3
|
+
import { PackageUtil, path } from '@travetto/manifest';
|
|
6
4
|
|
|
7
5
|
import { AnyType, ExternalType } from './resolver/types';
|
|
8
6
|
import { ImportUtil } from './util/import';
|
|
9
7
|
import { CoreUtil } from './util/core';
|
|
10
8
|
import { Import } from './types/shared';
|
|
9
|
+
import { LiteralUtil } from './util/literal';
|
|
10
|
+
import { DeclarationUtil } from './util/declaration';
|
|
11
|
+
import { TransformerIndex } from './manifest-index';
|
|
12
|
+
|
|
13
|
+
const D_OR_D_TS_EXT_RE = /[.]d([.]ts)?$/;
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* Manages imports within a ts.SourceFile
|
|
@@ -18,18 +21,83 @@ export class ImportManager {
|
|
|
18
21
|
#imports: Map<string, Import>;
|
|
19
22
|
#idx: Record<string, number> = {};
|
|
20
23
|
#ids = new Map<string, string>();
|
|
24
|
+
#importName: string;
|
|
25
|
+
#index: TransformerIndex;
|
|
21
26
|
|
|
22
|
-
constructor(public source: ts.SourceFile, public factory: ts.NodeFactory) {
|
|
27
|
+
constructor(public source: ts.SourceFile, public factory: ts.NodeFactory, index: TransformerIndex) {
|
|
23
28
|
this.#imports = ImportUtil.collectImports(source);
|
|
29
|
+
this.#index = index;
|
|
30
|
+
this.#importName = index.getImportName(source.fileName);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#getImportFile(spec?: ts.Expression): string | undefined {
|
|
34
|
+
if (spec && ts.isStringLiteral(spec)) {
|
|
35
|
+
return spec.text.replace(/^['"]|["']$/g, '');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#rewriteModuleSpecifier(spec: ts.Expression | undefined): ts.Expression | undefined {
|
|
40
|
+
const fileOrImport = this.#getImportFile(spec);
|
|
41
|
+
if (
|
|
42
|
+
fileOrImport &&
|
|
43
|
+
(fileOrImport.startsWith('.') || this.#index.getFromImport(fileOrImport)) &&
|
|
44
|
+
!/[.]([mc]?js|ts|json)$/.test(fileOrImport)
|
|
45
|
+
) {
|
|
46
|
+
return LiteralUtil.fromLiteral(this.factory, `${fileOrImport}.js`);
|
|
47
|
+
}
|
|
48
|
+
return spec;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#rewriteImportClause(
|
|
52
|
+
spec: ts.Expression | undefined,
|
|
53
|
+
clause: ts.ImportClause | undefined,
|
|
54
|
+
checker: ts.TypeChecker
|
|
55
|
+
): ts.ImportClause | undefined {
|
|
56
|
+
if (!(spec && clause?.namedBindings && ts.isNamedImports(clause.namedBindings))) {
|
|
57
|
+
return clause;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const fileOrImport = this.#getImportFile(spec);
|
|
61
|
+
if (!(fileOrImport && (fileOrImport.startsWith('.') || this.#index.getFromImport(fileOrImport)))) {
|
|
62
|
+
return clause;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const bindings = clause.namedBindings;
|
|
66
|
+
const newBindings: ts.ImportSpecifier[] = [];
|
|
67
|
+
// Remove all type only imports
|
|
68
|
+
for (const el of bindings.elements) {
|
|
69
|
+
if (!el.isTypeOnly) {
|
|
70
|
+
const type = checker.getTypeAtLocation(el.name);
|
|
71
|
+
const objFlags = DeclarationUtil.getObjectFlags(type);
|
|
72
|
+
const typeFlags = type.getFlags();
|
|
73
|
+
if (objFlags || typeFlags !== 1) {
|
|
74
|
+
newBindings.push(el);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (newBindings.length !== bindings.elements.length) {
|
|
79
|
+
return this.factory.updateImportClause(
|
|
80
|
+
clause,
|
|
81
|
+
clause.isTypeOnly,
|
|
82
|
+
clause.name,
|
|
83
|
+
this.factory.createNamedImports(newBindings)
|
|
84
|
+
);
|
|
85
|
+
} else {
|
|
86
|
+
return clause;
|
|
87
|
+
}
|
|
24
88
|
}
|
|
25
89
|
|
|
26
90
|
/**
|
|
27
91
|
* Produces a unique ID for a given file, importing if needed
|
|
28
92
|
*/
|
|
29
|
-
getId(file: string): string {
|
|
93
|
+
getId(file: string, name?: string): string {
|
|
30
94
|
if (!this.#ids.has(file)) {
|
|
31
|
-
|
|
32
|
-
|
|
95
|
+
if (name) {
|
|
96
|
+
this.#ids.set(file, name);
|
|
97
|
+
} else {
|
|
98
|
+
const key = path.basename(file).replace(/[.][^.]*$/, '').replace(/[^A-Za-z0-9]+/g, '_');
|
|
99
|
+
this.#ids.set(file, `Ⲑ_${key}_${this.#idx[key] = (this.#idx[key] || 0) + 1}`);
|
|
100
|
+
}
|
|
33
101
|
}
|
|
34
102
|
return this.#ids.get(file)!;
|
|
35
103
|
}
|
|
@@ -37,28 +105,16 @@ export class ImportManager {
|
|
|
37
105
|
/**
|
|
38
106
|
* Import a file if needed, and record it's identifier
|
|
39
107
|
*/
|
|
40
|
-
importFile(file: string,
|
|
41
|
-
file =
|
|
108
|
+
importFile(file: string, name?: string): Import {
|
|
109
|
+
file = this.#index.getImportName(file);
|
|
42
110
|
|
|
43
111
|
// Allow for node classes to be imported directly
|
|
44
112
|
if (/@types\/node/.test(file)) {
|
|
45
|
-
file =
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Handle relative imports
|
|
49
|
-
if (file.startsWith('.') && base &&
|
|
50
|
-
!base.startsWith('@travetto') && !base.includes('node_modules')
|
|
51
|
-
) { // Relative path
|
|
52
|
-
const fileDir = dirname(PathUtil.resolveUnix(file));
|
|
53
|
-
const baseDir = dirname(PathUtil.resolveUnix(base));
|
|
54
|
-
file = `${relative(baseDir, fileDir) || '.'}/${basename(file)}`;
|
|
55
|
-
if (/^[A-Za-z]/.test(file)) {
|
|
56
|
-
file = `./${file}`;
|
|
57
|
-
}
|
|
113
|
+
file = PackageUtil.resolveImport(file.replace(/.*@types\/node\//, '').replace(D_OR_D_TS_EXT_RE, ''));
|
|
58
114
|
}
|
|
59
115
|
|
|
60
|
-
if (
|
|
61
|
-
const id = this.getId(file);
|
|
116
|
+
if (!D_OR_D_TS_EXT_RE.test(file) && !this.#newImports.has(file)) {
|
|
117
|
+
const id = this.getId(file, name);
|
|
62
118
|
|
|
63
119
|
if (this.#imports.has(id)) { // Already imported, be cool
|
|
64
120
|
return this.#imports.get(id)!;
|
|
@@ -77,8 +133,8 @@ export class ImportManager {
|
|
|
77
133
|
*/
|
|
78
134
|
importFromResolved(...types: AnyType[]): void {
|
|
79
135
|
for (const type of types) {
|
|
80
|
-
if (type.key === 'external' && type.
|
|
81
|
-
this.importFile(type.
|
|
136
|
+
if (type.key === 'external' && type.importName && type.importName !== this.#importName) {
|
|
137
|
+
this.importFile(type.importName);
|
|
82
138
|
}
|
|
83
139
|
switch (type.key) {
|
|
84
140
|
case 'external':
|
|
@@ -99,11 +155,11 @@ export class ImportManager {
|
|
|
99
155
|
}
|
|
100
156
|
|
|
101
157
|
try {
|
|
102
|
-
const importStmts = [...this.#newImports.values()].map(({ path, ident }) => {
|
|
158
|
+
const importStmts = [...this.#newImports.values()].map(({ path: resolved, ident }) => {
|
|
103
159
|
const importStmt = this.factory.createImportDeclaration(
|
|
104
|
-
undefined,
|
|
160
|
+
undefined,
|
|
105
161
|
this.factory.createImportClause(false, undefined, this.factory.createNamespaceImport(ident)),
|
|
106
|
-
this.factory.createStringLiteral(
|
|
162
|
+
this.factory.createStringLiteral(resolved)
|
|
107
163
|
);
|
|
108
164
|
return importStmt;
|
|
109
165
|
});
|
|
@@ -116,27 +172,61 @@ export class ImportManager {
|
|
|
116
172
|
if (!(err instanceof Error)) {
|
|
117
173
|
throw err;
|
|
118
174
|
}
|
|
119
|
-
const out = new Error(`${err.message} in ${file.fileName.replace(
|
|
175
|
+
const out = new Error(`${err.message} in ${file.fileName.replace(process.cwd(), '.')}`);
|
|
120
176
|
out.stack = err.stack;
|
|
121
177
|
throw out;
|
|
122
178
|
}
|
|
123
179
|
}
|
|
124
180
|
|
|
181
|
+
finalizeImportExportExtension(ret: ts.SourceFile, checker: ts.TypeChecker): ts.SourceFile {
|
|
182
|
+
const toAdd: ts.Statement[] = [];
|
|
183
|
+
|
|
184
|
+
for (const stmt of ret.statements) {
|
|
185
|
+
if (ts.isExportDeclaration(stmt)) {
|
|
186
|
+
if (!stmt.isTypeOnly) {
|
|
187
|
+
toAdd.push(this.factory.updateExportDeclaration(
|
|
188
|
+
stmt,
|
|
189
|
+
stmt.modifiers,
|
|
190
|
+
stmt.isTypeOnly,
|
|
191
|
+
stmt.exportClause,
|
|
192
|
+
this.#rewriteModuleSpecifier(stmt.moduleSpecifier),
|
|
193
|
+
stmt.assertClause
|
|
194
|
+
));
|
|
195
|
+
}
|
|
196
|
+
} else if (ts.isImportDeclaration(stmt)) {
|
|
197
|
+
if (!stmt.importClause?.isTypeOnly) {
|
|
198
|
+
toAdd.push(this.factory.updateImportDeclaration(
|
|
199
|
+
stmt,
|
|
200
|
+
stmt.modifiers,
|
|
201
|
+
this.#rewriteImportClause(stmt.moduleSpecifier, stmt.importClause, checker)!,
|
|
202
|
+
this.#rewriteModuleSpecifier(stmt.moduleSpecifier)!,
|
|
203
|
+
stmt.assertClause
|
|
204
|
+
));
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
toAdd.push(stmt);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return CoreUtil.updateSource(this.factory, ret, toAdd);
|
|
211
|
+
}
|
|
212
|
+
|
|
125
213
|
/**
|
|
126
214
|
* Reset the imports into the source file
|
|
127
215
|
*/
|
|
128
|
-
finalize(ret: ts.SourceFile): ts.SourceFile {
|
|
129
|
-
|
|
216
|
+
finalize(ret: ts.SourceFile, checker: ts.TypeChecker): ts.SourceFile {
|
|
217
|
+
ret = this.finalizeNewImports(ret) ?? ret;
|
|
218
|
+
ret = this.finalizeImportExportExtension(ret, checker) ?? ret;
|
|
219
|
+
return ret;
|
|
130
220
|
}
|
|
131
221
|
|
|
132
222
|
/**
|
|
133
223
|
* Get the identifier and import if needed
|
|
134
224
|
*/
|
|
135
225
|
getOrImport(factory: ts.NodeFactory, type: ExternalType): ts.Identifier | ts.PropertyAccessExpression {
|
|
136
|
-
if (type.
|
|
226
|
+
if (type.importName === this.#importName) {
|
|
137
227
|
return factory.createIdentifier(type.name!);
|
|
138
228
|
} else {
|
|
139
|
-
const { ident } = this.#imports.get(type.
|
|
229
|
+
const { ident } = this.#imports.get(type.importName) ?? this.importFile(type.importName);
|
|
140
230
|
return factory.createPropertyAccessExpression(ident, type.name!);
|
|
141
231
|
}
|
|
142
232
|
}
|
package/src/manager.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
|
|
3
|
+
import { ManifestRoot } from '@travetto/manifest';
|
|
4
|
+
|
|
5
|
+
import { NodeTransformer } from './types/visitor';
|
|
6
|
+
import { VisitorFactory } from './visitor';
|
|
7
|
+
import { TransformerState } from './state';
|
|
8
|
+
import { getAllTransformers } from './register';
|
|
9
|
+
import { TransformerIndex } from './manifest-index';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Manages the typescript transformers
|
|
13
|
+
*/
|
|
14
|
+
export class TransformerManager {
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create transformer manager
|
|
18
|
+
* @param transformerFiles
|
|
19
|
+
* @param manifest
|
|
20
|
+
* @returns
|
|
21
|
+
*/
|
|
22
|
+
static async create(transformerFiles: string[], manifest: ManifestRoot): Promise<TransformerManager> {
|
|
23
|
+
const transformers: NodeTransformer<TransformerState>[] = [];
|
|
24
|
+
const idx = new TransformerIndex('.', manifest);
|
|
25
|
+
|
|
26
|
+
for (const file of transformerFiles) { // Exclude based on blacklist
|
|
27
|
+
const entry = idx.getEntry(file)!;
|
|
28
|
+
transformers.push(...getAllTransformers(await import(file), entry.module));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Prepare a new visitor factory with a given type checker
|
|
32
|
+
return new TransformerManager(transformers, idx);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#cached: ts.CustomTransformers | undefined;
|
|
36
|
+
#transformers: NodeTransformer<TransformerState>[];
|
|
37
|
+
#index: TransformerIndex;
|
|
38
|
+
|
|
39
|
+
constructor(transformers: NodeTransformer<TransformerState>[], index: TransformerIndex) {
|
|
40
|
+
this.#transformers = transformers;
|
|
41
|
+
this.#index = index;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Initialize with type checker
|
|
46
|
+
* @param checker
|
|
47
|
+
*/
|
|
48
|
+
init(checker: ts.TypeChecker): void {
|
|
49
|
+
const visitor = new VisitorFactory(
|
|
50
|
+
(ctx, src) => new TransformerState(src, ctx.factory, checker, this.#index, ctx.getCompilerOptions()),
|
|
51
|
+
this.#transformers
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Define transformers for the compiler
|
|
55
|
+
this.#cached = {
|
|
56
|
+
before: [visitor.visitor()]
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get typescript transformer object
|
|
62
|
+
*/
|
|
63
|
+
get(): ts.CustomTransformers | undefined {
|
|
64
|
+
return this.#cached!;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
|
|
3
|
+
import { ManifestIndex, path } from '@travetto/manifest';
|
|
4
|
+
import { DeclarationUtil } from './util/declaration';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Specific logic for the transformer
|
|
8
|
+
*/
|
|
9
|
+
export class TransformerIndex extends ManifestIndex {
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Resolve import name for a given type
|
|
13
|
+
*/
|
|
14
|
+
getImportName(type: ts.Type | string, removeExt = false): string {
|
|
15
|
+
const ogSource = typeof type === 'string' ? type : DeclarationUtil.getPrimaryDeclarationNode(type).getSourceFile().fileName;
|
|
16
|
+
let sourceFile = path.toPosix(ogSource);
|
|
17
|
+
|
|
18
|
+
if (!sourceFile.endsWith('.js') && !sourceFile.endsWith('.ts')) {
|
|
19
|
+
sourceFile = `${sourceFile}.ts`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const imp =
|
|
23
|
+
this.getEntry(/[.]ts$/.test(sourceFile) ? sourceFile : `${sourceFile}.js`)?.import ??
|
|
24
|
+
this.getFromImport(sourceFile.replace(/^.*node_modules\//, '').replace(/[.]ts$/, ''))?.import ??
|
|
25
|
+
ogSource;
|
|
26
|
+
|
|
27
|
+
return removeExt ? imp.replace(/[.]js$/, '') : imp;
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/register.ts
CHANGED
|
@@ -1,26 +1,55 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { DecoratorMeta, NodeTransformer, State, TransformPhase, TransformerType, Transformer, TransformerId } from './types/visitor';
|
|
1
|
+
import ts from 'typescript';
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
import { DecoratorMeta, NodeTransformer, State, TransformPhase, TransformerType, Transformer, ModuleNameⲐ } from './types/visitor';
|
|
4
|
+
|
|
5
|
+
const HandlersProp = Symbol.for('@travetto/transformer:handlers');
|
|
5
6
|
|
|
6
7
|
type TransformerWithHandlers = Transformer & { [HandlersProp]?: NodeTransformer[] };
|
|
7
8
|
|
|
9
|
+
function isTransformer(x: unknown): x is Transformer {
|
|
10
|
+
return x !== null && x !== undefined && typeof x === 'function';
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
/**
|
|
9
14
|
* Get all transformers
|
|
10
15
|
* @param obj Object to search for transformers
|
|
11
16
|
*/
|
|
12
|
-
export function getAllTransformers(obj: Record<string, { [HandlersProp]?: NodeTransformer[] }
|
|
13
|
-
return Object.values(obj)
|
|
17
|
+
export function getAllTransformers(obj: Record<string, { [HandlersProp]?: NodeTransformer[] }>, module: string): NodeTransformer[] {
|
|
18
|
+
return Object.values(obj)
|
|
19
|
+
.flatMap(x => {
|
|
20
|
+
if (isTransformer(x)) {
|
|
21
|
+
x[ModuleNameⲐ] = module;
|
|
22
|
+
}
|
|
23
|
+
return (x[HandlersProp] ?? []);
|
|
24
|
+
})
|
|
25
|
+
.map(handler => ({
|
|
26
|
+
...handler,
|
|
27
|
+
key: `${module}:${handler.key}`,
|
|
28
|
+
target: handler.target?.map(t => `${module}:${t}`)
|
|
29
|
+
}));
|
|
14
30
|
}
|
|
15
31
|
|
|
16
32
|
// Store handlers in class
|
|
17
33
|
function storeHandler(cls: TransformerWithHandlers, fn: Function, phase: TransformPhase, type: TransformerType, target?: string[]): void {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
(cls[HandlersProp] ??= []).push({ key: fn.name, [phase]: fn.bind(cls), type, target });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Wraps entire file before transforming
|
|
39
|
+
*/
|
|
40
|
+
export function OnFile(...target: string[]) {
|
|
41
|
+
return <S extends State = State, R extends ts.Node = ts.Node>(
|
|
42
|
+
inst: Transformer, __: unknown, d: TypedPropertyDescriptor<(state: S, node: ts.SourceFile) => R>
|
|
43
|
+
): void => storeHandler(inst, d.value!, 'before', 'file', target);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Wraps entire file after transforming
|
|
48
|
+
*/
|
|
49
|
+
export function AfterFile(...target: string[]) {
|
|
50
|
+
return <S extends State = State, R extends ts.Node = ts.Node>(
|
|
51
|
+
inst: Transformer, __: unknown, d: TypedPropertyDescriptor<(state: S, node: ts.SourceFile) => R>
|
|
52
|
+
): void => storeHandler(inst, d.value!, 'before', 'file', target);
|
|
24
53
|
}
|
|
25
54
|
|
|
26
55
|
/**
|
package/src/resolver/builder.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/* eslint-disable no-bitwise */
|
|
2
|
-
import
|
|
3
|
-
import { dirname } from 'path';
|
|
2
|
+
import ts from 'typescript';
|
|
4
3
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
4
|
+
import { ManifestIndex, path } from '@travetto/manifest';
|
|
5
|
+
|
|
6
|
+
import { DocUtil } from '../util/doc';
|
|
7
|
+
import { CoreUtil } from '../util/core';
|
|
8
|
+
import { DeclarationUtil } from '../util/declaration';
|
|
9
|
+
import { LiteralUtil } from '../util/literal';
|
|
7
10
|
|
|
8
11
|
import { Type, AnyType, UnionType, Checker } from './types';
|
|
9
|
-
import {
|
|
12
|
+
import { CoerceUtil } from './coerce';
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* List of global types that can be parameterized
|
|
@@ -35,7 +38,7 @@ type Category = 'void' | 'undefined' | 'concrete' | 'unknown' | 'tuple' | 'shape
|
|
|
35
38
|
/**
|
|
36
39
|
* Type categorizer, input for builder
|
|
37
40
|
*/
|
|
38
|
-
export function TypeCategorize(checker: ts.TypeChecker, type: ts.Type): { category: Category, type: ts.Type } {
|
|
41
|
+
export function TypeCategorize(checker: ts.TypeChecker, type: ts.Type, index: ManifestIndex): { category: Category, type: ts.Type } {
|
|
39
42
|
const flags = type.getFlags();
|
|
40
43
|
const objectFlags = DeclarationUtil.getObjectFlags(type) ?? 0;
|
|
41
44
|
|
|
@@ -62,9 +65,10 @@ export function TypeCategorize(checker: ts.TypeChecker, type: ts.Type): { catego
|
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
const source = DeclarationUtil.getPrimaryDeclarationNode(resolvedType).getSourceFile();
|
|
65
|
-
|
|
68
|
+
const sourceFile = source.fileName;
|
|
69
|
+
if (sourceFile?.includes('@types/node/globals') || sourceFile?.includes('typescript/lib')) {
|
|
66
70
|
return { category: 'literal', type };
|
|
67
|
-
} else if (
|
|
71
|
+
} else if (sourceFile?.endsWith('.d.ts') && !index.getFromSource(sourceFile)) {
|
|
68
72
|
return { category: 'unknown', type };
|
|
69
73
|
} else if (!resolvedType.isClass()) { // Not a real type
|
|
70
74
|
return { category: 'shape', type: resolvedType };
|
|
@@ -118,7 +122,7 @@ export const TypeBuilder: {
|
|
|
118
122
|
if (name in GLOBAL_SIMPLE) {
|
|
119
123
|
const cons = GLOBAL_SIMPLE[name];
|
|
120
124
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
121
|
-
const ret = LiteralUtil.isLiteralType(type) ?
|
|
125
|
+
const ret = LiteralUtil.isLiteralType(type) ? CoerceUtil.coerce(type.value, cons as typeof String, false) :
|
|
122
126
|
undefined;
|
|
123
127
|
|
|
124
128
|
return {
|
|
@@ -140,12 +144,10 @@ export const TypeBuilder: {
|
|
|
140
144
|
},
|
|
141
145
|
external: {
|
|
142
146
|
build: (checker, type) => {
|
|
143
|
-
const source = DeclarationUtil.getPrimaryDeclarationNode(type).getSourceFile();
|
|
144
147
|
const name = CoreUtil.getSymbol(type)?.getName();
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
};
|
|
148
|
+
const importName = checker.getIndex().getImportName(type);
|
|
149
|
+
const tsTypeArguments = checker.getAllTypeArguments(type);
|
|
150
|
+
return { key: 'external', name, importName, tsTypeArguments };
|
|
149
151
|
}
|
|
150
152
|
},
|
|
151
153
|
union: {
|
|
@@ -176,9 +178,10 @@ export const TypeBuilder: {
|
|
|
176
178
|
},
|
|
177
179
|
shape: {
|
|
178
180
|
build: (checker, type, alias?) => {
|
|
179
|
-
const
|
|
180
|
-
const name = CoreUtil.getSymbol(alias ?? type);
|
|
181
|
-
const
|
|
181
|
+
const tsFieldTypes: Record<string, ts.Type> = {};
|
|
182
|
+
const name = CoreUtil.getSymbol(alias ?? type)?.getName();
|
|
183
|
+
const importName = checker.getIndex().getImportName(type);
|
|
184
|
+
const tsTypeArguments = checker.getAllTypeArguments(type);
|
|
182
185
|
for (const member of checker.getPropertiesOfType(type)) {
|
|
183
186
|
const dec = DeclarationUtil.getPrimaryDeclarationNode(member);
|
|
184
187
|
if (DeclarationUtil.isPublic(dec)) { // If public
|
|
@@ -187,17 +190,11 @@ export const TypeBuilder: {
|
|
|
187
190
|
!member.getName().includes('@') && // if not a symbol
|
|
188
191
|
!memberType.getCallSignatures().length // if not a function
|
|
189
192
|
) {
|
|
190
|
-
|
|
193
|
+
tsFieldTypes[member.getName()] = memberType;
|
|
191
194
|
}
|
|
192
195
|
}
|
|
193
196
|
}
|
|
194
|
-
return {
|
|
195
|
-
key: 'shape', name: name?.getName(),
|
|
196
|
-
source: source?.fileName,
|
|
197
|
-
tsFieldTypes: fieldNodes,
|
|
198
|
-
tsTypeArguments: checker.getAllTypeArguments(type),
|
|
199
|
-
fieldTypes: {}
|
|
200
|
-
};
|
|
197
|
+
return { key: 'shape', name, importName, tsFieldTypes, tsTypeArguments, fieldTypes: {} };
|
|
201
198
|
}
|
|
202
199
|
},
|
|
203
200
|
concrete: {
|
|
@@ -205,23 +202,26 @@ export const TypeBuilder: {
|
|
|
205
202
|
const [tag] = DocUtil.readDocTag(type, 'concrete');
|
|
206
203
|
if (tag) {
|
|
207
204
|
// eslint-disable-next-line prefer-const
|
|
208
|
-
let [
|
|
205
|
+
let [importName, name] = tag.split(':');
|
|
209
206
|
if (!name) {
|
|
210
|
-
name =
|
|
211
|
-
|
|
207
|
+
name = importName;
|
|
208
|
+
importName = '.';
|
|
212
209
|
}
|
|
213
210
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
211
|
+
// Resolving relative to source file
|
|
212
|
+
if (importName.startsWith('.')) {
|
|
213
|
+
const rawSourceFile: string = DeclarationUtil.getDeclarations(type)
|
|
214
|
+
?.find(x => ts.getAllJSDocTags(x, (t): t is ts.JSDocTag => t.tagName.getText() === 'concrete').length)
|
|
215
|
+
?.getSourceFile().fileName ?? '';
|
|
217
216
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
217
|
+
if (importName === '.') {
|
|
218
|
+
importName = checker.getIndex().getImportName(rawSourceFile);
|
|
219
|
+
} else {
|
|
220
|
+
const base = path.dirname(rawSourceFile);
|
|
221
|
+
importName = checker.getIndex().getImportName(path.resolve(base, importName));
|
|
222
|
+
}
|
|
222
223
|
}
|
|
223
|
-
|
|
224
|
-
return { key: 'external', name, source: ext === 'node' ? source : PathUtil.resolveUnix(sourceFile, source) };
|
|
224
|
+
return { key: 'external', name, importName };
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
}
|