@travetto/transformer 3.1.4 → 3.2.0-rc.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/transformer",
3
- "version": "3.1.4",
3
+ "version": "3.2.0-rc.0",
4
4
  "description": "Functionality for AST transformations, with transformer registration, and general utils",
5
5
  "keywords": [
6
6
  "typescript",
@@ -24,7 +24,7 @@
24
24
  "directory": "module/transformer"
25
25
  },
26
26
  "dependencies": {
27
- "@travetto/manifest": "^3.1.1",
27
+ "@travetto/manifest": "^3.2.0-rc.0",
28
28
  "tslib": "^2.5.2",
29
29
  "typescript": "^5.0.4"
30
30
  },
@@ -7,8 +7,9 @@ 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
+ import { TemplateLiteralPart } from '../types/shared';
10
11
 
11
- import { Type, AnyType, UnionType, TransformResolver } from './types';
12
+ import { Type, AnyType, UnionType, TransformResolver, TemplateType } from './types';
12
13
  import { CoerceUtil } from './coerce';
13
14
 
14
15
  /**
@@ -33,7 +34,10 @@ const GLOBAL_SIMPLE: Record<string, Function> = {
33
34
  PromiseConstructor: Promise.constructor
34
35
  };
35
36
 
36
- type Category = 'void' | 'undefined' | 'concrete' | 'unknown' | 'tuple' | 'shape' | 'literal' | 'managed' | 'union' | 'foreign';
37
+ type Category =
38
+ 'void' | 'undefined' | 'concrete' | 'unknown' |
39
+ 'tuple' | 'shape' | 'literal' | 'template' | 'managed' |
40
+ 'union' | 'foreign';
37
41
 
38
42
  /**
39
43
  * Type categorizer, input for builder
@@ -82,6 +86,8 @@ export function TypeCategorize(resolver: TransformResolver, type: ts.Type): { ca
82
86
  } else {
83
87
  return { category: 'managed', type: resolvedType };
84
88
  }
89
+ } else if (flags & (ts.TypeFlags.TemplateLiteral)) {
90
+ return { category: 'template', type };
85
91
  } else if (flags & (
86
92
  ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral |
87
93
  ts.TypeFlags.Number | ts.TypeFlags.NumberLiteral |
@@ -120,6 +126,36 @@ export const TypeBuilder: {
120
126
  tuple: {
121
127
  build: (resolver, type) => ({ key: 'tuple', tsTupleTypes: resolver.getAllTypeArguments(type), subTypes: [] })
122
128
  },
129
+ template: {
130
+ build: (resolver, type) => {
131
+ // If we are have a template literal type, we need to make our own type node
132
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
133
+ if (type.flags & ts.TypeFlags.TemplateLiteral) {
134
+ const values: TemplateLiteralPart[] = [];
135
+ const texts = 'texts' in type && (typeof type.texts === 'object') && Array.isArray(type.texts) ? type.texts : undefined;
136
+ const types = 'types' in type && (typeof type.types === 'object') && Array.isArray(type.types) ? type.types : undefined;
137
+ if (texts?.length && types?.length) {
138
+ for (let i = 0; i < texts?.length; i += 1) {
139
+ if (texts[i] && texts[i] !== 'undefined') {
140
+ values.push(texts[i]);
141
+ }
142
+ if (types[i]) {
143
+ switch (types[i].intrinsicName) {
144
+ case 'number': values.push(Number); break;
145
+ case 'string': values.push(String); break;
146
+ case 'boolean': values.push(Boolean); break;
147
+ case 'undefined': values.push(''); break;
148
+ }
149
+ }
150
+ }
151
+ if (values.length > 0) {
152
+ return ({ key: 'template', template: { op: 'and', values }, ctor: String });
153
+ }
154
+ }
155
+ }
156
+ return { key: 'literal', ctor: Object };
157
+ }
158
+ },
123
159
  literal: {
124
160
  build: (resolver, type) => {
125
161
  // Handle void/undefined
@@ -182,7 +218,16 @@ export const TypeBuilder: {
182
218
  const { undefinable, nullable, subTypes } = type;
183
219
  const [first] = subTypes;
184
220
 
185
- if (subTypes.length === 1) {
221
+ if (first.key === 'template') {
222
+ return {
223
+ key: 'template',
224
+ ctor: String,
225
+ nullable: type.nullable,
226
+ undefinable: type.undefinable,
227
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
228
+ template: { op: 'or', values: subTypes.map(x => (x as TemplateType).template!) }
229
+ };
230
+ } else if (subTypes.length === 1) {
186
231
  return { undefinable, nullable, ...first };
187
232
  } else if (first.key === 'literal' && subTypes.every(el => el.name === first.name)) { // We have a common
188
233
  type.commonType = first;
@@ -1,4 +1,5 @@
1
1
  import type ts from 'typescript';
2
+ import { TemplateLiteral } from '../types/shared';
2
3
 
3
4
  /**
4
5
  * Base type for a simplistic type structure
@@ -99,6 +100,20 @@ export interface LiteralType extends Type<'literal'> {
99
100
  tsTypeArguments?: ts.Type[];
100
101
  }
101
102
 
103
+ /**
104
+ * A literal template type
105
+ */
106
+ export interface TemplateType extends Type<'template'> {
107
+ /**
108
+ * Pointer to real type (String)
109
+ */
110
+ ctor: Function;
111
+ /**
112
+ * Type arguments
113
+ */
114
+ template?: TemplateLiteral;
115
+ }
116
+
102
117
  /**
103
118
  * Union type
104
119
  */
