@lwc-bolts/sf2ts 0.0.2 → 0.0.4

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.
@@ -2,6 +2,7 @@ export { ApexClass } from "./apex-class.mjs";
2
2
  export { ContentAsset } from "./content-asset.mjs";
3
3
  export { CustomPermission } from "./custom-permission.mjs";
4
4
  export { CustomField } from "./custom-field.mjs";
5
+ export { CustomLabel } from "./custom-label.mjs";
5
6
  export { CustomLabels } from "./custom-labels.mjs";
6
7
  export { CustomObject } from "./custom-object.mjs";
7
8
  export { StaticResource } from "./static-resource.mjs";
@@ -1,8 +1,7 @@
1
1
  /** @import Parser from "web-tree-sitter" */
2
- /** @import { Component, ConvertedComponent } from "../index.mjs" */
2
+ /** @import { Component, ConvertedComponent, MapPair } from "../index.mjs" */
3
3
  import { getApexParser } from "web-tree-sitter-sfapex";
4
-
5
- // TOOD: Apex Continuation
4
+ import { PositionAwareTextBuilder } from "../util.mjs";
6
5
 
7
6
  /** @type { Parser } */
8
7
  let prsr;
@@ -25,33 +24,25 @@ export function ApexClass(comp) {
25
24
  throw err;
26
25
  }
27
26
 
28
- const [className, declarationContent, mappingContent] =
29
- buildDeclarationForApexFile(prsr, comp.path, comp.content, false);
27
+ const [declarationContent, mappingContent] = buildDeclarationForApexFile(
28
+ prsr,
29
+ comp.content,
30
+ false
31
+ );
30
32
  if (declarationContent === undefined) {
31
33
  throw new Error("Problem building declaration content");
32
34
  }
35
+ if (mappingContent === undefined) {
36
+ throw new Error("Problem building declaration content mappings");
37
+ }
33
38
 
34
- return { ...comp, declarationContent: declarationContent, mapData: [] };
39
+ return {
40
+ ...comp,
41
+ declarationContent: declarationContent,
42
+ mapData: mappingContent,
43
+ };
35
44
  }
36
45
 
37
- /**
38
- * @typedef DeltaPoint
39
- * @property {number} rowDelta
40
- * @property {number} column
41
- */
42
-
43
- /**
44
- * @typedef MappingPos
45
- * @property {DeltaPoint} startPosition
46
- * @property {DeltaPoint} endPosition
47
- */
48
-
49
- /**
50
- * @typedef Mapping
51
- * @property { MappingPos } dPos
52
- * @property { {startPosition: Parser.Point, endPosition: Parser.Point }?} sPos
53
- */
54
-
55
46
  /** @type { Record<string, string> } */
