@travetto/transformer 3.0.0-rc.8 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -4
- package/package.json +4 -4
- package/src/importer.ts +14 -19
- package/src/manager.ts +19 -11
- package/src/resolver/builder.ts +29 -24
- package/src/resolver/service.ts +44 -12
- package/src/resolver/types.ts +4 -4
- package/src/state.ts +26 -44
- package/src/types/shared.ts +1 -1
- package/src/util/declaration.ts +2 -2
- package/src/util/log.ts +2 -1
- package/src/visitor.ts +3 -4
- package/src/manifest-index.ts +0 -29
package/README.md
CHANGED
|
@@ -6,14 +6,21 @@
|
|
|
6
6
|
**Install: @travetto/transformer**
|
|
7
7
|
```bash
|
|
8
8
|
npm install @travetto/transformer
|
|
9
|
+
|
|
10
|
+
# or
|
|
11
|
+
|
|
12
|
+
yarn add @travetto/transformer
|
|
9
13
|
```
|
|
10
14
|
|
|
11
15
|
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
16
|
|
|
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.
|
|
17
|
+
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 [Manifest](https://github.com/travetto/travetto/tree/main/module/manifest#readme "Support for project indexing, manifesting, along with file watching"), [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
18
|
|
|
15
19
|
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
20
|
|
|
21
|
+
## Monorepos and Idempotency
|
|
22
|
+
Within the framework, any build or compile step will target the entire workspace, and for mono-repo projects, will include all modules. The optimization this provides is great, but comes with a strict requirement that all compilation processes need to be idempotent. This means that compiling a module directly, versus as a dependency should always produce the same output. This produces a requirement that all transformers are opt-in by the source code, and which transformers are needed in a file should be code-evident. This also means that no transformers are optional, as that could produce different output depending on the dependency graph for a given module.
|
|
23
|
+
|
|
17
24
|
## Custom Transformer
|
|
18
25
|
|
|
19
26
|
Below is an example of a transformer that upper cases all `class`, `method` and `param` declarations. This will break any code that depends upon it as we are redefining all the identifiers at compile time.
|
|
@@ -28,7 +35,7 @@ export class MakeUpper {
|
|
|
28
35
|
|
|
29
36
|
@OnProperty()
|
|
30
37
|
static handleProperty(state: TransformerState, node: ts.PropertyDeclaration): ts.PropertyDeclaration {
|
|
31
|
-
if (!state.
|
|
38
|
+
if (!state.importName.startsWith('@travetto/transformer/doc/upper')) {
|
|
32
39
|
return node;
|
|
33
40
|
}
|
|
34
41
|
return state.factory.updatePropertyDeclaration(
|
|
@@ -43,7 +50,7 @@ export class MakeUpper {
|
|
|
43
50
|
|
|
44
51
|
@OnClass()
|
|
45
52
|
static handleClass(state: TransformerState, node: ts.ClassDeclaration): ts.ClassDeclaration {
|
|
46
|
-
if (!state.
|
|
53
|
+
if (!state.importName.startsWith('@travetto/transformer/doc/upper')) {
|
|
47
54
|
return node;
|
|
48
55
|
}
|
|
49
56
|
return state.factory.updateClassDeclaration(
|
|
@@ -58,7 +65,7 @@ export class MakeUpper {
|
|
|
58
65
|
|
|
59
66
|
@OnMethod()
|
|
60
67
|
static handleMethod(state: TransformerState, node: ts.MethodDeclaration): ts.MethodDeclaration {
|
|
61
|
-
if (!state.
|
|
68
|
+
if (!state.importName.startsWith('@travetto/transformer/doc/upper')) {
|
|
62
69
|
return node;
|
|
63
70
|
}
|
|
64
71
|
return state.factory.updateMethodDeclaration(
|
|
@@ -77,3 +84,40 @@ export class MakeUpper {
|
|
|
77
84
|
```
|
|
78
85
|
|
|
79
86
|
**Note**: This should be a strong indicator that it is very easy to break code in unexpected ways.
|
|
87
|
+
|
|
88
|
+
**Code: Sample Input**
|
|
89
|
+
```typescript
|
|
90
|
+
export class Test {
|
|
91
|
+
name: string;
|
|
92
|
+
age: number;
|
|
93
|
+
dob: Date;
|
|
94
|
+
|
|
95
|
+
computeAge(): void {
|
|
96
|
+
this['age'] = (Date.now() - this.dob.getTime());
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Code: Sample Output**
|
|
102
|
+
```javascript
|
|
103
|
+
"use strict";
|
|
104
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
105
|
+
exports.TEST = void 0;
|
|
106
|
+
const tslib_1 = require("tslib");
|
|
107
|
+
const Ⲑ_root_index_1 = tslib_1.__importStar(require("@travetto/manifest/src/root-index.js"));
|
|
108
|
+
const Ⲑ_decorator_1 = tslib_1.__importStar(require("@travetto/registry/src/decorator.js"));
|
|
109
|
+
var ᚕf = "@travetto/transformer/doc/upper.js";
|
|
110
|
+
let TEST = class TEST {
|
|
111
|
+
static Ⲑinit = Ⲑ_root_index_1.RootIndex.registerFunction(TEST, ᚕf, 649563175, { COMPUTEAGE: { hash: 1286718349 } }, false, false);
|
|
112
|
+
NAME;
|
|
113
|
+
AGE;
|
|
114
|
+
DOB;
|
|
115
|
+
COMPUTEAGE() {
|
|
116
|
+
this['AGE'] = (Date.now() - this.DOB.getTime());
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
TEST = tslib_1.__decorate([
|
|
120
|
+
Ⲑ_decorator_1.Register()
|
|
121
|
+
], TEST);
|
|
122
|
+
exports.TEST = TEST;
|
|
123
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/transformer",
|
|
3
|
-
"version": "3.0.0
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Functionality for AST transformations, with transformer registration, and general utils",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"directory": "module/transformer"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@travetto/manifest": "^3.0.0
|
|
28
|
-
"tslib": "^2.
|
|
29
|
-
"typescript": "^4.9.
|
|
27
|
+
"@travetto/manifest": "^3.0.0",
|
|
28
|
+
"tslib": "^2.5.0",
|
|
29
|
+
"typescript": "^4.9.5"
|
|
30
30
|
},
|
|
31
31
|
"travetto": {
|
|
32
32
|
"displayName": "Transformation",
|
package/src/importer.ts
CHANGED
|
@@ -2,13 +2,12 @@ import ts from 'typescript';
|
|
|
2
2
|
|
|
3
3
|
import { PackageUtil, path } from '@travetto/manifest';
|
|
4
4
|
|
|
5
|
-
import { AnyType, ExternalType } from './resolver/types';
|
|
5
|
+
import { AnyType, TransformResolver, ExternalType } from './resolver/types';
|
|
6
6
|
import { ImportUtil } from './util/import';
|
|
7
7
|
import { CoreUtil } from './util/core';
|
|
8
8
|
import { Import } from './types/shared';
|
|
9
9
|
import { LiteralUtil } from './util/literal';
|
|
10
10
|
import { DeclarationUtil } from './util/declaration';
|
|
11
|
-
import { TransformerIndex } from './manifest-index';
|
|
12
11
|
|
|
13
12
|
const D_OR_D_TS_EXT_RE = /[.]d([.]ts)?$/;
|
|
14
13
|
|
|
@@ -22,12 +21,12 @@ export class ImportManager {
|
|
|
22
21
|
#idx: Record<string, number> = {};
|
|
23
22
|
#ids = new Map<string, string>();
|
|
24
23
|
#importName: string;
|
|
25
|
-
#
|
|
24
|
+
#resolver: TransformResolver;
|
|
26
25
|
|
|
27
|
-
constructor(public source: ts.SourceFile, public factory: ts.NodeFactory,
|
|
26
|
+
constructor(public source: ts.SourceFile, public factory: ts.NodeFactory, resolver: TransformResolver) {
|
|
28
27
|
this.#imports = ImportUtil.collectImports(source);
|
|
29
|
-
this.#
|
|
30
|
-
this.#importName =
|
|
28
|
+
this.#resolver = resolver;
|
|
29
|
+
this.#importName = this.#resolver.getFileImportName(source.fileName);
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
#getImportFile(spec?: ts.Expression): string | undefined {
|
|
@@ -40,7 +39,7 @@ export class ImportManager {
|
|
|
40
39
|
const fileOrImport = this.#getImportFile(spec);
|
|
41
40
|
if (
|
|
42
41
|
fileOrImport &&
|
|
43
|
-
(fileOrImport.startsWith('.') || this.#
|
|
42
|
+
(fileOrImport.startsWith('.') || this.#resolver.isKnownFile(fileOrImport)) &&
|
|
44
43
|
!/[.]([mc]?js|ts|json)$/.test(fileOrImport)
|
|
45
44
|
) {
|
|
46
45
|
return LiteralUtil.fromLiteral(this.factory, `${fileOrImport}.js`);
|
|
@@ -48,17 +47,13 @@ export class ImportManager {
|
|
|
48
47
|
return spec;
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
#rewriteImportClause(
|
|
52
|
-
spec: ts.Expression | undefined,
|
|
53
|
-
clause: ts.ImportClause | undefined,
|
|
54
|
-
checker: ts.TypeChecker
|
|
55
|
-
): ts.ImportClause | undefined {
|
|
50
|
+
#rewriteImportClause(spec: ts.Expression | undefined, clause: ts.ImportClause | undefined): ts.ImportClause | undefined {
|
|
56
51
|
if (!(spec && clause?.namedBindings && ts.isNamedImports(clause.namedBindings))) {
|
|
57
52
|
return clause;
|
|
58
53
|
}
|
|
59
54
|
|
|
60
55
|
const fileOrImport = this.#getImportFile(spec);
|
|
61
|
-
if (!(fileOrImport && (fileOrImport.startsWith('.') || this.#
|
|
56
|
+
if (!(fileOrImport && (fileOrImport.startsWith('.') || this.#resolver.isKnownFile(fileOrImport)))) {
|
|
62
57
|
return clause;
|
|
63
58
|
}
|
|
64
59
|
|
|
@@ -67,7 +62,7 @@ export class ImportManager {
|
|
|
67
62
|
// Remove all type only imports
|
|
68
63
|
for (const el of bindings.elements) {
|
|
69
64
|
if (!el.isTypeOnly) {
|
|
70
|
-
const type =
|
|
65
|
+
const type = this.#resolver.getType(el.name);
|
|
71
66
|
const objFlags = DeclarationUtil.getObjectFlags(type);
|
|
72
67
|
const typeFlags = type.getFlags();
|
|
73
68
|
if (objFlags || typeFlags !== 1) {
|
|
@@ -106,7 +101,7 @@ export class ImportManager {
|
|
|
106
101
|
* Import a file if needed, and record it's identifier
|
|
107
102
|
*/
|
|
108
103
|
importFile(file: string, name?: string): Import {
|
|
109
|
-
file = this.#
|
|
104
|
+
file = this.#resolver.getFileImportName(file);
|
|
110
105
|
|
|
111
106
|
// Allow for node classes to be imported directly
|
|
112
107
|
if (/@types\/node/.test(file)) {
|
|
@@ -178,7 +173,7 @@ export class ImportManager {
|
|
|
178
173
|
}
|
|
179
174
|
}
|
|
180
175
|
|
|
181
|
-
finalizeImportExportExtension(ret: ts.SourceFile
|
|
176
|
+
finalizeImportExportExtension(ret: ts.SourceFile): ts.SourceFile {
|
|
182
177
|
const toAdd: ts.Statement[] = [];
|
|
183
178
|
|
|
184
179
|
for (const stmt of ret.statements) {
|
|
@@ -198,7 +193,7 @@ export class ImportManager {
|
|
|
198
193
|
toAdd.push(this.factory.updateImportDeclaration(
|
|
199
194
|
stmt,
|
|
200
195
|
stmt.modifiers,
|
|
201
|
-
this.#rewriteImportClause(stmt.moduleSpecifier, stmt.importClause
|
|
196
|
+
this.#rewriteImportClause(stmt.moduleSpecifier, stmt.importClause)!,
|
|
202
197
|
this.#rewriteModuleSpecifier(stmt.moduleSpecifier)!,
|
|
203
198
|
stmt.assertClause
|
|
204
199
|
));
|
|
@@ -213,9 +208,9 @@ export class ImportManager {
|
|
|
213
208
|
/**
|
|
214
209
|
* Reset the imports into the source file
|
|
215
210
|
*/
|
|
216
|
-
finalize(ret: ts.SourceFile
|
|
211
|
+
finalize(ret: ts.SourceFile): ts.SourceFile {
|
|
217
212
|
ret = this.finalizeNewImports(ret) ?? ret;
|
|
218
|
-
ret = this.finalizeImportExportExtension(ret
|
|
213
|
+
ret = this.finalizeImportExportExtension(ret) ?? ret;
|
|
219
214
|
return ret;
|
|
220
215
|
}
|
|
221
216
|
|
package/src/manager.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { ManifestIndex, path, RootIndex } from '@travetto/manifest';
|
|
4
4
|
|
|
5
5
|
import { NodeTransformer } from './types/visitor';
|
|
6
6
|
import { VisitorFactory } from './visitor';
|
|
7
7
|
import { TransformerState } from './state';
|
|
8
8
|
import { getAllTransformers } from './register';
|
|
9
|
-
import { TransformerIndex } from './manifest-index';
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* Manages the typescript transformers
|
|
@@ -19,26 +18,35 @@ export class TransformerManager {
|
|
|
19
18
|
* @param manifest
|
|
20
19
|
* @returns
|
|
21
20
|
*/
|
|
22
|
-
static async create(
|
|
21
|
+
static async create(manifestIndex: ManifestIndex): Promise<TransformerManager> {
|
|
22
|
+
const transformerFiles = Object.values(manifestIndex.manifest.modules).flatMap(
|
|
23
|
+
x => (x.files.$transformer ?? []).map(([f]) =>
|
|
24
|
+
path.resolve(manifestIndex.manifest.workspacePath, x.sourceFolder, f)
|
|
25
|
+
)
|
|
26
|
+
);
|
|
27
|
+
|
|
23
28
|
const transformers: NodeTransformer<TransformerState>[] = [];
|
|
24
|
-
const idx = new TransformerIndex('.', manifest);
|
|
25
29
|
|
|
26
30
|
for (const file of transformerFiles) { // Exclude based on blacklist
|
|
27
|
-
const entry =
|
|
28
|
-
transformers.push(...getAllTransformers(await import(
|
|
31
|
+
const entry = RootIndex.getEntry(file)!;
|
|
32
|
+
transformers.push(...getAllTransformers(await import(entry.import), entry.module));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const x of transformers) {
|
|
36
|
+
process.send?.(['debug', `Loaded Transformer: ${x.key}#${x.type}`]);
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
// Prepare a new visitor factory with a given type checker
|
|
32
|
-
return new TransformerManager(
|
|
40
|
+
return new TransformerManager(manifestIndex, transformers);
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
#cached: ts.CustomTransformers | undefined;
|
|
36
44
|
#transformers: NodeTransformer<TransformerState>[];
|
|
37
|
-
#
|
|
45
|
+
#manifestIndex: ManifestIndex;
|
|
38
46
|
|
|
39
|
-
constructor(transformers: NodeTransformer<TransformerState>[]
|
|
47
|
+
constructor(manifestIndex: ManifestIndex, transformers: NodeTransformer<TransformerState>[]) {
|
|
40
48
|
this.#transformers = transformers;
|
|
41
|
-
this.#
|
|
49
|
+
this.#manifestIndex = manifestIndex;
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
/**
|
|
@@ -47,7 +55,7 @@ export class TransformerManager {
|
|
|
47
55
|
*/
|
|
48
56
|
init(checker: ts.TypeChecker): void {
|
|
49
57
|
const visitor = new VisitorFactory(
|
|
50
|
-
(ctx, src) => new TransformerState(src, ctx.factory, checker, this.#
|
|
58
|
+
(ctx, src) => new TransformerState(src, ctx.factory, checker, this.#manifestIndex),
|
|
51
59
|
this.#transformers
|
|
52
60
|
);
|
|
53
61
|
|
package/src/resolver/builder.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/* eslint-disable no-bitwise */
|
|
2
2
|
import ts from 'typescript';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { path } from '@travetto/manifest';
|
|
5
5
|
|
|
6
6
|
import { DocUtil } from '../util/doc';
|
|
7
7
|
import { CoreUtil } from '../util/core';
|
|
8
8
|
import { DeclarationUtil } from '../util/declaration';
|
|
9
9
|
import { LiteralUtil } from '../util/literal';
|
|
10
10
|
|
|
11
|
-
import { Type, AnyType, UnionType,
|
|
11
|
+
import { Type, AnyType, UnionType, TransformResolver } from './types';
|
|
12
12
|
import { CoerceUtil } from './coerce';
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -38,7 +38,7 @@ type Category = 'void' | 'undefined' | 'concrete' | 'unknown' | 'tuple' | 'shape
|
|
|
38
38
|
/**
|
|
39
39
|
* Type categorizer, input for builder
|
|
40
40
|
*/
|
|
41
|
-
export function TypeCategorize(
|
|
41
|
+
export function TypeCategorize(resolver: TransformResolver, type: ts.Type): { category: Category, type: ts.Type } {
|
|
42
42
|
const flags = type.getFlags();
|
|
43
43
|
const objectFlags = DeclarationUtil.getObjectFlags(type) ?? 0;
|
|
44
44
|
|
|
@@ -68,7 +68,7 @@ export function TypeCategorize(checker: ts.TypeChecker, type: ts.Type, index: Ma
|
|
|
68
68
|
const sourceFile = source.fileName;
|
|
69
69
|
if (sourceFile?.includes('@types/node/globals') || sourceFile?.includes('typescript/lib')) {
|
|
70
70
|
return { category: 'literal', type };
|
|
71
|
-
} else if (sourceFile?.endsWith('.d.ts') && !
|
|
71
|
+
} else if (sourceFile?.endsWith('.d.ts') && !resolver.isKnownFile(sourceFile)) {
|
|
72
72
|
return { category: 'unknown', type };
|
|
73
73
|
} else if (!resolvedType.isClass()) { // Not a real type
|
|
74
74
|
return { category: 'shape', type: resolvedType };
|
|
@@ -97,26 +97,26 @@ export function TypeCategorize(checker: ts.TypeChecker, type: ts.Type, index: Ma
|
|
|
97
97
|
*/
|
|
98
98
|
export const TypeBuilder: {
|
|
99
99
|
[K in Category]: {
|
|
100
|
-
build(
|
|
100
|
+
build(resolver: TransformResolver, type: ts.Type, alias?: ts.Symbol): AnyType | undefined;
|
|
101
101
|
finalize?(type: Type<K>): AnyType;
|
|
102
102
|
}
|
|
103
103
|
} = {
|
|
104
104
|
unknown: {
|
|
105
|
-
build: (
|
|
105
|
+
build: (resolver, type) => undefined
|
|
106
106
|
},
|
|
107
107
|
undefined: {
|
|
108
|
-
build: (
|
|
108
|
+
build: (resolver, type) => ({ key: 'literal', name: 'undefined', ctor: undefined })
|
|
109
109
|
},
|
|
110
110
|
void: {
|
|
111
|
-
build: (
|
|
111
|
+
build: (resolver, type) => ({ key: 'literal', name: 'void', ctor: undefined })
|
|
112
112
|
},
|
|
113
113
|
tuple: {
|
|
114
|
-
build: (
|
|
114
|
+
build: (resolver, type) => ({ key: 'tuple', tsTupleTypes: resolver.getAllTypeArguments(type), subTypes: [] })
|
|
115
115
|
},
|
|
116
116
|
literal: {
|
|
117
|
-
build: (
|
|
117
|
+
build: (resolver, type) => {
|
|
118
118
|
// Handle void/undefined
|
|
119
|
-
const name =
|
|
119
|
+
const name = resolver.getTypeAsString(type) ?? '';
|
|
120
120
|
const complexName = CoreUtil.getSymbol(type)?.getName() ?? '';
|
|
121
121
|
|
|
122
122
|
if (name in GLOBAL_SIMPLE) {
|
|
@@ -137,21 +137,21 @@ export const TypeBuilder: {
|
|
|
137
137
|
key: 'literal',
|
|
138
138
|
name: cons.name,
|
|
139
139
|
ctor: cons,
|
|
140
|
-
tsTypeArguments:
|
|
140
|
+
tsTypeArguments: resolver.getAllTypeArguments(type)
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
},
|
|
145
145
|
external: {
|
|
146
|
-
build: (
|
|
146
|
+
build: (resolver, type) => {
|
|
147
147
|
const name = CoreUtil.getSymbol(type)?.getName();
|
|
148
|
-
const importName =
|
|
149
|
-
const tsTypeArguments =
|
|
148
|
+
const importName = resolver.getTypeImportName(type)!;
|
|
149
|
+
const tsTypeArguments = resolver.getAllTypeArguments(type);
|
|
150
150
|
return { key: 'external', name, importName, tsTypeArguments };
|
|
151
151
|
}
|
|
152
152
|
},
|
|
153
153
|
union: {
|
|
154
|
-
build: (
|
|
154
|
+
build: (resolver, uType: ts.UnionType) => {
|
|
155
155
|
let undefinable = false;
|
|
156
156
|
let nullable = false;
|
|
157
157
|
const remainder = uType.types.filter(ut => {
|
|
@@ -177,15 +177,20 @@ export const TypeBuilder: {
|
|
|
177
177
|
}
|
|
178
178
|
},
|
|
179
179
|
shape: {
|
|
180
|
-
build: (
|
|
180
|
+
build: (resolver, type, alias?) => {
|
|
181
181
|
const tsFieldTypes: Record<string, ts.Type> = {};
|
|
182
182
|
const name = CoreUtil.getSymbol(alias ?? type)?.getName();
|
|
183
|
-
const importName =
|
|
184
|
-
const tsTypeArguments =
|
|
185
|
-
|
|
183
|
+
const importName = resolver.getTypeImportName(type) ?? '<unknown>';
|
|
184
|
+
const tsTypeArguments = resolver.getAllTypeArguments(type);
|
|
185
|
+
const props = resolver.getPropertiesOfType(type);
|
|
186
|
+
if (props.length === 0) {
|
|
187
|
+
return { key: 'unknown', name, importName };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (const member of props) {
|
|
186
191
|
const dec = DeclarationUtil.getPrimaryDeclarationNode(member);
|
|
187
192
|
if (DeclarationUtil.isPublic(dec)) { // If public
|
|
188
|
-
const memberType =
|
|
193
|
+
const memberType = resolver.getType(dec);
|
|
189
194
|
if (
|
|
190
195
|
!member.getName().includes('@') && // if not a symbol
|
|
191
196
|
!memberType.getCallSignatures().length // if not a function
|
|
@@ -198,7 +203,7 @@ export const TypeBuilder: {
|
|
|
198
203
|
}
|
|
199
204
|
},
|
|
200
205
|
concrete: {
|
|
201
|
-
build: (
|
|
206
|
+
build: (resolver, type) => {
|
|
202
207
|
const [tag] = DocUtil.readDocTag(type, 'concrete');
|
|
203
208
|
if (tag) {
|
|
204
209
|
// eslint-disable-next-line prefer-const
|
|
@@ -215,10 +220,10 @@ export const TypeBuilder: {
|
|
|
215
220
|
?.getSourceFile().fileName ?? '';
|
|
216
221
|
|
|
217
222
|
if (importName === '.') {
|
|
218
|
-
importName =
|
|
223
|
+
importName = resolver.getFileImportName(rawSourceFile);
|
|
219
224
|
} else {
|
|
220
225
|
const base = path.dirname(rawSourceFile);
|
|
221
|
-
importName =
|
|
226
|
+
importName = resolver.getFileImportName(path.resolve(base, importName));
|
|
222
227
|
}
|
|
223
228
|
}
|
|
224
229
|
return { key: 'external', name, importName };
|
package/src/resolver/service.ts
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { ManifestIndex, path } from '@travetto/manifest';
|
|
4
|
+
|
|
5
|
+
import type { AnyType, TransformResolver } from './types';
|
|
4
6
|
import { TypeCategorize, TypeBuilder } from './builder';
|
|
5
7
|
import { VisitCache } from './cache';
|
|
6
8
|
import { DocUtil } from '../util/doc';
|
|
7
|
-
import {
|
|
9
|
+
import { DeclarationUtil } from '../util/declaration';
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
|
-
*
|
|
12
|
+
* Implementation of TransformResolver
|
|
11
13
|
*/
|
|
12
|
-
export class
|
|
14
|
+
export class SimpleResolver implements TransformResolver {
|
|
13
15
|
#tsChecker: ts.TypeChecker;
|
|
14
|
-
#
|
|
16
|
+
#manifestIndex: ManifestIndex;
|
|
15
17
|
|
|
16
|
-
constructor(tsChecker: ts.TypeChecker,
|
|
18
|
+
constructor(tsChecker: ts.TypeChecker, manifestIndex: ManifestIndex) {
|
|
17
19
|
this.#tsChecker = tsChecker;
|
|
18
|
-
this.#
|
|
20
|
+
this.#manifestIndex = manifestIndex;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
/**
|
|
@@ -26,8 +28,38 @@ export class TypeResolver implements Checker {
|
|
|
26
28
|
return this.#tsChecker;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Resolve an import name (e.g. @module/path/file) for a file
|
|
33
|
+
*/
|
|
34
|
+
getFileImportName(file: string, removeExt?: boolean): string {
|
|
35
|
+
let sourceFile = path.toPosix(file);
|
|
36
|
+
|
|
37
|
+
if (!sourceFile.endsWith('.js') && !sourceFile.endsWith('.ts')) {
|
|
38
|
+
sourceFile = `${sourceFile}.ts`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const imp =
|
|
42
|
+
this.#manifestIndex.getEntry(/[.]ts$/.test(sourceFile) ? sourceFile : `${sourceFile}.js`)?.import ??
|
|
43
|
+
this.#manifestIndex.getFromImport(sourceFile.replace(/^.*node_modules\//, '').replace(/[.]ts$/, ''))?.import ??
|
|
44
|
+
file;
|
|
45
|
+
|
|
46
|
+
return removeExt ? imp.replace(/[.]js$/, '') : imp;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resolve an import name (e.g. @module/path/file) for a type
|
|
51
|
+
*/
|
|
52
|
+
getTypeImportName(type: ts.Type, removeExt?: boolean): string | undefined {
|
|
53
|
+
const ogSource = DeclarationUtil.getPrimaryDeclarationNode(type)?.getSourceFile()?.fileName;
|
|
54
|
+
return ogSource ? this.getFileImportName(ogSource, removeExt) : undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Is the file/import known to the index, helpful for determine ownership
|
|
59
|
+
*/
|
|
60
|
+
isKnownFile(fileOrImport: string): boolean {
|
|
61
|
+
return (this.#manifestIndex.getFromSource(fileOrImport) !== undefined) ||
|
|
62
|
+
(this.#manifestIndex.getFromImport(fileOrImport) !== undefined);
|
|
31
63
|
}
|
|
32
64
|
|
|
33
65
|
/**
|
|
@@ -72,7 +104,7 @@ export class TypeResolver implements Checker {
|
|
|
72
104
|
/**
|
|
73
105
|
* Resolve an `AnyType` from a `ts.Type` or a `ts.Node`
|
|
74
106
|
*/
|
|
75
|
-
resolveType(node: ts.Type | ts.Node): AnyType {
|
|
107
|
+
resolveType(node: ts.Type | ts.Node, importName: string): AnyType {
|
|
76
108
|
const visited = new VisitCache();
|
|
77
109
|
const resolve = (resType: ts.Type, alias?: ts.Symbol, depth = 0): AnyType => {
|
|
78
110
|
|
|
@@ -80,7 +112,7 @@ export class TypeResolver implements Checker {
|
|
|
80
112
|
throw new Error('Object structure too nested');
|
|
81
113
|
}
|
|
82
114
|
|
|
83
|
-
const { category, type } = TypeCategorize(this
|
|
115
|
+
const { category, type } = TypeCategorize(this, resType);
|
|
84
116
|
const { build, finalize } = TypeBuilder[category];
|
|
85
117
|
|
|
86
118
|
let result = build(this, type, alias);
|
|
@@ -124,7 +156,7 @@ export class TypeResolver implements Checker {
|
|
|
124
156
|
if (!(err instanceof Error)) {
|
|
125
157
|
throw err;
|
|
126
158
|
}
|
|
127
|
-
console.error(
|
|
159
|
+
console.error(`Unable to resolve type in ${importName}`, err.stack);
|
|
128
160
|
return { key: 'literal', ctor: Object, name: 'object' };
|
|
129
161
|
}
|
|
130
162
|
}
|
package/src/resolver/types.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type ts from 'typescript';
|
|
2
2
|
|
|
3
|
-
import { TransformerIndex } from '../manifest-index';
|
|
4
|
-
|
|
5
3
|
/**
|
|
6
4
|
* Base type for a simplistic type structure
|
|
7
5
|
*/
|
|
@@ -153,10 +151,12 @@ export type AnyType = TupleType | ShapeType | UnionType | LiteralType | External
|
|
|
153
151
|
/**
|
|
154
152
|
* Simple interface for checked methods
|
|
155
153
|
*/
|
|
156
|
-
export interface
|
|
154
|
+
export interface TransformResolver {
|
|
155
|
+
isKnownFile(file: string): boolean;
|
|
156
|
+
getFileImportName(file: string, removeExt?: boolean): string;
|
|
157
|
+
getTypeImportName(type: ts.Type, removeExt?: boolean): string | undefined;
|
|
157
158
|
getAllTypeArguments(type: ts.Type): ts.Type[];
|
|
158
159
|
getPropertiesOfType(type: ts.Type): ts.Symbol[];
|
|
159
160
|
getTypeAsString(type: ts.Type): string | undefined;
|
|
160
161
|
getType(node: ts.Node): ts.Type;
|
|
161
|
-
getIndex(): TransformerIndex;
|
|
162
162
|
}
|
package/src/state.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
|
|
3
|
-
import { path } from '@travetto/manifest';
|
|
3
|
+
import { ManifestIndex, path } from '@travetto/manifest';
|
|
4
4
|
|
|
5
5
|
import { ExternalType, AnyType } from './resolver/types';
|
|
6
6
|
import { State, DecoratorMeta, Transformer, ModuleNameⲐ } from './types/visitor';
|
|
7
|
-
import {
|
|
7
|
+
import { SimpleResolver } from './resolver/service';
|
|
8
8
|
import { ImportManager } from './importer';
|
|
9
9
|
import { Import } from './types/shared';
|
|
10
10
|
|
|
@@ -14,7 +14,6 @@ import { DeclarationUtil } from './util/declaration';
|
|
|
14
14
|
import { CoreUtil } from './util/core';
|
|
15
15
|
import { LiteralUtil } from './util/literal';
|
|
16
16
|
import { SystemUtil } from './util/system';
|
|
17
|
-
import { TransformerIndex } from './manifest-index';
|
|
18
17
|
|
|
19
18
|
function hasOriginal(n: unknown): n is { original: ts.Node } {
|
|
20
19
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -32,38 +31,23 @@ function hasEscapedName(n: unknown): n is { name: { escapedText: string } } {
|
|
|
32
31
|
export class TransformerState implements State {
|
|
33
32
|
static SYNTHETIC_EXT = 'Ⲑsyn';
|
|
34
33
|
|
|
35
|
-
#resolver:
|
|
34
|
+
#resolver: SimpleResolver;
|
|
36
35
|
#imports: ImportManager;
|
|
37
|
-
#
|
|
36
|
+
#fileIdent: ts.Identifier;
|
|
37
|
+
#manifestIndex: ManifestIndex;
|
|
38
38
|
#syntheticIdentifiers = new Map<string, ts.Identifier>();
|
|
39
39
|
#decorators = new Map<string, ts.PropertyAccessExpression>();
|
|
40
|
-
#options: ts.CompilerOptions;
|
|
41
40
|
added = new Map<number, ts.Statement[]>();
|
|
42
41
|
importName: string;
|
|
43
42
|
file: string;
|
|
44
43
|
|
|
45
|
-
constructor(public source: ts.SourceFile, public factory: ts.NodeFactory, checker: ts.TypeChecker,
|
|
46
|
-
this.#
|
|
47
|
-
this.#
|
|
48
|
-
this.#
|
|
44
|
+
constructor(public source: ts.SourceFile, public factory: ts.NodeFactory, checker: ts.TypeChecker, manifestIndex: ManifestIndex) {
|
|
45
|
+
this.#manifestIndex = manifestIndex;
|
|
46
|
+
this.#resolver = new SimpleResolver(checker, manifestIndex);
|
|
47
|
+
this.#imports = new ImportManager(source, factory, this.#resolver);
|
|
49
48
|
this.file = path.toPosix(this.source.fileName);
|
|
50
|
-
this.importName = this.#index.getImportName(this.file, true);
|
|
51
|
-
this.#options = options;
|
|
52
|
-
}
|
|
53
49
|
|
|
54
|
-
|
|
55
|
-
* Are we building ESM Output?
|
|
56
|
-
*/
|
|
57
|
-
isEsmOutput(): boolean {
|
|
58
|
-
return this.#options.module !== ts.ModuleKind.CommonJS;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Allow access to resolver
|
|
63
|
-
* @private
|
|
64
|
-
*/
|
|
65
|
-
getResolver(): TypeResolver {
|
|
66
|
-
return this.#resolver;
|
|
50
|
+
this.importName = this.#resolver.getFileImportName(this.file, true);
|
|
67
51
|
}
|
|
68
52
|
|
|
69
53
|
/**
|
|
@@ -84,7 +68,7 @@ export class TransformerState implements State {
|
|
|
84
68
|
* Resolve an `AnyType` from a `ts.Type` or `ts.Node`
|
|
85
69
|
*/
|
|
86
70
|
resolveType(node: ts.Type | ts.Node): AnyType {
|
|
87
|
-
const resolved = this.#resolver.resolveType(node);
|
|
71
|
+
const resolved = this.#resolver.resolveType(node, this.importName);
|
|
88
72
|
this.#imports.importFromResolved(resolved);
|
|
89
73
|
return resolved;
|
|
90
74
|
}
|
|
@@ -96,7 +80,7 @@ export class TransformerState implements State {
|
|
|
96
80
|
const resolved = this.resolveType(node);
|
|
97
81
|
if (resolved.key !== 'external') {
|
|
98
82
|
const file = node.getSourceFile().fileName;
|
|
99
|
-
const src = this.#
|
|
83
|
+
const src = this.#resolver.getFileImportName(file);
|
|
100
84
|
throw new Error(`Unable to import non-external type: ${node.getText()} ${resolved.key}: ${src}`);
|
|
101
85
|
}
|
|
102
86
|
return resolved;
|
|
@@ -163,8 +147,8 @@ export class TransformerState implements State {
|
|
|
163
147
|
this.#resolver.getType(ident)
|
|
164
148
|
);
|
|
165
149
|
const src = decl?.getSourceFile().fileName;
|
|
166
|
-
const mod = src ? this.#
|
|
167
|
-
const file = this.#
|
|
150
|
+
const mod = src ? this.#resolver.getFileImportName(src, true) : undefined;
|
|
151
|
+
const file = this.#manifestIndex.getFromImport(mod ?? '')?.outputFile;
|
|
168
152
|
const targets = DocUtil.readAugments(this.#resolver.getType(ident));
|
|
169
153
|
const module = file ? mod : undefined;
|
|
170
154
|
const name = ident ?
|
|
@@ -228,7 +212,7 @@ export class TransformerState implements State {
|
|
|
228
212
|
* Finalize the source file for emission
|
|
229
213
|
*/
|
|
230
214
|
finalize(ret: ts.SourceFile): ts.SourceFile {
|
|
231
|
-
ret = this.#imports.finalize(ret
|
|
215
|
+
ret = this.#imports.finalize(ret);
|
|
232
216
|
return ret;
|
|
233
217
|
}
|
|
234
218
|
|
|
@@ -279,18 +263,16 @@ export class TransformerState implements State {
|
|
|
279
263
|
* Get filename identifier, regardless of module system
|
|
280
264
|
*/
|
|
281
265
|
getFilenameIdentifier(): ts.Expression {
|
|
282
|
-
|
|
283
|
-
this.
|
|
284
|
-
this.
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
return this
|
|
292
|
-
this.createAccess('process', 'argv', 1) :
|
|
293
|
-
this.createAccess('require', 'main', 'filename');
|
|
266
|
+
if (this.#fileIdent === undefined) {
|
|
267
|
+
this.#fileIdent = this.createIdentifier('ᚕf');
|
|
268
|
+
const decl = this.factory.createVariableDeclaration(this.#fileIdent, undefined, undefined,
|
|
269
|
+
this.fromLiteral(this.#resolver.getFileImportName(this.source.fileName) ?? this.source.fileName)
|
|
270
|
+
);
|
|
271
|
+
this.addStatements([
|
|
272
|
+
this.factory.createVariableStatement([], this.factory.createVariableDeclarationList([decl]))
|
|
273
|
+
], -1);
|
|
274
|
+
}
|
|
275
|
+
return this.#fileIdent;
|
|
294
276
|
}
|
|
295
277
|
|
|
296
278
|
/**
|
|
@@ -357,7 +339,7 @@ export class TransformerState implements State {
|
|
|
357
339
|
(m): m is ts.MethodDeclaration => ts.isMethodDeclaration(m) && ts.isIdentifier(m.name) && m.name.escapedText === method
|
|
358
340
|
);
|
|
359
341
|
} else {
|
|
360
|
-
const props = this.
|
|
342
|
+
const props = this.#resolver.getPropertiesOfType(cls);
|
|
361
343
|
for (const prop of props) {
|
|
362
344
|
const decl = prop.declarations?.[0];
|
|
363
345
|
if (decl && prop.escapedName === method && ts.isMethodDeclaration(decl)) {
|
package/src/types/shared.ts
CHANGED
package/src/util/declaration.ts
CHANGED
|
@@ -57,8 +57,8 @@ export class DeclarationUtil {
|
|
|
57
57
|
* Resolve the `ts.ObjectFlags`
|
|
58
58
|
*/
|
|
59
59
|
static getObjectFlags(type: ts.Type): ts.ObjectFlags {
|
|
60
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
61
|
-
return (ts as unknown as { getObjectFlags(t: ts.Type): ts.ObjectFlags }).getObjectFlags(type);
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, no-bitwise
|
|
61
|
+
return (ts as unknown as { getObjectFlags(t: ts.Type): ts.ObjectFlags }).getObjectFlags(type) & ~(ts.NodeFlags.ThisNodeOrAnySubNodesHasError);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/**
|
package/src/util/log.ts
CHANGED
|
@@ -36,7 +36,8 @@ export class LogUtil {
|
|
|
36
36
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
37
37
|
const ox = x as object;
|
|
38
38
|
const out: Record<string, unknown> = {};
|
|
39
|
-
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
40
|
+
for (const key of Object.keys(ox) as (keyof typeof ox)[]) {
|
|
40
41
|
if (Object.getPrototypeOf(ox[key]) === Function.prototype || exclude.has(key) || ox[key] === undefined) {
|
|
41
42
|
continue;
|
|
42
43
|
}
|
package/src/visitor.ts
CHANGED
|
@@ -89,12 +89,11 @@ export class VisitorFactory<S extends State = State> {
|
|
|
89
89
|
const changed = state.added.size;
|
|
90
90
|
let statements: ts.NodeArray<ts.Statement> | ts.Statement[] = ret.statements;
|
|
91
91
|
while (state.added.size) {
|
|
92
|
-
for (const [
|
|
93
|
-
const idx = k === -1 ? state.added.size : k;
|
|
92
|
+
for (const [idx, all] of [...state.added].sort(([idxA], [idxB]) => idxB - idxA)) {
|
|
94
93
|
statements = [
|
|
95
|
-
...statements.slice(0, idx),
|
|
94
|
+
...statements.slice(0, Math.max(idx, 0)),
|
|
96
95
|
...all.map(v => this.visit(state, context, v)),
|
|
97
|
-
...statements.slice(idx)
|
|
96
|
+
...statements.slice(Math.max(idx, 0))
|
|
98
97
|
];
|
|
99
98
|
state.added.delete(idx);
|
|
100
99
|
}
|
package/src/manifest-index.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
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
|
-
}
|