@@ -161,7 +176,9 @@ export interface ForeignType extends Type<'foreign'> {
161
176
  */
162
177
  export interface UnknownType extends Type<'unknown'> { }
163
178
 
164
- export type AnyType = TupleType | ShapeType | UnionType | LiteralType | ManagedType | PointerType | UnknownType | ForeignType;
179
+ export type AnyType =
180
+ TupleType | ShapeType | UnionType | LiteralType |
181
+ ManagedType | PointerType | UnknownType | ForeignType | TemplateType;
165
182
 
166
183
  /**
167
184
  * Simple interface for checked methods
@@ -25,3 +25,8 @@ export type Import = {
25
25
  ident: ts.Identifier;
26
26
  stmt?: ts.ImportDeclaration;
27
27
  };
28
+
29
+
30
+ /** Template Literal Types */
31
+ export type TemplateLiteralPart = string | NumberConstructor | StringConstructor | BooleanConstructor;
32
+ export type TemplateLiteral = { op: 'and' | 'or', values: (TemplateLiteralPart | TemplateLiteral)[] };
@@ -1,5 +1,7 @@
1
1
  import ts from 'typescript';
2
2
 
3
+ import { TemplateLiteral } from '../types/shared';
4
+
3
5
  /**
4
6
  * Utilities for dealing with literals
5
7
  */
@@ -41,6 +43,8 @@ export class LiteralUtil {
41
43
  val = factory.createNumericLiteral(val);
42
44
  } else if (typeof val === 'boolean') {
43
45
  val = val ? factory.createTrue() : factory.createFalse();
46
+ } else if (val instanceof RegExp) {
47
+ val = factory.createRegularExpressionLiteral(`/${val.source}/${val.flags ?? ''}`);
44
48
  } else if (val === String || val === Number || val === Boolean || val === Date || val === RegExp) {
45
49
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
46
50
  val = factory.createIdentifier((val as Function).name);
@@ -144,4 +148,31 @@ export class LiteralUtil {
144
148
  }
145
149
  return undefined;
146
150
  }
151
+
152
+ /**
153
+ * Flatten a template literal into a regex
154
+ */
155
+ static templateLiteralToRegex(template: TemplateLiteral, exact = true): string {
156
+ const out: string[] = [];
157
+ for (const el of template.values) {
158
+ if (el === Number) {
159
+ out.push('\\d+');
160
+ } else if (el === Boolean) {
161
+ out.push('(?:true|false)');
162
+ } else if (el === String) {
163
+ out.push('.+');
164
+ } else if (typeof el === 'string' || typeof el === 'number' || typeof el === 'boolean') {
165
+ out.push(`${el}`);
166
+ } else {
167
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
168
+ out.push(`(?:${this.templateLiteralToRegex(el as TemplateLiteral, false)})`);
169
+ }
170
+ }
171
+ const body = out.join(template.op === 'and' ? '' : '|');
172
+ if (exact) {
173
+ return `^(?:${body})$`;
174
+ } else {
175
+ return body;
176
+ }
177
+ }
147
178
  }