@xaendar/compiler 0.7.29 → 0.7.31

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.
@@ -1,5 +1,8 @@
1
1
  import { Stack, indent } from "@xaendar/common";
2
- import { ScriptTarget, SyntaxKind, createSourceFile, forEachChild, isExpressionStatement, isIdentifier, isPropertyAccessExpression, isPropertyAssignment } from "typescript";
2
+ import { ScriptTarget, SyntaxKind, createSourceFile, forEachChild, getNameOfDeclaration, isAccessor, isArrayLiteralExpression, isCallExpression, isClassDeclaration, isExpressionStatement, isIdentifier, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isStringLiteral } from "typescript";
3
+ //#region \0rolldown/runtime.js
4
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
5
+ //#endregion
3
6
  //#region ../packages/compiler/src/parser/types/node.enum.ts
4
7
  /**
5
8
  * Discriminant values that identify the type of each AST node produced by the parser.
@@ -154,7 +157,7 @@ var CompilerContext = class {
154
157
  * @returns `true` if the identifier exists in the scope chain, `false` otherwise.
155
158
  */
156
159
  hasUnresolvableIdentifier(name) {
157
- return this._unresolvableIdentifiers.includes(name);
160
+ return this._unresolvableIdentifiers.includes(name) || (this._parent?.hasUnresolvableIdentifier(name) ?? false);
158
161
  }
159
162
  };
160
163
  //#endregion
