restfuncs-transformer 0.9.8 → 1.1.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.
@@ -0,0 +1,481 @@
1
+ import ts, {
2
+ ArrowFunction,
3
+ ClassDeclaration, Expression, FunctionTypeNode, Identifier,
4
+ MethodDeclaration,
5
+ Node, NodeArray,
6
+ ObjectLiteralExpression, ParameterDeclaration,
7
+ PropertyAccessExpression,
8
+ SyntaxKind, TypeNode, TypeReference, TypeReferenceNode
9
+ } from 'typescript';
10
+ import {FileTransformRun, TextPatch} from "./transformerUtil";
11
+ import {transformerVersion} from "./index";
12
+ import {visitReplace} from "restfuncs-common";
13
+
14
+
15
+ /**
16
+ * Transformer that adds the following statement to the method body (like in readme.md#how-it-works)
17
+ * <code><pre>
18
+ * static getRemoteMethodsMeta() {
19
+ * ....
20
+ * }
21
+ * </pre></code>
22
+ */
23
+ export class AddRemoteMethodsMeta extends FileTransformRun {
24
+ /**
25
+ * Should this transformer squeeze the added declarations (result) into one line to keep the line numbers in the source text intact ?
26
+ * Prevents the [broken source maps bug](https://github.com/bogeeee/restfuncs/issues/2)
27
+ */
28
+ static squeezeDeclarationsIntoOneLine = true;
29
+ diagnosis_currentClass_instanceMethodsSeen?: Set<string>;
30
+ currentClass_instanceMethodsMeta?: Record<string, string>; // The {...} that should be added below... see example in readme.md
31
+ result = new TextPatch();
32
+
33
+ /* Visitor Function */
34
+ visit(node: Node): Node {
35
+ if (node.kind === SyntaxKind.MethodDeclaration) { // @remote ?
36
+ const methodDeclaration = (node as MethodDeclaration)
37
+ if(this.getChilds(methodDeclaration).some(n => n.kind == SyntaxKind.StaticKeyword)) { // static ?
38
+ return node; // no special handling
39
+ }
40
+ if(this.getParent().kind !== SyntaxKind.ClassDeclaration) { // Method not under a class (i.e. an anonymous object ?}
41
+ return node; // no special handling
42
+ }
43
+
44
+ const methodName = (methodDeclaration.name as any).escapedText;
45
+ try {
46
+ let remoteDecorator = this.getChilds(methodDeclaration).find(d => d.kind === SyntaxKind.Decorator);
47
+ if (!remoteDecorator) { // no @remote found ?
48
+ return node; // no special handling
49
+ }
50
+
51
+ // Diagnosis: Check for overloads:
52
+ if(this.diagnosis_currentClass_instanceMethodsSeen?.has(methodName)) { // Seen twice ?
53
+ throw new Error(`@remote methods cannot have multiple/overloaded signatures: ${methodName}. Location: ${this.diag_sourceLocation(node)}`) // TODO: add diagnosis
54
+ }
55
+
56
+ this.currentClass_instanceMethodsMeta![methodName] = this.createMethodMetaExpression(methodDeclaration, methodName) // create the code:
57
+ }
58
+ finally {
59
+ this.diagnosis_currentClass_instanceMethodsSeen!.add(methodName);
60
+ }
61
+
62
+ return node;
63
+ }
64
+ else if(node.kind === SyntaxKind.ClassDeclaration) {
65
+ this.diagnosis_currentClass_instanceMethodsSeen = new Set()
66
+ this.currentClass_instanceMethodsMeta = {}
67
+ try {
68
+ let result = this.visitChilds(node) as ClassDeclaration
69
+
70
+ if(Object.keys(this.currentClass_instanceMethodsMeta).length > 0) { // Current class has @remote methods ?
71
+ if(this.result.patches.length === 0) { // First patch
72
+ this.result.patches.push({position: 0, contentToInsert: '/* inserted by restfuncs-transformer:*/import _rf_typia from "typia";'}); // add the type import, which is needed by the following
73
+ }
74
+
75
+ // *** Create the "getRemoteMethodsMeta()" function and add a patch for the source file to the result: ***
76
+ let methodDeclarationSourceText = this.create_static_getRemoteMethodsMeta_expression();
77
+ if(AddRemoteMethodsMeta.squeezeDeclarationsIntoOneLine) {
78
+ methodDeclarationSourceText = '/* code squeezed into one line to keep line numbers intact. You can output it prettier by setting "pretty":true in the plugin configuration in tsconfig.json */' + methodDeclarationSourceText.replaceAll("\n","");
79
+ }
80
+ methodDeclarationSourceText = ";" + methodDeclarationSourceText; // prepend with semicolon to prevent syntax error if there's stuff in the same line of the closing bracket
81
+
82
+ this.result.patches.push({position: node.end -1 /* insert before the closing bracket*/, contentToInsert: methodDeclarationSourceText}); // Add patch to result
83
+ }
84
+
85
+
86
+ return result
87
+ }
88
+ finally {
89
+ this.currentClass_instanceMethodsMeta = undefined
90
+ this.diagnosis_currentClass_instanceMethodsSeen = undefined
91
+ }
92
+ }
93
+ else {
94
+ return this.visitChilds(node);
95
+ }
96
+ }
97
+
98
+
99
+ /**
100
+ * Crates the following expression like in readme.md#how-it-works
101
+ * <code><pre>
102
+ * {
103
+ * arguments: {
104
+ * ...
105
+ * }
106
+ * result: {
107
+ * ...
108
+ * }
109
+ * callbacks:[...]
110
+ * jsDoc: {
111
+ * ...
112
+ * }
113
+ * }
114
+ * </pre></code>
115
+ *
116
+ * @param methodName
117
+ * @private
118
+ */
119
+ createMethodMetaExpression(node: MethodDeclaration, methodName: string): string {
120
+ // Note: These `factory.create...` "pyramids of doom", which you see all along in this method's code, were mostly created by copying the example code from readme.md#how-it-works through the [AST viewer tool](https://ts-ast-viewer.com/)
121
+ // TODO: remove them und use plain strings
122
+ const factory = this.context.factory;
123
+
124
+ const arguments_typiaFuncs:Record<string, string> = {
125
+ "validateEquals": "_rf_typia.validateEquals",
126
+ "validatePrune": "_rf_typia.misc.validatePrune"
127
+ }
128
+ const result_typiaFuncs = {...arguments_typiaFuncs};
129
+
130
+ const old_typiaFuncs:Record<string, PropertyAccessExpression> = {
131
+ /**
132
+ * typia.validateEquals
133
+ */
134
+ validateEquals: factory.createPropertyAccessExpression(
135
+ factory.createIdentifier("_rf_typia"),
136
+ factory.createIdentifier("validateEquals")
137
+ ),
138
+ "validatePrune": factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(
139
+ factory.createIdentifier("_rf_typia"),
140
+ factory.createIdentifier("misc")
141
+ ), factory.createIdentifier("validatePrune"))
142
+ }
143
+
144
+ // Clone parameters and convert all ParameterDeclarations to NamedTupleMembers (They look the same in the .ts code but are of a different SyntaxKind).
145
+ const methodParametersWithPlaceholders = structuredClone(node.parameters).map(paramDecl => {
146
+ return factory.createNamedTupleMember(
147
+ paramDecl.dotDotDotToken,
148
+ paramDecl.name as Identifier,
149
+ paramDecl.questionToken,
150
+ paramDecl.type || factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)
151
+ )
152
+ });
153
+
154
+ // ** Callbacks: **
155
+ // Replace the (arrow-) function declarations inside methodParametersWithPlaceholders with "_callback" placeholders and create the callbackDeclarations array which contains the
156
+ // declarations like in readme.md saying "[{ // here for the `someCallbackFn: (p:number) => Promise<string>` declaration ...}]"
157
+ let callbackDeclarations: ObjectLiteralExpression[] = [];
158
+ visitReplace(methodParametersWithPlaceholders, (value, visitChilds, context) => {
159
+ if (value && (value as Node).kind === SyntaxKind.FunctionType) { // found an arrow-style function type declaration ?
160
+ const functionTypeNode = value as FunctionTypeNode;
161
+ // **** Create the callbackDeclaration like `{ // here for the `someCallbackFn: (p:number) => Promise<string>` declaration...}` in readme.md ****
162
+ // Create argumentsDeclaration. `{ validateEquals: ..., validatePrune: ...}` like in readme.md
163
+ const argumentsDeclaration = factory.createObjectLiteralExpression(
164
+ Object.keys(arguments_typiaFuncs).map((typiaFnName) => // for validateEquals + validatePrune
165
+ factory.createPropertyAssignment(
166
+ factory.createIdentifier(typiaFnName),
167
+ factory.createArrowFunction(
168
+ undefined,
169
+ undefined,
170
+ [factory.createParameterDeclaration(
171
+ undefined,
172
+ undefined,
173
+ factory.createIdentifier("args"),
174
+ undefined,
175
+ factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
176
+ undefined
177
+ )],
178
+ undefined,
179
+ factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
180
+ factory.createCallExpression(old_typiaFuncs[typiaFnName],
181
+ [factory.createTupleTypeNode(structuredClone(functionTypeNode.parameters).map(paramDecl => { // Must convert ParameterDeclarations to NamedTupleMembers (They look the same in the .ts code but are of a different SyntaxKind).
182
+ return factory.createNamedTupleMember(
183
+ paramDecl.dotDotDotToken,
184
+ paramDecl.name as Identifier,
185
+ paramDecl.questionToken,
186
+ paramDecl.type || factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)
187
+ )
188
+ }))],
189
+ [factory.createIdentifier("args")]
190
+ )
191
+ )
192
+ )),
193
+ true
194
+ );
195
+ // Create awaitedResultDeclaration. `awaitedResult = ...` like in readme.md:
196
+ let awaitedResultDeclaration: Expression;
197
+ if (functionTypeNode.type.kind == SyntaxKind.VoidKeyword) { // Return type is (sync) void ?
198
+ awaitedResultDeclaration = factory.createIdentifier("undefined");
199
+ } else {
200
+ if (functionTypeNode.type.kind == SyntaxKind.TypeReference && ((functionTypeNode.type as TypeReferenceNode).typeName as any).escapedText == "Promise") { // Returns via Promise
201
+ // Validity check:
202
+ if (!(functionTypeNode.type as TypeReferenceNode).typeArguments || (functionTypeNode.type as TypeReferenceNode).typeArguments!.length != 1) {
203
+ throw new Error(`A callback function, declared in ${methodName}'s parameters, returns a Promise with an invalid number of type arguments. Location: ${this.diag_sourceLocation(node)}`);
204
+ }
205
+
206
+ const awaitedType: TypeReferenceNode = (functionTypeNode.type as TypeReferenceNode).typeArguments![0] as TypeReferenceNode;
207
+
208
+ awaitedResultDeclaration = factory.createObjectLiteralExpression(
209
+ Object.keys(result_typiaFuncs).map((typiaFnName) => // for validateEquals + validatePrune
210
+ factory.createPropertyAssignment(
211
+ factory.createIdentifier(typiaFnName),
212
+ factory.createArrowFunction(
213
+ undefined,
214
+ undefined,
215
+ [factory.createParameterDeclaration(
216
+ undefined,
217
+ undefined,
218
+ factory.createIdentifier("value"),
219
+ undefined,
220
+ factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
221
+ undefined
222
+ )],
223
+ undefined,
224
+ factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
225
+ factory.createCallExpression(old_typiaFuncs[typiaFnName],
226
+ [awaitedType],
227
+ [factory.createIdentifier("value")]
228
+ )
229
+ )
230
+ )),
231
+ true
232
+ );
233
+ } else {
234
+ throw new Error(`A callback function, declared in ${methodName}'s parameters, declares that it returns a value directly (sync) and not via Promise (this is not possible over the wire). Please use Promise<${this.nodeToString(functionTypeNode.type, true, true)}> instead. Location: ${this.diag_sourceLocation(functionTypeNode.type)}`);
235
+ }
236
+ }
237
+
238
+ // Compose result:
239
+ const callbackDeclaration = factory.createObjectLiteralExpression(
240
+ [
241
+ // arguments: {...}
242
+ factory.createPropertyAssignment(
243
+ factory.createIdentifier("arguments"),
244
+ argumentsDeclaration
245
+ ),
246
+
247
+ // awaitedResult: {...}
248
+ factory.createPropertyAssignment(
249
+ factory.createIdentifier("awaitedResult"),
250
+ awaitedResultDeclaration
251
+ ),
252
+ // source:
253
+ factory.createPropertyAssignment(
254
+ factory.createIdentifier("diagnosis_source"), this.createSourceNodeDiagnosisExpression(functionTypeNode)
255
+ )
256
+ ],
257
+ true
258
+ );
259
+
260
+ callbackDeclarations.push(callbackDeclaration);
261
+ const declarationIndex = callbackDeclarations.length -1;
262
+ return factory.createExpressionWithTypeArguments(factory.createIdentifier("CallbackPlaceholder"),[factory.createLiteralTypeNode(factory.createNumericLiteral(declarationIndex))]); // replace with CallbackPlaceholder<n>
263
+ }
264
+ return visitChilds(value, context)
265
+ });
266
+
267
+ // ** Obtain, what goes into typia.validate...<...> ***
268
+ const hasPlaceholders = callbackDeclarations.length > 0;
269
+ let typeParamForTypiaValidate = `[${node.parameters.map(param => this.nodeToString(param, true, true)).join(", ")}]`; // [param1: type1, param2: type2, ...]
270
+ let typeParamForTypiaValidateWithPlaceholders: string = this.nodeToString(factory.createTupleTypeNode(methodParametersWithPlaceholders),true, true);
271
+
272
+
273
+ if (node.parameters.some(p => p.type === undefined)) { // Some parameters don't have an explicit type ? (i.e. the type is inherited from the base class)
274
+ if (hasPlaceholders) {
275
+ throw new Error(`Parameter '${(node.parameters.find(p => p.type === undefined) as any)!.name.escapedText}' does not have a (/ an explicit) type in remote method ${methodName}. In combination with declared callback functions, all parameters must have explicit types. Location: ${this.diag_sourceLocation(node)}`) // TODO: add diagnosis
276
+ }
277
+ // Use the old style: Parameters<typeof this.prototype["method name"]>. This worked very fine in old versions but we now want to battle-test the other style
278
+ typeParamForTypiaValidate = typeParamForTypiaValidateWithPlaceholders = `Parameters<typeof this.prototype["${methodName}"]>`;
279
+ }
280
+
281
+ // ** compose result **
282
+ return `{
283
+ arguments: {${/* for validateEquals + validatePrune */ Object.keys(arguments_typiaFuncs).map((typiaFnName) => `
284
+ ${typiaFnName}: (args: unknown) => ${arguments_typiaFuncs[typiaFnName]}<${typeParamForTypiaValidate}>(args)`).join(",")}
285
+ ,
286
+ withPlaceholders: ${hasPlaceholders?`{${/* for validateEquals + validatePrune */ Object.keys(arguments_typiaFuncs).map((typiaFnName) => `
287
+ ${typiaFnName}: (args: unknown, onValidateCallbackArg: (placeholderId: string, declarationIndex: number) => boolean) => ${arguments_typiaFuncs[typiaFnName]}<${typeParamForTypiaValidateWithPlaceholders}>(args)`).join(",")}
288
+ }`:"undefined"}
289
+ },
290
+ result: {${/* for validateEquals + validatePrune */ Object.keys(result_typiaFuncs).map((typiaFnName) => `
291
+ ${typiaFnName}: (value: unknown) => ${result_typiaFuncs[typiaFnName]}<Awaited<ReturnType<typeof this.prototype["${methodName}"]>>>(value)`).join(",")}
292
+ },
293
+ callbacks: [${callbackDeclarations.map(cbd => `
294
+ ${this.nodeToString(cbd, true)}`).join(",")}
295
+ ],
296
+ jsDoc: ${this.nodeToString(this.createJsDocExpression(node, methodName), true)},
297
+ diagnosis_source: ${this.nodeToString(this.createSourceNodeDiagnosisExpression(node), true)},
298
+ }`;
299
+ }
300
+
301
+ /**
302
+ * Crates the following expression like in readme.md#how-it-works
303
+ * <code><pre>
304
+ * {
305
+ * comment: "...",
306
+ * params: {...},
307
+ * ...
308
+ * }
309
+ * </pre></code>
310
+ *
311
+ * or
312
+ *
313
+ * <code><pre>
314
+ * undefined
315
+ * </pre></code>
316
+ *
317
+ *
318
+ * @param methodName
319
+ * @private
320
+ */
321
+ private createJsDocExpression(node: MethodDeclaration, diagnosis_methodName: string) {
322
+ const factory = this.context.factory;
323
+
324
+ if(!node.jsDoc || node.jsDoc.length == 0 ) { // no jsdoc
325
+ return factory.createIdentifier("undefined");
326
+ }
327
+
328
+ const jsdoc = node.jsDoc[node.jsDoc.length - 1]; // use last jsDoc
329
+
330
+ const comment = jsdoc.comment || "";
331
+ if(typeof comment !== "string") {
332
+ throw new Error(`Expected comment to be a string. Got: ${comment}. Location: ${this.diag_sourceLocation(node)}`); // TODO add to diagnostics instead
333
+ }
334
+
335
+ // Collect params and tags
336
+ const params: Record<string, string> = {}
337
+ const tags: {name:string, comment?: string}[] = []
338
+ jsdoc.tags?.forEach(tag => {
339
+ const name = (tag.tagName.escapedText as string).toLowerCase()
340
+ const comment = tag.comment;
341
+ if(comment && typeof comment !== "string") {
342
+ throw new Error(`Expected comment to be a string. Got: ${comment}. Location: ${this.diag_sourceLocation(node)}` ); // TODO add to diagnostics instead
343
+ }
344
+
345
+ if(name === "param") {
346
+ const paramname = (tag as any).name?.escapedText;
347
+ if(paramname && comment) {
348
+ params[paramname] = comment;
349
+ }
350
+ }
351
+ else {
352
+ const tagName: Node | undefined = (tag as any).name;
353
+ tags.push({name, comment})
354
+ }
355
+ })
356
+
357
+ // JSDoc. Example from readme.md#how-it-works Copy&pasted through the [AST viewer tool](https://ts-ast-viewer.com/)
358
+ let jsDocExpression = factory.createObjectLiteralExpression(
359
+ [
360
+ factory.createPropertyAssignment(
361
+ factory.createIdentifier("comment"),
362
+ factory.createStringLiteral(comment)
363
+ ),
364
+ factory.createPropertyAssignment(
365
+ factory.createIdentifier("params"),
366
+ factory.createObjectLiteralExpression(
367
+ Object.keys(params).map(paramName =>
368
+ factory.createPropertyAssignment(
369
+ factory.createIdentifier(paramName),
370
+ factory.createStringLiteral(params[paramName])
371
+ )),
372
+ false
373
+ )
374
+ ),
375
+ factory.createPropertyAssignment(
376
+ factory.createIdentifier("tags"),
377
+ factory.createArrayLiteralExpression(
378
+ tags.map(tag => factory.createObjectLiteralExpression(
379
+ [
380
+ factory.createPropertyAssignment(
381
+ factory.createIdentifier("name"),
382
+ factory.createStringLiteral(tag.name)
383
+ ),
384
+ factory.createPropertyAssignment(
385
+ factory.createIdentifier("comment"),
386
+ tag.comment !== undefined?factory.createStringLiteral(tag.comment):factory.createIdentifier("undefined")
387
+ )
388
+ ],
389
+ false
390
+ )),
391
+ false
392
+ )
393
+ )
394
+ ],
395
+ true
396
+ )
397
+ return jsDocExpression;
398
+ }
399
+
400
+ /**
401
+ * Crates the following expression like in readme.md#how-it-works
402
+ * <code><pre>
403
+ * static getRemoteMethodsMeta(...) :... {
404
+ * ....
405
+ * }
406
+ * </pre></code>
407
+ *
408
+ * @param methodName
409
+ * @private
410
+ */
411
+ private create_static_getRemoteMethodsMeta_expression() : string {
412
+ return `` +
413
+ `static getRemoteMethodsMeta(): (typeof this.type_remoteMethodsMeta) {
414
+ this.__hello_developer__make_sure_your_class_is_a_subclass_of_ServerSession;` /* Attempt to give a friendly error message when this is not the case. Otherwise the previous line would fail and leaves the user wondering. */ + `
415
+ type CallbackPlaceholder<DECL_INDEX extends number> = string & _rf_typia.tags.TagBase<{kind: "callbackFn", target: "string", value: undefined, validate: \`onValidateCallbackArg($input, \${DECL_INDEX})\`}>;
416
+ const result= {
417
+ transformerVersion: {major: ${transformerVersion.major}, feature: ${transformerVersion.feature} },
418
+ instanceMethods: {${Object.keys(this.currentClass_instanceMethodsMeta!).map(methodName => `
419
+ ${methodName}: ${this.currentClass_instanceMethodsMeta![methodName]}`).join(",")}
420
+ }
421
+ };
422
+
423
+ return result; /* Code style note for this line: Why not do \`return {...}\` directly ? This tiny difference allows for extra properties which ensure backward compatibility with older "restfuncs-server" packages. */
424
+ }`
425
+ }
426
+
427
+ nodeToString(node: Node, removeComments= false, removeLineBreaks = false): string {
428
+ const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed, removeComments});
429
+ let result = printer.printNode(ts.EmitHint.Unspecified, node, this.sourceFile) as string;
430
+ if(removeLineBreaks) {
431
+ result = result.replaceAll(/\n\s*/g," ")
432
+ }
433
+ return result;
434
+ }
435
+
436
+ /**
437
+ * Creates a node, that declares a SourceNodeDiagnosis (defined in ServerSession.ts) object.
438
+ *
439
+ * To output the info at runtime: i.e. "MyServerSessionClass.ts:23:30, reading: async myRemoteMethod(param1:...): Promise<void> {}
440
+ * @param node
441
+ */
442
+ createSourceNodeDiagnosisExpression(node: Node) {
443
+ const factory = this.context.factory;
444
+ if((node as any).body !== undefined) {
445
+ // Empty the body
446
+ node = structuredClone(node);
447
+ (node as any).body = factory.createBlock([],false);
448
+ }
449
+
450
+ const lineAndChar = this.sourceFile.getLineAndCharacterOfPosition((node.getStart?.(this.sourceFile, false) || node.pos));
451
+
452
+
453
+ return factory.createObjectLiteralExpression(
454
+ [
455
+ factory.createPropertyAssignment(
456
+ factory.createIdentifier("file"),
457
+ factory.createStringLiteral(this.sourceFile.fileName)
458
+ ),
459
+ factory.createPropertyAssignment(
460
+ factory.createIdentifier("line"),
461
+ factory.createNumericLiteral(lineAndChar.line + 1)
462
+ ),
463
+ factory.createPropertyAssignment(
464
+ factory.createIdentifier("character"),
465
+ factory.createNumericLiteral(lineAndChar.character + 1)
466
+ ),
467
+ factory.createPropertyAssignment(
468
+ factory.createIdentifier("signatureText"),
469
+ factory.createStringLiteral(this.nodeToString(node, false))
470
+ )
471
+ ],
472
+ false
473
+ )
474
+
475
+ }
476
+
477
+ diag_sourceLocation(node: Node) {
478
+ const lineAndChar = this.sourceFile.getLineAndCharacterOfPosition((node.getStart?.(this.sourceFile, false) || node.pos));
479
+ return `${this.sourceFile.fileName}:${lineAndChar.line+1}:${lineAndChar.character+1}`
480
+ }
481
+ }
@@ -0,0 +1,3 @@
1
+ type paramsLikeInFn = [a: string, b: number, ...restOfArgs: string[]];
2
+ declare const f: paramsLikeInFn;
3
+ //# sourceMappingURL=devPlayground.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devPlayground.d.ts","sourceRoot":"","sources":["devPlayground.ts"],"names":[],"mappings":"AAAA,KAAK,cAAc,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;AAGtE,QAAA,MAAM,CAAC,EAAE,cAAiC,CAAC"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ const f = ["", 123, "", ""];
3
+ //# sourceMappingURL=devPlayground.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devPlayground.js","sourceRoot":"","sources":["devPlayground.ts"],"names":[],"mappings":";AAGA,MAAM,CAAC,GAAmB,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,EAAC,EAAE,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ type paramsLikeInFn = [a: string, b: number, ...restOfArgs: string[]];
2
+
3
+
4
+ const f: paramsLikeInFn = ["", 123, "",""];
5
+
package/index.d.ts CHANGED
@@ -1,3 +1,8 @@
1
- import transformer from "typescript-rtti/dist/transformer";
2
- export default transformer;
1
+ import { CompilerHost, Program } from 'typescript';
2
+ import { PluginConfig, ProgramTransformerExtras } from "ts-patch";
3
+ export declare const transformerVersion: {
4
+ major: number;
5
+ feature: number;
6
+ };
7
+ export default function transformProgram(program: Program, host: CompilerHost | undefined, config: PluginConfig, extras?: ProgramTransformerExtras): Program;
3
8
  //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAEA,OAAO,WAAW,MAAM,kCAAkC,CAAC;AAC3D,eAAe,WAAW,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAW,EAAC,YAAY,EAAmB,OAAO,EAAa,MAAM,YAAY,CAAC;AAClF,OAAO,EAAC,YAAY,EAAE,wBAAwB,EAAC,MAAM,UAAU,CAAC;AAOhE,eAAO,MAAM,kBAAkB;;;CAA2B,CAAA;AAwC1D,MAAM,CAAC,OAAO,UAAU,gBAAgB,CACpC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,YAAY,GAAG,SAAS,EAC9B,MAAM,EAAE,YAAY,EACpB,MAAM,CAAC,EAAE,wBAAwB,GAClC,OAAO,CAiDT"}