56
47
  const typeOverrides = {
57
48
  string: "string",
@@ -67,20 +58,22 @@ let localNestedClasses = [];
67
58
  /** @type { string? } */
68
59
  let outerClassName;
69
60
 
61
+ // TODO: probably don't want this to be a global
62
+ let builder = new PositionAwareTextBuilder();
63
+
70
64
  /**
71
65
  *
72
66
  * @param {Parser} parser
73
- * @param {string} relativeFilePath
74
67
  * @param {string} apexText
75
68
  * @param {boolean} isSobject
76
- * @returns { string[] }
69
+ * @returns { [string, MapPair[]] | [] }
77
70
  */
78
71
  export function buildDeclarationForApexFile(
79
72
  parser,
80
- relativeFilePath,
81
73
  apexText,
82
74
  isSobject = false
83
75
  ) {
76
+ builder = new PositionAwareTextBuilder();
84
77
  try {
85
78
  const tree = parser.parse(apexText);
86
79
  // step into top-level class, if any
@@ -91,15 +84,10 @@ export function buildDeclarationForApexFile(
91
84
  return []; // this isn't a class
92
85
  }
93
86
 
94
- // TODO: build mapping file
95
- const [className, declarationText, mappings] = processClass(
96
- classes[0],
97
- isSobject
98
- );
99
- const mappingText = getMapFile(className, relativeFilePath, mappings);
87
+ processClass(classes[0], isSobject);
100
88
  localNestedClasses = [];
101
89
  outerClassName = null;
102
- return [className, declarationText, mappingText];
90
+ return [builder.build(), builder.getMappings()];
103
91
  } catch (error) {
104
92
  console.error(error);
105
93
 
@@ -113,20 +101,19 @@ export function buildDeclarationForApexFile(
113
101
  *
114
102
  * @param {Parser.SyntaxNode} classNode
115
103
  * @param {boolean} isSobject
116
- * @returns { [string, string, Mapping[]] }
117
104
  */
118
105
  function processClass(classNode, isSobject) {
119
- let className = classNode.childForFieldName("name")?.text ?? "";
106
+ const classNameNode = classNode.childForFieldName("name");
107
+ if (!classNameNode) {
108
+ throw new Error("Class Name Node not found");
109
+ }
110
+ let className = classNameNode?.text ?? "";
120
111
  if (!outerClassName) {
121
112
  outerClassName = className;
122
113
  }
123
114
  let classBody = classNode.childForFieldName("body");
124
- /** @type {Mapping[]} */
125
- const mappings = [];
126
- /** @type {string[]} */
127
- let outputText = [];
128
115
  if (!classBody) {
129
- return [className, outputText.join(""), mappings];
116
+ return;
130
117
  }
131
118
 
132
119
  const methodsToProcess = [];
@@ -153,194 +140,85 @@ function processClass(classNode, isSobject) {
153
140
  }
154
141
  }
155
142
  if (fieldsToProcess.length > 0) {
156
- outputText.push(`declare interface ${className} {\n`);
157
- mappings.push({
158
- dPos: {
159
- startPosition: {
160
- rowDelta: 0,
161
- column: 0,
162
- },
163
- endPosition: {
164
- rowDelta: 0,
165
- column: 0,
166
- },
167
- },
168
- sPos: null,
169
- });
143
+ builder.addText("declare interface ");
144
+ builder.addText(
145
+ className,
146
+ classNameNode.startIndex,
147
+ classNameNode.endIndex
148
+ );
149
+ builder.addText(" {\n");
170
150
  for (let fNode of fieldsToProcess) {
171
- const [newOutputText, newMappings] = transformFieldDeclaration(
172
- fNode,
173
- isSobject
174
- );
175
- outputText.push(newOutputText);
176
- mappings.push(...newMappings);
151
+ transformFieldDeclaration(fNode, isSobject);
177
152
  }
178
- outputText.push("}\n");
179
- mappings.push({
180
- dPos: {
181
- startPosition: {
182
- rowDelta: 1,
183
- column: 0,
184
- },
185
- endPosition: {
186
- rowDelta: 0,
187
- column: 0,
188
- },
189
- },
190
- sPos: null,
191
- });
153
+ builder.addText("}\n");
192
154
  }
193
155
 
194
156
  if (classesToProcess.length > 0) {
195
- outputText.push(`declare namespace ${className} {\n`);
196
- mappings.push({
197
- dPos: {
198
- startPosition: {
199
- rowDelta: 2,
200
- column: 0,
201
- },
202
- endPosition: {
203
- rowDelta: 0,
204
- column: 0,
205
- },
206
- },
207
- sPos: null,
208
- });
157
+ builder.addText("declare namespace ");
158
+ builder.addText(
159
+ className,
160
+ classNameNode.startIndex,
161
+ classNameNode.endIndex
162
+ );
163
+ builder.addText(" {\n");
209
164
  for (const cNode of classesToProcess) {
210
- const [className, newOutputText, newMappings] = processClass(
211
- cNode,
212
- isSobject
213
- );
214
- if (newOutputText.length > 0) {
215
- outputText.push(" " + newOutputText.replaceAll(/\n(.)/g, "\n $1"));
216
- mappings.push(...newMappings);
217
- }
165
+ processClass(cNode, isSobject);
218
166
  // TODO: somehow queue up local classes for type resolution
219
167
  }
220
168
 
221
- outputText.push(`}\n`);
222
- mappings.push({
223
- dPos: {
224
- startPosition: {
225
- rowDelta: 2,
226
- column: 0,
227
- },
228
- endPosition: {
229
- rowDelta: 0,
230
- column: 0,
231
- },
232
- },
233
- sPos: null,
234
- });
169
+ builder.addText("}\n");
235
170
  }
236
171
 
237
172
  for (const mNode of methodsToProcess) {
238
- const [newOutputText, newMappings] = transformMethodDeclaration(
239
- className,
240
- mNode
241
- );
242
- outputText.push(newOutputText);
243
- mappings.push(...newMappings);
173
+ transformMethodDeclaration(classNameNode, mNode);
244
174
  }
245
- return [className, outputText.join(""), mappings];
246
175
  }
247
176
 
248
177
  /**
249
178
  *
250
- * @param {string} className
179
+ * @param {Parser.SyntaxNode} classNameNode
251
180
  * @param {Parser.SyntaxNode} methodNode
252
- * @returns {[string, Mapping[]]}
253
181
  */
254
- function transformMethodDeclaration(className, methodNode) {
255
- /** @type {Mapping[]} */
256
- const mappings = [];
182
+ function transformMethodDeclaration(classNameNode, methodNode) {
257
183
  const methodNameNode = methodNode.childForFieldName("name");
258
184
  if (!methodNameNode) {
259
- return ["", mappings];
185
+ return;
260
186
  }
261
187
  const methodName = methodNameNode.text;
262
188
  const returnType = methodNode.childForFieldName("type");
263
189
  if (!returnType) {
264
- return ["", mappings];
190
+ return;
265
191
  }
266
192
  const parameters = getMethodParameters(methodNode);
267
193
 
268
194
  const auraEnabledNode = getAuraEnabledAnnotation(methodNode);
269
195
  if (!auraEnabledNode) {
270
196
  // method doesn't hold the AuraEnabled Annotation
271
- return ["", mappings];
197
+ return;
272
198
  }
273
199
  const isCacheableEnabled = getAuraEnabledIsCacheable(auraEnabledNode);
200
+ const isContinuation = getAuraEnabledIsContinuation(auraEnabledNode);
274
201
 
275
- const [newOutputText, methodNameMapping] = declarationString(
276
- className,
277
- methodName,
202
+ // TODO: continuation return type is actually the return type of the `continuationMethod` referenced in the Continuation object
203
+ declarationString(
204
+ classNameNode,
205
+ methodNameNode,
278
206
  getType(returnType),
279
207
  parameters,
280
- isCacheableEnabled
208
+ isCacheableEnabled,
209
+ isContinuation
281
210
  );
282
- const declarationParts = newOutputText.split("\n");
283
- methodNameMapping.rowDelta;
284
-
285
- mappings.push({
286
- dPos: {
287
- startPosition: methodNameMapping,
288
- endPosition: {
289
- rowDelta: methodNameMapping.rowDelta,
290
- column: methodNameMapping.column + methodName.length,
291
- },
292
- },
293
- sPos: {
294
- startPosition: methodNameNode.startPosition,
295
- endPosition: methodNameNode.endPosition,
296
- },
297
- });
298
- // add consumption of remaining lines
299
- mappings.push({
300
- dPos: {
301
- startPosition: {
302
- rowDelta: declarationParts.length - methodNameMapping.rowDelta,
303
- column: 0,
304
- },
305
- endPosition: {
306
- rowDelta: declarationParts.length - methodNameMapping.rowDelta,
307
- column: 0,
308
- },
309
- },
310
- sPos: null,
311
- });
312
- return [newOutputText, mappings];
313
211
  }
314
212
 
315
213
  /**
316
214
  *
317
215
  * @param {Parser.SyntaxNode} node
318
216
  * @param {boolean} isSobject
319
- * @returns {[string, Mapping[]]}
320
217
  */
321
218
  function transformFieldDeclaration(node, isSobject) {
322
219
  /** @type {string[]} */
323
220
  let deprecatedReasons = [];
324
- let output = "";
325
221
  const modifiers = node.descendantsOfType("modifiers");
326
- /** @type {Mapping} */
327
- const mapping = {
328
- dPos: {
329
- startPosition: {
330
- rowDelta: 1,
331
- column: 2,
332
- },
333
- endPosition: {
334
- // TODO
335
- rowDelta: 0,
336
- column: 0,
337
- },
338
- },
339
- sPos: {
340
- startPosition: node.startPosition,
341
- endPosition: node.endPosition,
342
- },
343
- };
344
222
 
345
223
  if (modifiers.length === 0 || !modifiers[0]) {
346
224
  deprecatedReasons.push("public or global");
@@ -373,21 +251,39 @@ function transformFieldDeclaration(node, isSobject) {
373
251
  deprecatedReasons.push("public or global");
374
252
  }
375
253
  }
376
- /** TODO:
254
+ /**
377
255
  * determine if AuraEnabled & public or global & not static
378
256
  * otherwise mark as deprecated with note (dynamic based on missing access component)
379
257
  * {property}: {getType({type})}?;
380
258
  */
381
259
  if (deprecatedReasons.length > 0) {
382
- mapping.dPos.startPosition.rowDelta++;
383
- output += ` /** @deprecated not exposed; property must be ${deprecatedReasons.join(
384
- ", "
385
- )} */\n`;
260
+ builder.addText(
261
+ ` /** @deprecated not exposed; property must be ${deprecatedReasons.join(
262
+ ", "
263
+ )} */\n`
264
+ );
265
+ }
266
+ const nameNode = node
267
+ .childForFieldName("declarator")
268
+ ?.childForFieldName("name");
269
+ builder.addText(" ");
270
+ if (!nameNode) {
271
+ throw new Error("Field doesn't have a name");
386
272
  }
387
- output += ` ${
388
- node.childForFieldName("declarator")?.childForFieldName("name")?.text ?? ""
389
- }?: ${getType(node.childForFieldName("type"))};\n`;
390
- return [output, [mapping]];
273
+ builder.addText(
274
+ `${nameNode?.text ?? ""}`,
275
+ nameNode.startIndex,
276
+ nameNode.endIndex
277
+ );
278
+ builder.addText("?: ");
279
+ const typeNode = node.childForFieldName("type");
280
+ if (!typeNode) {
281
+ throw new Error("Type node not found");
282
+ }
283
+ const typeText = getType(typeNode);
284
+ builder.addText(typeText, typeNode.startIndex, typeNode.endIndex);
285
+ builder.addText(";\n");
286
+ const moreText = `;\n`;
391
287
  }
392
288
 
393
289
  /**
@@ -414,6 +310,30 @@ function getAuraEnabledIsCacheable(node) {
414
310
  return false;
415
311
  }
416
312
 
313
+ /**
314
+ *
315
+ * @param {Parser.SyntaxNode} node
316
+ * @returns {boolean}
317
+ */
318
+ function getAuraEnabledIsContinuation(node) {
319
+ const args = node.childForFieldName("arguments");
320
+ if (args) {
321
+ for (const a of args.descendantsOfType("annotation_key_value")) {
322
+ const k = a.childForFieldName("key");
323
+ const v = a.childForFieldName("value");
324
+ if (
325
+ k &&
326
+ v &&
327
+ k.text.toLowerCase() === "continuation" &&
328
+ v.text.toLowerCase() === "true"
329
+ ) {
330
+ return true;
331
+ }
332
+ }
333
+ }
334
+ return false;
335
+ }
336
+
417
337
  /**
418
338
  *
419
339
  * @param {Parser.SyntaxNode} node
@@ -452,22 +372,36 @@ function getMethodParameters(node) {
452
372
 
453
373
  /**
454
374
  *
455
- * @param {string} className
456
- * @param {string} methodName
375
+ * @param {Parser.SyntaxNode} classNameNode
376
+ * @param {Parser.SyntaxNode} methodNameNode
457
377
  * @param {string} returnType
458
378
  * @param {Map<string, string>} params
459
379
  * @param {boolean} isCacheable
460
- * @returns {[string, DeltaPoint]}
380
+ * @param {boolean} isContinuation
461
381
  */
462
382
  function declarationString(
463
- className,
464
- methodName,
383
+ classNameNode,
384
+ methodNameNode,
465
385
  returnType,
466
386
  params,
467
- isCacheable
387
+ isCacheable,
388
+ isContinuation
468
389
  ) {
469
- let text = `declare module "@salesforce/apex/${className}.${methodName}" {
470
- const ${methodName}: {
390
+ builder.addText("declare module ");
391
+ // if method has Containuation
392
+ builder.addText(
393
+ `"@salesforce/apex${isContinuation ? "Continuation" : ""}/${classNameNode.text}.${methodNameNode.text}"`,
394
+ methodNameNode.startIndex,
395
+ methodNameNode.endIndex
396
+ );
397
+ builder.addText(` {
398
+ const `);
399
+ builder.addText(
400
+ methodNameNode.text,
401
+ methodNameNode.startIndex,
402
+ methodNameNode.endIndex
403
+ );
404
+ builder.addText(`: {
471
405
  (${
472
406
  params.size === 0
473
407
  ? ""
@@ -492,64 +426,9 @@ function declarationString(
492
426
  >;`
493
427
  }
494
428
  };
495
- export default `;
496
- // analyze how much text we're adding before the method name
497
- const lines = text.split("\n");
498
- /** @type {DeltaPoint} */
499
- const m = {
500
- rowDelta: lines.length - 1,
501
- column: lines[lines.length - 1]?.length ?? 0,
502
- };
503
- // finish building
504
- text += `${methodName};
429
+ export default ${methodNameNode.text};
505
430
  }
506
- `;
507
- return [text, m];
508
- }
509
-
510
- /**
511
- *
512
- * @param {string} className
513
- * @param {string} relativeClassPath
514
- * @param {Mapping[]} mappings
515
- * @returns
516
- */
517
- function getMapFile(className, relativeClassPath, mappings) {
518
- let mappingsString = "";
519
- let lastSourceRow = 0;
520
- let lastMethodColumnNum = 0;
521
- for (const m of mappings) {
522
- mappingsString += ";".repeat(m.dPos.startPosition.rowDelta);
523
- if (!m.sPos) {
524
- continue;
525
- }
526
- /**
527
- * Generated column
528
- * Original file this appeared in
529
- * Original line number relative to last given value in this field
530
- * Original column relative to last given value in this field
531
- */
532
- // const incrementalColumnCount =
533
- // m.sPos.startPosition.column - lastMethodColumnNum;
534
-
535
- // mappingsString += `${encode(m.dPos.startPosition.column)}${encode(
536
- // 0
537
- // )}${encode(m.sPos.startPosition.row - lastSourceRow)}${encode(
538
- // incrementalColumnCount
539
- // )}`;
540
-
541
- lastMethodColumnNum = m.sPos.startPosition.column;
542
- lastSourceRow = m.sPos.startPosition.row;
543
- }
544
- return JSON.stringify({
545
- version: 3,
546
- file: `${className}.d.ts`,
547
- sourceRoot: "",
548
- // TODO: not so badly hardcoded
549
- sources: [`../../../../../${relativeClassPath}`],
550
- names: [],
551
- mappings: mappingsString,
552
- });
431
+ `);
553
432
  }
554
433
 
555
434
  /**
@@ -1,5 +1,6 @@
1
1
  /** @import { Component, ConvertedComponent } from "../index.mjs" */
2
2
  import { parseString } from "xml2js";
3
+ import { PositionAwareTextBuilder } from "../util.mjs";
3
4
 
4
5
  /**
5
6
  *
@@ -16,9 +17,32 @@ export function ContentAsset(component) {
16
17
  }
17
18
  asset = result;
18
19
  });
19
- const dec = `declare module "@salesforce/contentAssetUrl/${asset.ContentAsset.masterLabel}" {
20
- var ${asset.ContentAsset.masterLabel}: string;
21
- export default ${asset.ContentAsset.masterLabel};
22
- }`;
23
- return { declarationContent: dec, mapData: [], ...component };
20
+ const label = asset.ContentAsset.masterLabel[0];
21
+ const namePosStart =
22
+ component.content.indexOf("<masterLabel>") + "<masterLabel>".length;
23
+ const namePosEnd = namePosStart + label.length;
24
+
25
+ const builder = new PositionAwareTextBuilder();
26
+
27
+ builder.addText(`declare module `);
28
+ builder.addText(
29
+ `"@salesforce/contentAssetUrl/${label}"`,
30
+ namePosStart,
31
+ namePosEnd
32
+ );
33
+ builder.addText(
34
+ ` {
35
+ const `
36
+ );
37
+ builder.addText(label, namePosStart, namePosEnd);
38
+ builder.addText(
39
+ `: string;
40
+ export default ${label};
41
+ }`
42
+ );
43
+ return {
44
+ declarationContent: builder.build(),
45
+ mapData: builder.getMappings(),
46
+ ...component,
47
+ };
24
48
  }
@@ -1,5 +1,6 @@
1
1
  /** @import { Component, ConvertedComponent } from "../index.mjs" */
2
2
  import { parseString } from "xml2js";
3
+ import { PositionAwareTextBuilder } from "../util.mjs";
3
4
 
4
5
  /**
5
6
  *
@@ -18,18 +19,45 @@ export function CustomField(component) {
18
19
  });
19
20
  const pathParts = component.path.split("/");
20
21
  const fieldName = pathParts.pop()?.split(".")[0];
22
+ if (!fieldName) {
23
+ throw new Error("Unable to resolve field name from file path");
24
+ }
21
25
  pathParts?.pop(); // discard `fields`
22
26
  const objectName = pathParts.pop();
23
- const dec = `declare module '@salesforce/schema/${objectName}.${fieldName}' {
27
+
28
+ const namePosStart =
29
+ component.content.indexOf("<fullName>") + "<fullName>".length;
30
+ const namePosEnd = namePosStart + fieldName.length;
31
+
32
+ const builder = new PositionAwareTextBuilder();
33
+ builder.addText(`declare module `);
34
+ builder.addText(
35
+ `"@salesforce/schema/${objectName}.${fieldName}"`,
36
+ namePosStart,
37
+ namePosEnd
38
+ );
39
+ builder.addText(
40
+ ` {
24
41
  /**
25
42
  * @description ${field.CustomField.inlineHelpText}
26
43
  * @description ${field.CustomField.description}
27
44
  */
28
- const ${fieldName}: {
45
+ const `
46
+ );
47
+
48
+ builder.addText(`${fieldName}`, namePosStart, namePosEnd);
49
+
50
+ builder.addText(
51
+ `: {
29
52
  fieldApiName: '${fieldName}';
30
53
  objectApiName: '${objectName}';
31
54
  }
32
55
  export default ${fieldName};
33
- }`;
34
- return { declarationContent: dec, mapData: [], ...component };
56
+ }`
57
+ );
58
+ return {
59
+ declarationContent: builder.build(),
60
+ mapData: builder.getMappings(),
61
+ ...component,
62
+ };
35
63
  }