Skip to content

Commit daf0317

Browse files
crisbetoatscott
authored andcommitted
fix(compiler): JIT mode incorrectly interpreting host directive configuration in partial compilation (#57002)
Fixes that the runtime implementation of `ɵɵngDeclareDirective` was interpreting the `hostDirectives` mapping incorrectly. Instead of treating the inputs/outputs as `['binding', 'alias']` arrays, it was parsing them as `['binding: alias']`. This was leading to runtime errors if a user is consuming a partially-compiled library in JIT mode. Fixes #54096. PR Close #57002
1 parent 6cfc4d8 commit daf0317

File tree

2 files changed

+86
-22
lines changed

2 files changed

+86
-22
lines changed

packages/compiler/src/jit_compiler_facade.ts

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,26 @@ function convertDirectiveFacadeToMetadata(facade: R3DirectiveMetadataFacade): R3
497497
}
498498
}
499499

500+
const hostDirectives = facade.hostDirectives?.length
501+
? facade.hostDirectives.map((hostDirective) => {
502+
return typeof hostDirective === 'function'
503+
? {
504+
directive: wrapReference(hostDirective),
505+
inputs: null,
506+
outputs: null,
507+
isForwardReference: false,
508+
}
509+
: {
510+
directive: wrapReference(hostDirective.directive),
511+
isForwardReference: false,
512+
inputs: hostDirective.inputs ? parseMappingStringArray(hostDirective.inputs) : null,
513+
outputs: hostDirective.outputs
514+
? parseMappingStringArray(hostDirective.outputs)
515+
: null,
516+
};
517+
})
518+
: null;
519+
500520
return {
501521
...facade,
502522
typeArgumentCount: 0,
@@ -512,14 +532,23 @@ function convertDirectiveFacadeToMetadata(facade: R3DirectiveMetadataFacade): R3
512532
providers: facade.providers != null ? new WrappedNodeExpr(facade.providers) : null,
513533
viewQueries: facade.viewQueries.map(convertToR3QueryMetadata),
514534
fullInheritance: false,
515-
hostDirectives: convertHostDirectivesToMetadata(facade),
535+
hostDirectives,
516536
};
517537
}
518538

519539
function convertDeclareDirectiveFacadeToMetadata(
520540
declaration: R3DeclareDirectiveFacade,
521541
typeSourceSpan: ParseSourceSpan,
522542
): R3DirectiveMetadata {
543+
const hostDirectives = declaration.hostDirectives?.length
544+
? declaration.hostDirectives.map((dir) => ({
545+
directive: wrapReference(dir.directive),
546+
isForwardReference: false,
547+
inputs: dir.inputs ? getHostDirectiveBindingMapping(dir.inputs) : null,
548+
outputs: dir.outputs ? getHostDirectiveBindingMapping(dir.outputs) : null,
549+
}))
550+
: null;
551+
523552
return {
524553
name: declaration.type.name,
525554
type: wrapReference(declaration.type),
@@ -540,7 +569,7 @@ function convertDeclareDirectiveFacadeToMetadata(
540569
fullInheritance: false,
541570
isStandalone: declaration.isStandalone ?? false,
542571
isSignal: declaration.isSignal ?? false,
543-
hostDirectives: convertHostDirectivesToMetadata(declaration),
572+
hostDirectives,
544573
};
545574
}
546575

@@ -558,28 +587,19 @@ function convertHostDeclarationToMetadata(
558587
};
559588
}
560589

561-
function convertHostDirectivesToMetadata(
562-
metadata: R3DeclareDirectiveFacade | R3DirectiveMetadataFacade,
563-
): R3HostDirectiveMetadata[] | null {
564-
if (metadata.hostDirectives?.length) {
565-
return metadata.hostDirectives.map((hostDirective) => {
566-
return typeof hostDirective === 'function'
567-
? {
568-
directive: wrapReference(hostDirective),
569-
inputs: null,
570-
outputs: null,
571-
isForwardReference: false,
572-
}
573-
: {
574-
directive: wrapReference(hostDirective.directive),
575-
isForwardReference: false,
576-
inputs: hostDirective.inputs ? parseMappingStringArray(hostDirective.inputs) : null,
577-
outputs: hostDirective.outputs ? parseMappingStringArray(hostDirective.outputs) : null,
578-
};
579-
});
590+
/**
591+
* Parses a host directive mapping where each odd array key is the name of an input/output
592+
* and each even key is its public name, e.g. `['one', 'oneAlias', 'two', 'two']`.
593+
*/
594+
function getHostDirectiveBindingMapping(array: string[]) {
595+
let result: {[publicName: string]: string} | null = null;
596+
597+
for (let i = 1; i < array.length; i += 2) {
598+
result = result || {};
599+
result[array[i - 1]] = array[i];
580600
}
581601

582-
return null;
602+
return result;
583603
}
584604

585605
function convertOpaqueValuesToExpressions(obj: {[key: string]: OpaqueValue}): {

packages/core/test/render3/jit/declare_directive_spec.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,47 @@ describe('directive declaration jit compilation', () => {
261261
features: [ɵɵNgOnChangesFeature],
262262
});
263263
});
264+
265+
it('should compile host directives', () => {
266+
class One {}
267+
class Two {}
268+
269+
const def = ɵɵngDeclareDirective({
270+
type: TestClass,
271+
hostDirectives: [
272+
{
273+
directive: One,
274+
inputs: ['firstInput', 'firstInput', 'secondInput', 'secondInputAlias'],
275+
outputs: ['firstOutput', 'firstOutput', 'secondOutput', 'secondOutputAlias'],
276+
},
277+
{
278+
directive: Two,
279+
},
280+
],
281+
}) as DirectiveDef<TestClass>;
282+
283+
expectDirectiveDef(def, {
284+
features: [jasmine.any(Function)],
285+
hostDirectives: [
286+
{
287+
directive: One,
288+
inputs: {
289+
'firstInput': 'firstInput',
290+
'secondInput': 'secondInputAlias',
291+
},
292+
outputs: {
293+
'firstOutput': 'firstOutput',
294+
'secondOutput': 'secondOutputAlias',
295+
},
296+
},
297+
{
298+
directive: Two,
299+
inputs: {},
300+
outputs: {},
301+
},
302+
],
303+
});
304+
});
264305
});
265306

266307
type DirectiveDefExpectations = jasmine.Expected<
@@ -278,6 +319,7 @@ type DirectiveDefExpectations = jasmine.Expected<
278319
| 'viewQuery'
279320
| 'exportAs'
280321
| 'providersResolver'
322+
| 'hostDirectives'
281323
>
282324
>;
283325

@@ -303,6 +345,7 @@ function expectDirectiveDef(
303345
viewQuery: null,
304346
exportAs: null,
305347
providersResolver: null,
348+
hostDirectives: null,
306349
...expected,
307350
};
308351

@@ -321,6 +364,7 @@ function expectDirectiveDef(
321364
expect(actual.providersResolver)
322365
.withContext('providersResolver')
323366
.toEqual(expectation.providersResolver);
367+
expect(actual.hostDirectives).withContext('hostDirectives').toEqual(expectation.hostDirectives);
324368
}
325369

326370
class TestClass {}

0 commit comments

Comments
 (0)