@travetto/transformer 6.0.0-rc.1 → 6.0.0-rc.3

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
@@ -33,12 +33,13 @@ import { OnProperty, TransformerState, OnMethod, OnClass } from '@travetto/trans
33
33
 
34
34
  export class MakeUpper {
35
35
 
36
+ static isValid(state: TransformerState): boolean {
37
+ return state.importName !== '@travetto/transformer/doc/upper.ts';
38
+ }
39
+
36
40
  @OnProperty()
37
41
  static handleProperty(state: TransformerState, node: ts.PropertyDeclaration): ts.PropertyDeclaration {
38
- if (!state.importName.startsWith('@travetto/transformer/doc/upper')) {
39
- return node;
40
- }
41
- return state.factory.updatePropertyDeclaration(
42
+ return !this.isValid(state) ? node : state.factory.updatePropertyDeclaration(
42
43
  node,
43
44
  node.modifiers,
44
45
  node.name.getText().toUpperCase(),
@@ -50,10 +51,7 @@ export class MakeUpper {
50
51
 
51
52
  @OnClass()
52
53
  static handleClass(state: TransformerState, node: ts.ClassDeclaration): ts.ClassDeclaration {
53
- if (!state.importName.startsWith('@travetto/transformer/doc/upper')) {
54
- return node;
55
- }
56
- return state.factory.updateClassDeclaration(
54
+ return !this.isValid(state) ? node : state.factory.updateClassDeclaration(
57
55
  node,
58
56
  node.modifiers,
59
57
  state.createIdentifier(node.name!.getText().toUpperCase()),
@@ -65,10 +63,7 @@ export class MakeUpper {
65
63
 
66
64
  @OnMethod()
67
65
  static handleMethod(state: TransformerState, node: ts.MethodDeclaration): ts.MethodDeclaration {
68
- if (!state.importName.startsWith('@travetto/transformer/doc/upper')) {
69
- return node;
70
- }
71
- return state.factory.updateMethodDeclaration(
66
+ return !this.isValid(state) ? node : state.factory.updateMethodDeclaration(
72
67
  node,
73
68
  node.modifiers,
74
69
  undefined,
package/__index__.ts CHANGED
@@ -1,15 +1,19 @@
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';
1
+ export * from './src/state.ts';
2
+ export * from './src/visitor.ts';
3
+ export * from './src/register.ts';
4
+ export * from './src/types/visitor.ts';
5
+ export * from './src/types/shared.ts';
6
+ export * from './src/manager.ts';
8
7
 
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';
8
+ export * from './src/util/core.ts';
9
+ export * from './src/util/declaration.ts';
10
+ export * from './src/util/decorator.ts';
11
+ export * from './src/util/doc.ts';
12
+ export * from './src/util/literal.ts';
13
+ export * from './src/util/log.ts';
14
+ export * from './src/util/system.ts';
15
+
16
+ export type {
17
+ AnyType, ForeignType, ManagedType, PointerType, LiteralType, ShapeType,
18
+ CompositionType, TupleType, UnknownType, TemplateType
19
+ } from './src/resolver/types.ts';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/transformer",
3
- "version": "6.0.0-rc.1",
3
+ "version": "6.0.0-rc.3",
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": "^6.0.0-rc.1",
27
+ "@travetto/manifest": "^6.0.0-rc.2",
28
28
  "tslib": "^2.8.1",
29
- "typescript": "^5.7.3"
29
+ "typescript": "^5.8.3"
30
30
  },
31
31
  "travetto": {
32
32
  "displayName": "Transformation",
package/src/importer.ts CHANGED
@@ -2,12 +2,12 @@ import ts from 'typescript';
2
2
 
3
3
  import { ManifestModuleUtil, PackageUtil, path } from '@travetto/manifest';
4
4
 
5
- import { AnyType, TransformResolver, ManagedType } from './resolver/types';
6
- import { ImportUtil } from './util/import';
7
- import { CoreUtil } from './util/core';
8
- import { Import } from './types/shared';
9
- import { LiteralUtil } from './util/literal';
10
- import { DeclarationUtil } from './util/declaration';
5
+ import { AnyType, TransformResolver, ManagedType } from './resolver/types.ts';
6
+ import { ImportUtil } from './util/import.ts';
7
+ import { CoreUtil } from './util/core.ts';
8
+ import { Import } from './types/shared.ts';
9
+ import { LiteralUtil } from './util/literal.ts';
10
+ import { DeclarationUtil } from './util/declaration.ts';
11
11
 
12
12
  const D_OR_D_TS_EXT_RE = /[.]d([.]ts)?$/;
13
13
 
@@ -34,19 +34,12 @@ export class ImportManager {
34
34
  this.factory = factory;
35
35
  }
36
36
 
37
- #rewriteModuleSpecifier(spec: ts.Expression | undefined): ts.Expression | undefined {
38
- if (spec && ts.isStringLiteral(spec) && this.isUntypedImport(spec)) {
39
- return LiteralUtil.fromLiteral(this.factory, `${spec.text.replace(/['"]/g, '')}.js`);
40
- }
41
- return spec;
42
- }
43
-
44
37
  #rewriteImportClause(spec: ts.Expression | undefined, clause: ts.ImportClause | undefined): ts.ImportClause | undefined {
45
38
  if (!(spec && clause?.namedBindings && ts.isNamedImports(clause.namedBindings))) {
46
39
  return clause;
47
40
  }
48
41
 
49
- if (spec && ts.isStringLiteral(spec) && !this.isKnownImport(spec)) {
42
+ if (spec && ts.isStringLiteral(spec) && !this.#isKnownImport(spec)) {
50
43
  return clause;
51
44
  }
52
45
 
@@ -80,7 +73,7 @@ export class ImportManager {
80
73
  * Is a known import an untyped file access
81
74
  * @param fileOrImport
82
75
  */
83
- isKnownImport(fileOrImport: ts.StringLiteral | string | undefined): boolean {
76
+ #isKnownImport(fileOrImport: ts.StringLiteral | string | undefined): boolean {
84
77
  if (fileOrImport && typeof fileOrImport !== 'string') {
85
78
  if (ts.isStringLiteral(fileOrImport)) {
86
79
  fileOrImport = fileOrImport.text.replace(/['"]g/, '');
@@ -95,13 +88,22 @@ export class ImportManager {
95
88
  }
96
89
 
97
90
  /**
98
- * Is a file or import an untyped file access
99
- * @param fileOrImport
91
+ * Normalize module specifier
100
92
  */
101
- isUntypedImport(fileOrImport: ts.StringLiteral | string | undefined): boolean {
102
- return this.isKnownImport(fileOrImport) &&
103
- !/[.](([mc]?[jt]s)|json)['"]?$/
104
- .test(typeof fileOrImport === 'string' ? fileOrImport : fileOrImport!.text);
93
+ normalizeModuleSpecifier<T extends ts.Expression | undefined>(spec: T): T {
94
+ if (spec && ts.isStringLiteral(spec) && this.#isKnownImport(spec.text)) {
95
+ const specText = spec.text.replace(/['"]/g, '');
96
+
97
+ const type = ManifestModuleUtil.getFileType(specText);
98
+ if (type === 'js' || type === 'ts') {
99
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
100
+ return LiteralUtil.fromLiteral(this.factory, ManifestModuleUtil.withOutputExtension(specText)) as unknown as T;
101
+ } else {
102
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
103
+ return LiteralUtil.fromLiteral(this.factory, `${specText}${ManifestModuleUtil.OUTPUT_EXT}`) as unknown as T;
104
+ }
105
+ }
106
+ return spec;
105
107
  }
106
108
 
107
109
  /**
@@ -126,7 +128,7 @@ export class ImportManager {
126
128
  importFile(file: string, name?: string): Import {
127
129
  file = this.#resolver.getFileImportName(file);
128
130
 
129
- if (file.endsWith('.ts') && !file.endsWith('.d.ts')) {
131
+ if (file.endsWith(ManifestModuleUtil.SOURCE_DEF_EXT) && !file.endsWith(ManifestModuleUtil.TYPINGS_EXT)) {
130
132
  file = ManifestModuleUtil.withOutputExtension(file);
131
133
  }
132
134
 
@@ -211,7 +213,7 @@ export class ImportManager {
211
213
  stmt.modifiers,
212
214
  stmt.isTypeOnly,
213
215
  stmt.exportClause,
214
- this.#rewriteModuleSpecifier(stmt.moduleSpecifier),
216
+ this.normalizeModuleSpecifier(stmt.moduleSpecifier),
215
217
  stmt.attributes
216
218
  ));
217
219
  }
@@ -221,7 +223,7 @@ export class ImportManager {
221
223
  stmt,
222
224
  stmt.modifiers,
223
225
  this.#rewriteImportClause(stmt.moduleSpecifier, stmt.importClause)!,
224
- this.#rewriteModuleSpecifier(stmt.moduleSpecifier)!,
226
+ this.normalizeModuleSpecifier(stmt.moduleSpecifier)!,
225
227
  stmt.attributes
226
228
  ));
227
229
  }
@@ -235,10 +237,10 @@ export class ImportManager {
235
237
  /**
236
238
  * Reset the imports into the source file
237
239
  */
238
- finalize(ret: ts.SourceFile): ts.SourceFile {
239
- ret = this.finalizeNewImports(ret) ?? ret;
240
- ret = this.finalizeImportExportExtension(ret) ?? ret;
241
- return ret;
240
+ finalize(src: ts.SourceFile): ts.SourceFile {
241
+ let node = this.finalizeNewImports(src) ?? src;
242
+ node = this.finalizeImportExportExtension(node) ?? node;
243
+ return node;
242
244
  }
243
245
 
244
246
  /**
package/src/manager.ts CHANGED
@@ -2,10 +2,10 @@ import ts from 'typescript';
2
2
 
3
3
  import { ManifestIndex, ManifestModuleUtil } from '@travetto/manifest';
4
4
 
5
- import { NodeTransformer } from './types/visitor';
6
- import { VisitorFactory } from './visitor';
7
- import { TransformerState } from './state';
8
- import { getAllTransformers } from './register';
5
+ import { NodeTransformer } from './types/visitor.ts';
6
+ import { VisitorFactory } from './visitor.ts';
7
+ import { TransformerState } from './state.ts';
8
+ import { getAllTransformers } from './register.ts';
9
9
 
10
10
  /**
11
11
  * Manages the typescript transformers
package/src/register.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import ts from 'typescript';
2
2
 
3
- import { DecoratorMeta, NodeTransformer, State, TransformPhase, TransformerType, Transformer, ModuleNameSymbol } from './types/visitor';
3
+ import { DecoratorMeta, NodeTransformer, State, TransformPhase, TransformerType, Transformer, ModuleNameSymbol } from './types/visitor.ts';
4
4
 
5
- const HandlersProp = Symbol.for('@travetto/transformer:handlers');
5
+ const HandlersSymbol = Symbol();
6
6
 
7
- type TransformerWithHandlers = Transformer & { [HandlersProp]?: NodeTransformer[] };
7
+ type TransformerWithHandlers = Transformer & { [HandlersSymbol]?: NodeTransformer[] };
8
8
 
9
9
  function isTransformer(x: unknown): x is Transformer {
10
10
  return x !== null && x !== undefined && typeof x === 'function';
@@ -14,13 +14,13 @@ function isTransformer(x: unknown): x is Transformer {
14
14
  * Get all transformers
15
15
  * @param obj Object to search for transformers
16
16
  */
17
- export function getAllTransformers(obj: Record<string, { [HandlersProp]?: NodeTransformer[] }>, module: string): NodeTransformer[] {
17
+ export function getAllTransformers(obj: Record<string, { [HandlersSymbol]?: NodeTransformer[] }>, module: string): NodeTransformer[] {
18
18
  return Object.values(obj)
19
19
  .flatMap(x => {
20
20
  if (isTransformer(x)) {
21
21
  x[ModuleNameSymbol] = module;
22
22
  }
23
- return (x[HandlersProp] ?? []);
23
+ return (x[HandlersSymbol] ?? []);
24
24
  })
25
25
  .map(handler => ({
26
26
  ...handler,
@@ -31,7 +31,7 @@ export function getAllTransformers(obj: Record<string, { [HandlersProp]?: NodeTr
31
31
 
32
32
  // Store handlers in class
33
33
  function storeHandler(cls: TransformerWithHandlers, fn: Function, phase: TransformPhase, type: TransformerType, target?: string[]): void {
34
- (cls[HandlersProp] ??= []).push({ key: fn.name, [phase]: fn.bind(cls), type, target });
34
+ (cls[HandlersSymbol] ??= []).push({ key: fn.name, [phase]: fn.bind(cls), type, target });
35
35
  }
36
36
 
37
37
  /**
@@ -133,6 +133,24 @@ export function OnClass(...target: string[]) {
133
133
  ): void => storeHandler(inst, d.value!, 'before', 'class', target);
134
134
  }
135
135
 
136
+ /**
137
+ * Listens for a `ts.TypeAliasDeclaration` on descent
138
+ */
139
+ export function OnTypeAlias(...target: string[]) {
140
+ return <S extends State = State, R extends ts.Node = ts.Node>(
141
+ inst: Transformer, __: unknown, d: TypedPropertyDescriptor<(state: S, node: ts.TypeAliasDeclaration) => R>
142
+ ): void => storeHandler(inst, d.value!, 'before', 'type', target);
143
+ }
144
+
145
+ /**
146
+ * Listens for a `ts.InterfaceDeclaration` on descent
147
+ */
148
+ export function OnInterface(...target: string[]) {
149
+ return <S extends State = State, R extends ts.Node = ts.Node>(
150
+ inst: Transformer, __: unknown, d: TypedPropertyDescriptor<(state: S, node: ts.InterfaceDeclaration) => R>
151
+ ): void => storeHandler(inst, d.value!, 'before', 'interface', target);
152
+ }
153
+
136
154
  /**
137
155
  * Listens for a `ts.CallExpression`, on ascent
138
156
  */
@@ -212,4 +230,23 @@ export function AfterClass(...target: string[]) {
212
230
  return <S extends State = State, R extends ts.Node = ts.Node>(
213
231
  inst: Transformer, __: unknown, d: TypedPropertyDescriptor<(state: S, node: ts.ClassDeclaration, dm?: DecoratorMeta) => R>
214
232
  ): void => storeHandler(inst, d.value!, 'after', 'class', target);
215
- }
233
+ }
234
+
235
+
236
+ /**
237
+ * Listens for a `ts.TypeAliasDeclaration` on ascent
238
+ */
239
+ export function AfterTypeAlias(...target: string[]) {
240
+ return <S extends State = State, R extends ts.Node = ts.Node>(
241
+ inst: Transformer, __: unknown, d: TypedPropertyDescriptor<(state: S, node: ts.TypeAliasDeclaration) => R>
242
+ ): void => storeHandler(inst, d.value!, 'after', 'type', target);
243
+ }
244
+
245
+ /**
246
+ * Listens for a `ts.InterfaceDeclaration` on ascent
247
+ */
248
+ export function AfterInterface(...target: string[]) {
249
+ return <S extends State = State, R extends ts.Node = ts.Node>(
250
+ inst: Transformer, __: unknown, d: TypedPropertyDescriptor<(state: S, node: ts.InterfaceDeclaration) => R>
251
+ ): void => storeHandler(inst, d.value!, 'after', 'interface', target);
252
+ }
@@ -1,18 +1,18 @@
1
1
  /* eslint-disable no-bitwise */
2
2
  import ts from 'typescript';
3
3
 
4
- import { path } from '@travetto/manifest';
4
+ import { path, ManifestModuleUtil } from '@travetto/manifest';
5
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';
10
- import { transformCast, TemplateLiteralPart } from '../types/shared';
6
+ import { DocUtil } from '../util/doc.ts';
7
+ import { CoreUtil } from '../util/core.ts';
8
+ import { DeclarationUtil } from '../util/declaration.ts';
9
+ import { LiteralUtil } from '../util/literal.ts';
10
+ import { transformCast, TemplateLiteralPart } from '../types/shared.ts';
11
11
 
12
- import { Type, AnyType, CompositionType, TransformResolver, TemplateType } from './types';
13
- import { CoerceUtil } from './coerce';
12
+ import { Type, AnyType, CompositionType, TransformResolver, TemplateType } from './types.ts';
13
+ import { CoerceUtil } from './coerce.ts';
14
14
 
15
- const TYPINGS_RE = /[.]d[.][cm]?ts$/;
15
+ const UNDEFINED = Symbol();
16
16
 
17
17
  /**
18
18
  * List of global types that can be parameterized
@@ -30,6 +30,7 @@ const GLOBAL_COMPLEX: Record<string, Function> = {
30
30
  /**
31
31
  * List of global types that are simple
32
32
  */
33
+ const UNDEFINED_GLOBAL = { undefined: 1, void: 1, null: 1 };
33
34
  const SIMPLE_NAMES: Record<string, string> = { String: 'string', Number: 'number', Boolean: 'boolean', Object: 'object' };
34
35
  const GLOBAL_SIMPLE: Record<string, Function> = {
35
36
  RegExp, Date, Number, Boolean, String, Function, Object, Error,
@@ -37,9 +38,8 @@ const GLOBAL_SIMPLE: Record<string, Function> = {
37
38
  };
38
39
 
39
40
  type Category =
40
- 'void' | 'undefined' | 'concrete' | 'unknown' |
41
41
  'tuple' | 'shape' | 'literal' | 'template' | 'managed' |
42
- 'composition' | 'foreign';
42
+ 'composition' | 'foreign' | 'concrete' | 'unknown';
43
43
 
44
44
  /**
45
45
  * Type categorizer, input for builder
@@ -48,21 +48,31 @@ export function TypeCategorize(resolver: TransformResolver, type: ts.Type): { ca
48
48
  const flags = type.getFlags();
49
49
  const objectFlags = DeclarationUtil.getObjectFlags(type) ?? 0;
50
50
 
51
- if (flags & ts.TypeFlags.Void) {
52
- return { category: 'void', type };
53
- } else if (flags & ts.TypeFlags.Undefined) {
54
- return { category: 'undefined', type };
55
- } else if (DocUtil.readDocTag(type, 'concrete').length) {
51
+ if (flags & (ts.TypeFlags.TemplateLiteral)) {
52
+ return { category: 'template', type };
53
+ } else if (flags & (
54
+ ts.TypeFlags.BigIntLike |
55
+ ts.TypeFlags.BooleanLike |
56
+ ts.TypeFlags.NumberLike |
57
+ ts.TypeFlags.StringLike |
58
+ ts.TypeFlags.Null |
59
+ ts.TypeFlags.Undefined |
60
+ ts.TypeFlags.Void
61
+ )) {
62
+ return { category: 'literal', type };
63
+ } else if (DocUtil.hasDocTag(type, 'concrete')) {
56
64
  return { category: 'concrete', type };
57
65
  } else if (flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never)) { // Any or unknown
58
66
  return { category: 'unknown', type };
59
67
  } else if (objectFlags & ts.ObjectFlags.Reference && !CoreUtil.getSymbol(type)) { // Tuple type?
60
68
  return { category: 'tuple', type };
69
+ } else if (type.isUnionOrIntersection()) {
70
+ return { category: 'composition', type };
61
71
  } else if (objectFlags & ts.ObjectFlags.Anonymous) {
62
72
  try {
63
73
  const source = DeclarationUtil.getPrimaryDeclarationNode(type).getSourceFile();
64
74
  const sourceFile = source.fileName;
65
- if (sourceFile && TYPINGS_RE.test(sourceFile) && !resolver.isKnownFile(sourceFile)) {
75
+ if (sourceFile && ManifestModuleUtil.TYPINGS_EXT_RE.test(sourceFile) && !resolver.isKnownFile(sourceFile)) {
66
76
  return { category: 'foreign', type };
67
77
  }
68
78
  } catch { }
@@ -72,7 +82,7 @@ export function TypeCategorize(resolver: TransformResolver, type: ts.Type): { ca
72
82
  if (CoreUtil.hasTarget(resolvedType)) {
73
83
  resolvedType = resolvedType.target;
74
84
  // If resolved target has a concrete type
75
- if (DocUtil.readDocTag(resolvedType, 'concrete').length) {
85
+ if (DocUtil.hasDocTag(resolvedType, 'concrete')) {
76
86
  return { category: 'concrete', type: resolvedType };
77
87
  }
78
88
  }
@@ -81,31 +91,21 @@ export function TypeCategorize(resolver: TransformResolver, type: ts.Type): { ca
81
91
  const sourceFile = source.fileName;
82
92
  if (sourceFile?.includes('typescript/lib')) {
83
93
  return { category: 'literal', type };
84
- } else if (sourceFile && TYPINGS_RE.test(sourceFile) && !resolver.isKnownFile(sourceFile)) {
94
+ } else if (sourceFile && ManifestModuleUtil.TYPINGS_EXT_RE.test(sourceFile) && !resolver.isKnownFile(sourceFile)) {
85
95
  return { category: 'foreign', type: resolvedType };
86
96
  } else if (!resolvedType.isClass()) { // Not a real type
87
97
  return { category: 'shape', type: resolvedType };
88
98
  } else {
89
99
  return { category: 'managed', type: resolvedType };
90
100
  }
91
- } else if (flags & (ts.TypeFlags.TemplateLiteral)) {
92
- return { category: 'template', type };
93
- } else if (flags & (
94
- ts.TypeFlags.BigIntLike |
95
- ts.TypeFlags.BooleanLike |
96
- ts.TypeFlags.NumberLike |
97
- ts.TypeFlags.StringLike |
98
- ts.TypeFlags.Void | ts.TypeFlags.Undefined
99
- )) {
100
- return { category: 'literal', type };
101
- } else if (type.isUnionOrIntersection()) {
102
- return { category: 'composition', type };
103
101
  } else if (objectFlags & ts.ObjectFlags.Tuple) {
104
102
  return { category: 'tuple', type };
105
103
  } else if (type.isLiteral()) {
106
104
  return { category: 'shape', type };
107
105
  } else if ((objectFlags & ts.ObjectFlags.Mapped)) { // Mapped types: Pick, Omit, Exclude, Retain
108
- return { category: 'shape', type };
106
+ if (type.getProperties().some(x => x.declarations || x.valueDeclaration)) {
107
+ return { category: 'shape', type };
108
+ }
109
109
  }
110
110
  return { category: 'literal', type };
111
111
  }
@@ -120,13 +120,10 @@ export const TypeBuilder: {
120
120
  }
121
121
  } = {
122
122
  unknown: {
123
- build: (resolver, type) => ({ key: 'unknown' })
124
- },
125
- undefined: {
126
- build: (resolver, type) => ({ key: 'literal', name: 'undefined', ctor: undefined })
127
- },
128
- void: {
129
- build: (resolver, type) => ({ key: 'literal', name: 'void', ctor: undefined })
123
+ build: (resolver, type) => {
124
+ const optional = UNDEFINED in type;
125
+ return { key: 'unknown', nullable: optional, undefinable: optional };
126
+ }
130
127
  },
131
128
  tuple: {
132
129
  build: (resolver, type) => ({ key: 'tuple', tsTupleTypes: resolver.getAllTypeArguments(type), subTypes: [] })
@@ -166,16 +163,18 @@ export const TypeBuilder: {
166
163
  const name = resolver.getTypeAsString(type) ?? '';
167
164
  const complexName = CoreUtil.getSymbol(type)?.getName() ?? '';
168
165
 
169
- if (name in GLOBAL_SIMPLE) {
166
+ if (name in UNDEFINED_GLOBAL) {
167
+ return { key: 'literal', ctor: undefined, name };
168
+ } else if (name in GLOBAL_SIMPLE) {
170
169
  const cons = GLOBAL_SIMPLE[name];
171
- const ret = LiteralUtil.isLiteralType(type) ? CoerceUtil.coerce(type.value, transformCast(cons), false) :
170
+ const literal = LiteralUtil.isLiteralType(type) ? CoerceUtil.coerce(type.value, transformCast(cons), false) :
172
171
  undefined;
173
172
 
174
173
  return {
175
174
  key: 'literal',
176
175
  ctor: cons,
177
176
  name: SIMPLE_NAMES[cons.name] ?? cons.name,
178
- value: ret
177
+ value: literal
179
178
  };
180
179
  } else if (complexName in GLOBAL_COMPLEX) {
181
180
  const cons = GLOBAL_COMPLEX[complexName];
@@ -225,6 +224,11 @@ export const TypeBuilder: {
225
224
  },
226
225
  finalize: (type: CompositionType) => {
227
226
  const { undefinable, nullable, subTypes } = type;
227
+
228
+ if (subTypes.length === 0) { // We have an unknown type?
229
+ return { key: 'unknown', nullable, undefinable };
230
+ }
231
+
228
232
  const [first] = subTypes;
229
233
 
230
234
  if (first.key === 'template') {
@@ -253,7 +257,7 @@ export const TypeBuilder: {
253
257
  const tsTypeArguments = resolver.getAllTypeArguments(type);
254
258
  const props = resolver.getPropertiesOfType(type);
255
259
  if (props.length === 0) {
256
- return { key: 'literal', name: 'Object', ctor: Object };
260
+ return { key: 'literal', name: 'Object', ctor: Object, importName };
257
261
  }
258
262
 
259
263
  for (const member of props) {
@@ -264,6 +268,9 @@ export const TypeBuilder: {
264
268
  !member.getName().includes('@') && // if not a symbol
265
269
  !memberType.getCallSignatures().length // if not a function
266
270
  ) {
271
+ if ((ts.isPropertySignature(dec) || ts.isPropertyDeclaration(dec)) && !!dec.questionToken) {
272
+ Object.defineProperty(memberType, UNDEFINED, { value: true });
273
+ }
267
274
  tsFieldTypes[member.getName()] = memberType;
268
275
  }
269
276
  }
@@ -273,26 +280,34 @@ export const TypeBuilder: {
273
280
  },
274
281
  concrete: {
275
282
  build: (resolver, type) => {
283
+ if (!DocUtil.hasDocTag(type, 'concrete')) {
284
+ return;
285
+ }
286
+
276
287
  const [tag] = DocUtil.readDocTag(type, 'concrete');
277
- if (tag) {
278
- // eslint-disable-next-line prefer-const
279
- let [importName, name] = tag.split('#');
288
+ let [importName, name] = tag?.split('#') ?? [];
280
289
 
281
- // Resolving relative to source file
282
- if (!importName || importName.startsWith('.')) {
283
- const rawSourceFile: string = DeclarationUtil.getDeclarations(type)
284
- ?.find(x => ts.getAllJSDocTags(x, (t): t is ts.JSDocTag => t.tagName.getText() === 'concrete').length)
285
- ?.getSourceFile().fileName ?? '';
290
+ // Resolving relative to source file
291
+ if (!importName || importName.startsWith('.')) {
292
+ const rawSourceFile: string = DeclarationUtil.getDeclarations(type)
293
+ ?.find(x => ts.getAllJSDocTags(x, (t): t is ts.JSDocTag => t.tagName.getText() === 'concrete').length)
294
+ ?.getSourceFile().fileName ?? '';
286
295
 
287
- if (!importName || importName === '.') {
288
- importName = resolver.getFileImportName(rawSourceFile);
289
- } else {
290
- const base = path.dirname(rawSourceFile);
291
- importName = resolver.getFileImportName(path.resolve(base, importName));
292
- }
296
+ if (!importName || importName === '.') {
297
+ importName = resolver.getFileImportName(rawSourceFile);
298
+ } else {
299
+ const base = path.dirname(rawSourceFile);
300
+ importName = resolver.getFileImportName(path.resolve(base, importName));
293
301
  }
294
- return { key: 'managed', name, importName };
295
302
  }
303
+
304
+ // Convert name to $Concrete suffix if not provided
305
+ if (!name) {
306
+ const [decl] = DeclarationUtil.getDeclarations(type).filter(x => ts.isInterfaceDeclaration(x) || ts.isTypeAliasDeclaration(x));
307
+ name = `${decl.name.text}$Concrete`;
308
+ }
309
+
310
+ return { key: 'managed', name, importName };
296
311
  }
297
312
  }
298
313
  };
@@ -1,5 +1,5 @@
1
1
  import ts from 'typescript';
2
- import type { AnyType } from './types';
2
+ import type { AnyType } from './types.ts';
3
3
 
4
4
  /**
5
5
  * Cache for handling recursive checks
@@ -50,19 +50,19 @@ export class CoerceUtil {
50
50
 
51
51
  switch (type) {
52
52
  case Date: {
53
- const res = typeof input === 'number' || /^[-]?\d+$/.test(`${input}`) ?
53
+ const value = typeof input === 'number' || /^[-]?\d+$/.test(`${input}`) ?
54
54
  new Date(parseInt(`${input}`, 10)) : new Date(`${input}`);
55
- if (strict && Number.isNaN(res.getTime())) {
55
+ if (strict && Number.isNaN(value.getTime())) {
56
56
  throw new Error(`Invalid date value: ${input}`);
57
57
  }
58
- return res;
58
+ return value;
59
59
  }
60
60
  case Number: {
61
- const res = `${input}`.includes('.') ? parseFloat(`${input}`) : parseInt(`${input}`, 10);
62
- if (strict && Number.isNaN(res)) {
61
+ const value = `${input}`.includes('.') ? parseFloat(`${input}`) : parseInt(`${input}`, 10);
62
+ if (strict && Number.isNaN(value)) {
63
63
  throw new Error(`Invalid numeric value: ${input}`);
64
64
  }
65
- return res;
65
+ return value;
66
66
  }
67
67
  case BigInt: {
68
68
  try {
@@ -2,12 +2,12 @@ import ts from 'typescript';
2
2
 
3
3
  import { path, ManifestIndex, ManifestModuleUtil, IndexedFile } from '@travetto/manifest';
4
4
 
5
- import type { AnyType, TransformResolver } from './types';
6
- import { TypeCategorize, TypeBuilder } from './builder';
7
- import { VisitCache } from './cache';
8
- import { DocUtil } from '../util/doc';
9
- import { DeclarationUtil } from '../util/declaration';
10
- import { transformCast } from '../types/shared';
5
+ import type { AnyType, TransformResolver } from './types.ts';
6
+ import { TypeCategorize, TypeBuilder } from './builder.ts';
7
+ import { VisitCache } from './cache.ts';
8
+ import { DocUtil } from '../util/doc.ts';
9
+ import { DeclarationUtil } from '../util/declaration.ts';
10
+ import { transformCast } from '../types/shared.ts';
11
11
 
12
12
  /**
13
13
  * Implementation of TransformResolver
@@ -38,10 +38,12 @@ export class SimpleResolver implements TransformResolver {
38
38
  const type = ManifestModuleUtil.getFileType(file);
39
39
 
40
40
  if (type !== 'js' && type !== 'ts') {
41
- sourceFile = `${sourceFile}.ts`;
41
+ sourceFile = `${sourceFile}${ManifestModuleUtil.SOURCE_DEF_EXT}`;
42
42
  }
43
43
 
44
- return this.#manifestIndex.getEntry(ManifestModuleUtil.getFileType(sourceFile) === 'ts' ? sourceFile : `${sourceFile}.js`) ??
44
+ const sourceType = ManifestModuleUtil.getFileType(sourceFile);
45
+
46
+ return this.#manifestIndex.getEntry((sourceType === 'ts' || sourceType === 'js') ? sourceFile : undefined!) ??
45
47
  this.#manifestIndex.getFromImport(ManifestModuleUtil.withoutSourceExtension(sourceFile).replace(/^.*node_modules\//, ''));
46
48
  }
47
49
 
@@ -132,7 +134,7 @@ export class SimpleResolver implements TransformResolver {
132
134
  result.comment = DocUtil.describeDocs(type).description;
133
135
 
134
136
  if ('tsTypeArguments' in result) {
135
- result.typeArguments = result.tsTypeArguments!.map((elType, i) => resolve(elType, type.aliasSymbol, depth + 1));
137
+ result.typeArguments = result.tsTypeArguments!.map((elType) => resolve(elType, type.aliasSymbol, depth + 1));
136
138
  delete result.tsTypeArguments;
137
139
  }
138
140
  if ('tsFieldTypes' in result) {
@@ -144,7 +146,7 @@ export class SimpleResolver implements TransformResolver {
144
146
  delete result.tsFieldTypes;
145
147
  }
146
148
  if ('tsSubTypes' in result) {
147
- result.subTypes = result.tsSubTypes!.map((elType, i) => resolve(elType, type.aliasSymbol, depth + 1));
149
+ result.subTypes = result.tsSubTypes!.map((elType) => resolve(elType, type.aliasSymbol, depth + 1));
148
150
  delete result.tsSubTypes;
149
151
  }
150
152
  if (finalize) {
@@ -1,5 +1,5 @@
1
1
  import type ts from 'typescript';
2
- import { TemplateLiteral } from '../types/shared';
2
+ import { TemplateLiteral } from '../types/shared.ts';
3
3
 
4
4
  /**
5
5
  * Base type for a simplistic type structure
package/src/state.ts CHANGED
@@ -2,18 +2,18 @@ import ts from 'typescript';
2
2
 
3
3
  import { path, ManifestIndex } from '@travetto/manifest';
4
4
 
5
- import { ManagedType, AnyType } from './resolver/types';
6
- import { State, DecoratorMeta, Transformer, ModuleNameSymbol } from './types/visitor';
7
- import { SimpleResolver } from './resolver/service';
8
- import { ImportManager } from './importer';
9
- import { Import } from './types/shared';
10
-
11
- import { DocUtil } from './util/doc';
12
- import { DecoratorUtil } from './util/decorator';
13
- import { DeclarationUtil } from './util/declaration';
14
- import { CoreUtil } from './util/core';
15
- import { LiteralUtil } from './util/literal';
16
- import { SystemUtil } from './util/system';
5
+ import { ManagedType, AnyType, ForeignType } from './resolver/types.ts';
6
+ import { State, DecoratorMeta, Transformer, ModuleNameSymbol } from './types/visitor.ts';
7
+ import { SimpleResolver } from './resolver/service.ts';
8
+ import { ImportManager } from './importer.ts';
9
+ import { Import } from './types/shared.ts';
10
+
11
+ import { DocUtil } from './util/doc.ts';
12
+ import { DecoratorUtil } from './util/decorator.ts';
13
+ import { DeclarationUtil } from './util/declaration.ts';
14
+ import { CoreUtil } from './util/core.ts';
15
+ import { LiteralUtil } from './util/literal.ts';
16
+ import { SystemUtil } from './util/system.ts';
17
17
 
18
18
  function hasOriginal(n: ts.Node): n is ts.Node & { original: ts.Node } {
19
19
  return !!n && !n.parent && 'original' in n && !!n.original;
@@ -49,17 +49,16 @@ export class TransformerState implements State {
49
49
  this.#resolver = new SimpleResolver(checker, manifestIndex);
50
50
  this.#imports = new ImportManager(source, factory, this.#resolver);
51
51
  this.file = path.toPosix(source.fileName);
52
- this.importName = this.#resolver.getFileImportName(this.file, true);
52
+ this.importName = this.#resolver.getFileImportName(this.file);
53
53
  this.source = source;
54
54
  this.factory = factory;
55
55
  }
56
56
 
57
57
  /**
58
- * Is a file or an import file path known by the framework, but missing an extensions
59
- * @param fileOrImport
58
+ * Rewrite module specifier normalizing output
60
59
  */
61
- isUntypedImport(fileOrImport: ts.StringLiteral | string | undefined): boolean {
62
- return this.#imports.isUntypedImport(fileOrImport);
60
+ normalizeModuleSpecifier<T extends ts.Expression | undefined>(spec: T): T {
61
+ return this.#imports.normalizeModuleSpecifier(spec);
63
62
  }
64
63
 
65
64
  /**
@@ -223,9 +222,8 @@ export class TransformerState implements State {
223
222
  /**
224
223
  * Finalize the source file for emission
225
224
  */
226
- finalize(ret: ts.SourceFile): ts.SourceFile {
227
- ret = this.#imports.finalize(ret);
228
- return ret;
225
+ finalize(source: ts.SourceFile): ts.SourceFile {
226
+ return this.#imports.finalize(source);
229
227
  }
230
228
 
231
229
  /**
@@ -295,6 +293,7 @@ export class TransformerState implements State {
295
293
  * @param module
296
294
  */
297
295
  findDecorator(mod: string | Transformer, node: ts.Node, name: string, module?: string): ts.Decorator | undefined {
296
+ module = module?.replace(/[.]ts$/, ''); // Replace extension if exists
298
297
  mod = typeof mod === 'string' ? mod : mod[ModuleNameSymbol]!;
299
298
  const target = `${mod}:${name}`;
300
299
  const list = this.getDecoratorList(node);
@@ -388,4 +387,39 @@ export class TransformerState implements State {
388
387
  getFileImportName(file: string): string {
389
388
  return this.#resolver.getFileImportName(file);
390
389
  }
390
+
391
+ /**
392
+ * Produce a foreign target type
393
+ */
394
+ getForeignTarget(ret: ForeignType): ts.Expression {
395
+ return this.fromLiteral({
396
+ Ⲑid: `${ret.source.split('node_modules/')[1]}+${ret.name}`
397
+ });
398
+ }
399
+
400
+ /**
401
+ * Return a concrete type the given type of a node
402
+ */
403
+ getConcreteType(node: ts.Node): ts.Expression {
404
+ const type = this.resolveType(node);
405
+
406
+ if (type.key === 'managed') {
407
+ return this.getOrImport(type);
408
+ } else if (type.key === 'foreign') {
409
+ return this.getForeignTarget(type);
410
+ } else {
411
+ const file = node.getSourceFile().fileName;
412
+ const src = this.getFileImportName(file);
413
+ throw new Error(`Unable to import non-external type: ${node.getText()} ${type.key}: ${src}`);
414
+ }
415
+ }
416
+
417
+ /**
418
+ * Get apparent type of requested field
419
+ */
420
+ getApparentTypeOfField(value: ts.Type, field: string): AnyType | undefined {
421
+ const checker = this.#resolver.getChecker();
422
+ const props = checker.getApparentType(value).getApparentProperties().find(x => x.escapedName === field);
423
+ return props ? this.resolveType(checker.getTypeOfSymbol(props)) : undefined;
424
+ }
391
425
  }
@@ -24,7 +24,7 @@ export type TransformPhase = 'before' | 'after';
24
24
 
25
25
  export type TransformerType =
26
26
  'class' | 'method' | 'property' | 'getter' | 'setter' | 'parameter' |
27
- 'static-method' | 'call' | 'function' | 'file';
27
+ 'static-method' | 'call' | 'function' | 'file' | 'type' | 'interface';
28
28
 
29
29
  export const ModuleNameSymbol = Symbol.for('@travetto/transformer:id');
30
30
 
@@ -1,5 +1,5 @@
1
1
  import ts from 'typescript';
2
- import { CoreUtil } from './core';
2
+ import { CoreUtil } from './core.ts';
3
3
 
4
4
  /**
5
5
  * Declaration utils
@@ -1,5 +1,5 @@
1
1
  import ts from 'typescript';
2
- import { CoreUtil } from './core';
2
+ import { CoreUtil } from './core.ts';
3
3
 
4
4
  /**
5
5
  * Utilities for dealing with decorators
package/src/util/doc.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import ts from 'typescript';
2
2
 
3
- import { transformCast, DeclDocumentation } from '../types/shared';
4
- import { CoreUtil } from './core';
5
- import { DeclarationUtil } from './declaration';
3
+ import { transformCast, DeclDocumentation } from '../types/shared.ts';
4
+ import { CoreUtil } from './core.ts';
5
+ import { DeclarationUtil } from './declaration.ts';
6
6
 
7
7
  /**
8
8
  * Utilities for dealing with docs
@@ -76,6 +76,15 @@ export class DocUtil {
76
76
  .map(el => el.text!.map(x => x.text).join('')); // Join all text
77
77
  }
78
78
 
79
+ /**
80
+ * Has JS Doc tags for a type
81
+ */
82
+ static hasDocTag(type: ts.Type | ts.Symbol, name: string): boolean {
83
+ const tags = CoreUtil.getSymbol(type)?.getJsDocTags() ?? [];
84
+ return tags.some(el => el.name === name);
85
+ }
86
+
87
+
79
88
  /**
80
89
  * Read augments information
81
90
  * @param type
@@ -2,7 +2,7 @@ import ts from 'typescript';
2
2
 
3
3
  import { path, ManifestModuleUtil, PackageUtil } from '@travetto/manifest';
4
4
 
5
- import { Import } from '../types/shared';
5
+ import { Import } from '../types/shared.ts';
6
6
 
7
7
  /**
8
8
  * Import utilities
@@ -1,6 +1,6 @@
1
1
  import ts from 'typescript';
2
2
 
3
- import { transformCast, TemplateLiteral } from '../types/shared';
3
+ import { transformCast, TemplateLiteral } from '../types/shared.ts';
4
4
 
5
5
  const TypedObject: {
6
6
  keys<T = unknown, K extends keyof T = keyof T>(o: T): K[];
@@ -39,7 +39,9 @@ export class LiteralUtil {
39
39
  static fromLiteral(factory: ts.NodeFactory, val: null): ts.NullLiteral;
40
40
  static fromLiteral(factory: ts.NodeFactory, val: object): ts.ObjectLiteralExpression;
41
41
  static fromLiteral(factory: ts.NodeFactory, val: unknown[]): ts.ArrayLiteralExpression;
42
- static fromLiteral(factory: ts.NodeFactory, val: string | boolean | number): ts.LiteralExpression;
42
+ static fromLiteral(factory: ts.NodeFactory, val: string): ts.StringLiteral;
43
+ static fromLiteral(factory: ts.NodeFactory, val: number): ts.NumericLiteral;
44
+ static fromLiteral(factory: ts.NodeFactory, val: boolean): ts.BooleanLiteral;
43
45
  static fromLiteral(factory: ts.NodeFactory, val: unknown): ts.Node {
44
46
  if (isNode(val)) { // If already a node
45
47
  return val;
@@ -137,14 +139,14 @@ export class LiteralUtil {
137
139
  * Extend object literal, whether JSON or ts.ObjectLiteralExpression
138
140
  */
139
141
  static extendObjectLiteral(factory: ts.NodeFactory, src: object | ts.Expression, ...rest: (object | ts.Expression)[]): ts.ObjectLiteralExpression {
140
- let ret = this.fromLiteral(factory, src);
142
+ let literal = this.fromLiteral(factory, src);
141
143
  if (rest.find(x => !!x)) {
142
- ret = factory.createObjectLiteralExpression([
143
- factory.createSpreadAssignment(ret),
144
+ literal = factory.createObjectLiteralExpression([
145
+ factory.createSpreadAssignment(literal),
144
146
  ...(rest.filter(x => !!x).map(r => factory.createSpreadAssignment(this.fromLiteral(factory, r))))
145
147
  ]);
146
148
  }
147
- return ret;
149
+ return literal;
148
150
  }
149
151
 
150
152
  /**
package/src/visitor.ts CHANGED
@@ -2,8 +2,8 @@ import ts from 'typescript';
2
2
 
3
3
  import { ManifestModuleFolderType, ManifestModuleUtil } from '@travetto/manifest';
4
4
 
5
- import { DecoratorMeta, TransformerType, NodeTransformer, TransformerSet, State, TransformPhase } from './types/visitor';
6
- import { CoreUtil } from './util/core';
5
+ import { DecoratorMeta, TransformerType, NodeTransformer, TransformerSet, State, TransformPhase } from './types/visitor.ts';
6
+ import { CoreUtil } from './util/core.ts';
7
7
 
8
8
  const COMPILER_SRC = new Set<ManifestModuleFolderType>(['support', 'src', '$index']);
9
9
 
@@ -35,6 +35,10 @@ export class VisitorFactory<S extends State = State> {
35
35
  return 'setter';
36
36
  } else if (ts.isSourceFile(node)) {
37
37
  return 'file';
38
+ } else if (ts.isInterfaceDeclaration(node)) {
39
+ return 'interface';
40
+ } else if (ts.isTypeAliasDeclaration(node)) {
41
+ return 'type';
38
42
  }
39
43
  }
40
44
 
@@ -58,7 +62,7 @@ export class VisitorFactory<S extends State = State> {
58
62
  this.#transformers.set(trn.type, {});
59
63
  }
60
64
  const set = this.#transformers.get(trn.type)!;
61
- const targets = trn.target && trn.target.length ? trn.target : ['ALL'];
65
+ const targets = trn.target && trn.target.length ? trn.target : ['__all__'];
62
66
 
63
67
  for (const target of targets) {
64
68
  for (const phase of ['before', 'after'] as const) {
@@ -96,13 +100,13 @@ export class VisitorFactory<S extends State = State> {
96
100
  return state.finalize(file);
97
101
  }
98
102
 
99
- let ret = this.visit(state, context, file);
103
+ let node = this.visit(state, context, file);
100
104
 
101
105
  // Process added content
102
106
  const changed = state.added.size;
103
- let statements: ts.NodeArray<ts.Statement> | ts.Statement[] = ret.statements;
107
+ let statements: ts.NodeArray<ts.Statement> | ts.Statement[] = node.statements;
104
108
  while (state.added.size) {
105
- for (const [idx, all] of [...state.added].sort(([idxA], [idxB]) => idxB - idxA)) {
109
+ for (const [idx, all] of [...state.added].toSorted(([idxA], [idxB]) => idxB - idxA)) {
106
110
  statements = [
107
111
  ...statements.slice(0, Math.max(idx, 0)),
108
112
  ...all.map(v => this.visit(state, context, v)),
@@ -113,9 +117,9 @@ export class VisitorFactory<S extends State = State> {
113
117
  }
114
118
 
115
119
  if (changed) {
116
- ret = CoreUtil.updateSource(context.factory, ret, statements);
120
+ node = CoreUtil.updateSource(context.factory, node, statements);
117
121
  }
118
- return state.finalize(ret);
122
+ return state.finalize(node);
119
123
  } catch (err) {
120
124
  if (!(err instanceof Error)) {
121
125
  throw err;
@@ -136,7 +140,7 @@ export class VisitorFactory<S extends State = State> {
136
140
  return;
137
141
  }
138
142
 
139
- for (const all of set[phase]!.get('ALL') ?? []) {
143
+ for (const all of set[phase]!.get('__all__') ?? []) {
140
144
  node = all[phase]!<T>(state, node) ?? node;
141
145
  }
142
146
  return node;