6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
import { resolveForwardRef } from '../../di' ;
9
+ import { RuntimeError , RuntimeErrorCode } from '../../errors' ;
9
10
import { Type } from '../../interface/type' ;
10
11
import { EMPTY_OBJ } from '../../util/empty' ;
11
- import { getDirectiveDef } from '../definition' ;
12
- import { DirectiveDef , HostDirectiveBindingMap , HostDirectiveDefs } from '../interfaces/definition' ;
12
+ import { getComponentDef , getDirectiveDef } from '../definition' ;
13
+ import { DirectiveDef , HostDirectiveBindingMap , HostDirectiveDef , HostDirectiveDefs } from '../interfaces/definition' ;
13
14
14
15
/** Values that can be used to define a host directive through the `HostDirectivesFeature`. */
15
16
type HostDirectiveConfig = Type < unknown > | {
@@ -62,7 +63,9 @@ function findHostDirectiveDefs(
62
63
for ( const hostDirectiveConfig of currentDef . hostDirectives ) {
63
64
const hostDirectiveDef = getDirectiveDef ( hostDirectiveConfig . directive ) ! ;
64
65
65
- // TODO(crisbeto): assert that the def exists.
66
+ if ( typeof ngDevMode === 'undefined' || ngDevMode ) {
67
+ validateHostDirective ( hostDirectiveConfig , hostDirectiveDef , matchedDefs ) ;
68
+ }
66
69
67
70
// Host directives execute before the host so that its host bindings can be overwritten.
68
71
findHostDirectiveDefs ( hostDirectiveDef , matchedDefs , hostDirectiveDefs ) ;
@@ -89,3 +92,80 @@ function bindingArrayToMap(bindings: string[]|undefined): HostDirectiveBindingMa
89
92
90
93
return result ;
91
94
}
95
+
96
+ /**
97
+ * Verifies that the host directive has been configured correctly.
98
+ * @param hostDirectiveConfig Host directive configuration object.
99
+ * @param directiveDef Directive definition of the host directive.
100
+ * @param matchedDefs Directives that have been matched so far.
101
+ */
102
+ function validateHostDirective (
103
+ hostDirectiveConfig : HostDirectiveDef < unknown > , directiveDef : DirectiveDef < any > | null ,
104
+ matchedDefs : DirectiveDef < unknown > [ ] ) : asserts directiveDef is DirectiveDef < unknown > {
105
+ // TODO(crisbeto): implement more of these checks in the compiler.
106
+ const type = hostDirectiveConfig . directive ;
107
+
108
+ if ( directiveDef === null ) {
109
+ if ( getComponentDef ( type ) !== null ) {
110
+ throw new RuntimeError (
111
+ RuntimeErrorCode . HOST_DIRECTIVE_COMPONENT ,
112
+ `Host directive ${ type . name } cannot be a component.` ) ;
113
+ }
114
+
115
+ throw new RuntimeError (
116
+ RuntimeErrorCode . HOST_DIRECTIVE_UNRESOLVABLE ,
117
+ `Could not resolve metadata for host directive ${ type . name } . ` +
118
+ `Make sure that the ${ type . name } class is annotated with an @Directive decorator.` ) ;
119
+ }
120
+
121
+ if ( ! directiveDef . standalone ) {
122
+ throw new RuntimeError (
123
+ RuntimeErrorCode . HOST_DIRECTIVE_NOT_STANDALONE ,
124
+ `Host directive ${ directiveDef . type . name } must be standalone.` ) ;
125
+ }
126
+
127
+ if ( matchedDefs . indexOf ( directiveDef ) > - 1 ) {
128
+ throw new RuntimeError (
129
+ RuntimeErrorCode . DUPLICATE_DIRECTITVE ,
130
+ `Directive ${ directiveDef . type . name } matches multiple times on the same element. ` +
131
+ `Directives can only match an element once.` ) ;
132
+ }
133
+
134
+ validateMappings ( 'input' , directiveDef , hostDirectiveConfig . inputs ) ;
135
+ validateMappings ( 'output' , directiveDef , hostDirectiveConfig . outputs ) ;
136
+ }
137
+
138
+ /**
139
+ * Checks that the host directive inputs/outputs configuration is valid.
140
+ * @param bindingType Kind of binding that is being validated. Used in the error message.
141
+ * @param def Definition of the host directive that is being validated against.
142
+ * @param hostDirectiveDefs Host directive mapping object that shold be validated.
143
+ */
144
+ function validateMappings (
145
+ bindingType : 'input' | 'output' , def : DirectiveDef < unknown > ,
146
+ hostDirectiveDefs : HostDirectiveBindingMap ) {
147
+ const className = def . type . name ;
148
+ const bindings : Record < string , string > = bindingType === 'input' ? def . inputs : def . outputs ;
149
+
150
+ for ( const publicName in hostDirectiveDefs ) {
151
+ if ( hostDirectiveDefs . hasOwnProperty ( publicName ) ) {
152
+ if ( ! bindings . hasOwnProperty ( publicName ) ) {
153
+ throw new RuntimeError (
154
+ RuntimeErrorCode . HOST_DIRECTIVE_UNDEFINED_BINDING ,
155
+ `Directive ${ className } does not have an ${ bindingType } with a public name of ${
156
+ publicName } .`) ;
157
+ }
158
+
159
+ const remappedPublicName = hostDirectiveDefs [ publicName ] ;
160
+
161
+ if ( bindings . hasOwnProperty ( remappedPublicName ) &&
162
+ bindings [ remappedPublicName ] !== publicName ) {
163
+ throw new RuntimeError (
164
+ RuntimeErrorCode . HOST_DIRECTIVE_CONFLICTING_ALIAS ,
165
+ `Cannot alias ${ bindingType } ${ publicName } of host directive ${ className } to ${
166
+ remappedPublicName } , because it already has a different ${
167
+ bindingType } with the same public name.`) ;
168
+ }
169
+ }
170
+ }
171
+ }
0 commit comments