@@ -14,6 +14,7 @@ import {
1414 isDataModelField ,
1515 isLiteralExpr ,
1616 isMemberAccessExpr ,
17+ isNullExpr ,
1718 isReferenceExpr ,
1819 isThisExpr ,
1920 isUnaryExpr ,
@@ -125,13 +126,19 @@ export class ConstraintTransformer {
125126 }
126127
127128 private transformMemberAccess ( expr : MemberAccessExpr ) {
129+ // "this.x" is transformed into a named variable
128130 if ( isThisExpr ( expr . operand ) ) {
129- // "this.x" is transformed into a named variable
130131 return this . variable ( expr . member . $refText , 'boolean' ) ;
131132 }
132133
133- // other member access expressions are not supported and thus
134- // transformed into a free variable
134+ // top-level auth access
135+ const authAccess = this . getAuthAccess ( expr ) ;
136+ if ( authAccess ) {
137+ return this . value ( `${ authAccess } ?? false` , 'boolean' ) ;
138+ }
139+
140+ // other top-level member access expressions are not supported
141+ // and thus transformed into a free variable
135142 return this . nextVar ( ) ;
136143 }
137144
@@ -153,14 +160,19 @@ export class ConstraintTransformer {
153160 }
154161
155162 private transformComparison ( expr : BinaryExpr ) {
156- const leftOperand = this . getComparisonOperand ( expr . left ) ;
157- const rightOperand = this . getComparisonOperand ( expr . right ) ;
163+ if ( this . isAuthEqualNull ( expr ) ) {
164+ // `auth() == null` => `user === null`
165+ return this . value ( `${ this . options . authAccessor } === null` , 'boolean' ) ;
166+ }
158167
159- if ( leftOperand === undefined || rightOperand === undefined ) {
160- // if either operand is not supported, transform into a free variable
161- return this . nextVar ( ) ;
168+ if ( this . isAuthNotEqualNull ( expr ) ) {
169+ // `auth() != null` => `user !== null`
170+ return this . value ( ` ${ this . options . authAccessor } !== null` , 'boolean' ) ;
162171 }
163172
173+ const leftOperand = this . getComparisonOperand ( expr . left ) ;
174+ const rightOperand = this . getComparisonOperand ( expr . right ) ;
175+
164176 const op = match ( expr . operator )
165177 . with ( '==' , ( ) => 'eq' )
166178 . with ( '!=' , ( ) => 'eq' )
@@ -175,12 +187,52 @@ export class ConstraintTransformer {
175187 let result = `{ kind: '${ op } ', left: ${ leftOperand } , right: ${ rightOperand } }` ;
176188 if ( expr . operator === '!=' ) {
177189 // transform "!=" into "not eq"
178- result = `{ kind: 'not', children: [${ result } ] }` ;
190+ result = this . not ( result ) ;
191+ }
192+
193+ // `auth()` access can be undefined, when that happens, we assume a false condition
194+ // for the comparison, unless we're directly comparing `auth() != null`
195+
196+ const leftAuthAccess = this . getAuthAccess ( expr . left ) ;
197+ const rightAuthAccess = this . getAuthAccess ( expr . right ) ;
198+
199+ if ( leftAuthAccess ) {
200+ // `auth().f op x` => `auth().f !== undefined && auth().f op x`
201+ return this . and ( this . value ( `${ this . normalizeToNull ( leftAuthAccess ) } !== null` , 'boolean' ) , result ) ;
202+ } else if ( rightAuthAccess ) {
203+ // `x op auth().f` => `auth().f !== undefined && x op auth().f`
204+ return this . and ( this . value ( `${ this . normalizeToNull ( rightAuthAccess ) } !== null` , 'boolean' ) , result ) ;
205+ }
206+
207+ if ( leftOperand === undefined || rightOperand === undefined ) {
208+ // if either operand is not supported, transform into a free variable
209+ return this . nextVar ( ) ;
179210 }
180211
181212 return result ;
182213 }
183214
215+ // normalize `auth()` access undefined value to null
216+ private normalizeToNull ( expr : string ) {
217+ return `(${ expr } ?? null)` ;
218+ }
219+
220+ private isAuthEqualNull ( expr : BinaryExpr ) {
221+ return (
222+ expr . operator === '==' &&
223+ ( ( isAuthInvocation ( expr . left ) && isNullExpr ( expr . right ) ) ||
224+ ( isAuthInvocation ( expr . right ) && isNullExpr ( expr . left ) ) )
225+ ) ;
226+ }
227+
228+ private isAuthNotEqualNull ( expr : BinaryExpr ) {
229+ return (
230+ expr . operator === '!=' &&
231+ ( ( isAuthInvocation ( expr . left ) && isNullExpr ( expr . right ) ) ||
232+ ( isAuthInvocation ( expr . right ) && isNullExpr ( expr . left ) ) )
233+ ) ;
234+ }
235+
184236 private getComparisonOperand ( expr : Expression ) {
185237 if ( isLiteralExpr ( expr ) ) {
186238 return this . transformLiteral ( expr ) ;
@@ -199,16 +251,9 @@ export class ConstraintTransformer {
199251
200252 const authAccess = this . getAuthAccess ( expr ) ;
201253 if ( authAccess ) {
202- // `auth().` access is transformed into a runtime boolean value if it
203- // doesn't evaluate to undefined (due to ?. chaining), otherwise into
204- // a named variable
205- const fieldAccess = `${ this . options . authAccessor } ?.${ authAccess } ` ;
206254 const mappedType = this . mapType ( expr ) ;
207255 if ( mappedType ) {
208- return `${ fieldAccess } === undefined ? ${ this . expressionVariable ( expr , mappedType ) } : ${ this . value (
209- fieldAccess ,
210- mappedType
211- ) } `;
256+ return `${ this . value ( authAccess , mappedType ) } ` ;
212257 } else {
213258 return undefined ;
214259 }
@@ -241,7 +286,7 @@ export class ConstraintTransformer {
241286 }
242287
243288 if ( isAuthInvocation ( expr . operand ) ) {
244- return expr . member . $refText ;
289+ return ` ${ this . options . authAccessor } ?. ${ expr . member . $refText } ` ;
245290 } else {
246291 const operand = this . getAuthAccess ( expr . operand ) ;
247292 return operand ? `${ operand } ?.${ expr . member . $refText } ` : undefined ;
0 commit comments