@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 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.file.includes('doc/src')) {
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.file.includes('doc/src')) {
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.file.includes('doc/src')) {
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-rc.8",
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-rc.4",
28
- "tslib": "^2.4.1",
29
- "typescript": "^4.9.4"
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
- #index: TransformerIndex;
24
+ #resolver: TransformResolver;
26
25
 
27
- constructor(public source: ts.SourceFile, public factory: ts.NodeFactory, index: TransformerIndex) {
26
+ constructor(public source: ts.SourceFile, public factory: ts.NodeFactory, resolver: TransformResolver) {
28
27
  this.#imports = ImportUtil.collectImports(source);
29
- this.#index = index;
30
- this.#importName = index.getImportName(source.fileName);
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.#index.getFromImport(fileOrImport)) &&
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.#index.getFromImport(fileOrImport)))) {
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 = checker.getTypeAtLocation(el.name);
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.#index.getImportName(file);
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, checker: ts.TypeChecker): 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, checker)!,
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, checker: ts.TypeChecker): ts.SourceFile {
211
+ finalize(ret: ts.SourceFile): ts.SourceFile {
217
212
  ret = this.finalizeNewImports(ret) ?? ret;
218
- ret = this.finalizeImportExportExtension(ret, checker) ?? 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 { ManifestRoot } from '@travetto/manifest';
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(transformerFiles: string[], manifest: ManifestRoot): Promise<TransformerManager> {
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 = idx.getEntry(file)!;
28
- transformers.push(...getAllTransformers(await import(file), entry.module));
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(transformers, idx);
40
+ return new TransformerManager(manifestIndex, transformers);
33
41
  }
34
42
 
35
43
  #cached: ts.CustomTransformers | undefined;
36
44
  #transformers: NodeTransformer<TransformerState>[];
37
- #index: TransformerIndex;
45
+ #manifestIndex: ManifestIndex;
38
46
 
39
- constructor(transformers: NodeTransformer<TransformerState>[], index: TransformerIndex) {
47
+ constructor(manifestIndex: ManifestIndex, transformers: NodeTransformer<TransformerState>[]) {
40
48
  this.#transformers = transformers;
41
- this.#index = index;
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.#index, ctx.getCompilerOptions()),
58
+ (ctx, src) => new TransformerState(src, ctx.factory, checker, this.#manifestIndex),
51
59
  this.#transformers
52
60
  );
53
61
 
@@ -1,14 +1,14 @@
1
1
  /* eslint-disable no-bitwise */
2
2
  import ts from 'typescript';
3
3
 
4
- import { ManifestIndex, path } from '@travetto/manifest';
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, Checker } from './types';
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(checker: ts.TypeChecker, type: ts.Type, index: ManifestIndex): { category: Category, type: ts.Type } {
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') && !index.getFromSource(sourceFile)) {
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(checker: Checker, type: ts.Type, alias?: ts.Symbol): AnyType | undefined;
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: (checker, type) => undefined
105
+ build: (resolver, type) => undefined
106
106
  },
107
107
  undefined: {
108
- build: (checker, type) => ({ key: 'literal', name: 'undefined', ctor: undefined })
108
+ build: (resolver, type) => ({ key: 'literal', name: 'undefined', ctor: undefined })
109
109
  },
110
110
  void: {
111
- build: (checker, type) => ({ key: 'literal', name: 'void', ctor: undefined })
111
+ build: (resolver, type) => ({ key: 'literal', name: 'void', ctor: undefined })
112
112
  },
113
113
  tuple: {
114
- build: (checker, type) => ({ key: 'tuple', tsTupleTypes: checker.getAllTypeArguments(type), subTypes: [] })
114
+ build: (resolver, type) => ({ key: 'tuple', tsTupleTypes: resolver.getAllTypeArguments(type), subTypes: [] })
115
115
  },
116
116
  literal: {
117
- build: (checker, type) => {
117
+ build: (resolver, type) => {
118
118
  // Handle void/undefined
119
- const name = checker.getTypeAsString(type) ?? '';
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: checker.getAllTypeArguments(type)
140
+ tsTypeArguments: resolver.getAllTypeArguments(type)
141
141
  };
142
142
  }
143
143
  }
144
144
  },
145
145
  external: {
146
- build: (checker, type) => {
146
+ build: (resolver, type) => {
147
147
  const name = CoreUtil.getSymbol(type)?.getName();
148
- const importName = checker.getIndex().getImportName(type);
149
- const tsTypeArguments = checker.getAllTypeArguments(type);
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: (checker, uType: ts.UnionType) => {
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: (checker, type, alias?) => {
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 = checker.getIndex().getImportName(type);
184
- const tsTypeArguments = checker.getAllTypeArguments(type);
185
- for (const member of checker.getPropertiesOfType(type)) {
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 = checker.getType(dec);
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: (checker, type) => {
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 = checker.getIndex().getImportName(rawSourceFile);
223
+ importName = resolver.getFileImportName(rawSourceFile);
219
224
  } else {
220
225
  const base = path.dirname(rawSourceFile);
221
- importName = checker.getIndex().getImportName(path.resolve(base, importName));
226
+ importName = resolver.getFileImportName(path.resolve(base, importName));
222
227
  }
223
228
  }
224
229
  return { key: 'external', name, importName };
@@ -1,21 +1,23 @@
1
1
  import ts from 'typescript';
2
2
 
3
- import type { AnyType, Checker } from './types';
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 { TransformerIndex } from '../manifest-index';
9
+ import { DeclarationUtil } from '../util/declaration';
8
10
 
9
11
  /**
10
- * Type resolver
12
+ * Implementation of TransformResolver
11
13
  */
12
- export class TypeResolver implements Checker {
14
+ export class SimpleResolver implements TransformResolver {
13
15
  #tsChecker: ts.TypeChecker;
14
- #index: TransformerIndex;
16
+ #manifestIndex: ManifestIndex;
15
17
 
16
- constructor(tsChecker: ts.TypeChecker, idx: TransformerIndex) {
18
+ constructor(tsChecker: ts.TypeChecker, manifestIndex: ManifestIndex) {
17
19
  this.#tsChecker = tsChecker;
18
- this.#index = idx;
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
- getIndex(): TransformerIndex {
30
- return this.#index;
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.#tsChecker, resType, this.#index);
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('Unable to resolve type', err.stack);
159
+ console.error(`Unable to resolve type in ${importName}`, err.stack);
128
160
  return { key: 'literal', ctor: Object, name: 'object' };
129
161
  }
130
162
  }
@@ -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 Checker {
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 { TypeResolver } from './resolver/service';
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: TypeResolver;
34
+ #resolver: SimpleResolver;
36
35
  #imports: ImportManager;
37
- #index: TransformerIndex;
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, index: TransformerIndex, options: ts.CompilerOptions) {
46
- this.#index = index;
47
- this.#imports = new ImportManager(source, factory, index);
48
- this.#resolver = new TypeResolver(checker, index);
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.#index.getImportName(file);
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.#index.getImportName(src, true) : undefined;
167
- const file = this.#index.getFromImport(mod ?? '')?.output;
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, this.#resolver.getChecker());
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
- return this.isEsmOutput() ?
283
- this.createAccess('import', 'meta', 'url') :
284
- this.createIdentifier('__filename');
285
- }
286
-
287
- /**
288
- * Get the entry file identifier, supports both ESM and commonjs
289
- */
290
- getEntryFileIdentifier(): ts.Expression {
291
- return this.isEsmOutput() ?
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.getResolver().getPropertiesOfType(cls);
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)) {
@@ -1,4 +1,4 @@
1
- import type { default as ts } from 'typescript';
1
+ import type ts from 'typescript';
2
2
 
3
3
  /**
4
4
  * Param documentation
@@ -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
- for (const key of Object.keys(ox)) {
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 [k, all] of [...state.added].sort(([idxA], [idxB]) => idxB - idxA)) {
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
  }
@@ -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
- }