@@ -1514,6 +1517,7 @@ function lexImport(cursor, _context) {
1514
1517
  while (read) switch (cursor.peek()) {
1515
1518
  case 32:
1516
1519
  cursor.skipSpaces();
1520
+ if (importValue && cursor.peek() !== 44 && cursor.peek() !== 125) importValue = `${importValue} `;
1517
1521
  break;
1518
1522
  case 44:
1519
1523
  addImport(retVal, cursor, importValue);
@@ -1812,7 +1816,7 @@ function lexTagOpenName(cursor, _context) {
1812
1816
  return retVal;
1813
1817
  }
1814
1818
  //#endregion
1815
- //#region ../packages/compiler/src/utils/chars.utils.ts
1819
+ //#region ../packages/compiler/src/lexer/utils/chars.utils.ts
1816
1820
  /**
1817
1821
  * Checks whether a string contains at least one non-whitespace character.
1818
1822
  *
@@ -2752,19 +2756,57 @@ function parseIfOrElseIf(cursor, parseNode, token) {
2752
2756
  }
2753
2757
  //#endregion
2754
2758
  //#region ../packages/compiler/src/parser/states/parse-import.state.ts
2759
+ /**
2760
+ * Parses an `@import` statement.
2761
+ *
2762
+ * Extracts specifiers with optional aliases and maps them to structured
2763
+ * `ImportSpecifier` objects. Supports:
2764
+ * - `foo` → imported: 'foo', local: 'foo'
2765
+ * - `foo as bar` → imported: 'foo', local: 'bar'
2766
+ * - `default as D` → imported: 'default', local: 'D'
2767
+ * - `* as ns` → imported: '*', local: 'ns'
2768
+ */
2755
2769
  function parseImport(cursor, _parseNode, _token) {
2756
- const imports = new Array();
2770
+ const specifiers = new Array();
2757
2771
  while (cursor.peek().type === TokenType.IMPORT) {
2758
2772
  cursor.advance();
2759
- imports.push(cursor.getCurrentToken().value.parts[0]);
2773
+ const rawSpecifier = cursor.getCurrentToken().value.parts[0];
2774
+ const specifier = parseSpecifier(rawSpecifier);
2775
+ specifiers.push(specifier);
2760
2776
  }
2761
2777
  cursor.advance();
2762
2778
  return {
2763
2779
  type: ASTNodeType.Import,
2764
- values: imports,
2780
+ specifiers,
2765
2781
  path: cursor.getCurrentToken().value.parts[0]
2766
2782
  };
2767
2783
  }
2784
+ /**
2785
+ * Parses a single import specifier string.
2786
+ *
2787
+ * @param raw - The raw specifier string (may include 'as' alias).
2788
+ * @returns An `ImportSpecifier` with `imported` and `local` fields.
2789
+ * @throws If the specifier format is invalid.
2790
+ */
2791
+ function parseSpecifier(raw) {
2792
+ const trimmed = raw.trim();
2793
+ const namespaceMatch = trimmed.match(/^\*\s+as\s+([A-Za-z_$][\w$]*)$/);
2794
+ if (namespaceMatch) return {
2795
+ imported: "*",
2796
+ local: namespaceMatch[1]
2797
+ };
2798
+ const defaultMatch = trimmed.match(/^default\s+as\s+([A-Za-z_$][\w$]*)$/);
2799
+ if (defaultMatch) return {
2800
+ imported: "default",
2801
+ local: defaultMatch[1]
2802
+ };
2803
+ const namedMatch = trimmed.match(/^([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/);
2804
+ if (namedMatch) return {
2805
+ imported: namedMatch[1],
2806
+ local: namedMatch[2] ?? namedMatch[1]
2807
+ };
2808
+ throw new Error(`[Parser] Invalid import specifier "${raw}".`);
2809
+ }
2768
2810
  //#endregion
2769
2811
  //#region ../packages/compiler/src/parser/states/parse-switch.state.ts
2770
2812
  /**
@@ -2902,27 +2944,148 @@ var Parser = class {
2902
2944
  }
2903
2945
  };
2904
2946
  //#endregion
2947
+ //#region ../packages/compiler/src/type-checker/models/type-checker-context.ts
2948
+ /**
2949
+ * Type checker context that manages imported component and directive metadata
2950
+ * for the compilation process. Extends the base CompilerContext to provide
2951
+ * import-specific functionality.
2952
+ */
2953
+ var TypeCheckContext = class extends CompilerContext {
2954
+ /**
2955
+ * Array of component and directive imports to be tracked during type checking.
2956
+ * Stores metadata extracted from @WebComponent and @Directive decorators.
2957
+ */
2958
+ _imports = new Array();
2959
+ /**
2960
+ * Adds a new component or directive import to the type checker context.
2961
+ *
2962
+ * @param value - The component or directive import metadata to be added
2963
+ */
2964
+ addImport(value) {
2965
+ this._imports.push(value);
2966
+ }
2967
+ /**
2968
+ * Retrieves all registered component and directive imports.
2969
+ *
2970
+ * @returns Array of all tracked imports
2971
+ */
2972
+ getImportBySelector(tagName) {
2973
+ return this._imports.find((importValue) => importValue.type === "component" && importValue.selectors.includes(tagName));
2974
+ }
2975
+ };
2976
+ //#endregion
2905
2977
  //#region ../packages/compiler/src/type-checker/states/type-check-element.state.ts
2906
2978
  /**
2907
2979
  * Type-checks an element node: its attribute/property bindings, its event
2908
2980
  * handlers, and recursively its children.
2909
2981
  *
2982
+ * Two completely different validation paths, depending on the tag:
2983
+ *
2984
+ * - **Native tags** (`div`, `button`, ...): bindings are just resolved
2985
+ * expressions/handler calls, exactly as before — there's no component
2986
+ * contract to check them against.
2987
+ * - **Custom elements** (tag contains a hyphen): every binding is checked
2988
+ * by NAME against the `@Property`/`@Event` metadata extracted from the
2989
+ * imported component class. An unknown name is a hard error (the
2990
+ * template is binding to something the component doesn't expose) — not
2991
+ * a warning, consistent with the "no matching @import" check already in
2992
+ * place for the tag itself.
2993
+ *
2910
2994
  * No variable is declared for the element itself (see the module doc on
2911
2995
  * `TypeChecker` for why) — attribute expressions and event calls are
2912
2996
  * emitted as bare statements, validated in place.
2913
2997
  */
2914
2998
  function typeCheckElement(node, processNode, context) {
2915
2999
  const lines = new Array();
2916
- node.attributes.forEach(({ value }) => {
2917
- if (typeof value !== "string") lines.push(`${resolveExpression(value.expression, context, { resolver: "root" })};`);
3000
+ if (isCustomElementTag(node.tagName)) {
3001
+ const metadata = context.getImportBySelector(node.tagName);
3002
+ if (!metadata) throw new Error(`[Type Checker] ${node.tagName} selector is not associated to any WebComponent imported in the template`);
3003
+ lines.push(...typeCheckComponentBindings(node, metadata, context));
3004
+ } else {
3005
+ node.attributes.forEach(({ value }) => {
3006
+ if (typeof value !== "string") lines.push(`${resolveExpression(value.expression, context, { resolver: "root" })};`);
3007
+ });
3008
+ node.events.forEach(({ handler, parameters }) => {
3009
+ const eventContext = new CompilerContext([], context);
3010
+ eventContext.addUnresolvableIdentifier("$event");
3011
+ const args = parameters.map((parameter) => resolveExpression(parameter, eventContext, { resolver: "root" })).join(", ");
3012
+ lines.push(`root.${handler}(${args});`);
3013
+ });
3014
+ }
3015
+ node.children.forEach((child) => lines.push(...processNode(child, context)));
3016
+ return lines;
3017
+ }
3018
+ /**
3019
+ * Validates every attribute/property and event binding on a custom-element
3020
+ * node against the `@Property`/`@Event` metadata of the resolved component.
3021
+ *
3022
+ * Property values are checked with `satisfies` against `property.type` —
3023
+ * chosen over a typed `const` so an unused local never shows up if the
3024
+ * consuming project has `noUnusedLocals` enabled. Event handlers get a
3025
+ * block-scoped `$event` typed from `event.detailType`, wrapped in its own
3026
+ * `{ }` block so multiple event bindings on sibling elements never clash
3027
+ * on the `$event` name.
3028
+ *
3029
+ * ⚠️ Assumptions I couldn't verify against your actual types — please
3030
+ * confirm/adjust:
3031
+ * - `node.events` entries carry a `name` field (the template-facing event
3032
+ * name used to look up `@Event` metadata), the same way attributes carry
3033
+ * `name`. The snippets I've seen only destructured `handler`/`parameters`
3034
+ * for events, never `name` — if the field is called something else,
3035
+ * swap it in `findEvent`'s call site below.
3036
+ * - Literal string attribute values (`value` is a plain `string`, no
3037
+ * `{ expression }`) are only checked for NAME existence, not type — I
3038
+ * don't validate e.g. `collapsed="true"` against a `boolean` property,
3039
+ * since I don't know whether your DSL treats un-bound string attributes
3040
+ * as always-string or allows some literal coercion syntax. Flagging
3041
+ * rather than guessing.
3042
+ * - `property.type`/`event.detailType` are text that must resolve as a
3043
+ * standalone type expression in the shim's scope (e.g. `'NavItem[]'`
3044
+ * requires `NavItem` to be importable there too, not just the component
3045
+ * class). If your shim only imports the component class today, custom
3046
+ * exported types referenced by `type`/`detailType` will fail to resolve
3047
+ * as "cannot find name" — unrelated to the actual binding being right or
3048
+ * wrong. Worth double-checking against a component that has a non-
3049
+ * primitive `@Property` type, like `NavItem[]` in your own example.
3050
+ */
3051
+ function typeCheckComponentBindings(node, metadata, context) {
3052
+ const lines = new Array();
3053
+ node.attributes.forEach(({ name, value }) => {
3054
+ const property = findProperty(metadata, name);
3055
+ if (!property) throw new Error(`[Type Checker] Unknown property "${name}" on <${node.tagName}> (${metadata.className} has no @Property with this name or alias).`);
3056
+ if (typeof value === "string") return;
3057
+ const expression = resolveExpression(value.expression, context, { resolver: "root" });
3058
+ lines.push(property.type ? `(${expression}) satisfies ${property.type};` : `${expression};`);
2918
3059
  });
2919
- node.events.forEach(({ handler, parameters }) => {
2920
- const args = parameters.map((parameter) => resolveExpression(parameter, context, { resolver: "root" })).join(", ");
2921
- lines.push(`root.${handler}(${args});`);
3060
+ node.events.forEach(({ name, handler, parameters }) => {
3061
+ const event = findEvent(metadata, name);
3062
+ if (!event) throw new Error(`[Type Checker] Unknown event "${name}" on <${node.tagName}> (${metadata.className} has no @Event with this name).`);
3063
+ const eventContext = new CompilerContext([], context);
3064
+ eventContext.addUnresolvableIdentifier("$event");
3065
+ const args = parameters.map((parameter) => resolveExpression(parameter, eventContext, { resolver: "root" })).join(", ");
3066
+ lines.push("{");
3067
+ lines.push(` let $event!: ${event.detailType ?? "unknown"};`);
3068
+ lines.push(` root.${handler}(${args});`);
3069
+ lines.push("}");
2922
3070
  });
2923
- node.children.forEach((child) => lines.push(...processNode(child, context)));
2924
3071
  return lines;
2925
3072
  }
3073
+ function findProperty(metadata, externalName) {
3074
+ return metadata.properties.find((property) => (property.alias ?? property.name) === externalName);
3075
+ }
3076
+ function findEvent(metadata, externalName) {
3077
+ return metadata.events.find((event) => event.name === externalName);
3078
+ }
3079
+ /**
3080
+ * Returns true if `tagName` has the shape of a custom element (per the
3081
+ * Custom Elements spec: contains a hyphen). This is a pure classification
3082
+ * check — it does NOT validate whether the name is actually usable as a
3083
+ * new custom element (reserved names, etc.); use `isValidCustomElementName`
3084
+ * for that, at declaration time.
3085
+ */
3086
+ function isCustomElementTag(tagName) {
3087
+ return /^[a-z][a-z0-9._\-]*-[a-z0-9._\-]*$/.test(tagName);
3088
+ }
2926
3089
  //#endregion
2927
3090
  //#region ../packages/compiler/src/type-checker/states/type-check-for.state.ts
2928
3091
  /**
@@ -2936,7 +3099,7 @@ function typeCheckElement(node, processNode, context) {
2936
3099
  * function boundary needed.
2937
3100
  */
2938
3101
  function typeCheckFor(node, processNode, context) {
2939
- const forContext = new CompilerContext([], context);
3102
+ const forContext = new TypeCheckContext([], context);
2940
3103
  const indexName = resolveImplicit(node, "$index");
2941
3104
  const firstName = resolveImplicit(node, "$first");
2942
3105
  const lastName = resolveImplicit(node, "$last");
@@ -3014,6 +3177,43 @@ function typeCheckIf(node, processNode, context) {
3014
3177
  return lines;
3015
3178
  }
3016
3179
  //#endregion
3180
+ //#region ../packages/compiler/src/type-checker/states/type-check-import.state.ts
3181
+ /**
3182
+ * Type-checks an `@import` node.
3183
+ *
3184
+ * Emits a typed variable declaration for each specifier so symbols are
3185
+ * available in template expressions. Metadata extraction (component/directive
3186
+ * decorator analysis) happens in a separate async phase via
3187
+ * `TypeChecker.populateImportMetadata()`.
3188
+ */
3189
+ function typeCheckImport(node, _processNode, _context) {
3190
+ const source = node.path;
3191
+ const escapedSource = escapeTypeString(source);
3192
+ const lines = new Array();
3193
+ for (const specifier of node.specifiers) {
3194
+ const { imported, local } = specifier;
3195
+ switch (imported) {
3196
+ case "*":
3197
+ lines.push(`let ${local}!: typeof import('${escapedSource}');`);
3198
+ break;
3199
+ case "default":
3200
+ lines.push(`let ${local}!: typeof import('${escapedSource}')['default'];`);
3201
+ break;
3202
+ default:
3203
+ const escapedImported = escapeTypeString(imported);
3204
+ lines.push(`let ${local}!: typeof import('${escapedSource}')['${escapedImported}'];`);
3205
+ break;
3206
+ }
3207
+ }
3208
+ return lines;
3209
+ }
3210
+ /**
3211
+ * Escapes path/specifier text for safe use in TS single-quoted type strings.
3212
+ */
3213
+ function escapeTypeString(value) {
3214
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
3215
+ }
3216
+ //#endregion
3017
3217
  //#region ../packages/compiler/src/type-checker/states/type-check-switch.state.ts
3018
3218
  /**
3019
3219
  * Type-checks a `@switch` block using a real TypeScript `switch` statement.
@@ -3052,6 +3252,174 @@ function typeCheckTextAndInterpolation(node, _processNode, context) {
3052
3252
  return node.type === ASTNodeType.Interpolation ? [`${resolveExpression(node.expression, context, { resolver: "root" })};`] : [];
3053
3253
  }
3054
3254
  //#endregion
3255
+ //#region ../packages/compiler/src/type-checker/utils/component-metadata-extractor.ts
3256
+ var import___vite_browser_external = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
3257
+ module.exports = {};
3258
+ })))();
3259
+ /**
3260
+ * Extracts component metadata from a source file by parsing decorators.
3261
+ *
3262
+ * @param modulePath - The import module path (e.g., './button.component')
3263
+ * @param symbolName - The exported symbol name to look for
3264
+ * @param baseDir - The directory context for resolving relative paths
3265
+ * @returns Component metadata if found, undefined otherwise
3266
+ */
3267
+ async function extractComponentMetadata(modulePath, symbolName, baseDir = process.cwd()) {
3268
+ try {
3269
+ const filePath = resolveModulePath(modulePath, baseDir);
3270
+ if (!filePath) return;
3271
+ const sourceFile = createSourceFile(filePath, await (0, import___vite_browser_external.readFile)(filePath, "utf8"), ScriptTarget.Latest, true);
3272
+ let componentMetadata;
3273
+ forEachChild(sourceFile, (node) => {
3274
+ if (componentMetadata) return;
3275
+ if (isClassDeclaration(node)) {
3276
+ const className = node.name?.text;
3277
+ if (className !== symbolName) return;
3278
+ const modifiers = node.modifiers ?? [];
3279
+ const webComponentDecorator = Array.from(modifiers).find((member) => isWebComponentDecorator(member));
3280
+ if (!webComponentDecorator) return;
3281
+ const selectors = extractSelectorsFromDecorator(webComponentDecorator);
3282
+ if (!selectors?.length) return;
3283
+ const properties = new Array();
3284
+ const events = new Array();
3285
+ node.members.forEach((member) => {
3286
+ if (!isAccessor(member)) return;
3287
+ const memberModifiers = member.modifiers ?? [];
3288
+ const propDecorator = Array.from(memberModifiers).find((member) => isPropertyDecorator(member));
3289
+ if (propDecorator) {
3290
+ const nameNode = getNameOfDeclaration(member);
3291
+ const propName = nameNode && isIdentifier(nameNode) ? nameNode.text : void 0;
3292
+ if (propName) {
3293
+ const metadata = extractPropertyMetadata(propName, propDecorator);
3294
+ if (metadata) properties.push(metadata);
3295
+ }
3296
+ return;
3297
+ }
3298
+ const eventDecorator = Array.from(memberModifiers).find((member) => isEventDecorator(member));
3299
+ if (eventDecorator) {
3300
+ const nameNode = getNameOfDeclaration(member);
3301
+ const eventName = nameNode && isIdentifier(nameNode) ? nameNode.text : void 0;
3302
+ if (eventName) {
3303
+ const metadata = extractEventMetadata(eventName, eventDecorator);
3304
+ if (metadata) events.push(metadata);
3305
+ }
3306
+ }
3307
+ });
3308
+ componentMetadata = {
3309
+ type: "component",
3310
+ className,
3311
+ selectors,
3312
+ properties,
3313
+ events
3314
+ };
3315
+ }
3316
+ });
3317
+ return componentMetadata;
3318
+ } catch (error) {
3319
+ console.error(`Failed to extract component metadata from ${modulePath}:`, error);
3320
+ return;
3321
+ }
3322
+ }
3323
+ /**
3324
+ * Resolves a module import path to an actual file system path.
3325
+ * Handles both relative paths (./button.component) and package paths (@scope/pkg).
3326
+ *
3327
+ * @param modulePath - The import module path
3328
+ * @param baseDir - The directory to resolve relative imports from
3329
+ * @returns The resolved file path, or undefined if not found
3330
+ */
3331
+ function resolveModulePath(modulePath, baseDir) {
3332
+ if (modulePath.startsWith(".")) {
3333
+ const resolvedPath = (0, import___vite_browser_external.resolve)(baseDir, modulePath);
3334
+ if ((0, import___vite_browser_external.existsSync)(`${resolvedPath}.ts`)) return resolvedPath + ".ts";
3335
+ if ((0, import___vite_browser_external.existsSync)(`${resolvedPath}/index.ts`)) return `${resolvedPath}/index.ts`;
3336
+ if ((0, import___vite_browser_external.existsSync)(resolvedPath)) return resolvedPath;
3337
+ }
3338
+ }
3339
+ /**
3340
+ * Checks if a modifier is a @WebComponent decorator.
3341
+ */
3342
+ function isWebComponentDecorator(modifier) {
3343
+ if (modifier.kind !== SyntaxKind.Decorator) return false;
3344
+ const expr = isCallExpression(modifier.expression) ? modifier.expression.expression : modifier.expression;
3345
+ return isIdentifier(expr) && expr.text === "WebComponent";
3346
+ }
3347
+ /**
3348
+ * Checks if a modifier is a @Property decorator.
3349
+ */
3350
+ function isPropertyDecorator(modifier) {
3351
+ if (modifier.kind !== SyntaxKind.Decorator) return false;
3352
+ const expr = modifier.expression;
3353
+ if (isCallExpression(expr)) return isIdentifier(expr.expression) && expr.expression.text === "Property";
3354
+ if (isPropertyAccessExpression(expr)) return isIdentifier(expr.expression) && expr.expression.text === "Property" && isIdentifier(expr.name) && expr.name.text === "required";
3355
+ return isIdentifier(expr) && expr.text === "Property";
3356
+ }
3357
+ /**
3358
+ * Checks if a modifier is an @Event decorator.
3359
+ */
3360
+ function isEventDecorator(modifier) {
3361
+ if (modifier.kind !== SyntaxKind.Decorator) return false;
3362
+ const expr = isCallExpression(modifier.expression) ? modifier.expression.expression : modifier.expression;
3363
+ return isIdentifier(expr) && expr.text === "Event";
3364
+ }
3365
+ /**
3366
+ * Extracts selector(s) from @WebComponent decorator arguments.
3367
+ *
3368
+ * @example
3369
+ * @WebComponent({ selector: 'my-button' })
3370
+ * @WebComponent({ selector: ['my-btn', 'button-el'] })
3371
+ */
3372
+ function extractSelectorsFromDecorator(modifier) {
3373
+ try {
3374
+ const expr = modifier.expression;
3375
+ if (!isCallExpression(expr)) return [];
3376
+ const args = expr.arguments;
3377
+ if (args.length === 0) return [];
3378
+ const arg = args[0];
3379
+ if (!arg || !isObjectLiteralExpression(arg)) return [];
3380
+ return extractStringOrStringArray(Array.from(arg.properties).find((prop) => isPropertyAssignment(prop) && isIdentifier(prop.name) && prop.name.text === "selector").initializer);
3381
+ } catch {
3382
+ return [];
3383
+ }
3384
+ }
3385
+ /**
3386
+ * Extracts a string or string array value from a TypeScript node.
3387
+ */
3388
+ function extractStringOrStringArray(node) {
3389
+ if (isStringLiteral(node)) return [node.text];
3390
+ return isArrayLiteralExpression(node) ? node.elements?.filter((node) => isStringLiteral(node)).map((node) => node.text) : [];
3391
+ }
3392
+ /**
3393
+ * Extracts property metadata from @Property or @Property.required decorator.
3394
+ */
3395
+ function extractPropertyMetadata(propName, modifier) {
3396
+ const metadata = {
3397
+ name: propName,
3398
+ required: false
3399
+ };
3400
+ const expr = modifier.expression;
3401
+ if (isPropertyAccessExpression(expr)) {
3402
+ if (isIdentifier(expr.name) && expr.name.text === "required") metadata.required = true;
3403
+ }
3404
+ if (isCallExpression(expr)) {
3405
+ const args = expr.arguments;
3406
+ if (args.length) {
3407
+ const arg = args[0];
3408
+ if (isObjectLiteralExpression(arg)) {
3409
+ const aliasNode = arg.properties?.find((prop) => isPropertyAssignment(prop) && isIdentifier(prop.name) && prop.name.text === "alias");
3410
+ if (aliasNode && isStringLiteral(aliasNode.initializer)) metadata.alias = aliasNode.initializer.text;
3411
+ }
3412
+ }
3413
+ }
3414
+ return metadata;
3415
+ }
3416
+ /**
3417
+ * Extracts event metadata from @Event decorator.
3418
+ */
3419
+ function extractEventMetadata(eventName, modifier) {
3420
+ return { name: eventName };
3421
+ }
3422
+ //#endregion
3055
3423
  //#region ../packages/compiler/src/type-checker/type-checker.ts
3056
3424
  /**
3057
3425
  * Generates a single, flat TypeScript function body ("shim") from a
@@ -3077,6 +3445,7 @@ function typeCheckTextAndInterpolation(node, _processNode, context) {
3077
3445
  */
3078
3446
  var TypeChecker = class {
3079
3447
  _ast;
3448
+ _context = new TypeCheckContext();
3080
3449
  _states = {
3081
3450
  [ASTNodeType.Text]: typeCheckTextAndInterpolation,
3082
3451
  [ASTNodeType.Interpolation]: typeCheckTextAndInterpolation,
@@ -3084,12 +3453,29 @@ var TypeChecker = class {
3084
3453
  [ASTNodeType.If]: typeCheckIf,
3085
3454
  [ASTNodeType.For]: typeCheckFor,
3086
3455
  [ASTNodeType.Switch]: typeCheckSwitch,
3087
- [ASTNodeType.Import]: skipGeneration
3456
+ [ASTNodeType.Import]: typeCheckImport
3088
3457
  };
3089
3458
  constructor(_ast) {
3090
3459
  this._ast = _ast;
3091
3460
  }
3092
3461
  /**
3462
+ * Pre-populates the shared context with component and directive metadata
3463
+ * by parsing source files for all `@import` nodes in the AST.
3464
+ *
3465
+ * Must be awaited before calling `generate()` if metadata-driven
3466
+ * validation (e.g. unknown component inputs) is desired.
3467
+ *
3468
+ * @param baseDir - Absolute path used to resolve relative import paths.
3469
+ */
3470
+ async populateImportMetadata(baseDir) {
3471
+ const importNodes = this._ast.filter((node) => node.type === ASTNodeType.Import);
3472
+ await Promise.all(importNodes.flatMap((node) => node.specifiers.filter(({ imported }) => imported !== "*").map(async ({ imported, local }) => {
3473
+ const symbolName = imported === "default" ? local : imported;
3474
+ const metadata = await extractComponentMetadata(node.path, symbolName, baseDir);
3475
+ if (metadata) this._context.addImport(metadata);
3476
+ })));
3477
+ }
3478
+ /**
3093
3479
  * Generates the full `function typeCheck() { ... }` shim body for the
3094
3480
  * component's template.
3095
3481
  *
@@ -3097,12 +3483,14 @@ var TypeChecker = class {
3097
3483
  * body actually references `$event` (event handler bindings), so shims
3098
3484
  * for templates with no event bindings don't carry an unused local —
3099
3485
  * relevant if the consuming project has `noUnusedLocals` enabled.
3486
+ *
3487
+ * @param baseDir - Absolute path used to resolve relative import paths
3100
3488
  */
3101
- generate() {
3102
- const body = this._ast.flatMap((node) => this._processNode(node));
3489
+ generate(baseDir) {
3490
+ this.populateImportMetadata(baseDir);
3103
3491
  return [
3104
3492
  "function typeCheck() {",
3105
- ...indent(body.some((line) => line.includes("$event")) ? ["let $event!: Event;", ...body] : body),
3493
+ ...indent(this._ast.flatMap((node) => this._processNode(node, this._context))),
3106
3494
  "}"
3107
3495
  ].join("\n");
3108
3496
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xaendar/compiler",
3
- "version": "0.7.29",
3
+ "version": "0.7.31",
4
4
  "description": "A library for transpiling Xaendar Templates into JavaScript code",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -16,8 +16,8 @@
16
16
  }
17
17
  },
18
18
  "dependencies": {
19
- "@xaendar/common": "0.7.29",
20
- "@xaendar/types": "0.7.29",
19
+ "@xaendar/common": "0.7.31",
20
+ "@xaendar/types": "0.7.31",
21
21
  "typescript": "^6.0.3"
22
22
  }
23
23
  }