@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,21 @@
1
+ // Parsing component source (JavaScript or TypeScript), for the one fact sf2ts reads from it: its default
2
+ // export's name.
3
+ import { tsPlugin } from "@sveltejs/acorn-typescript";
4
+ import { Parser } from "acorn";
5
+ /**
6
+ * acorn with TypeScript syntax, which also parses decorators (`@api`, `@wire`). JavaScript components
7
+ * are parsed with it too, since plain acorn rejects decorators.
8
+ */
9
+ const ComponentParser = Parser.extend(tsPlugin());
10
+ /**
11
+ * Parses a component's module, or returns undefined when it doesn't parse (such as a file being edited),
12
+ * so callers find nothing in it rather than guessing.
13
+ */
14
+ export function parseComponentSource(source) {
15
+ try {
16
+ return ComponentParser.parse(source, { ecmaVersion: "latest", sourceType: "module" });
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ }
@@ -0,0 +1,36 @@
1
+ import type { ConversionContext } from "../context.ts";
2
+ import type { ConversionOutput, LightningComponentBundleComponent } from "../types.ts";
3
+ /**
4
+ * Declares the `c/<name>` module that LWC components import another component through, from the
5
+ * component's `<name>.js-meta.xml`. The declaration re-exports the component's own module by type,
6
+ * so its default and named exports keep their types and definitions, without listing them:
7
+ *
8
+ * ```ts
9
+ * declare module "c/myComponent" {
10
+ * const myComponent: typeof import("./myComponent.js");
11
+ * export = myComponent;
12
+ * }
13
+ * ```
14
+ *
15
+ * The declaration's variable is named after the module's default export (`component.defaultExportName`),
16
+ * which editors suggest when auto-importing it, or after the bundle without one.
17
+ *
18
+ * A bundle of another namespace (an installed package's, named `dlrs__manageRollups`) is imported
19
+ * through its namespace, as `dlrs/manageRollups`, and only when it's exposed (`<isExposed>true`); other
20
+ * namespaces can't import it otherwise, so it declares nothing. A template's tag names its namespace in
21
+ * lower case (`<sbqq-quote-line>`), so a namespace that isn't (`SBQQ`) is declared that way too.
22
+ */
23
+ export declare function convertBundleMetadata(component: LightningComponentBundleComponent, context: ConversionContext): ConversionOutput;
24
+ /**
25
+ * A bundle's API version, from its `.js-meta.xml`, or undefined when it has none that can be read (such
26
+ * as one being written).
27
+ */
28
+ export declare function bundleApiVersion(metadata: string): number | undefined;
29
+ /**
30
+ * Finds the name of a module's default export: a named class or function, an identifier
31
+ * (`export default recordCard;`), or `export { recordCard as default }`, optionally re-exported from
32
+ * another module. Returns undefined for anonymous defaults (`export default class extends ...`, arrow
33
+ * functions, objects, calls, any other expression), `export { default } from "..."`, modules without a
34
+ * default export, and modules that don't parse.
35
+ */
36
+ export declare function findDefaultExportName(source: string): string | undefined;
@@ -0,0 +1,93 @@
1
+ import { importNameParts, metadataName } from "../names.js";
2
+ import { parseComponentSource } from "./component-source.js";
3
+ import { variableName } from "../identifiers.js";
4
+ import { docParagraphs, docTag, jsDoc } from "../jsdoc.js";
5
+ import { TextBuilder } from "../text-builder.js";
6
+ import { parseXml } from "../xml.js";
7
+ /**
8
+ * Declares the `c/<name>` module that LWC components import another component through, from the
9
+ * component's `<name>.js-meta.xml`. The declaration re-exports the component's own module by type,
10
+ * so its default and named exports keep their types and definitions, without listing them:
11
+ *
12
+ * ```ts
13
+ * declare module "c/myComponent" {
14
+ * const myComponent: typeof import("./myComponent.js");
15
+ * export = myComponent;
16
+ * }
17
+ * ```
18
+ *
19
+ * The declaration's variable is named after the module's default export (`component.defaultExportName`),
20
+ * which editors suggest when auto-importing it, or after the bundle without one.
21
+ *
22
+ * A bundle of another namespace (an installed package's, named `dlrs__manageRollups`) is imported
23
+ * through its namespace, as `dlrs/manageRollups`, and only when it's exposed (`<isExposed>true`); other
24
+ * namespaces can't import it otherwise, so it declares nothing. A template's tag names its namespace in
25
+ * lower case (`<sbqq-quote-line>`), so a namespace that isn't (`SBQQ`) is declared that way too.
26
+ */
27
+ export function convertBundleMetadata(component, context) {
28
+ // read leniently: a .js-meta.xml being written still declares its bundle's module
29
+ const root = parseXml(component.content);
30
+ const bundle = root?.name === "LightningComponentBundle" ? root : undefined;
31
+ const fileName = metadataName(component.path);
32
+ const { namespace, name } = importNameParts(fileName, context.projectNamespace);
33
+ if (namespace !== undefined && bundle?.childText("isExposed")?.trim() !== "true") {
34
+ return { text: "", mapData: [] };
35
+ }
36
+ const label = bundle?.childText("masterLabel");
37
+ const description = docTag("description", bundle?.childText("description"));
38
+ // go-to-definition on the module's name lands on the label, or on the root element without one
39
+ const range = bundle?.child("masterLabel")?.contentRange ?? bundle?.nameRange;
40
+ const variable = variableName(component.defaultExportName ?? name);
41
+ const builder = new TextBuilder();
42
+ const modules = new Set([`${namespace ?? "c"}/${name}`, ...(namespace === undefined ? [] : [`${namespace.toLowerCase()}/${name}`])]);
43
+ for (const module of modules) {
44
+ builder.write(jsDoc("", docParagraphs(label, description)));
45
+ builder.write("declare module ");
46
+ builder.writeMapped(JSON.stringify(module), range);
47
+ builder.write(` {
48
+ const ${variable}: typeof import("./${fileName}.js");
49
+ export = ${variable};
50
+ }
51
+ `);
52
+ }
53
+ return builder.output();
54
+ }
55
+ /**
56
+ * A bundle's API version, from its `.js-meta.xml`, or undefined when it has none that can be read (such
57
+ * as one being written).
58
+ */
59
+ export function bundleApiVersion(metadata) {
60
+ const root = parseXml(metadata);
61
+ const version = root?.name === "LightningComponentBundle" ? Number(root.childText("apiVersion")?.trim() || NaN) : NaN;
62
+ return Number.isFinite(version) ? version : undefined;
63
+ }
64
+ /**
65
+ * Finds the name of a module's default export: a named class or function, an identifier
66
+ * (`export default recordCard;`), or `export { recordCard as default }`, optionally re-exported from
67
+ * another module. Returns undefined for anonymous defaults (`export default class extends ...`, arrow
68
+ * functions, objects, calls, any other expression), `export { default } from "..."`, modules without a
69
+ * default export, and modules that don't parse.
70
+ */
71
+ export function findDefaultExportName(source) {
72
+ for (const statement of parseComponentSource(source)?.body ?? []) {
73
+ if (statement.type === "ExportDefaultDeclaration") {
74
+ const { declaration } = statement;
75
+ const name = declaration.type === "Identifier"
76
+ ? declaration.name
77
+ : declaration.type === "ClassDeclaration" || declaration.type === "FunctionDeclaration"
78
+ ? declaration.id?.name
79
+ : undefined;
80
+ // `undefined` is an identifier, but no name for a variable
81
+ return name === "undefined" ? undefined : name;
82
+ }
83
+ if (statement.type === "ExportNamedDeclaration") {
84
+ for (const { local, exported } of statement.specifiers) {
85
+ const exportedName = exported.type === "Identifier" ? exported.name : exported.value;
86
+ if (exportedName === "default" && local.type === "Identifier" && local.name !== "default") {
87
+ return local.name;
88
+ }
89
+ }
90
+ }
91
+ }
92
+ return undefined;
93
+ }
@@ -0,0 +1,47 @@
1
+ import type { ChildNode, ComplexExpression, ElseBlock, ElseifBlock, Expression, ForEach, ForOf, If, IfBlock, Literal, Root } from "@lwc/template-compiler/dist/shared/types.js";
2
+ export type { BaseElement, ChildNode, ElseifBlock, ForEach, ForOf, If, IfBlock, Property, Root, ScopedSlotFragment, SourceLocation, Text, } from "@lwc/template-compiler/dist/shared/types.js";
3
+ /** Whether a component at an API version has complex expressions: the latest does, when it's not known. */
4
+ export declare function hasComplexExpressions(apiVersion: number | undefined): boolean;
5
+ /**
6
+ * A parsed expression: an ESTree node, which the compiler's types leave without its offsets. In an
7
+ * attribute or directive they're relative to its `{`. Where they're relative to in text depends on the API
8
+ * version (see TemplateSource.textExpression).
9
+ */
10
+ export type TemplateExpression = (Expression | ComplexExpression) & {
11
+ start: number;
12
+ end: number;
13
+ };
14
+ /** What an attribute, directive or text has: an expression, or a literal the compiler made of it. */
15
+ export type TemplateValue = Literal | Expression | ComplexExpression;
16
+ /** Whether a value is an expression, rather than the literal the compiler made of an attribute or text. */
17
+ export declare function isExpression(value: TemplateValue): value is TemplateExpression;
18
+ /** Nodes that don't create elements, so their content is their parent's content. */
19
+ export declare function isBlock(node: ChildNode): node is IfBlock | ElseifBlock | ElseBlock | If | ForEach | ForOf;
20
+ /** A diagnostic from the compiler, whose own type doesn't resolve either. */
21
+ export interface CompilerDiagnostic {
22
+ message: string;
23
+ level: number;
24
+ location?: {
25
+ start?: number;
26
+ length?: number;
27
+ };
28
+ }
29
+ /** A parsed template: as much of it as the compiler could read, and its errors, without its warnings. */
30
+ export interface ParsedTemplate {
31
+ root: Root | undefined;
32
+ errors: CompilerDiagnostic[];
33
+ }
34
+ /** What sf2ts uses the compiler for, which is where it's reached from. */
35
+ export interface TemplateCompiler {
36
+ /** Parses a template at an API version (see parseConfig). */
37
+ parse(source: string, apiVersion: number | undefined): ParsedTemplate;
38
+ /**
39
+ * The expression as LWC's codegen binds it: an ESTree node with each of the component's members wrapped
40
+ * in a member expression of `instance`. It throws for what only codegen rejects.
41
+ */
42
+ bind(expression: TemplateExpression, isLocal: (name: string) => boolean, instance: string, complexExpressions: boolean): unknown;
43
+ /** The module of a component's tag: `c-base-card` is `c/baseCard`. */
44
+ tagModule(tag: string): string;
45
+ }
46
+ /** Loads the compiler, on the first template. */
47
+ export declare function loadTemplateCompiler(): Promise<TemplateCompiler>;
@@ -0,0 +1,46 @@
1
+ /** The API version that complex expressions start at. */
2
+ const COMPLEX_EXPRESSIONS_API_VERSION = 66;
3
+ /** Whether a component at an API version has complex expressions: the latest does, when it's not known. */
4
+ export function hasComplexExpressions(apiVersion) {
5
+ return apiVersion === undefined || apiVersion >= COMPLEX_EXPRESSIONS_API_VERSION;
6
+ }
7
+ /**
8
+ * How a template is parsed: at its component's API version, as the compiler gates what's valid by it, or
9
+ * without one (a component whose metadata can't be read) at the compiler's latest. Complex expressions
10
+ * also need their flag, as it changes what's valid at older versions: with it, `"{x}"` is a binding, rather
11
+ * than an ambiguity the compiler reports.
12
+ */
13
+ function parseConfig(apiVersion) {
14
+ return {
15
+ experimentalComplexExpressions: hasComplexExpressions(apiVersion),
16
+ enableDynamicComponents: true,
17
+ enableLwcOn: true,
18
+ ...(apiVersion !== undefined && { apiVersion }),
19
+ };
20
+ }
21
+ /** Whether a value is an expression, rather than the literal the compiler made of an attribute or text. */
22
+ export function isExpression(value) {
23
+ return "start" in value;
24
+ }
25
+ /** Nodes that don't create elements, so their content is their parent's content. */
26
+ export function isBlock(node) {
27
+ return ["IfBlock", "ElseifBlock", "ElseBlock", "If", "ForEach", "ForOf"].includes(node.type);
28
+ }
29
+ /** Whether the compiler's diagnostic is an error. Its levels: 0 fatal, 1 error, 2 warning, 3 log. */
30
+ function isError(diagnostic) {
31
+ return diagnostic.level <= 1;
32
+ }
33
+ let compiler;
34
+ /** Loads the compiler, on the first template. */
35
+ export async function loadTemplateCompiler() {
36
+ compiler ??= import("@lwc/template-compiler");
37
+ const { bindExpression, kebabcaseToCamelcase, parse } = await compiler;
38
+ return {
39
+ parse(source, apiVersion) {
40
+ const { root, warnings } = parse(source, parseConfig(apiVersion));
41
+ return { root, errors: warnings.filter(isError) };
42
+ },
43
+ bind: (expression, isLocal, instance, complexExpressions) => bindExpression(expression, (node) => isLocal(node.name), instance, complexExpressions),
44
+ tagModule: kebabcaseToCamelcase,
45
+ };
46
+ }
@@ -0,0 +1,6 @@
1
+ import type { LightningComponent } from "@lwc-bolts/lightning-base-components";
2
+ /**
3
+ * A base component's types, named after `alias`: its properties (`<alias>`, with the host element's),
4
+ * and when it has any, its methods (`<alias>Methods`, which refs use).
5
+ */
6
+ export declare function baseComponentTypes(alias: string, component: LightningComponent): string;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * A base component's types, named after `alias`: its properties (`<alias>`, with the host element's),
3
+ * and when it has any, its methods (`<alias>Methods`, which refs use).
4
+ */
5
+ export function baseComponentTypes(alias, component) {
6
+ const members = (record, optional) => Object.entries(record)
7
+ .map(([name, { type, description }]) => {
8
+ const doc = description ? ` /** ${description.replace(/\*\//g, "*\\/")} */\n` : "";
9
+ return `${doc} ${JSON.stringify(name)}${optional ? "?" : ""}: ${type};\n`;
10
+ })
11
+ .join("");
12
+ let types = `type ${alias} = {\n${members(component.properties, true)}} & __lwcHostProperties;\n`;
13
+ if (Object.keys(component.methods).length > 0) {
14
+ types += `type ${alias}Methods = {\n${members(component.methods, false)}};\n`;
15
+ }
16
+ return types;
17
+ }
@@ -0,0 +1,30 @@
1
+ import type { LightningComponent } from "@lwc-bolts/lightning-base-components";
2
+ import type { BaseElement } from "./ast.ts";
3
+ import type { Writer } from "./writer.ts";
4
+ /** The type aliases of the components a template uses, each declared once. */
5
+ export declare class ComponentTypes {
6
+ #private;
7
+ constructor(baseComponents: Readonly<Record<string, LightningComponent>>);
8
+ /** The alias of a child component's class: `module` is its module, such as `c/baseCard` or an installed package's `dlrs/manageRollups`. */
9
+ component(module: string, node: BaseElement): string;
10
+ /** The alias of a base component's properties. */
11
+ base(tag: string): string;
12
+ /**
13
+ * Declares the aliases. A tag maps to both the module, so a component that doesn't exist is reported on
14
+ * it and hovering it shows the component's label, and its default export, so go-to-definition opens the
15
+ * class rather than the module's .js-meta.xml.
16
+ */
17
+ write(out: Writer): void;
18
+ }
19
+ /** `lwc:ref` names, with the types of the elements they name and where the template names them. */
20
+ export declare class TemplateRefs {
21
+ #private;
22
+ add(name: string, type: string, optional: boolean, position: number | undefined): void;
23
+ /**
24
+ * Augments the component's module so `this.refs` has them. Every template of a bundle declares the same
25
+ * `refs` type, over the `__LwcRefs` interface that each adds its own refs to, so a bundle's templates
26
+ * don't conflict. It's a mapped type over the interface, since `LightningElement`'s `refs` has an index
27
+ * signature, which an object type satisfies and an interface doesn't.
28
+ */
29
+ write(out: Writer, componentModule: string): void;
30
+ }
@@ -0,0 +1,82 @@
1
+ import { MapFeature } from "../../text-builder.js";
2
+ import { baseComponentTypes } from "./base-components.js";
3
+ /** The type aliases of the components a template uses, each declared once. */
4
+ export class ComponentTypes {
5
+ /** Child components by module name, with where the first tag is. */
6
+ #components = new Map();
7
+ /** Base components by tag. */
8
+ #lightning = new Map();
9
+ #baseComponents;
10
+ constructor(baseComponents) {
11
+ this.#baseComponents = baseComponents;
12
+ }
13
+ /** The alias of a child component's class: `module` is its module, such as `c/baseCard` or an installed package's `dlrs/manageRollups`. */
14
+ component(module, node) {
15
+ let component = this.#components.get(module);
16
+ if (component === undefined) {
17
+ // the tag's name, after its `<`, so a component that doesn't exist is reported on the tag
18
+ component = { alias: `__lwcComponent${this.#components.size}`, position: node.location.start + 1, length: node.name.length };
19
+ this.#components.set(module, component);
20
+ }
21
+ return component.alias;
22
+ }
23
+ /** The alias of a base component's properties. */
24
+ base(tag) {
25
+ let alias = this.#lightning.get(tag);
26
+ if (alias === undefined) {
27
+ alias = `__lwcLightning${this.#lightning.size}`;
28
+ this.#lightning.set(tag, alias);
29
+ }
30
+ return alias;
31
+ }
32
+ /**
33
+ * Declares the aliases. A tag maps to both the module, so a component that doesn't exist is reported on
34
+ * it and hovering it shows the component's label, and its default export, so go-to-definition opens the
35
+ * class rather than the module's .js-meta.xml.
36
+ */
37
+ write(out) {
38
+ for (const [module, { alias, position, length }] of this.#components) {
39
+ out.write(`type ${alias} = typeof import(`);
40
+ out.writeAt(JSON.stringify(module), position, length, MapFeature.All & ~MapFeature.Definition);
41
+ out.write(").");
42
+ out.writeAt("default", position, length, MapFeature.Definition);
43
+ out.write(";\n");
44
+ }
45
+ for (const [tag, alias] of this.#lightning) {
46
+ out.write(baseComponentTypes(alias, this.#baseComponents[tag]));
47
+ }
48
+ }
49
+ }
50
+ /** `lwc:ref` names, with the types of the elements they name and where the template names them. */
51
+ export class TemplateRefs {
52
+ #refs = new Map();
53
+ add(name, type, optional, position) {
54
+ const ref = this.#refs.get(name) ?? { types: new Set(), optional: false, position };
55
+ ref.types.add(type);
56
+ ref.optional ||= optional;
57
+ this.#refs.set(name, ref);
58
+ }
59
+ /**
60
+ * Augments the component's module so `this.refs` has them. Every template of a bundle declares the same
61
+ * `refs` type, over the `__LwcRefs` interface that each adds its own refs to, so a bundle's templates
62
+ * don't conflict. It's a mapped type over the interface, since `LightningElement`'s `refs` has an index
63
+ * signature, which an object type satisfies and an interface doesn't.
64
+ */
65
+ write(out, componentModule) {
66
+ if (this.#refs.size === 0) {
67
+ return;
68
+ }
69
+ out.write(`declare module ${JSON.stringify(componentModule)} {
70
+ export default interface __LwcTemplate {
71
+ refs: { [K in keyof __LwcRefs]: __LwcRefs[K] } | undefined;
72
+ }
73
+ export interface __LwcRefs {
74
+ `);
75
+ for (const [name, { types, optional, position }] of this.#refs) {
76
+ out.write(" ");
77
+ out.writeAt(name, position, name.length);
78
+ out.write(`${optional ? "?" : ""}: ${[...types].join(" | ")};\n`);
79
+ }
80
+ out.write(" }\n}\n");
81
+ }
82
+ }
@@ -0,0 +1,16 @@
1
+ import type { TemplateCompiler, TemplateExpression } from "./ast.ts";
2
+ /** A name in an expression that's a member of the component. */
3
+ export interface ComponentName {
4
+ start: number;
5
+ end: number;
6
+ /** Whether it's a shorthand property (`{ a }`), which needs its key spelled out to be prefixed. */
7
+ shorthand: boolean;
8
+ }
9
+ /**
10
+ * The names in an expression that are members of the component, in order, as LWC's codegen binds them.
11
+ * `bind` wraps each one in a member expression of an instance it makes up, which has no offsets, while
12
+ * the name itself keeps its own. It throws for an expression that only codegen rejects.
13
+ */
14
+ export declare function componentNames(expression: TemplateExpression, isLocal: (name: string) => boolean, complexExpressions: boolean, bind: TemplateCompiler["bind"]): ComponentName[];
15
+ /** Whether an expression is one that's safe anywhere: others are parenthesized where they're written. */
16
+ export declare function isSimple(expression: TemplateExpression): boolean;
@@ -0,0 +1,33 @@
1
+ function isNode(value) {
2
+ return value !== null && typeof value === "object" && typeof value.type === "string";
3
+ }
4
+ function children(node) {
5
+ return Object.values(node).flat().filter(isNode);
6
+ }
7
+ /** Marks the names LWC binds to the component in an expression: nothing else is called this. */
8
+ const INSTANCE = "$cmp";
9
+ /**
10
+ * The names in an expression that are members of the component, in order, as LWC's codegen binds them.
11
+ * `bind` wraps each one in a member expression of an instance it makes up, which has no offsets, while
12
+ * the name itself keeps its own. It throws for an expression that only codegen rejects.
13
+ */
14
+ export function componentNames(expression, isLocal, complexExpressions, bind) {
15
+ const names = [];
16
+ const visit = (node, parent) => {
17
+ const object = node.type === "MemberExpression" ? node.object : undefined;
18
+ if (object?.name === INSTANCE && object.start === undefined) {
19
+ const { start, end } = node.property;
20
+ names.push({ start, end, shorthand: parent?.type === "Property" && parent.shorthand === true });
21
+ return;
22
+ }
23
+ for (const child of children(node)) {
24
+ visit(child, node);
25
+ }
26
+ };
27
+ visit(bind(expression, isLocal, INSTANCE, complexExpressions), undefined);
28
+ return names.sort((a, b) => a.start - b.start);
29
+ }
30
+ /** Whether an expression is one that's safe anywhere: others are parenthesized where they're written. */
31
+ export function isSimple(expression) {
32
+ return expression.type === "Identifier" || expression.type === "MemberExpression";
33
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Declarations the converted code can use, by the name each declares. A template's TypeScript declares
3
+ * the ones it uses, after its code.
4
+ */
5
+ export declare const TEMPLATE_HELPERS: Readonly<Record<string, string>>;
6
+ /**
7
+ * The declarations of the helpers `text` uses, in TEMPLATE_HELPERS' order, in one pass over it. Helpers
8
+ * only use themselves, so the ones `text` names are all it needs.
9
+ */
10
+ export declare function helperDeclarations(text: string): string;
@@ -0,0 +1,47 @@
1
+ // Utility types templates might use, which are dynamically added to each component that uses them
2
+ /**
3
+ * Declarations the converted code can use, by the name each declares. A template's TypeScript declares
4
+ * the ones it uses, after its code.
5
+ */
6
+ export const TEMPLATE_HELPERS = {
7
+ __lwcElement: `type __lwcElement<T extends string> = T extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[T] : HTMLElement;
8
+ `,
9
+ __lwcSvgElement: `type __lwcSvgElement<T extends string> = T extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[T] : SVGElement;
10
+ `,
11
+ __lwcMathElement: `type __lwcMathElement<T extends string> = T extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[T] : MathMLElement;
12
+ `,
13
+ __lwcHostProperties: `type __lwcHostProperties = Partial<
14
+ Pick<HTMLElement, "id" | "title" | "hidden" | "tabIndex" | "accessKey" | "dir" | "lang" | "draggable" | "spellcheck" | "autofocus">
15
+ > &
16
+ Partial<ARIAMixin>;
17
+ `,
18
+ __lwcProperties: `// \`constructor\` is a property of LightningElement, which an object literal's own constructor doesn't match
19
+ type __lwcProperties<T> = Partial<Omit<T, "constructor">>;
20
+ `,
21
+ __lwcSlots: `declare global {
22
+ /** The slots of the components whose templates are in the program, which each template adds its own to. */
23
+ interface __LwcSlots {}
24
+ }
25
+ /** The slots of the component M (such as "c/baseCard"), or any slot when they aren't known. */
26
+ type __lwcSlots<M extends string, K = keyof __LwcSlots> = \`\${M} *\` extends keyof __LwcSlots
27
+ ? string
28
+ : M extends keyof __LwcSlots
29
+ ? K extends \`\${M} \${infer S}\`
30
+ ? S
31
+ : never
32
+ : string;
33
+ `,
34
+ };
35
+ /** Finds each helper's name as a whole word (`__lwcElement`, not `__lwcElements`). */
36
+ const HELPER_NAMES = new RegExp(`\\b(?:${Object.keys(TEMPLATE_HELPERS).join("|")})\\b`, "g");
37
+ /**
38
+ * The declarations of the helpers `text` uses, in TEMPLATE_HELPERS' order, in one pass over it. Helpers
39
+ * only use themselves, so the ones `text` names are all it needs.
40
+ */
41
+ export function helperDeclarations(text) {
42
+ const used = new Set(text.match(HELPER_NAMES));
43
+ return Object.keys(TEMPLATE_HELPERS)
44
+ .filter((name) => used.has(name))
45
+ .map((name) => TEMPLATE_HELPERS[name])
46
+ .join("");
47
+ }
@@ -0,0 +1,24 @@
1
+ import { type BaseElement, type Root } from "./ast.ts";
2
+ import type { TemplateSource } from "./source.ts";
3
+ /** A slot name in a slot check, with `(default)` for the unnamed slot. */
4
+ export declare const DEFAULT_SLOT = "(default)";
5
+ /** The slots a template declares, `(default)` for the unnamed one. */
6
+ export declare function templateSlots(root: Root): Set<string>;
7
+ /**
8
+ * Declares a template's slots for the templates that use its component, `module` (such as `c/baseCard`),
9
+ * which check what they pass it against `__lwcSlots<"c/baseCard">`. Each of the component's templates adds
10
+ * to the global `__LwcSlots` interface: `"c/baseCard"`, saying its slots are known, and `"c/baseCard footer"`
11
+ * for each slot. Slots are `undefined` when the template has errors (such as one being edited), whose slots
12
+ * may be missing: it adds `"c/baseCard *"`, which allows any slot. A component without templates in the
13
+ * program adds nothing, and isn't checked.
14
+ */
15
+ export declare function slotDeclarations(module: string, slots: ReadonlySet<string> | undefined): string;
16
+ /** A piece of content passed to a component, naming the slot it goes in. */
17
+ export interface SlottedContent {
18
+ slot: string;
19
+ /** Where to report a slot that doesn't exist, when that can be found. */
20
+ position: number | undefined;
21
+ length: number;
22
+ }
23
+ /** The content a component's tag passes to it: `lwc:if` and loops don't create elements, so theirs is too. */
24
+ export declare function slottedContent(node: BaseElement, source: TemplateSource): SlottedContent[];
@@ -0,0 +1,78 @@
1
+ // Slots: those a template declares, for templates that use its component, and the content a template
2
+ // passes to a child's.
3
+ import { isBlock } from "./ast.js";
4
+ /** A slot name in a slot check, with `(default)` for the unnamed slot. */
5
+ export const DEFAULT_SLOT = "(default)";
6
+ /** The slots a template declares, `(default)` for the unnamed one. */
7
+ export function templateSlots(root) {
8
+ const slots = new Set();
9
+ const visit = (node) => {
10
+ if (node === undefined || node.type === "Text" || node.type === "Comment")
11
+ return;
12
+ if (node.type === "Slot") {
13
+ slots.add(node.slotName || DEFAULT_SLOT);
14
+ }
15
+ node.children.forEach(visit);
16
+ if ("else" in node)
17
+ visit(node.else);
18
+ };
19
+ root.children.forEach(visit);
20
+ return slots;
21
+ }
22
+ /**
23
+ * Declares a template's slots for the templates that use its component, `module` (such as `c/baseCard`),
24
+ * which check what they pass it against `__lwcSlots<"c/baseCard">`. Each of the component's templates adds
25
+ * to the global `__LwcSlots` interface: `"c/baseCard"`, saying its slots are known, and `"c/baseCard footer"`
26
+ * for each slot. Slots are `undefined` when the template has errors (such as one being edited), whose slots
27
+ * may be missing: it adds `"c/baseCard *"`, which allows any slot. A component without templates in the
28
+ * program adds nothing, and isn't checked.
29
+ */
30
+ export function slotDeclarations(module, slots) {
31
+ const keys = [module, ...(slots === undefined ? [`${module} *`] : [...slots].map((slot) => `${module} ${slot}`))];
32
+ return `declare global {
33
+ interface __LwcSlots {
34
+ ${keys.map((key) => ` ${JSON.stringify(key)}: true;\n`).join("")} }
35
+ }
36
+ `;
37
+ }
38
+ /** The content a component's tag passes to it: `lwc:if` and loops don't create elements, so theirs is too. */
39
+ export function slottedContent(node, source) {
40
+ const nodes = [];
41
+ const collect = (children) => {
42
+ for (const child of children) {
43
+ if (isBlock(child)) {
44
+ collect(child.children);
45
+ if ("else" in child && child.else)
46
+ collect([child.else]);
47
+ }
48
+ else {
49
+ nodes.push(child);
50
+ }
51
+ }
52
+ };
53
+ collect(node.children);
54
+ return nodes.flatMap((child) => contentOf(child, source) ?? []);
55
+ }
56
+ /** What a node passes to a slot, if anything: a slot chosen at runtime can't be checked. */
57
+ function contentOf(child, source) {
58
+ if (child.type === "Comment" || isBlock(child)) {
59
+ return undefined;
60
+ }
61
+ if (child.type === "Text") {
62
+ const text = child.raw.trim();
63
+ return text === "" ? undefined : { slot: DEFAULT_SLOT, position: source.literal(text, child.location), length: text.length };
64
+ }
65
+ if (child.type === "ScopedSlotFragment") {
66
+ const name = child.slotName;
67
+ return name.type === "Literal" ? { slot: String(name.value) || DEFAULT_SLOT, position: undefined, length: 0 } : undefined;
68
+ }
69
+ const attribute = child.attributes.find(({ name }) => name === "slot");
70
+ if (attribute === undefined) {
71
+ return { slot: DEFAULT_SLOT, position: child.location.start + 1, length: child.name.length };
72
+ }
73
+ const { value } = attribute;
74
+ if (value.type !== "Literal" || typeof value.value !== "string") {
75
+ return undefined;
76
+ }
77
+ return { slot: value.value || DEFAULT_SLOT, position: source.name(value.value, attribute.location, "="), length: value.value.length };
78
+ }
@@ -0,0 +1,36 @@
1
+ import type { SourceLocation, TemplateExpression } from "./ast.ts";
2
+ /** An expression's place in the template. */
3
+ export interface ExpressionOrigin {
4
+ /** The `{` of the expression, which `$cmp.` maps to, so a problem with a whole expression starts there. */
5
+ brace: number;
6
+ /** The template offset of one of the offsets the expression's nodes have. */
7
+ at(offset: number): number;
8
+ }
9
+ /** How a template was parsed, which changes what the compiler says about it. */
10
+ export interface TemplateSourceOptions {
11
+ /** Whether it was parsed with complex expressions, which changes how it renders. */
12
+ complexExpressions: boolean;
13
+ }
14
+ export declare class TemplateSource {
15
+ #private;
16
+ readonly text: string;
17
+ constructor(text: string, options: TemplateSourceOptions);
18
+ /** The expression in an attribute or directive spanning `location`, if there is one. */
19
+ attribute(location: SourceLocation): ExpressionOrigin | undefined;
20
+ /**
21
+ * The expression of the next text node in a run of text, or undefined for a literal, which only moves
22
+ * the cursor. The compiler tidies a run's lines before parsing it, so a node's `raw` is in that text, not
23
+ * the template's.
24
+ *
25
+ * Without complex expressions, an expression is on one line, so `raw` is in the template as it is, and
26
+ * its offsets are relative to `raw`.
27
+ *
28
+ * With them, its offsets are relative to the tidied run. Each `{` in the text starts an expression, so
29
+ * the next one is this node's, and from it `raw` lines up with the template but for whitespace.
30
+ */
31
+ textExpression(raw: string, expression: TemplateExpression | undefined, location: SourceLocation): ExpressionOrigin | undefined;
32
+ /** The offset of some literal text in a run of text, if it's there as it is. */
33
+ literal(text: string, location: SourceLocation): number | undefined;
34
+ /** The offset of a name in an attribute such as `for:item="row"`, after its `=` (or its `:`). */
35
+ name(name: string, location: SourceLocation, after: "=" | ":"): number | undefined;
36
+ }