mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
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
This commit is contained in:
parent
6cfc4d8c85
commit
daf0317bdc
2 changed files with 86 additions and 22 deletions
|
|
@ -497,6 +497,26 @@ function convertDirectiveFacadeToMetadata(facade: R3DirectiveMetadataFacade): R3
|
|||
}
|
||||
}
|
||||
|
||||
const hostDirectives = facade.hostDirectives?.length
|
||||
? facade.hostDirectives.map((hostDirective) => {
|
||||
return typeof hostDirective === 'function'
|
||||
? {
|
||||
directive: wrapReference(hostDirective),
|
||||
inputs: null,
|
||||
outputs: null,
|
||||
isForwardReference: false,
|
||||
}
|
||||
: {
|
||||
directive: wrapReference(hostDirective.directive),
|
||||
isForwardReference: false,
|
||||
inputs: hostDirective.inputs ? parseMappingStringArray(hostDirective.inputs) : null,
|
||||
outputs: hostDirective.outputs
|
||||
? parseMappingStringArray(hostDirective.outputs)
|
||||
: null,
|
||||
};
|
||||
})
|
||||
: null;
|
||||
|
||||
return {
|
||||
...facade,
|
||||
typeArgumentCount: 0,
|
||||
|
|
@ -512,7 +532,7 @@ function convertDirectiveFacadeToMetadata(facade: R3DirectiveMetadataFacade): R3
|
|||
providers: facade.providers != null ? new WrappedNodeExpr(facade.providers) : null,
|
||||
viewQueries: facade.viewQueries.map(convertToR3QueryMetadata),
|
||||
fullInheritance: false,
|
||||
hostDirectives: convertHostDirectivesToMetadata(facade),
|
||||
hostDirectives,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -520,6 +540,15 @@ function convertDeclareDirectiveFacadeToMetadata(
|
|||
declaration: R3DeclareDirectiveFacade,
|
||||
typeSourceSpan: ParseSourceSpan,
|
||||
): R3DirectiveMetadata {
|
||||
const hostDirectives = declaration.hostDirectives?.length
|
||||
? declaration.hostDirectives.map((dir) => ({
|
||||
directive: wrapReference(dir.directive),
|
||||
isForwardReference: false,
|
||||
inputs: dir.inputs ? getHostDirectiveBindingMapping(dir.inputs) : null,
|
||||
outputs: dir.outputs ? getHostDirectiveBindingMapping(dir.outputs) : null,
|
||||
}))
|
||||
: null;
|
||||
|
||||
return {
|
||||
name: declaration.type.name,
|
||||
type: wrapReference(declaration.type),
|
||||
|
|
@ -540,7 +569,7 @@ function convertDeclareDirectiveFacadeToMetadata(
|
|||
fullInheritance: false,
|
||||
isStandalone: declaration.isStandalone ?? false,
|
||||
isSignal: declaration.isSignal ?? false,
|
||||
hostDirectives: convertHostDirectivesToMetadata(declaration),
|
||||
hostDirectives,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -558,28 +587,19 @@ function convertHostDeclarationToMetadata(
|
|||
};
|
||||
}
|
||||
|
||||
function convertHostDirectivesToMetadata(
|
||||
metadata: R3DeclareDirectiveFacade | R3DirectiveMetadataFacade,
|
||||
): R3HostDirectiveMetadata[] | null {
|
||||
if (metadata.hostDirectives?.length) {
|
||||
return metadata.hostDirectives.map((hostDirective) => {
|
||||
return typeof hostDirective === 'function'
|
||||
? {
|
||||
directive: wrapReference(hostDirective),
|
||||
inputs: null,
|
||||
outputs: null,
|
||||
isForwardReference: false,
|
||||
}
|
||||
: {
|
||||
directive: wrapReference(hostDirective.directive),
|
||||
isForwardReference: false,
|
||||
inputs: hostDirective.inputs ? parseMappingStringArray(hostDirective.inputs) : null,
|
||||
outputs: hostDirective.outputs ? parseMappingStringArray(hostDirective.outputs) : null,
|
||||
};
|
||||
});
|
||||
/**
|
||||
* Parses a host directive mapping where each odd array key is the name of an input/output
|
||||
* and each even key is its public name, e.g. `['one', 'oneAlias', 'two', 'two']`.
|
||||
*/
|
||||
function getHostDirectiveBindingMapping(array: string[]) {
|
||||
let result: {[publicName: string]: string} | null = null;
|
||||
|
||||
for (let i = 1; i < array.length; i += 2) {
|
||||
result = result || {};
|
||||
result[array[i - 1]] = array[i];
|
||||
}
|
||||
|
||||
return null;
|
||||
return result;
|
||||
}
|
||||
|
||||
function convertOpaqueValuesToExpressions(obj: {[key: string]: OpaqueValue}): {
|
||||
|
|
|
|||
|
|
@ -261,6 +261,47 @@ describe('directive declaration jit compilation', () => {
|
|||
features: [ɵɵNgOnChangesFeature],
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile host directives', () => {
|
||||
class One {}
|
||||
class Two {}
|
||||
|
||||
const def = ɵɵngDeclareDirective({
|
||||
type: TestClass,
|
||||
hostDirectives: [
|
||||
{
|
||||
directive: One,
|
||||
inputs: ['firstInput', 'firstInput', 'secondInput', 'secondInputAlias'],
|
||||
outputs: ['firstOutput', 'firstOutput', 'secondOutput', 'secondOutputAlias'],
|
||||
},
|
||||
{
|
||||
directive: Two,
|
||||
},
|
||||
],
|
||||
}) as DirectiveDef<TestClass>;
|
||||
|
||||
expectDirectiveDef(def, {
|
||||
features: [jasmine.any(Function)],
|
||||
hostDirectives: [
|
||||
{
|
||||
directive: One,
|
||||
inputs: {
|
||||
'firstInput': 'firstInput',
|
||||
'secondInput': 'secondInputAlias',
|
||||
},
|
||||
outputs: {
|
||||
'firstOutput': 'firstOutput',
|
||||
'secondOutput': 'secondOutputAlias',
|
||||
},
|
||||
},
|
||||
{
|
||||
directive: Two,
|
||||
inputs: {},
|
||||
outputs: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
type DirectiveDefExpectations = jasmine.Expected<
|
||||
|
|
@ -278,6 +319,7 @@ type DirectiveDefExpectations = jasmine.Expected<
|
|||
| 'viewQuery'
|
||||
| 'exportAs'
|
||||
| 'providersResolver'
|
||||
| 'hostDirectives'
|
||||
>
|
||||
>;
|
||||
|
||||
|
|
@ -303,6 +345,7 @@ function expectDirectiveDef(
|
|||
viewQuery: null,
|
||||
exportAs: null,
|
||||
providersResolver: null,
|
||||
hostDirectives: null,
|
||||
...expected,
|
||||
};
|
||||
|
||||
|
|
@ -321,6 +364,7 @@ function expectDirectiveDef(
|
|||
expect(actual.providersResolver)
|
||||
.withContext('providersResolver')
|
||||
.toEqual(expectation.providersResolver);
|
||||
expect(actual.hostDirectives).withContext('hostDirectives').toEqual(expectation.hostDirectives);
|
||||
}
|
||||
|
||||
class TestClass {}
|
||||
|
|
|
|||
Loading…
Reference in a new issue