@analogjs/vite-plugin-angular 2.5.0-beta.9 → 2.5.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 (82) hide show
  1. package/README.md +24 -0
  2. package/package.json +23 -5
  3. package/src/lib/angular-vite-plugin.d.ts +12 -1
  4. package/src/lib/angular-vite-plugin.js +102 -300
  5. package/src/lib/angular-vite-plugin.js.map +1 -1
  6. package/src/lib/angular-vitest-plugin.js +2 -2
  7. package/src/lib/angular-vitest-plugin.js.map +1 -1
  8. package/src/lib/compiler/angular-version.d.ts +19 -0
  9. package/src/lib/compiler/angular-version.js +42 -0
  10. package/src/lib/compiler/angular-version.js.map +1 -0
  11. package/src/lib/compiler/class-field-lowering.d.ts +23 -0
  12. package/src/lib/compiler/class-field-lowering.js +213 -0
  13. package/src/lib/compiler/class-field-lowering.js.map +1 -0
  14. package/src/lib/compiler/compile.d.ts +44 -0
  15. package/src/lib/compiler/compile.js +1160 -0
  16. package/src/lib/compiler/compile.js.map +1 -0
  17. package/src/lib/compiler/constants.d.ts +18 -0
  18. package/src/lib/compiler/constants.js +48 -0
  19. package/src/lib/compiler/constants.js.map +1 -0
  20. package/src/lib/compiler/debug.d.ts +22 -0
  21. package/src/lib/compiler/debug.js +35 -0
  22. package/src/lib/compiler/debug.js.map +1 -0
  23. package/src/lib/compiler/defer.d.ts +47 -0
  24. package/src/lib/compiler/defer.js +203 -0
  25. package/src/lib/compiler/defer.js.map +1 -0
  26. package/src/lib/compiler/dts-reader.d.ts +35 -0
  27. package/src/lib/compiler/dts-reader.js +526 -0
  28. package/src/lib/compiler/dts-reader.js.map +1 -0
  29. package/src/lib/compiler/hmr.d.ts +16 -0
  30. package/src/lib/compiler/hmr.js +80 -0
  31. package/src/lib/compiler/hmr.js.map +1 -0
  32. package/src/lib/compiler/index.d.ts +7 -0
  33. package/src/lib/compiler/index.js +8 -0
  34. package/src/lib/compiler/index.js.map +1 -0
  35. package/src/lib/compiler/jit-metadata.d.ts +14 -0
  36. package/src/lib/compiler/jit-metadata.js +224 -0
  37. package/src/lib/compiler/jit-metadata.js.map +1 -0
  38. package/src/lib/compiler/jit-transform.d.ts +24 -0
  39. package/src/lib/compiler/jit-transform.js +269 -0
  40. package/src/lib/compiler/jit-transform.js.map +1 -0
  41. package/src/lib/compiler/js-emitter.d.ts +10 -0
  42. package/src/lib/compiler/js-emitter.js +502 -0
  43. package/src/lib/compiler/js-emitter.js.map +1 -0
  44. package/src/lib/compiler/metadata.d.ts +57 -0
  45. package/src/lib/compiler/metadata.js +894 -0
  46. package/src/lib/compiler/metadata.js.map +1 -0
  47. package/src/lib/compiler/registry.d.ts +49 -0
  48. package/src/lib/compiler/registry.js +273 -0
  49. package/src/lib/compiler/registry.js.map +1 -0
  50. package/src/lib/compiler/resource-inliner.d.ts +21 -0
  51. package/src/lib/compiler/resource-inliner.js +200 -0
  52. package/src/lib/compiler/resource-inliner.js.map +1 -0
  53. package/src/lib/compiler/style-ast.d.ts +8 -0
  54. package/src/lib/compiler/style-ast.js +110 -0
  55. package/src/lib/compiler/style-ast.js.map +1 -0
  56. package/src/lib/compiler/styles.d.ts +13 -0
  57. package/src/lib/compiler/styles.js +60 -0
  58. package/src/lib/compiler/styles.js.map +1 -0
  59. package/src/lib/compiler/test-helpers.d.ts +7 -0
  60. package/src/lib/compiler/test-helpers.js +28 -0
  61. package/src/lib/compiler/test-helpers.js.map +1 -0
  62. package/src/lib/compiler/type-elision.d.ts +26 -0
  63. package/src/lib/compiler/type-elision.js +313 -0
  64. package/src/lib/compiler/type-elision.js.map +1 -0
  65. package/src/lib/compiler/utils.d.ts +10 -0
  66. package/src/lib/compiler/utils.js +95 -0
  67. package/src/lib/compiler/utils.js.map +1 -0
  68. package/src/lib/fast-compile-plugin.d.ts +28 -0
  69. package/src/lib/fast-compile-plugin.js +404 -0
  70. package/src/lib/fast-compile-plugin.js.map +1 -0
  71. package/src/lib/utils/plugin-config.d.ts +45 -0
  72. package/src/lib/utils/plugin-config.js +89 -0
  73. package/src/lib/utils/plugin-config.js.map +1 -0
  74. package/src/lib/utils/safe-module-paths.d.ts +16 -0
  75. package/src/lib/utils/safe-module-paths.js +26 -0
  76. package/src/lib/utils/safe-module-paths.js.map +1 -0
  77. package/src/lib/utils/virtual-ids.d.ts +4 -0
  78. package/src/lib/utils/virtual-ids.js +30 -0
  79. package/src/lib/utils/virtual-ids.js.map +1 -0
  80. package/src/lib/utils/virtual-resources.d.ts +19 -0
  81. package/src/lib/utils/virtual-resources.js +47 -0
  82. package/src/lib/utils/virtual-resources.js.map +1 -0
