restfuncs-transformer 1.0.0 → 1.1.1

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.
Files changed (38) hide show
  1. package/AddRemoteMethodsMeta.ts +244 -166
  2. package/devPlayground.ts +2 -12
  3. package/{AddRemoteMethodsMeta.d.ts → dist/commonjs/AddRemoteMethodsMeta.d.ts} +24 -6
  4. package/dist/commonjs/AddRemoteMethodsMeta.d.ts.map +1 -0
  5. package/dist/commonjs/AddRemoteMethodsMeta.js +348 -0
  6. package/dist/commonjs/AddRemoteMethodsMeta.js.map +1 -0
  7. package/dist/commonjs/devPlayground.d.ts +3 -0
  8. package/dist/commonjs/devPlayground.d.ts.map +1 -0
  9. package/dist/commonjs/devPlayground.js +3 -0
  10. package/dist/commonjs/devPlayground.js.map +1 -0
  11. package/dist/commonjs/index.d.ts.map +1 -0
  12. package/{index.js → dist/commonjs/index.js} +43 -7
  13. package/dist/commonjs/index.js.map +1 -0
  14. package/{transformerUtil.d.ts → dist/commonjs/transformerUtil.d.ts} +10 -0
  15. package/dist/commonjs/transformerUtil.d.ts.map +1 -0
  16. package/{transformerUtil.js → dist/commonjs/transformerUtil.js} +23 -1
  17. package/dist/commonjs/transformerUtil.js.map +1 -0
  18. package/dist/commonjs/transformerUtil.test.d.ts +2 -0
  19. package/dist/commonjs/transformerUtil.test.d.ts.map +1 -0
  20. package/dist/commonjs/transformerUtil.test.js +11 -0
  21. package/dist/commonjs/transformerUtil.test.js.map +1 -0
  22. package/index.ts +23 -7
  23. package/package.json +8 -4
  24. package/readme.md +37 -9
  25. package/transformerUtil.test.ts +12 -0
  26. package/transformerUtil.ts +23 -0
  27. package/AddRemoteMethodsMeta.d.ts.map +0 -1
  28. package/AddRemoteMethodsMeta.js +0 -221
  29. package/AddRemoteMethodsMeta.js.map +0 -1
  30. package/devPlayground.d.ts +0 -2
  31. package/devPlayground.d.ts.map +0 -1
  32. package/devPlayground.js +0 -31
  33. package/devPlayground.js.map +0 -1
  34. package/index.d.ts.map +0 -1
  35. package/index.js.map +0 -1
  36. package/transformerUtil.d.ts.map +0 -1
  37. package/transformerUtil.js.map +0 -1
  38. /package/{index.d.ts → dist/commonjs/index.d.ts} +0 -0
@@ -1,13 +1,15 @@
1
1
  import ts, {
2
- ClassDeclaration,
2
+ ArrowFunction,
3
+ ClassDeclaration, Expression, FunctionTypeNode, Identifier,
3
4
  MethodDeclaration,
4
- Node,
5
- ObjectLiteralExpression,
5
+ Node, NodeArray,
6
+ ObjectLiteralExpression, ParameterDeclaration,
6
7
  PropertyAccessExpression,
7
- SyntaxKind
8
+ SyntaxKind, TypeNode, TypeReference, TypeReferenceNode
8
9
  } from 'typescript';
9
- import {FileTransformRun} from "./transformerUtil";
10
+ import {FileTransformRun, TextPatch} from "./transformerUtil";
10
11
  import {transformerVersion} from "./index";
12
+ import {visitReplace} from "restfuncs-common";
11
13
 
12
14
 
13
15
  /**
@@ -19,32 +21,36 @@ import {transformerVersion} from "./index";
19
21
  * </pre></code>
20
22
  */