package/index.js CHANGED
@@ -1,33 +1,98 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
4
24
  };
5
25
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const __RΦ = { m: (k, v) => (t, ...a) => t && Reflect.metadata ? Reflect.metadata(k, v)(t, ...a) : void 0, f: (f, d, n) => (d.forEach(d => d(f)), Object.defineProperty(f, "name", { value: n, writable: false }), f), c: (c, d, dp, dsp, n) => (d.forEach(d => d(c)), dp.forEach(([p, d]) => d(c.prototype, p)), dsp.forEach(([p, d]) => d(c, p)), n ? Object.defineProperty(c, "name", { value: n, writable: false }) : undefined, c), r: (o, a) => (Object.assign(o, a)), a: id => {
7
- let t = __RΦ.t[id];
8
- if (t === void 0)
9
- return void 0;
10
- if (t.RΦ) {
11
- let r = t.RΦ;
12
- delete t.RΦ;
13
- __RΦ.r(t, r(t));
26
+ exports.transformerVersion = void 0;
27
+ const AddRemoteMethodsMeta_1 = require("./AddRemoteMethodsMeta");
28
+ const transformerUtil_1 = require("./transformerUtil");
29
+ const fs = __importStar(require("node:fs"));
30
+ // From: https://github.com/nonara/ts-patch/discussions/29#discussioncomment-325979
31
+ exports.transformerVersion = { major: 1, feature: 2 };
32
+ /* ****************************************************************************************************************** */
33
+ // region: Helpers
34
+ /* ****************************************************************************************************************** */
35
+ /**
36
+ * Patches existing Compiler Host (or creates new one) to allow feeding updated file content from cache
37
+ */
38
+ function getPatchedHost(maybeHost, tsInstance, compilerOptions) {
39
+ const fileCache = new Map();
40
+ const compilerHost = maybeHost !== null && maybeHost !== void 0 ? maybeHost : tsInstance.createCompilerHost(compilerOptions, true);
41
+ const originalGetSourceFile = compilerHost.getSourceFile;
42
+ return Object.assign(compilerHost, {
43
+ getSourceFile(fileName, languageVersion) {
44
+ fileName = tsInstance.normalizePath(fileName);
45
+ if (fileCache.has(fileName))
46
+ return fileCache.get(fileName);
47
+ const sourceFile = originalGetSourceFile.apply(void 0, Array.from(arguments));
48
+ fileCache.set(fileName, sourceFile);
49
+ return sourceFile;
50
+ },
51
+ fileCache
52
+ });
53
+ }
54
+ // endregion
55
+ /* ****************************************************************************************************************** */
56
+ // region: Program Transformer
57
+ /* ****************************************************************************************************************** */
58
+ function transformProgram(program, host, config, extras) {
59
+ if (!extras) {
60
+ throw new Error(`Please add the flag "transformProgram": true to the transformer inside tsconfig.json`);
61
+ }
62
+ const { ts: tsInstance } = extras;
63
+ const compilerOptions = program.getCompilerOptions();
64
+ const compilerHost = getPatchedHost(host, tsInstance, compilerOptions);
65
+ const rootFileNames = program.getRootFileNames().map(tsInstance.normalizePath);
66
+ const transformerFactoryOOP = new transformerUtil_1.TransformerFactoryOOP(AddRemoteMethodsMeta_1.AddRemoteMethodsMeta);
67
+ AddRemoteMethodsMeta_1.AddRemoteMethodsMeta.squeezeDeclarationsIntoOneLine = !(config.pretty);
68
+ // TODO: Do we still need all this transformer stuff or can we throw that out and use a simple file vistor ? Cause we don't actually **transform** a SourceFile anymore, but just patch text content and not an AST.
69
+ /* Transform AST */
70
+ tsInstance.transform(
71
+ /* sourceFiles */ program.getSourceFiles().filter(sourceFile => rootFileNames.includes(sourceFile.fileName) && !sourceFile.fileName.includes("after_restfuncs-transformer")),
72
+ /* transformerFactoryOOP */ [transformerFactoryOOP.asFunction], compilerOptions);
73
+ transformerFactoryOOP.transformRunsDone.forEach(transformRun => {
74
+ if (transformRun.result.patches.length > 0) { // wants to make changes to the file ?
75
+ /* Render modified files and create new SourceFiles for them to use in host's cache */
76
+ const sourceFile = transformRun.sourceFile;
77
+ const sourceText = fs.readFileSync(sourceFile.fileName, { encoding: "utf8" });
78
+ const patchedSourceText = transformRun.result.applyPatches(sourceText);
79
+ if (config.debug) {
80
+ // Write content to file so you can better inspect it.
81
+ const newFileName = sourceFile.fileName.replace(/\.ts$/, ".after_restfuncs-transformer.tmp");
82
+ if (newFileName === sourceFile.fileName) {
83
+ throw new Error("invalid file name");
84
+ }
85
+ fs.writeFileSync(newFileName, patchedSourceText, { encoding: "utf8" });
86
+ }
87
+ // Insert patchedSourceText into host's cache:
88
+ const newSourceFile = tsInstance.createSourceFile(sourceFile.fileName, patchedSourceText, sourceFile.languageVersion);
89
+ newSourceFile.version = `${sourceFile.version}_restfuncs_${exports.transformerVersion.major}.${exports.transformerVersion.feature}`;
90
+ compilerHost.fileCache.set(sourceFile.fileName, newSourceFile);
14
91
  }
15
- else if (t.LΦ) {
16
- let l = t.LΦ();
17
- delete t.LΦ;
18
- __RΦ.t[id] = t = l;
19
- }
20
- return t;
21
- }, oe: (o, k) => {
22
- try {
23
- return o[k];
24
- }
25
- catch (e) {
26
- return undefined;
27
- }
28
- }, t: {} };
29
- // Just pass-through the default export of typescript-rtti/dist/transformer
30
- // May be later, we need to extend the transformer for runtime inspection JSDoc information
31
- const transformer_1 = __importDefault(require("typescript-rtti/dist/transformer"));
32
- exports.default = transformer_1.default;
92
+ });
93
+ /* Re-create Program instance */
94
+ return tsInstance.createProgram(rootFileNames, compilerOptions, compilerHost);
95
+ }
96
+ exports.default = transformProgram;
97
+ // endregion
33
98
  //# sourceMappingURL=index.js.map
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2EAA2E;AAC3E,2FAA2F;AAC3F,mFAA2D;AAC3D,kBAAe,qBAAW,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,iEAA4D;AAC5D,uDAA0E;AAC1E,4CAA8B;AAE9B,mFAAmF;AAEtE,QAAA,kBAAkB,GAAG,EAAC,KAAK,EAAE,CAAC,EAAG,OAAO,EAAE,CAAC,EAAE,CAAA;AAE1D,wHAAwH;AACxH,kBAAkB;AAClB,wHAAwH;AAExH;;GAEG;AACH,SAAS,cAAc,CACnB,SAAmC,EACnC,UAAqB,EACrB,eAAgC;IAGhC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAE,CAAC;IAC5B,MAAM,YAAY,GAAG,SAAS,aAAT,SAAS,cAAT,SAAS,GAAI,UAAU,CAAC,kBAAkB,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;IACvF,MAAM,qBAAqB,GAAG,YAAY,CAAC,aAAa,CAAC;IAEzD,OAAO,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE;QAC/B,aAAa,CAAC,QAAgB,EAAE,eAAgC;YAC5D,QAAQ,GAAG,UAAU,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAC9C,IAAI,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;gBAAE,OAAO,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAE5D,MAAM,UAAU,GAAG,qBAAqB,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAQ,CAAC,CAAC;YACrF,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YAEpC,OAAO,UAAU,CAAC;QACtB,CAAC;QACD,SAAS;KACZ,CAAC,CAAC;AACP,CAAC;AAED,YAAY;AAGZ,wHAAwH;AACxH,8BAA8B;AAC9B,wHAAwH;AAExH,SAAwB,gBAAgB,CACpC,OAAgB,EAChB,IAA8B,EAC9B,MAAoB,EACpB,MAAiC;IAEjC,IAAG,CAAC,MAAM,EAAE;QACR,MAAM,IAAI,KAAK,CAAC,sFAAsF,CAAC,CAAC;KAC3G;IAED,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;IAClC,MAAM,eAAe,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IACrD,MAAM,YAAY,GAAG,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;IACvE,MAAM,aAAa,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IAE/E,MAAM,qBAAqB,GAAG,IAAI,uCAAqB,CAAC,2CAAoB,CAAC,CAAC;IAC9E,2CAAoB,CAAC,8BAA8B,GAAG,CAAC,CAAE,MAAc,CAAC,MAAM,CAAC,CAAC;IAEhF,oNAAoN;IAEpN,mBAAmB;IACnB,UAAU,CAAC,SAAS;IAChB,iBAAiB,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,6BAA6B,CAAC,CAAC;IAC5K,2BAA2B,CAAC,CAAE,qBAAqB,CAAC,UAAU,CAAE,EAChE,eAAe,CAClB,CAAA;IAED,qBAAqB,CAAC,iBAAiB,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE;QAC3D,IAAG,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,EAAE,sCAAsC;YAC/E,sFAAsF;YACtF,MAAM,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC;YAC3C,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAC,QAAQ,EAAE,MAAM,EAAC,CAAC,CAAC;YAC5E,MAAM,iBAAiB,GAAG,YAAY,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;YACvE,IAAI,MAAc,CAAC,KAAK,EAAE;gBACtB,sDAAsD;gBACtD,MAAM,WAAW,GAAG,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAC,kCAAkC,CAAC,CAAC;gBAC5F,IAAG,WAAW,KAAK,UAAU,CAAC,QAAQ,EAAE;oBACpC,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAA;iBACvC;gBACD,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,iBAAiB,EAAE,EAAC,QAAQ,EAAE,MAAM,EAAC,CAAC,CAAC;aACxE;YAGD,8CAA8C;YAC9C,MAAM,aAAa,GAAG,UAAU,CAAC,gBAAgB,CAAC,UAAU,CAAC,QAAQ,EAAE,iBAAiB,EAAE,UAAU,CAAC,eAAe,CAAC,CAAC;YACtH,aAAa,CAAC,OAAO,GAAG,GAAG,UAAU,CAAC,OAAO,cAAc,0BAAkB,CAAC,KAAK,IAAI,0BAAkB,CAAC,OAAO,EAAE,CAAC;YACpH,YAAY,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;SAClE;IACL,CAAC,CAAC,CAAA;IAIF,gCAAgC;IAChC,OAAO,UAAU,CAAC,aAAa,CAAC,aAAa,EAAE,eAAe,EAAE,YAAY,CAAC,CAAC;AAClF,CAAC;AAtDD,mCAsDC;AAED,YAAY"}