@@ -24,6 +24,7 @@ const binaryOperators = {
2424 '+' : ( l , r ) => l + r ,
2525 '-' : ( l , r ) => l - r ,
2626 '*' : ( l , r ) => l * r ,
27+ '**' : ( l , r ) => l ** r ,
2728 '/' : ( l , r ) => l / r ,
2829 '%' : ( l , r ) => l % r ,
2930 '<' : ( l , r ) => l < r ,
@@ -63,54 +64,53 @@ const expressionParsers = {
6364
6465 const left = convertExpressionToValue ( expression . left , model , isBackConvert , changedModel ) ;
6566
66- if ( expression . operator == '|' && expression . right . type == 'CallExpression' ) {
67- expression . right . requiresConverter = true ;
67+ let converterExpression = expression . right ;
68+ if ( expression . operator == '|' ) {
69+ if ( converterExpression . type == 'ChainExpression' ) {
70+ converterExpression = converterExpression . expression ;
71+ }
72+ if ( converterExpression . type == 'CallExpression' ) {
73+ ! converterExpression . arguments . includes ( expression . left ) && converterExpression . arguments . unshift ( expression . left ) ;
74+ expression . right . nsIsCallable = true ;
75+ converterExpression = converterExpression . callee ;
76+ }
77+
78+ switch ( converterExpression . type ) {
79+ case 'Identifier' :
80+ case 'MemberExpression' :
81+ case 'NewExpression' :
82+ converterExpression . nsRequiresConverter = true ;
83+ converterExpression . nsIsPendingCall = true ;
84+ break ;
85+ default :
86+ throw new Error ( 'Invalid converter syntax' ) ;
87+ }
6888 }
89+
6990 const right = convertExpressionToValue ( expression . right , model , isBackConvert , changedModel ) ;
7091
7192 if ( expression . operator == '|' ) {
72- if ( expression . right . requiresConverter && right != null ) {
73- right . args . unshift ( left ) ;
74- return right . callback . apply ( right . context , right . args ) ;
93+ if ( converterExpression . nsRequiresConverter ) {
94+ return expression . right . nsIsCallable ? right : right ?.( left ) ;
7595 }
76- throw new Error ( 'Invalid converter after ' + expression . operator + ' operator ') ;
96+ throw new Error ( 'Invalid converter syntax ' ) ;
7797 }
7898 return binaryOperators [ expression . operator ] ( left , right ) ;
7999 } ,
80100 'CallExpression' : ( expression : ASTExpression , model , isBackConvert : boolean , changedModel ) => {
81- expression . callee . requiresObjectAndProperty = true ;
82-
83- const { object, property } = convertExpressionToValue ( expression . callee , model , isBackConvert , changedModel ) ;
84-
85- let callback ;
86- if ( object == FORCED_CHAIN_VALUE ) {
87- callback = undefined ;
88- } else {
89- callback = expression . callee . optional ? object ?. [ property ] : object [ property ] ;
90- }
101+ expression . callee . nsIsPendingCall = true ;
102+ const callback = convertExpressionToValue ( expression . callee , model , isBackConvert , changedModel ) ;
91103
92- if ( ( ! expression . optional || expression . requiresConverter ) && isNullOrUndefined ( callback ) ) {
104+ if ( ! expression . optional && isNullOrUndefined ( callback ) ) {
93105 throw new Error ( 'Cannot perform a call using a null or undefined property' ) ;
94106 }
95107
96- if ( expression . requiresConverter ) {
97- if ( isFunction ( callback ) ) {
98- callback = { toView : callback } ;
99- } else if ( ! isObject ( callback ) || ! isFunction ( callback . toModel ) && ! isFunction ( callback . toView ) ) {
100- throw new Error ( 'Invalid converter call' ) ;
101- }
102- }
103-
104108 const parsedArgs = [ ] ;
105109 for ( let argument of expression . arguments ) {
106110 let value = convertExpressionToValue ( argument , model , isBackConvert , changedModel ) ;
107111 argument . type == 'SpreadElement' ? parsedArgs . push ( ...value ) : parsedArgs . push ( value ) ;
108112 }
109-
110- if ( expression . requiresConverter ) {
111- return getConverter ( callback , object , parsedArgs , isBackConvert ) ;
112- }
113- return expression . optional ? callback ?. apply ( object , parsedArgs ) : callback . apply ( object , parsedArgs ) ;
113+ return expression . optional ? callback ?.( ...parsedArgs ) : callback ( ...parsedArgs ) ;
114114 } ,
115115 'ChainExpression' : ( expression : ASTExpression , model , isBackConvert : boolean , changedModel ) => {
116116 return convertExpressionToValue ( expression . expression , model , isBackConvert , changedModel ) ;
@@ -121,10 +121,11 @@ const expressionParsers = {
121121 } ,
122122 'Identifier' : ( expression : ASTExpression , model , isBackConvert : boolean , changedModel ) => {
123123 const context = getContext ( expression . name , model , changedModel ) ;
124- if ( expression . requiresObjectAndProperty ) {
125- return { object : context , property : expression . name } ;
126- }
127- return context [ expression . name ] ;
124+ let value = context [ expression . name ] ;
125+ if ( expression . nsRequiresConverter ) {
126+ value = getConverterCallback ( value , isBackConvert ) ;
127+ }
128+ return expression . nsIsPendingCall && typeof value === 'function' ? value . bind ( context ) : value ;
128129 } ,
129130 'Literal' : ( expression : ASTExpression , model , isBackConvert : boolean , changedModel ) => {
130131 return expression . regex != null ? new RegExp ( expression . regex . pattern , expression . regex . flags ) : expression . value ;
@@ -138,34 +139,34 @@ const expressionParsers = {
138139 } ,
139140 'MemberExpression' : ( expression : ASTExpression , model , isBackConvert : boolean , changedModel ) => {
140141 if ( expression . object . type == 'MemberExpression' ) {
141- expression . object . isChained = true ;
142+ expression . object . nsIsChained = true ;
142143 }
143144
144145 const object = convertExpressionToValue ( expression . object , model , isBackConvert , changedModel ) ;
145146 const property = expression . computed ? convertExpressionToValue ( expression . property , model , isBackConvert , changedModel ) : expression . property ?. name ;
146- const propertyInfo = { object, property } ;
147-
148- if ( expression . requiresObjectAndProperty ) {
149- return propertyInfo ;
150- }
151147
152148 /**
153- * If first member is undefined, make sure that no error is thrown later but return undefined instead .
154- * This behaviour is kept in order to cope with components whose binding context takes a bit long to load.
155- * Old parser would return undefined for an expression like 'property1.property2.property3'
156- * even if expression as a whole consisted of undefined properties .
157- * The new one will keep the same principle only if first member is undefined for safety reasons .
149+ * If an expression parent property is null or undefined, apply null-safety .
150+ * This behaviour also helps cope with components whose binding context takes a bit longer to load.
151+ * Old parser would be null-safe for properties and sub-properties
152+ * even if expression as a whole consisted of undefined ones .
153+ * The new parser will keep the same principle only if parent property is null or undefined, resulting in better control over code and errors .
158154 * It meddles with members specifically, so that it will not affect expression result as a whole.
159155 * For example, an 'isLoading || isBusy' expression will be validated as 'undefined || undefined'
160156 * if context is not ready.
161157 */
162- if ( object === undefined && expression . object . type == 'Identifier' ) {
163- return expression . isChained ? FORCED_CHAIN_VALUE : object ;
158+ if ( object == null && expression . object . type == 'Identifier' ) {
159+ return expression . nsIsChained ? FORCED_CHAIN_VALUE : undefined ;
164160 }
165161 if ( object == FORCED_CHAIN_VALUE ) {
166- return expression . isChained ? object : undefined ;
162+ return expression . nsIsChained ? object : undefined ;
163+ }
164+
165+ let value = expression . optional ? object ?. [ property ] : object [ property ] ;
166+ if ( expression . nsRequiresConverter ) {
167+ value = getConverterCallback ( value , isBackConvert ) ;
167168 }
168- return expression . optional ? object ?. [ property ] : object [ property ] ;
169+ return expression . nsIsPendingCall && typeof value === 'function' ? value . bind ( object ) : value ;
169170 } ,
170171 'NewExpression' : ( expression : ASTExpression , model , isBackConvert : boolean , changedModel ) => {
171172 const callback = convertExpressionToValue ( expression . callee , model , isBackConvert , changedModel ) ;
@@ -174,7 +175,12 @@ const expressionParsers = {
174175 let value = convertExpressionToValue ( argument , model , isBackConvert , changedModel ) ;
175176 argument . type == 'SpreadElement' ? parsedArgs . push ( ...value ) : parsedArgs . push ( value ) ;
176177 }
177- return new callback ( ...parsedArgs ) ;
178+
179+ let value = new callback ( ...parsedArgs ) ;
180+ if ( expression . nsRequiresConverter ) {
181+ value = getConverterCallback ( value , isBackConvert ) ;
182+ }
183+ return value ;
178184 } ,
179185 'ObjectExpression' : ( expression : ASTExpression , model , isBackConvert : boolean , changedModel ) => {
180186 const parsedObject = { } ;
@@ -198,12 +204,14 @@ const expressionParsers = {
198204 } ,
199205 'TemplateLiteral' : ( expression : ASTExpression , model , isBackConvert : boolean , changedModel ) => {
200206 let parsedText = '' ;
201- for ( let q of expression . quasis ) {
202- parsedText += convertExpressionToValue ( q , model , isBackConvert , changedModel ) ;
203- }
207+ const length = expression . quasis . length ;
204208
205- for ( let ex of expression . expressions ) {
206- parsedText += convertExpressionToValue ( ex , model , isBackConvert , changedModel ) ;
209+ for ( let i = 0 ; i < length ; i ++ ) {
210+ let q = expression . quasis [ i ] ;
211+ parsedText += convertExpressionToValue ( q , model , isBackConvert , changedModel ) ;
212+ if ( ! q . tail ) {
213+ parsedText += convertExpressionToValue ( expression . expressions [ i ] , model , isBackConvert , changedModel ) ;
214+ }
207215 }
208216 return parsedText ;
209217 } ,
@@ -225,14 +233,18 @@ function getContext(key, model, changedModel) {
225233 return context ;
226234}
227235
228- function getConverter ( converterSchema , context , args , isBackConvert : boolean ) {
229- const converter = { callback : null , context, args } ;
230- let callback = isBackConvert ? converterSchema . toModel : converterSchema . toView ;
231- if ( callback == null ) {
232- callback = Function . prototype ;
236+ function getConverterCallback ( value , isBackConvert : boolean ) {
237+ let callback = null ;
238+ if ( isNullOrUndefined ( value ) ) {
239+ callback = value ;
240+ } else if ( isFunction ( value ) ) {
241+ callback = isBackConvert ? Function . prototype : value ;
242+ } else if ( isObject ( value ) && ( isFunction ( value . toModel ) || isFunction ( value . toView ) ) ) {
243+ callback = ( isBackConvert ? value . toModel : value . toView ) || Function . prototype ;
244+ } else {
245+ callback = value ;
233246 }
234- converter . callback = callback ;
235- return converter ;
247+ return callback ;
236248}
237249
238250export function parseExpression ( expressionText : string ) : ASTExpression {
0 commit comments