@lwc-bolts/sf2ts 0.0.5 → 1.0.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.
Files changed (98) hide show
  1. package/LICENSE.md +7 -0
  2. package/README.md +285 -0
  3. package/dist/apex/apexdoc.d.ts +19 -0
  4. package/dist/apex/apexdoc.js +79 -0
  5. package/dist/apex/file.d.ts +37 -0
  6. package/dist/apex/file.js +623 -0
  7. package/dist/apex/library.config.d.ts +23 -0
  8. package/dist/apex/library.config.js +17 -0
  9. package/dist/apex/library.d.ts +54 -0
  10. package/dist/apex/library.generated.d.ts +2 -0
  11. package/dist/apex/library.generated.js +215 -0
  12. package/dist/apex/library.js +47 -0
  13. package/dist/apex/namespace.d.ts +32 -0
  14. package/dist/apex/namespace.js +41 -0
  15. package/dist/apex/resolution.d.ts +77 -0
  16. package/dist/apex/resolution.js +306 -0
  17. package/dist/apex/sobjects.d.ts +45 -0
  18. package/dist/apex/sobjects.js +141 -0
  19. package/dist/context.d.ts +16 -0
  20. package/dist/context.js +28 -0
  21. package/dist/converters/apex-class.d.ts +7 -0
  22. package/dist/converters/apex-class.js +23 -0
  23. package/dist/converters/content-asset.d.ts +3 -0
  24. package/dist/converters/content-asset.js +20 -0
  25. package/dist/converters/custom-field.d.ts +3 -0
  26. package/dist/converters/custom-field.js +107 -0
  27. package/dist/converters/custom-label.d.ts +10 -0
  28. package/dist/converters/custom-label.js +36 -0
  29. package/dist/converters/custom-labels.d.ts +3 -0
  30. package/dist/converters/custom-labels.js +10 -0
  31. package/dist/converters/custom-object.d.ts +3 -0
  32. package/dist/converters/custom-object.js +37 -0
  33. package/dist/converters/custom-permission.d.ts +3 -0
  34. package/dist/converters/custom-permission.js +21 -0
  35. package/dist/converters/lightning-component-bundle.d.ts +7 -0
  36. package/dist/converters/lightning-component-bundle.js +31 -0
  37. package/dist/converters/lightning-message-channel.d.ts +10 -0
  38. package/dist/converters/lightning-message-channel.js +39 -0
  39. package/dist/converters/registry.d.ts +22 -0
  40. package/dist/converters/registry.js +18 -0
  41. package/dist/converters/static-resource.d.ts +3 -0
  42. package/dist/converters/static-resource.js +20 -0
  43. package/dist/diagnostics.d.ts +5 -0
  44. package/dist/diagnostics.js +18 -0
  45. package/dist/identifiers.d.ts +12 -0
  46. package/dist/identifiers.js +19 -0
  47. package/dist/index.d.ts +34 -0
  48. package/dist/index.js +47 -0
  49. package/dist/jsdoc.d.ts +14 -0
  50. package/dist/jsdoc.js +28 -0
  51. package/dist/lightning-component-bundle/component-source.d.ts +6 -0
  52. package/dist/lightning-component-bundle/component-source.js +21 -0
  53. package/dist/lightning-component-bundle/metadata.d.ts +36 -0
  54. package/dist/lightning-component-bundle/metadata.js +93 -0
  55. package/dist/lightning-component-bundle/templates/ast.d.ts +47 -0
  56. package/dist/lightning-component-bundle/templates/ast.js +46 -0
  57. package/dist/lightning-component-bundle/templates/base-components.d.ts +6 -0
  58. package/dist/lightning-component-bundle/templates/base-components.js +17 -0
  59. package/dist/lightning-component-bundle/templates/declarations.d.ts +30 -0
  60. package/dist/lightning-component-bundle/templates/declarations.js +82 -0
  61. package/dist/lightning-component-bundle/templates/expressions.d.ts +16 -0
  62. package/dist/lightning-component-bundle/templates/expressions.js +33 -0
  63. package/dist/lightning-component-bundle/templates/helpers.d.ts +10 -0
  64. package/dist/lightning-component-bundle/templates/helpers.js +47 -0
  65. package/dist/lightning-component-bundle/templates/slots.d.ts +24 -0
  66. package/dist/lightning-component-bundle/templates/slots.js +78 -0
  67. package/dist/lightning-component-bundle/templates/source.d.ts +36 -0
  68. package/dist/lightning-component-bundle/templates/source.js +81 -0
  69. package/dist/lightning-component-bundle/templates/tags.d.ts +41 -0
  70. package/dist/lightning-component-bundle/templates/tags.js +36 -0
  71. package/dist/lightning-component-bundle/templates/template.d.ts +53 -0
  72. package/dist/lightning-component-bundle/templates/template.js +518 -0
  73. package/dist/lightning-component-bundle/templates/writer.d.ts +9 -0
  74. package/dist/lightning-component-bundle/templates/writer.js +32 -0
  75. package/dist/module-declaration.d.ts +27 -0
  76. package/dist/module-declaration.js +28 -0
  77. package/dist/names.d.ts +39 -0
  78. package/dist/names.js +67 -0
  79. package/dist/text-builder.d.ts +34 -0
  80. package/dist/text-builder.js +46 -0
  81. package/dist/types.d.ts +128 -0
  82. package/dist/types.js +3 -0
  83. package/dist/xml.d.ts +32 -0
  84. package/dist/xml.js +80 -0
  85. package/package.json +37 -26
  86. package/README.MD +0 -5
  87. package/lib/converts/_index.mjs +0 -8
  88. package/lib/converts/apex-class.mjs +0 -469
  89. package/lib/converts/content-asset.mjs +0 -48
  90. package/lib/converts/custom-field.mjs +0 -63
  91. package/lib/converts/custom-label.mjs +0 -61
  92. package/lib/converts/custom-labels.mjs +0 -34
  93. package/lib/converts/custom-object.mjs +0 -51
  94. package/lib/converts/custom-permission.mjs +0 -51
  95. package/lib/converts/static-resource.mjs +0 -56
  96. package/lib/index.mjs +0 -51
  97. package/lib/util.mjs +0 -68
  98. package/lib-cjs/bundle.cjs +0 -962
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Apex types as LWC receives them: Apex values are serialized to JSON, so dates, times and blobs
3
+ * arrive as strings (ISO-8601 / base64) and every numeric type as a number.
4
+ */
5
+ export declare const PRIMITIVE_TYPES: Readonly<Record<string, string>>;
6
+ export interface ResolverOptions {
7
+ /** The namespace Apex types are declared in; `null` declares them globally. */
8
+ readonly namespace: string | null;
9
+ /** The project's namespace, or undefined without one. */
10
+ readonly projectNamespace: string | undefined;
11
+ /** The installed package the file's class belongs to, when it isn't the project's. */
12
+ readonly packageNamespace: string | undefined;
13
+ /** The file's top-level type. */
14
+ readonly className: string;
15
+ /** Whether the file is an SObject definition, whose aliases are apart from classes' (see `#alias`). */
16
+ readonly isSobject: boolean;
17
+ /**
18
+ * The top-level type's nested classes, interfaces and enums, keyed by lower-cased name to their declared
19
+ * spelling. The file adds them as it finds them, before its fields refer to them.
20
+ */
21
+ readonly nestedTypes: ReadonlyMap<string, string>;
22
+ }
23
+ /**
24
+ * Resolves one Apex file's type references, and collects what its declarations then need: the library
25
+ * types they use, and the registries they read.
26
+ */
27
+ export declare class TypeResolver {
28
+ #private;
29
+ constructor(options: ResolverOptions);
30
+ /** The namespace the file's types are declared in: the Apex namespace, or a package's in it. */
31
+ get typesNamespace(): string | null;
32
+ /** A name in the Apex namespace, as a reference: `Apex.Account`, or `Account` without one. */
33
+ qualify(parts: readonly string[]): string;
34
+ /** A type declared with the file's types (its classes), as a reference. */
35
+ qualifyOwn(parts: readonly string[]): string;
36
+ /**
37
+ * Resolves an Apex type reference (`Outer.Inner`, `Inner` from inside `Outer`, `Exception`,
38
+ * `System.Exception`) to its TypeScript type, as Apex looks names up. In order:
39
+ *
40
+ * 1. Through a namespace: the package's (`dlrs.RollupSummary`), which is one of its types, or the
41
+ * project's (`acme.Invoices`), which is the name without it.
42
+ * 2. The file's own types, in their declared spelling, since Apex names are case-insensitive but
43
+ * TypeScript names aren't.
44
+ * 3. A custom object (`Invoice__c`), named with the namespace of the code it's in.
45
+ * 4. A name a library type has (see library.ts): the project's types and SObjects, which shadow it,
46
+ * then the library's namespaces in their configured order, through registries.
47
+ * 5. A library type through its namespace (`System.Exception`). Apex's primitive types are System's
48
+ * (`System.String`), and a name in Schema that's no library type is an SObject (`Schema.Account`).
49
+ * 6. Any other name, as written in the Apex namespace (the project's classes in other files, and
50
+ * standard objects).
51
+ *
52
+ * An installed package's class can't see the project's classes, and its copy may not have what it
53
+ * refers to, so its names are its package's types when the copy has them (`RollupSummary`,
54
+ * `RollupController.Result`), then library types and SObjects, and `unknown` otherwise, never an error
55
+ * in code that isn't the project's to fix.
56
+ *
57
+ * A reference that looks through registries is a conditional type, written where it's used (see
58
+ * `#lookup`).
59
+ *
60
+ * @param base whether the reference is a class's base class, which an interface can only extend by
61
+ * name and which can't be `unknown`: a name a package's class can't find is `{}` instead
62
+ */
63
+ resolve(parts: readonly string[], { base }?: {
64
+ base?: boolean;
65
+ }): string;
66
+ /**
67
+ * The declarations the file's references need, in a block of their own, or "" without any: the
68
+ * library types they use (and their base classes), the registries they read, and the file's own type
69
+ * in `ownRegistry`, if given. Other files declare the same library types and registries, which merge.
70
+ */
71
+ declarations(ownName: string, ownRegistry: string | undefined): string;
72
+ /**
73
+ * The SObjects' registry (`Apex.__lwcSObjects`), which every declared SObject adds itself to, by
74
+ * lower-cased name: what `registryLookup` reads to refer to an SObject that may not be declared.
75
+ */
76
+ get sobjectsRegistry(): string;
77
+ }
@@ -0,0 +1,306 @@
1
+ // How an Apex file's type references become TypeScript types: Apex's primitive types, the file's own
2
+ // types, the project's and installed packages' types, SObjects, and the standard library's (library.ts).
3
+ // Which type a name is often depends on the whole project, while sf2ts converts one file at a time, so
4
+ // such references check registries that the project's files add their types to (namespace.ts), and
5
+ // TypeScript picks the type.
6
+ import { apexLibrary } from "./library.js";
7
+ import { CLASSES_REGISTRY, qualifyApex, registration, registryLookup, SOBJECTS_REGISTRY } from "./namespace.js";
8
+ import { recordName } from "../names.js";
9
+ import { RESERVED_WORDS } from "../identifiers.js";
10
+ /**
11
+ * Apex types as LWC receives them: Apex values are serialized to JSON, so dates, times and blobs
12
+ * arrive as strings (ISO-8601 / base64) and every numeric type as a number.
13
+ */
14
+ export const PRIMITIVE_TYPES = {
15
+ string: "string",
16
+ id: "string",
17
+ date: "string",
18
+ datetime: "string",
19
+ time: "string",
20
+ blob: "string",
21
+ decimal: "number",
22
+ double: "number",
23
+ integer: "number",
24
+ long: "number",
25
+ boolean: "boolean",
26
+ // any value, including null, which TypeScript's `Object` rejects
27
+ object: "unknown",
28
+ // a record of any SObject type, such as a polymorphic lookup (`Owner`, `What`)
29
+ sobject: "Record<string, unknown>",
30
+ // used by the generated SObject definitions for JSON-typed fields
31
+ json: "unknown",
32
+ void: "void",
33
+ };
34
+ /** Names TypeScript's types are, which an alias can't have. */
35
+ const TYPE_KEYWORDS = new Set(["any", "bigint", "boolean", "never", "number", "object", "string", "symbol", "undefined", "unknown"]);
36
+ /**
37
+ * Resolves one Apex file's type references, and collects what its declarations then need: the library
38
+ * types they use, and the registries they read.
39
+ */
40
+ export class TypeResolver {
41
+ #options;
42
+ /** The library types the declarations reference, by namespace and name; they're declared with them. */
43
+ #libraryTypes = new Map();
44
+ /** The registries references resolve through, which are declared (empty) so they always exist. */
45
+ #registries = new Set();
46
+ /**
47
+ * The resolved types of references that look through registries, by type, to the names of the aliases
48
+ * the file declares for them, so each is written once however many references it has.
49
+ */
50
+ #aliases = new Map();
51
+ constructor(options) {
52
+ this.#options = options;
53
+ }
54
+ /** The namespace the file's types are declared in: the Apex namespace, or a package's in it. */
55
+ get typesNamespace() {
56
+ const { namespace, packageNamespace } = this.#options;
57
+ if (packageNamespace === undefined) {
58
+ return namespace;
59
+ }
60
+ return namespace === null ? packageNamespace : `${namespace}.${packageNamespace}`;
61
+ }
62
+ /** A name in the Apex namespace, as a reference: `Apex.Account`, or `Account` without one. */
63
+ qualify(parts) {
64
+ return qualifyApex(this.#options.namespace, parts);
65
+ }
66
+ /** A type declared with the file's types (its classes), as a reference. */
67
+ qualifyOwn(parts) {
68
+ const { packageNamespace } = this.#options;
69
+ return this.qualify(packageNamespace === undefined ? parts : [packageNamespace, ...parts]);
70
+ }
71
+ /**
72
+ * Resolves an Apex type reference (`Outer.Inner`, `Inner` from inside `Outer`, `Exception`,
73
+ * `System.Exception`) to its TypeScript type, as Apex looks names up. In order:
74
+ *
75
+ * 1. Through a namespace: the package's (`dlrs.RollupSummary`), which is one of its types, or the
76
+ * project's (`acme.Invoices`), which is the name without it.
77
+ * 2. The file's own types, in their declared spelling, since Apex names are case-insensitive but
78
+ * TypeScript names aren't.
79
+ * 3. A custom object (`Invoice__c`), named with the namespace of the code it's in.
80
+ * 4. A name a library type has (see library.ts): the project's types and SObjects, which shadow it,
81
+ * then the library's namespaces in their configured order, through registries.
82
+ * 5. A library type through its namespace (`System.Exception`). Apex's primitive types are System's
83
+ * (`System.String`), and a name in Schema that's no library type is an SObject (`Schema.Account`).
84
+ * 6. Any other name, as written in the Apex namespace (the project's classes in other files, and
85
+ * standard objects).
86
+ *
87
+ * An installed package's class can't see the project's classes, and its copy may not have what it
88
+ * refers to, so its names are its package's types when the copy has them (`RollupSummary`,
89
+ * `RollupController.Result`), then library types and SObjects, and `unknown` otherwise, never an error
90
+ * in code that isn't the project's to fix.
91
+ *
92
+ * A reference that looks through registries is a conditional type, written where it's used (see
93
+ * `#lookup`).
94
+ *
95
+ * @param base whether the reference is a class's base class, which an interface can only extend by
96
+ * name and which can't be `unknown`: a name a package's class can't find is `{}` instead
97
+ */
98
+ resolve(parts, { base = false } = {}) {
99
+ const unknown = base ? "{}" : "unknown";
100
+ const [first, ...rest] = parts;
101
+ if (first === undefined) {
102
+ return "";
103
+ }
104
+ const { projectNamespace, packageNamespace, className, nestedTypes } = this.#options;
105
+ // the namespace the code is in, whose custom objects it names without a namespace
106
+ const ownNamespace = packageNamespace ?? projectNamespace;
107
+ const inPackage = packageNamespace !== undefined;
108
+ // 1. through a namespace
109
+ if (inPackage && rest.length > 0 && first.toLowerCase() === packageNamespace.toLowerCase()) {
110
+ // the class itself, or another of the package's, which the copy may not have
111
+ return rest[0].toLowerCase() === className.toLowerCase()
112
+ ? this.resolve(rest, { base })
113
+ : this.#lookup(rest, this.#packageLookup(rest, unknown), base);
114
+ }
115
+ if (projectNamespace !== undefined && rest.length > 0 && first.toLowerCase() === projectNamespace.toLowerCase()) {
116
+ return this.resolve(rest, { base });
117
+ }
118
+ // 2. the file's own types
119
+ if (first.toLowerCase() === className.toLowerCase()) {
120
+ return this.qualifyOwn([className, ...rest.map((part) => nestedTypes.get(part.toLowerCase()) ?? part)]);
121
+ }
122
+ const nested = rest.length === 0 ? nestedTypes.get(first.toLowerCase()) : undefined;
123
+ if (nested !== undefined) {
124
+ return this.qualifyOwn([className, nested]);
125
+ }
126
+ if (rest.length === 0) {
127
+ // 3. a custom object, which a package's copy may refer to without its definition
128
+ const custom = recordName(first, ownNamespace);
129
+ if (custom !== first) {
130
+ return inPackage ? this.#lookup([custom], this.#sobjectLookup(custom, unknown), base) : this.qualify([custom]);
131
+ }
132
+ // 4. a name a library type has
133
+ const steps = apexLibrary.resolve(first);
134
+ if (!inPackage) {
135
+ return steps === undefined ? this.qualify([first]) : this.#lookup([first], this.#libraryLookup(first, steps, true), base);
136
+ }
137
+ return this.#lookup([first], this.#packageLookup([first], steps === undefined ? this.#sobjectLookup(first, unknown) : this.#libraryLookup(first, steps, false)), base);
138
+ }
139
+ // 5. a library type through its namespace
140
+ const namespace = apexLibrary.namespace(first);
141
+ const [name] = rest;
142
+ if (namespace !== undefined && name !== undefined && rest.length === 1) {
143
+ const primitive = namespace.name === "System" ? PRIMITIVE_TYPES[name.toLowerCase()] : undefined;
144
+ if (primitive !== undefined) {
145
+ return primitive;
146
+ }
147
+ const ref = apexLibrary.type(namespace.name, name);
148
+ if (ref !== undefined) {
149
+ return this.#libraryReference(ref);
150
+ }
151
+ if (namespace.sobjects) {
152
+ const sobject = recordName(name, ownNamespace);
153
+ return inPackage ? this.#lookup([sobject], this.#sobjectLookup(sobject, unknown), base) : this.qualify([sobject]);
154
+ }
155
+ }
156
+ // 6. any other name: in a package, another of its classes' nested types, or another package's type
157
+ return inPackage ? this.#lookup(parts, this.#packageLookup(parts, unknown), base) : this.qualify(parts);
158
+ }
159
+ /**
160
+ * The declarations the file's references need, in a block of their own, or "" without any: the
161
+ * library types they use (and their base classes), the registries they read, and the file's own type
162
+ * in `ownRegistry`, if given. Other files declare the same library types and registries, which merge.
163
+ */
164
+ declarations(ownName, ownRegistry) {
165
+ if (this.#libraryTypes.size === 0 && this.#registries.size === 0 && ownRegistry === undefined) {
166
+ // a package's registry is declared by its classes' files themselves, so its aliases can be all there is
167
+ return this.#aliasDeclarations();
168
+ }
169
+ // base classes are declared too
170
+ for (const ref of this.#libraryTypes.values()) {
171
+ const base = ref.type.kind === "class" ? ref.type.extends : undefined;
172
+ const [namespace, name] = base?.split(".") ?? [];
173
+ const baseRef = namespace !== undefined && name !== undefined ? apexLibrary.type(namespace, name) : undefined;
174
+ if (baseRef !== undefined) {
175
+ this.#libraryReference(baseRef);
176
+ }
177
+ }
178
+ const { namespace: apexNamespace } = this.#options;
179
+ const indent = apexNamespace === null ? "" : " ";
180
+ const lines = [];
181
+ for (const registry of new Set([...this.#registries, ...(ownRegistry === undefined ? [] : [ownRegistry])])) {
182
+ lines.push(registry === ownRegistry ? registration(indent, registry, [ownName]) : `${indent}interface ${registry} {}\n`);
183
+ }
184
+ const byNamespace = new Map();
185
+ for (const ref of this.#libraryTypes.values()) {
186
+ byNamespace.set(ref.namespace, [...(byNamespace.get(ref.namespace) ?? []), ref]);
187
+ }
188
+ for (const [namespace, refs] of [...byNamespace].sort(([a], [b]) => a.localeCompare(b))) {
189
+ lines.push(`${indent}${indent === "" ? "declare " : ""}namespace ${namespace} {\n`);
190
+ const enums = [];
191
+ for (const { name, type } of refs.sort((a, b) => a.name.localeCompare(b.name))) {
192
+ if (type.kind === "enum") {
193
+ enums.push(`${name}: ${type.values.length === 0 ? "never" : type.values.map((value) => JSON.stringify(value)).join(" | ")};`);
194
+ continue;
195
+ }
196
+ const bases = [
197
+ ...(type.kind === "class" && type.extends !== undefined ? [this.qualify(type.extends.split("."))] : []),
198
+ // its properties' JSON names aren't known exactly, so any can be read
199
+ ...(type.kind === "interface" || (type.kind === "class" && type.open) ? ["Record<string, unknown>"] : []),
200
+ ];
201
+ lines.push(`${indent} interface ${name}${bases.length === 0 ? "" : ` extends ${bases.join(", ")}`} {}\n`);
202
+ }
203
+ if (enums.length > 0) {
204
+ lines.push(`${indent} interface __lwcEnums {\n${enums.map((line) => `${indent} ${line}\n`).join("")}${indent} }\n`);
205
+ }
206
+ lines.push(`${indent}}\n`);
207
+ }
208
+ const libraryBlock = apexNamespace === null ? lines.join("") : `declare namespace ${apexNamespace} {\n${lines.join("")}}\n`;
209
+ return libraryBlock + this.#aliasDeclarations();
210
+ }
211
+ /** The file's aliases (see `#alias`), in a namespace named for its class, or "" without any. */
212
+ #aliasDeclarations() {
213
+ if (this.#aliases.size === 0) {
214
+ return "";
215
+ }
216
+ const aliases = [...this.#aliases].map(([type, name]) => ` type ${name} = ${type};\n`).join("");
217
+ return `declare namespace ${this.#aliasNamespace} {\n${aliases}}\n`;
218
+ }
219
+ /**
220
+ * The namespace the file's aliases are in: one of their own for the file's class, in the namespace of
221
+ * aliases for classes (`Apex.__lwcRefs.Billing`, `Apex.dlrs.__lwcRefs.RollupController`) or for SObject
222
+ * definitions (`Apex.__lwcSObjectRefs.Account`). A class has one file, and so does an SObject
223
+ * definition, so no other file declares aliases in it, even when a class and an SObject share a name.
224
+ * Being apart from the class's own namespace also keeps them out of editors' suggestions for it.
225
+ */
226
+ get #aliasNamespace() {
227
+ const { isSobject, className } = this.#options;
228
+ const scope = isSobject ? this.qualify(["__lwcSObjectRefs"]) : this.qualifyOwn(["__lwcRefs"]);
229
+ return `${scope}.${className}`;
230
+ }
231
+ /**
232
+ * A reference that looks through registries, of type `type`: written where it's used, in parentheses
233
+ * so it can be an array's element or part of an intersection; or, for a base class, as an alias, since
234
+ * an interface can only extend a type by name.
235
+ */
236
+ #lookup(parts, type, base) {
237
+ return base ? this.#alias(parts, type) : `(${type})`;
238
+ }
239
+ /** An alias for `type`, a base class's resolved type, named for the reference (`base`). */
240
+ #alias(parts, type) {
241
+ let name = this.#aliases.get(type);
242
+ if (name === undefined) {
243
+ let base = parts
244
+ .join("_")
245
+ .toLowerCase()
246
+ .replace(/[^\w$]/g, "_");
247
+ // names a type can't have, such as `object`, or a reserved word
248
+ if (TYPE_KEYWORDS.has(base) || RESERVED_WORDS.has(base)) {
249
+ base = `${base}_`;
250
+ }
251
+ const taken = new Set(this.#aliases.values());
252
+ name = base;
253
+ for (let n = 2; taken.has(name); n++) {
254
+ name = `${base}_${n}`;
255
+ }
256
+ this.#aliases.set(type, name);
257
+ }
258
+ return `${this.#aliasNamespace}.${name}`;
259
+ }
260
+ /** A library type, which is declared with the file's declarations. */
261
+ #libraryReference(ref) {
262
+ this.#libraryTypes.set(`${ref.namespace}.${ref.name}`.toLowerCase(), ref);
263
+ return ref.type.kind === "enum"
264
+ ? `${this.qualify([ref.namespace, "__lwcEnums"])}[${JSON.stringify(ref.name)}]`
265
+ : this.qualify([ref.namespace, ref.name]);
266
+ }
267
+ /**
268
+ * The SObjects' registry (`Apex.__lwcSObjects`), which every declared SObject adds itself to, by
269
+ * lower-cased name: what `registryLookup` reads to refer to an SObject that may not be declared.
270
+ */
271
+ get sobjectsRegistry() {
272
+ return this.#registry(SOBJECTS_REGISTRY);
273
+ }
274
+ /** A registry in the Apex namespace, which the file's declarations then declare. */
275
+ #registry(name) {
276
+ this.#registries.add(name);
277
+ return this.qualify([name]);
278
+ }
279
+ /**
280
+ * A name a library type has: the project's type of that name if it has one (unless `projectClasses` is
281
+ * false, in a package's class), else what `steps` find (an SObject, or the library type).
282
+ */
283
+ #libraryLookup(name, steps, projectClasses) {
284
+ const resolve = ([step, ...rest]) => {
285
+ if (step === undefined) {
286
+ return "never";
287
+ }
288
+ return step.kind === "type"
289
+ ? this.#libraryReference(step.ref)
290
+ : registryLookup(this.#registry(SOBJECTS_REGISTRY), name, resolve(rest));
291
+ };
292
+ return projectClasses ? registryLookup(this.#registry(CLASSES_REGISTRY), name, resolve(steps)) : resolve(steps);
293
+ }
294
+ /** An SObject that may not be declared: `unknown` when no file declares it, rather than an error. */
295
+ #sobjectLookup(name, unknown) {
296
+ return registryLookup(this.#registry(SOBJECTS_REGISTRY), name, unknown);
297
+ }
298
+ /**
299
+ * In a package's class, the package's type of a name (`RollupSummary`, `RollupController.Result`) when
300
+ * it has one, and otherwise `otherwise`. Its types are in its registry, which each of its classes' files
301
+ * adds itself and its nested types to, by lower-cased dotted name.
302
+ */
303
+ #packageLookup(path, otherwise) {
304
+ return registryLookup(this.qualifyOwn([CLASSES_REGISTRY]), path.join("."), otherwise);
305
+ }
306
+ }
@@ -0,0 +1,45 @@
1
+ import type { TextBuilder, SourceRange } from "../text-builder.ts";
2
+ export interface SObjectProperty {
3
+ name: string;
4
+ /** TypeScript type, with Apex types referenced in the namespace they're declared in (see `qualifyApex`) */
5
+ type: string;
6
+ nameRange?: SourceRange | null;
7
+ typeRange?: SourceRange | null;
8
+ /** JSDoc lines */
9
+ doc?: (string | undefined)[];
10
+ }
11
+ export interface SObjectInterface {
12
+ name: string;
13
+ nameRange?: SourceRange | null;
14
+ /** JSDoc lines */
15
+ doc?: (string | undefined)[];
16
+ properties: SObjectProperty[];
17
+ }
18
+ /**
19
+ * The standard fields of an object, by the kind its API name's suffix identifies. They match the types
20
+ * the Salesforce extensions' SObject definitions (`.sfdx/tools/sobjects`) give them, since a property
21
+ * declared in both must have the same type. Standard objects' fields vary, so only `Id` is declared.
22
+ * `owned` is whether records have an owner, which children of a master-detail relationship
23
+ * (`ControlledByParent` sharing) don't; only the owner's Id is declared (see below). The users who
24
+ * created and last modified a record are `User`s in `apexNamespace`.
25
+ */
26
+ export declare function standardFields(objectName: string, { owned, apexNamespace }: {
27
+ owned: boolean;
28
+ apexNamespace: string | null;
29
+ }): SObjectProperty[];
30
+ /**
31
+ * The TypeScript type of a field, or undefined when it can't be known from the field alone (such as a
32
+ * roll-up summary of a field's minimum) or the type isn't supported. Such fields are left undeclared
33
+ * rather than risk a type that conflicts with the generated SObject definitions.
34
+ */
35
+ export declare function fieldTsType(fieldType: string | undefined, summaryOperation: string | undefined): string | undefined;
36
+ /**
37
+ * Documentation shared by a field's schema module and its Apex property: its description, and the
38
+ * help text users see next to it.
39
+ */
40
+ export declare function fieldDoc(description: string | undefined, inlineHelpText: string | undefined): (string | undefined)[];
41
+ /**
42
+ * Declares SObject interfaces in the Apex namespace (or globally, when `namespace` is null). Interfaces
43
+ * merge across files, so each object and field file declares the part of a type it knows about.
44
+ */
45
+ export declare function addSObjectInterfaces(builder: TextBuilder, namespace: string | null, interfaces: SObjectInterface[]): void;
@@ -0,0 +1,141 @@
1
+ // SObject record types: the interfaces object and field metadata declare in the Apex namespace.
2
+ import { docTag, jsDoc } from "../jsdoc.js";
3
+ import { qualifyApex, registration, SOBJECTS_REGISTRY } from "./namespace.js";
4
+ /**
5
+ * The standard fields of an object, by the kind its API name's suffix identifies. They match the types
6
+ * the Salesforce extensions' SObject definitions (`.sfdx/tools/sobjects`) give them, since a property
7
+ * declared in both must have the same type. Standard objects' fields vary, so only `Id` is declared.
8
+ * `owned` is whether records have an owner, which children of a master-detail relationship
9
+ * (`ControlledByParent` sharing) don't; only the owner's Id is declared (see below). The users who
10
+ * created and last modified a record are `User`s in `apexNamespace`.
11
+ */
12
+ export function standardFields(objectName, { owned, apexNamespace }) {
13
+ const user = qualifyApex(apexNamespace, ["User"]);
14
+ let fields;
15
+ if (objectName.endsWith("__c")) {
16
+ fields = [
17
+ ["Id", "string"],
18
+ ["Name", "string"],
19
+ ["IsDeleted", "boolean"],
20
+ ["CreatedById", "string"],
21
+ ["CreatedBy", user],
22
+ ["CreatedDate", "string"],
23
+ ["LastModifiedById", "string"],
24
+ ["LastModifiedBy", user],
25
+ ["LastModifiedDate", "string"],
26
+ ["SystemModstamp", "string"],
27
+ // The owner (a user or a queue) is left to the SObject definition, whose type for it depends on the
28
+ // org: its targets, when the project has the object's describe, and `SObject` otherwise.
29
+ ...(owned ? [["OwnerId", "string"]] : []),
30
+ ];
31
+ }
32
+ else if (objectName.endsWith("__mdt")) {
33
+ fields = [
34
+ ["Id", "string"],
35
+ ["DeveloperName", "string"],
36
+ ["MasterLabel", "string"],
37
+ ["Label", "string"],
38
+ ["Language", "string"],
39
+ ["NamespacePrefix", "string"],
40
+ ["QualifiedApiName", "string"],
41
+ ["SystemModstamp", "string"],
42
+ ];
43
+ }
44
+ else if (objectName.endsWith("__e")) {
45
+ fields = [
46
+ ["ReplayId", "string"],
47
+ ["EventUuid", "string"],
48
+ ["CreatedById", "string"],
49
+ ["CreatedBy", user],
50
+ ["CreatedDate", "string"],
51
+ ];
52
+ }
53
+ else {
54
+ fields = [["Id", "string"]];
55
+ }
56
+ return fields.map(([name, type]) => ({ name, type }));
57
+ }
58
+ /**
59
+ * TypeScript types of field types, as LWC receives their values (see the Apex types in resolution.ts).
60
+ * Lookups hold the related record's Id; their related records are declared separately.
61
+ */
62
+ const FIELD_TYPES = {
63
+ autonumber: "string",
64
+ checkbox: "boolean",
65
+ currency: "number",
66
+ date: "string",
67
+ datetime: "string",
68
+ email: "string",
69
+ encryptedtext: "string",
70
+ hierarchy: "string",
71
+ html: "string",
72
+ lookup: "string",
73
+ masterdetail: "string",
74
+ metadatarelationship: "string",
75
+ multiselectpicklist: "string",
76
+ number: "number",
77
+ percent: "number",
78
+ phone: "string",
79
+ picklist: "string",
80
+ text: "string",
81
+ textarea: "string",
82
+ longtextarea: "string",
83
+ time: "string",
84
+ url: "string",
85
+ };
86
+ /**
87
+ * The TypeScript type of a field, or undefined when it can't be known from the field alone (such as a
88
+ * roll-up summary of a field's minimum) or the type isn't supported. Such fields are left undeclared
89
+ * rather than risk a type that conflicts with the generated SObject definitions.
90
+ */
91
+ export function fieldTsType(fieldType, summaryOperation) {
92
+ const type = fieldType?.toLowerCase();
93
+ if (type === "summary") {
94
+ const operation = summaryOperation?.toLowerCase();
95
+ return operation === "count" || operation === "sum" ? "number" : undefined;
96
+ }
97
+ return type === undefined ? undefined : FIELD_TYPES[type];
98
+ }
99
+ /**
100
+ * Documentation shared by a field's schema module and its Apex property: its description, and the
101
+ * help text users see next to it.
102
+ */
103
+ export function fieldDoc(description, inlineHelpText) {
104
+ return [docTag("description", description), docTag("inlineHelpText", inlineHelpText)];
105
+ }
106
+ /**
107
+ * Declares SObject interfaces in the Apex namespace (or globally, when `namespace` is null). Interfaces
108
+ * merge across files, so each object and field file declares the part of a type it knows about.
109
+ */
110
+ export function addSObjectInterfaces(builder, namespace, interfaces) {
111
+ const indent = namespace === null ? "" : " ";
112
+ if (namespace !== null) {
113
+ builder.write(`declare namespace ${namespace} {\n`);
114
+ }
115
+ for (const { name, nameRange, doc, properties } of interfaces) {
116
+ builder.write(`${jsDoc(indent, doc ?? [])}${indent}interface `);
117
+ builder.writeMapped(name, nameRange ?? null);
118
+ if (properties.length === 0) {
119
+ builder.write(" {}\n");
120
+ continue;
121
+ }
122
+ builder.write(" {\n");
123
+ for (const property of properties) {
124
+ builder.write(`${jsDoc(`${indent} `, property.doc ?? [])}${indent} `);
125
+ builder.writeMapped(property.name, property.nameRange ?? null);
126
+ builder.write("?: ");
127
+ builder.writeMapped(property.type, property.typeRange ?? null);
128
+ builder.write(";\n");
129
+ }
130
+ builder.write(`${indent}}\n`);
131
+ }
132
+ // every SObject registers, so references can look one up without an error when it isn't declared:
133
+ // those whose names library types have shadow them (`Location`, over `System.Location`), and installed
134
+ // packages' classes fall back to them
135
+ if (interfaces.length > 0) {
136
+ builder.write(registration(indent, SOBJECTS_REGISTRY, interfaces.map(({ name }) => name)));
137
+ }
138
+ if (namespace !== null) {
139
+ builder.write("}\n");
140
+ }
141
+ }
@@ -0,0 +1,16 @@
1
+ import type { ConvertOptions, InaccessibleApexProperties } from "./types.ts";
2
+ /** `convert`'s options as the converters use them: defaults applied, and an empty namespace as none. */
3
+ export interface ConversionContext {
4
+ /** The namespace Apex types are declared in (`"Apex"` by default), or `null` to declare them globally. */
5
+ readonly apexNamespace: string | null;
6
+ /** What becomes of Apex classes' fields LWC can't read (`"include as deprecated"` by default). */
7
+ readonly inaccessibleApexProperties: InaccessibleApexProperties;
8
+ /** The project's namespace, or undefined without one. */
9
+ readonly projectNamespace: string | undefined;
10
+ }
11
+ /**
12
+ * Applies `convert`'s defaults, and throws for a namespace that would make the declarations invalid: an
13
+ * `apexNamespace` that isn't a dotted TypeScript identifier, or a `projectNamespace` that isn't a
14
+ * namespace prefix, and for an `inaccessibleApexProperties` that isn't one of its values.
15
+ */
16
+ export declare function conversionContext(options?: ConvertOptions): ConversionContext;
@@ -0,0 +1,28 @@
1
+ import { DEFAULT_APEX_NAMESPACE } from "./apex/namespace.js";
2
+ import { isIdentifier, NAMESPACE_RESERVED_WORDS } from "./identifiers.js";
3
+ const INACCESSIBLE_APEX_PROPERTIES = ["include", "exclude", "include as deprecated"];
4
+ /**
5
+ * A Salesforce namespace prefix: letters, digits and single underscores, starting with a letter and not
6
+ * ending with an underscore.
7
+ */
8
+ const NAMESPACE_PREFIX = /^[A-Za-z][A-Za-z0-9]*(?:_[A-Za-z0-9]+)*$/;
9
+ /**
10
+ * Applies `convert`'s defaults, and throws for a namespace that would make the declarations invalid: an
11
+ * `apexNamespace` that isn't a dotted TypeScript identifier, or a `projectNamespace` that isn't a
12
+ * namespace prefix, and for an `inaccessibleApexProperties` that isn't one of its values.
13
+ */
14
+ export function conversionContext(options) {
15
+ const apexNamespace = options?.apexNamespace === undefined ? DEFAULT_APEX_NAMESPACE : options.apexNamespace;
16
+ if (apexNamespace !== null && !apexNamespace.split(".").every((part) => isIdentifier(part) && !NAMESPACE_RESERVED_WORDS.has(part))) {
17
+ throw new TypeError(`apexNamespace ${JSON.stringify(apexNamespace)} isn't a namespace TypeScript can declare; use dotted identifiers (such as "Acme.Apex"), or null`);
18
+ }
19
+ const inaccessibleApexProperties = options?.inaccessibleApexProperties ?? "include as deprecated";
20
+ if (!INACCESSIBLE_APEX_PROPERTIES.includes(inaccessibleApexProperties)) {
21
+ throw new TypeError(`inaccessibleApexProperties must be one of ${INACCESSIBLE_APEX_PROPERTIES.map((value) => JSON.stringify(value)).join(", ")}, but got ${JSON.stringify(inaccessibleApexProperties)}`);
22
+ }
23
+ const projectNamespace = options?.projectNamespace?.trim() || undefined;
24
+ if (projectNamespace !== undefined && !NAMESPACE_PREFIX.test(projectNamespace)) {
25
+ throw new TypeError(`projectNamespace ${JSON.stringify(projectNamespace)} isn't a Salesforce namespace prefix`);
26
+ }
27
+ return { apexNamespace, inaccessibleApexProperties, projectNamespace };
28
+ }
@@ -0,0 +1,7 @@
1
+ import type { ConversionContext } from "../context.ts";
2
+ import type { ApexClassComponent, ConversionOutput } from "../types.ts";
3
+ /**
4
+ * Converts an Apex class. The Apex parser loads on the first class (reading its WebAssembly from disk),
5
+ * once: concurrent conversions share the load, and later ones reuse the parser.
6
+ */
7
+ export declare function ApexClass(comp: ApexClassComponent, context: ConversionContext): Promise<ConversionOutput>;
@@ -0,0 +1,23 @@
1
+ import { getApexParser } from "web-tree-sitter-sfapex";
2
+ import { convertApexFile } from "../apex/file.js";
3
+ import { importNameParts, metadataName } from "../names.js";
4
+ let apexParser;
5
+ /**
6
+ * Converts an Apex class. The Apex parser loads on the first class (reading its WebAssembly from disk),
7
+ * once: concurrent conversions share the load, and later ones reuse the parser.
8
+ */
9
+ export async function ApexClass(comp, context) {
10
+ apexParser ??= getApexParser();
11
+ const prsr = await apexParser;
12
+ const isSobject = comp.isSobject === true;
13
+ return (convertApexFile(prsr, comp.content, {
14
+ isSobject,
15
+ apexNamespace: context.apexNamespace,
16
+ projectNamespace: context.projectNamespace,
17
+ // a class of another namespace (an installed package's) is named for it, as `dlrs__RollupController.cls`;
18
+ // an SObject definition's name is its object's
19
+ packageNamespace: isSobject ? undefined : importNameParts(metadataName(comp.path), context.projectNamespace).namespace,
20
+ polymorphicTargets: new Map(Object.entries(isSobject ? (comp.polymorphicTargets ?? {}) : {}).map(([name, targets]) => [name.toLowerCase(), targets])),
21
+ inaccessibleProperties: context.inaccessibleApexProperties,
22
+ }) ?? { text: "", mapData: [] });
23
+ }
@@ -0,0 +1,3 @@
1
+ import type { ConversionContext } from "../context.ts";
2
+ import type { Component, ConversionOutput } from "../types.ts";
3
+ export declare function ContentAsset(component: Component, context: ConversionContext): ConversionOutput;