@@ -0,0 +1,894 @@
1
+ import * as o from '@angular/compiler';
2
+ import { unwrapForwardRefOxc } from './utils.js';
3
+ import { SIGNAL_APIS } from './constants.js';
4
+ function getCallApi(call) {
5
+ const callee = call.callee;
6
+ if (!callee)
7
+ return null;
8
+ if (callee.type === 'Identifier') {
9
+ return { api: callee.name, required: false };
10
+ }
11
+ if ((callee.type === 'StaticMemberExpression' ||
12
+ callee.type === 'MemberExpression') &&
13
+ callee.object?.type === 'Identifier' &&
14
+ callee.property?.name === 'required') {
15
+ return { api: callee.object.name, required: true };
16
+ }
17
+ return null;
18
+ }
19
+ /**
20
+ * Extract the string value from an OXC string literal or template literal node.
21
+ *
22
+ * If `consts` is provided, interpolated template literals (e.g.
23
+ * `\`hello ${NAME}\``) are resolved when every `${...}` expression is a bare
24
+ * `Identifier` whose name is present in the map. This lets metadata fields
25
+ * such as `template:` reference module-level string constants via JS
26
+ * template-literal interpolation:
27
+ *
28
+ * const tw = `text-zinc-700 hover:text-zinc-900`;
29
+ * @Component({ template: `<a class="${tw}">x</a>` })
30
+ *
31
+ * Returns `null` if the node is not statically resolvable.
32
+ */
33
+ function stringValue(node, consts) {
34
+ if (node?.type === 'StringLiteral')
35
+ return node.value;
36
+ if (node?.type === 'Literal' && typeof node.value === 'string')
37
+ return node.value;
38
+ if (node?.type === 'TemplateLiteral') {
39
+ const quasis = node.quasis ?? [];
40
+ const expressions = node.expressions ?? [];
41
+ if (expressions.length === 0) {
42
+ return quasis[0]?.value?.cooked ?? null;
43
+ }
44
+ if (!consts)
45
+ return null;
46
+ let result = '';
47
+ for (let i = 0; i < quasis.length; i++) {
48
+ const cooked = quasis[i]?.value?.cooked;
49
+ if (cooked == null)
50
+ return null;
51
+ result += cooked;
52
+ if (i < expressions.length) {
53
+ const expr = expressions[i];
54
+ if (expr?.type !== 'Identifier')
55
+ return null;
56
+ const resolved = consts.get(expr.name);
57
+ if (resolved == null)
58
+ return null;
59
+ result += resolved;
60
+ }
61
+ }
62
+ return result;
63
+ }
64
+ return null;
65
+ }
66
+ /** Check if an OXC node is a string-like literal. */
67
+ function isStringLike(node, consts) {
68
+ return stringValue(node, consts) !== null;
69
+ }
70
+ /**
71
+ * Walk the top-level statements of an OXC program and collect a map of
72
+ * statically-resolvable string-valued `const NAME = ...` declarations.
73
+ *
74
+ * Used so decorator metadata fields like `template:` can reference
75
+ * module-level Tailwind class chains (or any other string constants) via
76
+ * JS template-literal interpolation. Resolution is iterative: a const may
77
+ * reference earlier-resolved consts via `${other}` interpolation.
78
+ *
79
+ * Only `const` declarations are considered. Non-string initializers,
80
+ * function calls, member access, and any expression that cannot be reduced
81
+ * to a string at parse time are ignored.
82
+ */
83
+ export function collectStringConstants(oxcProgram) {
84
+ const rawDecls = new Map();
85
+ for (const stmt of oxcProgram?.body || []) {
86
+ const decl = stmt.type === 'ExportNamedDeclaration' ? stmt.declaration : stmt;
87
+ if (!decl || decl.type !== 'VariableDeclaration' || decl.kind !== 'const')
88
+ continue;
89
+ for (const d of decl.declarations || []) {
90
+ if (d.id?.type === 'Identifier' && d.init) {
91
+ rawDecls.set(d.id.name, d.init);
92
+ }
93
+ }
94
+ }
95
+ const resolved = new Map();
96
+ // Iterative fixpoint: each pass resolves consts whose dependencies are
97
+ // already known. Bounded by the number of declarations to prevent cycles.
98
+ for (let pass = 0; pass < rawDecls.size + 1; pass++) {
99
+ let progress = false;
100
+ for (const [name, init] of rawDecls) {
101
+ if (resolved.has(name))
102
+ continue;
103
+ const value = stringValue(init, resolved);
104
+ if (value !== null) {
105
+ resolved.set(name, value);
106
+ progress = true;
107
+ }
108
+ }
109
+ if (!progress)
110
+ break;
111
+ }
112
+ return resolved;
113
+ }
114
+ /** Get the property key name from an OXC object property. */
115
+ function propKeyName(prop) {
116
+ if (!prop.key)
117
+ return null;
118
+ return prop.key.name ?? prop.key.value ?? null;
119
+ }
120
+ /**
121
+ * Extract decorator metadata from an OXC decorator AST node.
122
+ * Parses @Component, @Directive, @Pipe, @Injectable, @NgModule arguments.
123
+ *
124
+ * `stringConsts`, when provided, lets string-typed metadata fields
125
+ * (`template`, `selector`, `templateUrl`, `styles`, `styleUrl`, `styleUrls`,
126
+ * `name`, `exportAs`, `providedIn`) resolve module-level string constants
127
+ * referenced via template-literal interpolation, e.g.
128
+ * `template: \`<div class="${tw}">x</div>\``.
129
+ */
130
+ export function extractMetadata(dec, sourceCode, stringConsts) {
131
+ if (!dec)
132
+ return null;
133
+ const call = dec.expression;
134
+ if (!call || call.type !== 'CallExpression')
135
+ return null;
136
+ const arg = call.arguments?.[0];
137
+ const meta = {
138
+ hostRaw: {},
139
+ inputs: {},
140
+ outputs: {},
141
+ standalone: true,
142
+ imports: [],
143
+ providers: null,
144
+ viewProviders: null,
145
+ animations: null,
146
+ changeDetection: null,
147
+ encapsulation: null,
148
+ preserveWhitespaces: false,
149
+ exportAs: null,
150
+ selector: undefined,
151
+ styles: [],
152
+ templateUrl: null,
153
+ styleUrls: [],
154
+ };
155
+ if (!arg || arg.type !== 'ObjectExpression')
156
+ return meta;
157
+ for (const p of arg.properties || []) {
158
+ if (p.type !== 'ObjectProperty' && p.type !== 'Property')
159
+ continue;
160
+ const key = propKeyName(p);
161
+ if (!key)
162
+ continue;
163
+ const valNode = p.value;
164
+ const valText = sourceCode.slice(valNode.start, valNode.end);
165
+ switch (key) {
166
+ case 'host':
167
+ if (valNode.type === 'ObjectExpression') {
168
+ for (const hp of valNode.properties || []) {
169
+ if (hp.type !== 'ObjectProperty' && hp.type !== 'Property')
170
+ continue;
171
+ const hKey = propKeyName(hp);
172
+ if (!hKey)
173
+ continue;
174
+ // Prefer the parsed string value so embedded quotes (e.g. the
175
+ // empty `""` in `'expr ? "" : null'`) survive. Falling back to
176
+ // the source slice for non-string values keeps prior behavior
177
+ // for unusual host bindings (e.g. references to constants).
178
+ const sv = stringValue(hp.value, stringConsts);
179
+ const hVal = sv !== null
180
+ ? sv
181
+ : sourceCode
182
+ .slice(hp.value.start, hp.value.end)
183
+ .replace(/^['"`]|['"`]$/g, '');
184
+ meta.hostRaw[hKey.replace(/^['"`]|['"`]$/g, '')] = hVal;
185
+ }
186
+ }
187
+ break;
188
+ case 'changeDetection':
189
+ meta.changeDetection = valText.includes('OnPush') ? 0 : 1;
190
+ break;
191
+ case 'encapsulation':
192
+ meta.encapsulation = valText.includes('None')
193
+ ? 2
194
+ : valText.includes('ShadowDom')
195
+ ? 3
196
+ : 0;
197
+ break;
198
+ case 'preserveWhitespaces':
199
+ meta.preserveWhitespaces = valText === 'true';
200
+ break;
201
+ case 'pure':
202
+ case 'standalone':
203
+ meta[key] = valText !== 'false';
204
+ break;
205
+ case 'template':
206
+ case 'selector':
207
+ case 'name':
208
+ case 'exportAs':
209
+ case 'templateUrl':
210
+ case 'providedIn': {
211
+ const sv = stringValue(valNode, stringConsts);
212
+ if (sv !== null) {
213
+ meta[key] = sv;
214
+ }
215
+ else if (valNode.type === 'TemplateLiteral') {
216
+ // Template literal with `${...}` interpolations that couldn't
217
+ // all be resolved at parse time (e.g. they reference imports
218
+ // or non-string values). The previous fallback stripped every
219
+ // quote character from the source, which silently corrupted
220
+ // templates such as `<a class="${cls} foo">…</a>` into
221
+ // `<a class=${cls} foo>…</a>` — making Angular's HTML parser
222
+ // fail with confusing errors like `Opening tag "a" not
223
+ // terminated`. Instead, walk the quasis and substitute each
224
+ // unresolved interpolation with the empty string so the
225
+ // surrounding HTML (including quoted attributes) is preserved.
226
+ const quasis = valNode.quasis ?? [];
227
+ const expressions = valNode.expressions ?? [];
228
+ let result = '';
229
+ for (let i = 0; i < quasis.length; i++) {
230
+ result += quasis[i]?.value?.cooked ?? '';
231
+ if (i < expressions.length) {
232
+ const expr = expressions[i];
233
+ if (expr?.type === 'Identifier') {
234
+ const resolved = stringConsts?.get(expr.name);
235
+ if (resolved != null) {
236
+ result += resolved;
237
+ continue;
238
+ }
239
+ }
240
+ // Unresolvable — substitute empty string. This keeps the
241
+ // surrounding HTML well-formed even if the resulting class
242
+ // list is incomplete.
243
+ }
244
+ }
245
+ meta[key] = result;
246
+ }
247
+ else {
248
+ // Non-string, non-template-literal expression. Strip only the
249
+ // outermost JS string delimiters, not every embedded quote.
250
+ meta[key] = valText.replace(/^['"`]|['"`]$/g, '');
251
+ }
252
+ if (key === 'exportAs')
253
+ meta.exportAs = [meta.exportAs];
254
+ break;
255
+ }
256
+ case 'styleUrl': {
257
+ const sv = stringValue(valNode, stringConsts);
258
+ meta.styleUrls = [sv !== null ? sv : valText.replace(/['"`]/g, '')];
259
+ break;
260
+ }
261
+ case 'styleUrls':
262
+ if (valNode.type === 'ArrayExpression') {
263
+ meta.styleUrls = (valNode.elements || []).map((e) => {
264
+ const sv = stringValue(e, stringConsts);
265
+ return sv !== null
266
+ ? sv
267
+ : sourceCode.slice(e.start, e.end).replace(/['"`]/g, '');
268
+ });
269
+ }
270
+ break;
271
+ case 'styles':
272
+ if (valNode.type === 'ArrayExpression') {
273
+ meta.styles = (valNode.elements || []).map((e) => {
274
+ const sv = stringValue(e, stringConsts);
275
+ return sv !== null
276
+ ? sv
277
+ : sourceCode.slice(e.start, e.end).replace(/['"`]/g, '');
278
+ });
279
+ }
280
+ else {
281
+ const sv = stringValue(valNode, stringConsts);
282
+ if (sv !== null)
283
+ meta.styles = [sv];
284
+ }
285
+ break;
286
+ case 'imports':
287
+ case 'providers':
288
+ case 'viewProviders':
289
+ case 'animations':
290
+ case 'rawImports':
291
+ case 'declarations':
292
+ case 'exports':
293
+ case 'bootstrap':
294
+ if (valNode.type === 'ArrayExpression') {
295
+ meta[key] = (valNode.elements || []).map((e) => new o.WrappedNodeExpr(unwrapForwardRefOxc(e)));
296
+ }
297
+ break;
298
+ // @Injectable provider configuration. Pass these through to
299
+ // compileInjectable so the emitted `ɵprov` reflects the user's
300
+ // intent. `useClass`/`useExisting`/`useValue` are
301
+ // `MaybeForwardRefExpression` (`{ expression, forwardRef }`);
302
+ // `useFactory` is a bare `Expression`.
303
+ case 'useClass':
304
+ case 'useExisting':
305
+ case 'useValue': {
306
+ const unwrapped = unwrapForwardRefOxc(valNode);
307
+ const isForwardRef = valNode.type === 'CallExpression' && unwrapped !== valNode;
308
+ meta[key] = {
309
+ expression: new o.WrappedNodeExpr(unwrapped),
310
+ forwardRef: isForwardRef ? 2 : 0,
311
+ };
312
+ break;
313
+ }
314
+ case 'useFactory':
315
+ meta[key] = new o.WrappedNodeExpr(valNode);
316
+ break;
317
+ case 'hostDirectives':
318
+ if (valNode.type === 'ArrayExpression') {
319
+ meta.hostDirectives = (valNode.elements || [])
320
+ .map((el) => {
321
+ // Bare identifier: hostDirectives: [MatTooltip]
322
+ if (el.type === 'Identifier' || el.type === 'CallExpression') {
323
+ const unwrapped = unwrapForwardRefOxc(el);
324
+ const ref = {
325
+ value: new o.WrappedNodeExpr(unwrapped),
326
+ type: new o.WrappedNodeExpr(unwrapped),
327
+ };
328
+ return {
329
+ directive: ref,
330
+ isForwardReference: el.type === 'CallExpression',
331
+ inputs: null,
332
+ outputs: null,
333
+ };
334
+ }
335
+ // Object form: { directive: MatTooltip, inputs: [...], outputs: [...] }
336
+ if (el.type === 'ObjectExpression') {
337
+ let directiveNode = null;
338
+ let isForwardRef = false;
339
+ let inputs = null;
340
+ let outputs = null;
341
+ for (const prop of el.properties || []) {
342
+ if (prop.type !== 'ObjectProperty' &&
343
+ prop.type !== 'Property')
344
+ continue;
345
+ const pName = propKeyName(prop);
346
+ if (pName === 'directive') {
347
+ directiveNode = unwrapForwardRefOxc(prop.value);
348
+ isForwardRef =
349
+ prop.value?.type === 'CallExpression' &&
350
+ sourceCode
351
+ .slice(prop.value.start, prop.value.end)
352
+ .includes('forwardRef');
353
+ }
354
+ else if (pName === 'inputs' &&
355
+ prop.value?.type === 'ArrayExpression') {
356
+ inputs = {};
357
+ for (const e of prop.value.elements || []) {
358
+ const sv = stringValue(e);
359
+ if (sv !== null) {
360
+ const [source, alias = source] = sv
361
+ .split(':')
362
+ .map((part) => part.trim());
363
+ if (source)
364
+ inputs[source] = alias;
365
+ }
366
+ }
367
+ }
368
+ else if (pName === 'outputs' &&
369
+ prop.value?.type === 'ArrayExpression') {
370
+ outputs = {};
371
+ for (const e of prop.value.elements || []) {
372
+ const sv = stringValue(e);
373
+ if (sv !== null) {
374
+ const [source, alias = source] = sv
375
+ .split(':')
376
+ .map((part) => part.trim());
377
+ if (source)
378
+ outputs[source] = alias;
379
+ }
380
+ }
381
+ }
382
+ }
383
+ if (directiveNode) {
384
+ const ref = {
385
+ value: new o.WrappedNodeExpr(directiveNode),
386
+ type: new o.WrappedNodeExpr(directiveNode),
387
+ };
388
+ return {
389
+ directive: ref,
390
+ isForwardReference: isForwardRef,
391
+ inputs,
392
+ outputs,
393
+ };
394
+ }
395
+ }
396
+ return null;
397
+ })
398
+ .filter(Boolean);
399
+ }
400
+ break;
401
+ default:
402
+ meta[key] = valText.replace(/['"`]/g, '');
403
+ }
404
+ }
405
+ return meta;
406
+ }
407
+ /**
408
+ * Detect signal-based APIs on class members: input(), model(), output(),
409
+ * viewChild(), contentChild(), viewChildren(), contentChildren().
410
+ */
411
+ export function detectSignals(classNode, sourceCode) {
412
+ const inputs = {}, outputs = {}, viewQueries = [], contentQueries = [];
413
+ const members = classNode.body?.body || [];
414
+ for (const m of members) {
415
+ if (m.type !== 'PropertyDefinition' ||
416
+ !m.key?.name ||
417
+ !m.value ||
418
+ m.value.type !== 'CallExpression')
419
+ continue;
420
+ const name = m.key.name;
421
+ const signalCall = getCallApi(m.value);
422
+ if (!signalCall)
423
+ continue;
424
+ const { api, required } = signalCall;
425
+ if (!SIGNAL_APIS.has(api))
426
+ continue;
427
+ const args = m.value.arguments || [];
428
+ // 1. SIGNAL INPUTS (Standard & Required)
429
+ if (api === 'input') {
430
+ let transform = null;
431
+ let alias = null;
432
+ const optionsArg = required ? args[0] : args[1];
433
+ if (optionsArg?.type === 'ObjectExpression') {
434
+ for (const prop of optionsArg.properties || []) {
435
+ if (prop.type !== 'ObjectProperty' && prop.type !== 'Property')
436
+ continue;
437
+ const k = propKeyName(prop);
438
+ if (k === 'transform') {
439
+ transform = new o.WrappedNodeExpr(prop.value);
440
+ }
441
+ else if (k === 'alias') {
442
+ const sv = stringValue(prop.value);
443
+ if (sv !== null)
444
+ alias = sv;
445
+ }
446
+ }
447
+ }
448
+ inputs[name] = {
449
+ classPropertyName: name,
450
+ // The binding (public) name is the alias if provided, otherwise
451
+ // the class property name. Without honoring `alias`, host
452
+ // directives that map by public name (e.g.
453
+ // `inputs: ['aria-label']` against `ariaLabel = input(null, {
454
+ // alias: 'aria-label' })`) fail at runtime with NG0311.
455
+ bindingPropertyName: alias ?? name,
456
+ isSignal: true,
457
+ required,
458
+ transform,
459
+ };
460
+ }
461
+ // 2. MODEL SIGNALS (Writable Inputs)
462
+ else if (api === 'model') {
463
+ // model() supports the same options object as input(); honor `alias`
464
+ // for the same reason (host-directive mappings use the public name).
465
+ let alias = null;
466
+ const optionsArg = required ? args[0] : args[1];
467
+ if (optionsArg?.type === 'ObjectExpression') {
468
+ for (const prop of optionsArg.properties || []) {
469
+ if (prop.type !== 'ObjectProperty' && prop.type !== 'Property')
470
+ continue;
471
+ if (propKeyName(prop) === 'alias') {
472
+ const sv = stringValue(prop.value);
473
+ if (sv !== null)
474
+ alias = sv;
475
+ }
476
+ }
477
+ }
478
+ inputs[name] = {
479
+ classPropertyName: name,
480
+ bindingPropertyName: alias ?? name,
481
+ isSignal: true,
482
+ };
483
+ // The compiled `outputs` field is `{ classPropertyName: bindingName }`.
484
+ // Angular inverts this at runtime via
485
+ // `parseAndConvertOutputsForDefinition`, producing the lookup map
486
+ // `{ bindingName: classPropertyName }` used by `listenToOutput`.
487
+ // For a `model()` signal, the class property is `name` and the
488
+ // binding event is `${aliasOrName}Change`, and the model signal
489
+ // itself is the subscribable, so `instance[name]` is what
490
+ // `listenToOutput` needs to resolve.
491
+ outputs[name] = (alias ?? name) + 'Change';
492
+ }
493
+ // 3. SIGNAL QUERIES (viewChild, contentChild)
494
+ else if (api === 'viewChild' ||
495
+ api === 'viewChildren' ||
496
+ api === 'contentChild' ||
497
+ api === 'contentChildren') {
498
+ const isViewQuery = api === 'viewChild' || api === 'viewChildren';
499
+ const isChildrenQuery = api === 'viewChildren' || api === 'contentChildren';
500
+ const firstArg = args[0];
501
+ const sv = stringValue(firstArg);
502
+ // Parse the optional second-arg options object for `read` and
503
+ // (content queries only) `descendants`. Without this, queries like
504
+ // `viewChild('ref', { read: ElementRef })` and
505
+ // `contentChildren(Foo, { descendants: false })` silently lose
506
+ // their options at runtime.
507
+ let read = null;
508
+ // Defaults match Angular's signal-query API and the decorator
509
+ // path (`isView || isFirst` in detectFieldDecorators):
510
+ // viewChild → true (view queries always traverse)
511
+ // viewChildren → true
512
+ // contentChild → true (single-result content queries
513
+ // traverse by default)
514
+ // contentChildren → false (multi-result content queries are
515
+ // shallow unless explicitly opted in)
516
+ let descendants = isViewQuery || !isChildrenQuery;
517
+ const optArg = args[1];
518
+ if (optArg?.type === 'ObjectExpression') {
519
+ for (const prop of optArg.properties || []) {
520
+ if (prop.type !== 'ObjectProperty' && prop.type !== 'Property')
521
+ continue;
522
+ const k = propKeyName(prop);
523
+ if (k === 'read') {
524
+ read = new o.WrappedNodeExpr(unwrapForwardRefOxc(prop.value));
525
+ }
526
+ else if (k === 'descendants') {
527
+ const t = prop.value?.type;
528
+ if (t === 'BooleanLiteral' ||
529
+ (t === 'Literal' && typeof prop.value.value === 'boolean')) {
530
+ descendants = prop.value.value;
531
+ }
532
+ }
533
+ }
534
+ }
535
+ const query = {
536
+ propertyName: name,
537
+ // Class predicates must be wrapped in an `R3QueryReference`
538
+ // (`{ forwardRef, expression }`); Angular's `getQueryPredicate`
539
+ // dispatches on `predicate.forwardRef` and reads
540
+ // `predicate.expression`, so a bare `WrappedNodeExpr` is silently
541
+ // dropped to `undefined` and emitted as `null`, leaving the query
542
+ // with no target. `0 = ForwardRefHandling.None`.
543
+ predicate: sv !== null
544
+ ? [sv]
545
+ : { forwardRef: 0, expression: new o.WrappedNodeExpr(firstArg) },
546
+ first: !isChildrenQuery,
547
+ descendants,
548
+ read,
549
+ static: false,
550
+ emitFlags: 0,
551
+ isSignal: true,
552
+ };
553
+ if (isViewQuery)
554
+ viewQueries.push(query);
555
+ else
556
+ contentQueries.push(query);
557
+ }
558
+ // 4. STANDARD OUTPUTS (output() and outputFromObservable())
559
+ else if (api === 'output' || api === 'outputFromObservable') {
560
+ let alias = name;
561
+ const optArg = args[0];
562
+ if (optArg?.type === 'ObjectExpression') {
563
+ for (const prop of optArg.properties || []) {
564
+ if ((prop.type === 'ObjectProperty' || prop.type === 'Property') &&
565
+ propKeyName(prop) === 'alias') {
566
+ const sv = stringValue(prop.value);
567
+ if (sv !== null)
568
+ alias = sv;
569
+ }
570
+ }
571
+ }
572
+ outputs[name] = alias;
573
+ }
574
+ }
575
+ return { inputs, outputs, viewQueries, contentQueries };
576
+ }
577
+ /**
578
+ * Detect decorator-based field metadata: @Input, @Output, @ViewChild,
579
+ * @ContentChild, @ViewChildren, @ContentChildren, @HostBinding, @HostListener.
580
+ */
581
+ export function detectFieldDecorators(classNode, sourceCode) {
582
+ const inputs = {};
583
+ const outputs = {};
584
+ const viewQueries = [];
585
+ const contentQueries = [];
586
+ const hostProperties = {};
587
+ const hostListeners = {};
588
+ const members = classNode.body?.body || [];
589
+ for (const member of members) {
590
+ const decorators = member.decorators || [];
591
+ if (decorators.length === 0)
592
+ continue;
593
+ const memberName = member.key?.name || '';
594
+ for (const dec of decorators) {
595
+ const expr = dec.expression;
596
+ if (!expr || expr.type !== 'CallExpression')
597
+ continue;
598
+ const decName = expr.callee?.name;
599
+ if (!decName)
600
+ continue;
601
+ const args = expr.arguments || [];
602
+ switch (decName) {
603
+ case 'Input': {
604
+ let bindingName = memberName;
605
+ let required = false;
606
+ let transformFunction = null;
607
+ if (args.length > 0) {
608
+ const arg = args[0];
609
+ const sv = stringValue(arg);
610
+ if (sv !== null) {
611
+ bindingName = sv;
612
+ }
613
+ else if (arg.type === 'ObjectExpression') {
614
+ for (const prop of arg.properties || []) {
615
+ if (prop.type !== 'ObjectProperty' && prop.type !== 'Property')
616
+ continue;
617
+ const k = propKeyName(prop);
618
+ if (k === 'alias') {
619
+ const asv = stringValue(prop.value);
620
+ if (asv !== null)
621
+ bindingName = asv;
622
+ }
623
+ if (k === 'required')
624
+ required =
625
+ sourceCode.slice(prop.value.start, prop.value.end) ===
626
+ 'true';
627
+ if (k === 'transform')
628
+ transformFunction = new o.WrappedNodeExpr(prop.value);
629
+ }
630
+ }
631
+ }
632
+ inputs[memberName] = {
633
+ classPropertyName: memberName,
634
+ bindingPropertyName: bindingName,
635
+ isSignal: false,
636
+ required,
637
+ transformFunction,
638
+ };
639
+ break;
640
+ }
641
+ case 'Output': {
642
+ const sv = args.length > 0 ? stringValue(args[0]) : null;
643
+ const alias = sv !== null ? sv : memberName;
644
+ outputs[memberName] = alias;
645
+ break;
646
+ }
647
+ case 'ViewChild':
648
+ case 'ViewChildren':
649
+ case 'ContentChild':
650
+ case 'ContentChildren': {
651
+ const isView = decName.startsWith('View');
652
+ const isFirst = decName === 'ViewChild' || decName === 'ContentChild';
653
+ // Default predicate when no argument is given is the member
654
+ // name. Class predicates (the non-string case) must be wrapped
655
+ // in an `R3QueryReference` (`{ forwardRef, expression }`) so
656
+ // Angular's `getQueryPredicate` can dispatch on `forwardRef`
657
+ // and read `.expression`. A bare `WrappedNodeExpr` is silently
658
+ // dropped to undefined and emitted as `null`.
659
+ let predicate = [memberName];
660
+ if (args.length > 0) {
661
+ const pred = args[0];
662
+ const sv = stringValue(pred);
663
+ if (sv !== null) {
664
+ predicate = [sv];
665
+ }
666
+ else {
667
+ predicate = {
668
+ forwardRef: 0,
669
+ expression: new o.WrappedNodeExpr(unwrapForwardRefOxc(pred)),
670
+ };
671
+ }
672
+ }
673
+ let read = null;
674
+ let isStatic = false;
675
+ let descendants = isView || isFirst;
676
+ if (args.length > 1 && args[1]?.type === 'ObjectExpression') {
677
+ for (const prop of args[1].properties || []) {
678
+ if (prop.type !== 'ObjectProperty' && prop.type !== 'Property')
679
+ continue;
680
+ const k = propKeyName(prop);
681
+ if (k === 'read')
682
+ read = new o.WrappedNodeExpr(prop.value);
683
+ if (k === 'static')
684
+ isStatic =
685
+ sourceCode.slice(prop.value.start, prop.value.end) === 'true';
686
+ if (k === 'descendants')
687
+ descendants =
688
+ sourceCode.slice(prop.value.start, prop.value.end) === 'true';
689
+ }
690
+ }
691
+ const query = {
692
+ propertyName: memberName,
693
+ predicate,
694
+ first: isFirst,
695
+ descendants,
696
+ read,
697
+ static: isStatic,
698
+ emitFlags: 0,
699
+ isSignal: false,
700
+ };
701
+ if (isView)
702
+ viewQueries.push(query);
703
+ else
704
+ contentQueries.push(query);
705
+ break;
706
+ }
707
+ case 'HostBinding': {
708
+ const sv = args.length > 0 ? stringValue(args[0]) : null;
709
+ const target = sv !== null ? sv : memberName;
710
+ hostProperties[target] = memberName;
711
+ break;
712
+ }
713
+ case 'HostListener': {
714
+ const sv = args.length > 0 ? stringValue(args[0]) : null;
715
+ if (sv !== null) {
716
+ const event = sv;
717
+ let handler = `${memberName}()`;
718
+ if (args.length > 1 && args[1]?.type === 'ArrayExpression') {
719
+ const handlerArgs = (args[1].elements || [])
720
+ .map((e) => stringValue(e))
721
+ .filter((v) => v !== null)
722
+ .join(', ');
723
+ handler = `${memberName}(${handlerArgs})`;
724
+ }
725
+ hostListeners[event] = handler;
726
+ }
727
+ break;
728
+ }
729
+ }
730
+ }
731
+ }
732
+ return {
733
+ inputs,
734
+ outputs,
735
+ viewQueries,
736
+ contentQueries,
737
+ hostProperties,
738
+ hostListeners,
739
+ };
740
+ }
741
+ /**
742
+ * Analyze constructor parameters for dependency injection.
743
+ * Returns:
744
+ * - R3DependencyMetadata[] for normal constructors
745
+ * - null if class extends another without own constructor (use inherited factory)
746
+ * - 'invalid' if any parameter has a type-only import token
747
+ *
748
+ * Accepts an OXC ClassDeclaration node and the original source string.
749
+ */
750
+ export function extractConstructorDeps(classNode, sourceCode, typeOnlyImports) {
751
+ const heritage = classNode.superClass ? [classNode.superClass] : [];
752
+ const hasSuper = heritage.length > 0;
753
+ const members = classNode.body?.body || [];
754
+ const ctor = members.find((m) => m.type === 'MethodDefinition' && m.kind === 'constructor');
755
+ if (!ctor) {
756
+ return hasSuper ? null : [];
757
+ }
758
+ const params = ctor.value?.params?.items || ctor.value?.params || [];
759
+ const deps = [];
760
+ let invalid = false;
761
+ for (const param of params) {
762
+ let token = null;
763
+ let attributeNameType = null;
764
+ let host = false, optional = false, self = false, skipSelf = false;
765
+ // Handle TSParameterProperty (e.g., constructor(private foo: Bar))
766
+ const actualParam = param.type === 'TSParameterProperty' ? param.parameter : param;
767
+ const typeAnn = actualParam?.typeAnnotation?.typeAnnotation ??
768
+ param?.typeAnnotation?.typeAnnotation;
769
+ // Extract type annotation as token
770
+ if (typeAnn) {
771
+ if (typeAnn.type === 'TSTypeReference' && typeAnn.typeName) {
772
+ token =
773
+ typeAnn.typeName.name ??
774
+ sourceCode.slice(typeAnn.typeName.start, typeAnn.typeName.end);
775
+ }
776
+ else if (typeAnn.type === 'TSUnionType') {
777
+ // Filter out null/undefined/void arms — `T | null` and
778
+ // `T | undefined` are common patterns that should resolve to T.
779
+ // Anything else (multiple TypeReference arms, primitives, etc.)
780
+ // is ambiguous and ngtsc rejects it. Mark as invalid so the
781
+ // factory falls through to ɵɵinvalidFactory rather than
782
+ // silently picking the first arm.
783
+ const nonNullTypes = (typeAnn.types || []).filter((t) => {
784
+ if (!t)
785
+ return false;
786
+ if (t.type === 'TSNullKeyword')
787
+ return false;
788
+ if (t.type === 'TSUndefinedKeyword')
789
+ return false;
790
+ if (t.type === 'TSVoidKeyword')
791
+ return false;
792
+ if (t.type === 'TSLiteralType' &&
793
+ (t.literal?.type === 'NullLiteral' ||
794
+ (t.literal?.type === 'Literal' && t.literal.value === null))) {
795
+ return false;
796
+ }
797
+ return true;
798
+ });
799
+ if (nonNullTypes.length === 1 &&
800
+ nonNullTypes[0].type === 'TSTypeReference' &&
801
+ nonNullTypes[0].typeName) {
802
+ const t = nonNullTypes[0];
803
+ token =
804
+ t.typeName.name ??
805
+ sourceCode.slice(t.typeName.start, t.typeName.end);
806
+ }
807
+ else {
808
+ // Ambiguous union — no suitable injection token.
809
+ invalid = true;
810
+ continue;
811
+ }
812
+ }
813
+ else if (typeAnn.type === 'TSIntersectionType') {
814
+ // Intersection types (`A & B`) have no single injection token.
815
+ invalid = true;
816
+ continue;
817
+ }
818
+ }
819
+ // Process parameter decorators (may live on TSParameterProperty or inner param)
820
+ const paramDecs = [
821
+ ...(param.decorators || []),
822
+ ...(param.type === 'TSParameterProperty' && actualParam?.decorators
823
+ ? actualParam.decorators
824
+ : []),
825
+ ];
826
+ for (const dec of paramDecs) {
827
+ const expr = dec.expression;
828
+ if (!expr || expr.type !== 'CallExpression')
829
+ continue;
830
+ const decName = expr.callee?.name;
831
+ if (!decName)
832
+ continue;
833
+ const args = expr.arguments || [];
834
+ switch (decName) {
835
+ case 'Inject':
836
+ if (args.length > 0) {
837
+ const sv = stringValue(args[0]);
838
+ if (sv !== null) {
839
+ token = sv;
840
+ }
841
+ else {
842
+ // Unwrap `@Inject(forwardRef(() => TOKEN))` so the
843
+ // emitted token references TOKEN directly. Without this
844
+ // the raw `forwardRef(() => TOKEN)` source slice would
845
+ // appear in the factory output, producing broken code
846
+ // that calls forwardRef at definition time.
847
+ const unwrapped = unwrapForwardRefOxc(args[0]);
848
+ token = sourceCode.slice(unwrapped.start, unwrapped.end);
849
+ }
850
+ }
851
+ break;
852
+ case 'Optional':
853
+ optional = true;
854
+ break;
855
+ case 'Self':
856
+ self = true;
857
+ break;
858
+ case 'SkipSelf':
859
+ skipSelf = true;
860
+ break;
861
+ case 'Host':
862
+ host = true;
863
+ break;
864
+ case 'Attribute':
865
+ if (args.length > 0) {
866
+ const sv = stringValue(args[0]);
867
+ if (sv !== null) {
868
+ attributeNameType = new o.LiteralExpr(sv);
869
+ token = '';
870
+ }
871
+ }
872
+ break;
873
+ }
874
+ }
875
+ if (!token && !attributeNameType) {
876
+ invalid = true;
877
+ continue;
878
+ }
879
+ if (token && typeOnlyImports.has(token)) {
880
+ invalid = true;
881
+ continue;
882
+ }
883
+ deps.push({
884
+ token: token ? new o.WrappedNodeExpr(token) : new o.LiteralExpr(null),
885
+ attributeNameType,
886
+ host,
887
+ optional,
888
+ self,
889
+ skipSelf,
890
+ });
891
+ }
892
+ return invalid ? 'invalid' : deps;
893
+ }
894
+ //# sourceMappingURL=metadata.js.map