21
23
  export class AddRemoteMethodsMeta extends FileTransformRun {
22
-
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;
23
29
  diagnosis_currentClass_instanceMethodsSeen?: Set<string>;
24
- currentClass_instanceMethodsMeta?: Record<string, ObjectLiteralExpression>; // The {...} that should be added below... see example in readme.md
25
- astWasModified = false;
30
+ currentClass_instanceMethodsMeta?: Record<string, string>; // The {...} that should be added below... see example in readme.md
31
+ result = new TextPatch();
26
32
 
27
33
  /* Visitor Function */
28
34
  visit(node: Node): Node {
29
35
  if (node.kind === SyntaxKind.MethodDeclaration) { // @remote ?
30
36
  const methodDeclaration = (node as MethodDeclaration)
31
37
  if(this.getChilds(methodDeclaration).some(n => n.kind == SyntaxKind.StaticKeyword)) { // static ?
32
- return node;
38
+ return node; // no special handling
33
39
  }
34
40
  if(this.getParent().kind !== SyntaxKind.ClassDeclaration) { // Method not under a class (i.e. an anonymous object ?}
35
- return node;
41
+ return node; // no special handling
36
42
  }
37
43
 
38
44
  const methodName = (methodDeclaration.name as any).escapedText;
39
45
  try {
40
46
  let remoteDecorator = this.getChilds(methodDeclaration).find(d => d.kind === SyntaxKind.Decorator);
41
47
  if (!remoteDecorator) { // no @remote found ?
42
- return node;
48
+ return node; // no special handling
43
49
  }
44
50
 
45
51
  // Diagnosis: Check for overloads:
46
52
  if(this.diagnosis_currentClass_instanceMethodsSeen?.has(methodName)) { // Seen twice ?
47
- throw new Error(`@remote methods cannot have multiple/overloaded signatures: ${methodName}`) // TODO: add diagnosis
53
+ throw new Error(`@remote methods cannot have multiple/overloaded signatures: ${methodName}. Location: ${this.diag_sourceLocation(node)}`) // TODO: add diagnosis
48
54
  }
49
55
 
50
56
  this.currentClass_instanceMethodsMeta![methodName] = this.createMethodMetaExpression(methodDeclaration, methodName) // create the code:
@@ -62,10 +68,18 @@ export class AddRemoteMethodsMeta extends FileTransformRun {
62
68
  let result = this.visitChilds(node) as ClassDeclaration
63
69
 
64
70
  if(Object.keys(this.currentClass_instanceMethodsMeta).length > 0) { // Current class has @remote methods ?
65
- // add "static getRemoteMethodsMeta() {...}" method:
66
- // @ts-ignore yes, a bit hacky, but with factory.crateClassDeclation we might also miss some properties
67
- result.members = this.context.factory.createNodeArray([...result.members.values(), this.create_static_getRemoteMethodsMeta_expression()])
68
- this.astWasModified = true;
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
69
83
  }
70
84
 
71
85
 
@@ -89,6 +103,10 @@ export class AddRemoteMethodsMeta extends FileTransformRun {
89
103
  * arguments: {
90
104
  * ...
91
105
  * }
106
+ * result: {
107
+ * ...
108
+ * }
109
+ * callbacks:[...]
92
110
  * jsDoc: {
93
111
  * ...
94
112
  * }
@@ -98,33 +116,52 @@ export class AddRemoteMethodsMeta extends FileTransformRun {
98
116
  * @param methodName
99
117
  * @private
100
118
  */
101
- createMethodMetaExpression(node: MethodDeclaration, methodName: string) {
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
102
122
  const factory = this.context.factory;
103
123
 
104
- const arguments_typiaFuncs:Record<string, PropertyAccessExpression> = {
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> = {
105
131
  /**
106
132
  * typia.validateEquals
107
133
  */
108
134
  validateEquals: factory.createPropertyAccessExpression(
109
- factory.createIdentifier("typia"),
135
+ factory.createIdentifier("_rf_typia"),
110
136
  factory.createIdentifier("validateEquals")
111
137
  ),
112
138
  "validatePrune": factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(
113
- factory.createIdentifier("typia"),
139
+ factory.createIdentifier("_rf_typia"),
114
140
  factory.createIdentifier("misc")
115
141
  ), factory.createIdentifier("validatePrune"))
116
142
  }
117
- const result_typiaFuncs = {...arguments_typiaFuncs};
118
143
 
119
- // Example from readme.md#how-it-works Copy&pasted through the [AST viewer tool](https://ts-ast-viewer.com/)
120
- // @ts-ignore
121
- return factory.createObjectLiteralExpression(
122
- [
123
- // arguments: {...}
124
- factory.createPropertyAssignment(
125
- factory.createIdentifier("arguments"),
126
- factory.createObjectLiteralExpression(
127
- Object.keys(arguments_typiaFuncs).map((typiaFnName) =>
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
128
165
  factory.createPropertyAssignment(
129
166
  factory.createIdentifier(typiaFnName),
130
167
  factory.createArrowFunction(
@@ -140,76 +177,125 @@ export class AddRemoteMethodsMeta extends FileTransformRun {
140
177
  )],
141
178
  undefined,
142
179
  factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
143
- factory.createCallExpression(arguments_typiaFuncs[typiaFnName],
144
- [factory.createTypeReferenceNode(
145
- factory.createIdentifier("Parameters"),
146
- [factory.createIndexedAccessTypeNode(
147
- factory.createTypeQueryNode(
148
- factory.createQualifiedName(
149
- factory.createIdentifier("this"),
150
- factory.createIdentifier("prototype")
151
- ),
152
- undefined
153
- ),
154
- factory.createLiteralTypeNode(factory.createStringLiteral(methodName))
155
- )]
156
- )],
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
+ }))],
157
189
  [factory.createIdentifier("args")]
158
190
  )
159
191
  )
160
192
  )),
161
- true
162
- )
163
- ),
164
- // result: {...}
165
- factory.createPropertyAssignment(
166
- factory.createIdentifier("result"),
167
- factory.createObjectLiteralExpression(
168
- Object.keys(result_typiaFuncs).map((typiaFnName) =>
169
- factory.createPropertyAssignment(
170
- factory.createIdentifier(typiaFnName),
171
- factory.createArrowFunction(
172
- undefined,
173
- undefined,
174
- [factory.createParameterDeclaration(
175
- undefined,
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(
176
213
  undefined,
177
- factory.createIdentifier("value"),
178
214
  undefined,
179
- factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
180
- undefined
181
- )],
182
- undefined,
183
- factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
184
- factory.createCallExpression(arguments_typiaFuncs[typiaFnName],
185
- [factory.createTypeReferenceNode(
186
- factory.createIdentifier("Awaited"), [factory.createTypeReferenceNode(
187
- factory.createIdentifier("ReturnType"),
188
- [factory.createIndexedAccessTypeNode(
189
- factory.createTypeQueryNode(
190
- factory.createQualifiedName(
191
- factory.createIdentifier("this"),
192
- factory.createIdentifier("prototype")
193
- ),
194
- undefined
195
- ),
196
- factory.createLiteralTypeNode(factory.createStringLiteral(methodName))
197
- )]
198
- )]
215
+ [factory.createParameterDeclaration(
216
+ undefined,
217
+ undefined,
218
+ factory.createIdentifier("value"),
219
+ undefined,
220
+ factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
221
+ undefined
199
222
  )],
200
- [factory.createIdentifier("value")]
223
+ undefined,
224
+ factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
225
+ factory.createCallExpression(old_typiaFuncs[typiaFnName],
226
+ [awaitedType],
227
+ [factory.createIdentifier("value")]
228
+ )
201
229
  )
202
- )
203
- )),
204
- true
205
- )
206
- ),
207
- factory.createPropertyAssignment(
208
- factory.createIdentifier("jsDoc"), this.createJsDocExpression(node, methodName)
209
- )
210
- ],
211
- true
212
- );
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
+ }`;
213
299
  }
214
300
 
215
301
  /**
@@ -243,7 +329,7 @@ export class AddRemoteMethodsMeta extends FileTransformRun {
243
329
 
244
330
  const comment = jsdoc.comment || "";
245
331
  if(typeof comment !== "string") {
246
- throw new Error(`Expected comment to be a string. Got: ${comment}` ); // TODO add to diagnostics instead
332
+ throw new Error(`Expected comment to be a string. Got: ${comment}. Location: ${this.diag_sourceLocation(node)}`); // TODO add to diagnostics instead
247
333
  }
248
334
 
249
335
  // Collect params and tags
@@ -253,7 +339,7 @@ export class AddRemoteMethodsMeta extends FileTransformRun {
253
339
  const name = (tag.tagName.escapedText as string).toLowerCase()
254
340
  const comment = tag.comment;
255
341
  if(comment && typeof comment !== "string") {
256
- throw new Error(`Expected comment to be a string. Got: ${comment}` ); // TODO add to diagnostics instead
342
+ throw new Error(`Expected comment to be a string. Got: ${comment}. Location: ${this.diag_sourceLocation(node)}` ); // TODO add to diagnostics instead
257
343
  }
258
344
 
259
345
  if(name === "param") {
@@ -314,7 +400,7 @@ export class AddRemoteMethodsMeta extends FileTransformRun {
314
400
  /**
315
401
  * Crates the following expression like in readme.md#how-it-works
316
402
  * <code><pre>
317
- * static getRemoteMethodsMeta() {
403
+ * static getRemoteMethodsMeta(...) :... {
318
404
  * ....
319
405
  * }
320
406
  * </pre></code>
@@ -322,82 +408,74 @@ export class AddRemoteMethodsMeta extends FileTransformRun {
322
408
  * @param methodName
323
409
  * @private
324
410
  */
325
- private create_static_getRemoteMethodsMeta_expression() : MethodDeclaration {
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) {
326
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
+ }
327
449
 
450
+ const lineAndChar = this.sourceFile.getLineAndCharacterOfPosition((node.getStart?.(this.sourceFile, false) || node.pos));
328
451
 
329
- let instanceMethods = factory.createObjectLiteralExpression(
330
- Object.keys(this.currentClass_instanceMethodsMeta!).map(methodName => factory.createPropertyAssignment(
331
- factory.createStringLiteral(methodName), this.currentClass_instanceMethodsMeta![methodName]
332
- )),
333
- true
334
- );
335
-
336
- // Example from readme.md#how-it-works Copy&pasted through the [AST viewer tool](https://ts-ast-viewer.com/)
337
- return factory.createMethodDeclaration(
338
- [factory.createToken(ts.SyntaxKind.StaticKeyword)],
339
- undefined,
340
- factory.createIdentifier("getRemoteMethodsMeta"),
341
- undefined,
342
- undefined,
343
- [],
344
- factory.createParenthesizedType(factory.createTypeQueryNode(
345
- factory.createQualifiedName(
346
- factory.createIdentifier("this"),
347
- factory.createIdentifier("type_remoteMethodsMeta")
452
+
453
+ return factory.createObjectLiteralExpression(
454
+ [
455
+ factory.createPropertyAssignment(
456
+ factory.createIdentifier("file"),
457
+ factory.createStringLiteral(this.sourceFile.fileName)
348
458
  ),
349
- undefined
350
- )),
351
- factory.createBlock(
352
- [
353
- factory.createExpressionStatement(factory.createPropertyAccessExpression(
354
- factory.createThis(),
355
- factory.createIdentifier("__hello_developer__make_sure_your_class_is_a_subclass_of_ServerSession")
356
- )),
357
- factory.createVariableStatement(
358
- undefined,
359
- factory.createVariableDeclarationList(
360
- [factory.createVariableDeclaration(
361
- factory.createIdentifier("typia"),
362
- undefined,
363
- undefined,
364
- factory.createPropertyAccessExpression(
365
- factory.createThis(),
366
- factory.createIdentifier("typiaRuntime")
367
- )
368
- )],
369
- ts.NodeFlags.Let
370
- )
371
- ),
372
- factory.createReturnStatement(factory.createObjectLiteralExpression(
373
- [
374
- factory.createPropertyAssignment(
375
- factory.createIdentifier("transformerVersion"),
376
- factory.createObjectLiteralExpression(
377
- [
378
- factory.createPropertyAssignment(
379
- factory.createIdentifier("major"),
380
- factory.createIdentifier(`${transformerVersion.major}`)
381
- ),
382
- factory.createPropertyAssignment(
383
- factory.createIdentifier("feature"),
384
- factory.createIdentifier(`${transformerVersion.feature}`)
385
- )
386
- ],
387
- false
388
- )
389
- ),
390
- factory.createPropertyAssignment(
391
- factory.createIdentifier("instanceMethods"),
392
- instanceMethods
393
- )
394
- ],
395
- true
396
- ))
397
- ],
398
- true
399
- )
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
400
473
  )
401
474
 
402
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
+ }
403
481
  }
package/devPlayground.ts CHANGED
@@ -1,15 +1,5 @@
1
- import * as ts from 'typescript';
2
- import {ScriptTarget} from 'typescript';
3
- import ScriptKind = ts.ScriptKind;
1
+ type paramsLikeInFn = [a: string, b: number, ...restOfArgs: string[]];
4
2
 
5
3
 
6
- let sourceFile = ts.createSourceFile("dev/some.ts","const i:string = 'myStr';", ScriptTarget.ES2020,false, ScriptKind.JS);
4
+ const f: paramsLikeInFn = ["", 123, "",""];
7
5
 
8
-
9
-
10
- type X = {
11
-
12
- }
13
-
14
-
15
- let i: Awaited<X> | undefined = undefined
@@ -1,5 +1,5 @@
1
- import ts, { MethodDeclaration, Node, ObjectLiteralExpression } from 'typescript';
2
- import { FileTransformRun } from "./transformerUtil";
1
+ import ts, { MethodDeclaration, Node } from 'typescript';
2
+ import { FileTransformRun, TextPatch } from "./transformerUtil";
3
3
  /**
4
4
  * Transformer that adds the following statement to the method body (like in readme.md#how-it-works)
5
5
  * <code><pre>
@@ -9,9 +9,14 @@ import { FileTransformRun } from "./transformerUtil";
9
9
  * </pre></code>
10
10
  */
11
11
  export declare class AddRemoteMethodsMeta extends FileTransformRun {
12
+ /**
13
+ * Should this transformer squeeze the added declarations (result) into one line to keep the line numbers in the source text intact ?
14
+ * Prevents the [broken source maps bug](https://github.com/bogeeee/restfuncs/issues/2)
15
+ */
16
+ static squeezeDeclarationsIntoOneLine: boolean;
12
17
  diagnosis_currentClass_instanceMethodsSeen?: Set<string>;
13
- currentClass_instanceMethodsMeta?: Record<string, ObjectLiteralExpression>;
14
- astWasModified: boolean;
18
+ currentClass_instanceMethodsMeta?: Record<string, string>;
19
+ result: TextPatch;
15
20
  visit(node: Node): Node;
16
21
  /**
17
22
  * Crates the following expression like in readme.md#how-it-works
@@ -20,6 +25,10 @@ export declare class AddRemoteMethodsMeta extends FileTransformRun {
20
25
  * arguments: {
21
26
  * ...
22
27
  * }
28
+ * result: {
29
+ * ...
30
+ * }
31
+ * callbacks:[...]
23
32
  * jsDoc: {
24
33
  * ...
25
34
  * }
@@ -29,7 +38,7 @@ export declare class AddRemoteMethodsMeta extends FileTransformRun {
29
38
  * @param methodName
30
39
  * @private
31
40
  */
32
- createMethodMetaExpression(node: MethodDeclaration, methodName: string): ts.ObjectLiteralExpression;
41
+ createMethodMetaExpression(node: MethodDeclaration, methodName: string): string;
33
42
  /**
34
43
  * Crates the following expression like in readme.md#how-it-works
35
44
  * <code><pre>
@@ -54,7 +63,7 @@ export declare class AddRemoteMethodsMeta extends FileTransformRun {
54
63
  /**
55
64
  * Crates the following expression like in readme.md#how-it-works
56
65
  * <code><pre>
57
- * static getRemoteMethodsMeta() {
66
+ * static getRemoteMethodsMeta(...) :... {
58
67
  * ....
59
68
  * }
60
69
  * </pre></code>
@@ -63,5 +72,14 @@ export declare class AddRemoteMethodsMeta extends FileTransformRun {
63
72
  * @private
64
73
  */
65
74
  private create_static_getRemoteMethodsMeta_expression;
75
+ nodeToString(node: Node, removeComments?: boolean, removeLineBreaks?: boolean): string;
76
+ /**
77
+ * Creates a node, that declares a SourceNodeDiagnosis (defined in ServerSession.ts) object.
78
+ *
79
+ * To output the info at runtime: i.e. "MyServerSessionClass.ts:23:30, reading: async myRemoteMethod(param1:...): Promise<void> {}
80
+ * @param node
81
+ */
82
+ createSourceNodeDiagnosisExpression(node: Node): ts.ObjectLiteralExpression;
83
+ diag_sourceLocation(node: Node): string;
66
84
  }
67
85
  //# sourceMappingURL=AddRemoteMethodsMeta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AddRemoteMethodsMeta.d.ts","sourceRoot":"","sources":["../../AddRemoteMethodsMeta.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAGP,iBAAiB,EACjB,IAAI,EAIP,MAAM,YAAY,CAAC;AACpB,OAAO,EAAC,gBAAgB,EAAE,SAAS,EAAC,MAAM,mBAAmB,CAAC;AAK9D;;;;;;;GAOG;AACH,qBAAa,oBAAqB,SAAQ,gBAAgB;IACtD;;;OAGG;IACH,MAAM,CAAC,8BAA8B,UAAQ;IAC7C,0CAA0C,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzD,gCAAgC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1D,MAAM,YAAmB;IAGzB,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAiEvB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,0BAA0B,CAAC,IAAI,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;IAsL/E;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,qBAAqB;IA+E7B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,6CAA6C;IAgBrD,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,cAAc,UAAO,EAAE,gBAAgB,UAAQ,GAAG,MAAM;IASjF;;;;;OAKG;IACH,mCAAmC,CAAC,IAAI,EAAE,IAAI;IAmC9C,mBAAmB,CAAC,IAAI,EAAE,IAAI;CAIjC"}