@@ -25,7 +25,7 @@ abstract class _Specializer {
25
25
final InteropSpecializerFactory factory ;
26
26
final Procedure interopMethod;
27
27
final String jsString;
28
- late final bool firstParameterIsObject =
28
+ late final bool isInstanceMember =
29
29
factory ._extensionIndex.isInstanceInteropMember (interopMethod);
30
30
31
31
_Specializer (this .factory , this .interopMethod, this .jsString);
@@ -53,25 +53,31 @@ abstract class _Specializer {
53
53
54
54
/// Returns the string that will be the body of the JS trampoline.
55
55
///
56
- /// [object] is the callee if there is one for this config. [callArguments] is
57
- /// the remaining arguments of the `interopMethod` .
58
- String bodyString (String object, List <String > callArguments);
56
+ /// [receiver] is the callee if there is one for this config. If empty, it's
57
+ /// assumed to be `globalThis` , which the implementation may elide.
58
+ /// [callArguments] is the remaining arguments of the `interopMethod` .
59
+ String bodyString (String receiver, List <String > callArguments);
59
60
60
61
/// Compute and return the JS trampoline string needed for this method
61
62
/// lowering.
63
+ // TODO(srujzs): We check whether this specializer is a constructor, setter,
64
+ // and an instance member, as well as assert that we don't pass the wrong
65
+ // receivers into `bodyString` calls. This feel like a code smell and likely
66
+ // means we should push this method further down into the implementations,
67
+ // possibly with more intermediate types to reduce code duplication.
62
68
String generateJS (List <String > parameterNames) {
63
- final object = isConstructor
69
+ final receiver = isConstructor
64
70
? ''
65
- : firstParameterIsObject
71
+ : isInstanceMember
66
72
? parameterNames[0 ]
67
73
: 'globalThis' ;
68
74
final callArguments =
69
- firstParameterIsObject ? parameterNames.sublist (1 ) : parameterNames;
75
+ isInstanceMember ? parameterNames.sublist (1 ) : parameterNames;
70
76
final callArgumentsString = callArguments.join (',' );
71
- String functionParameters = firstParameterIsObject
72
- ? '$object ${callArguments .isEmpty ? '' : ',$callArgumentsString ' }'
77
+ String functionParameters = isInstanceMember
78
+ ? '$receiver ${callArguments .isEmpty ? '' : ',$callArgumentsString ' }'
73
79
: callArgumentsString;
74
- final body = bodyString (object , callArguments);
80
+ final body = bodyString (receiver , callArguments);
75
81
76
82
if (parametersNeedParens (parameterNames)) {
77
83
functionParameters = '($functionParameters )' ;
@@ -135,6 +141,46 @@ abstract class _Specializer {
135
141
interopMethod, () => {})[parameters.length] = dartProcedure;
136
142
return dartProcedure;
137
143
}
144
+
145
+ // Determines if a selector in an `@JS` rename can be used in dot notation as
146
+ // an identifier. Note that this is conservative, and it's possible to have
147
+ // other valid identifiers (such as Unicode characters) that don't match this
148
+ // regex. Enumerating all such characters in `ID_Start` and `ID_Continue`
149
+ // would make this a long regex. Almost any "real" rename will be ASCII, so
150
+ // being conservative here is okay.
151
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers
152
+ static final RegExp _dotNotationable = RegExp (r'^[A-Za-z_$][\w$]*$' );
153
+
154
+ /// Given a [fullDottedString] that represents a target for a JS interop call,
155
+ /// splits it and recombines the string into a valid JS property access,
156
+ /// including the provider [receiver] at the beginning.
157
+ ///
158
+ /// If [receiver] is empty, potentially adds a `globalThis` at the beginning
159
+ /// if needed for bracket notation.
160
+ String _splitSelectorsAndRecombine (
161
+ {required String fullDottedString, required String receiver}) {
162
+ final selectors = jsString.split ('.' );
163
+ final validPropertyStr = StringBuffer ('' );
164
+ validPropertyStr.write (receiver);
165
+ for (var i = 0 ; i < selectors.length; i++ ) {
166
+ final selector = selectors[i];
167
+ if (_dotNotationable.hasMatch (selector)) {
168
+ // Prefer dot notation when possible as it's fewer characters.
169
+ if (validPropertyStr.isEmpty) {
170
+ validPropertyStr.write (selector);
171
+ } else {
172
+ validPropertyStr.write (".$selector " );
173
+ }
174
+ } else {
175
+ if (validPropertyStr.isEmpty) {
176
+ // Bracket notation needs a receiver.
177
+ validPropertyStr.write ('globalThis' );
178
+ }
179
+ validPropertyStr.write ("['$selector ']" );
180
+ }
181
+ }
182
+ return validPropertyStr.toString ();
183
+ }
138
184
}
139
185
140
186
/// Config class for interop members that get lowered on the procedure side.
@@ -176,8 +222,12 @@ class _ConstructorSpecializer extends _ProcedureSpecializer {
176
222
bool get isSetter => false ;
177
223
178
224
@override
179
- String bodyString (String object, List <String > callArguments) =>
180
- "new $jsString (${callArguments .join (',' )})" ;
225
+ String bodyString (String receiver, List <String > callArguments) {
226
+ assert (receiver.isEmpty);
227
+ final constructorStr = _splitSelectorsAndRecombine (
228
+ fullDottedString: jsString, receiver: receiver);
229
+ return "new $constructorStr (${callArguments .join (',' )})" ;
230
+ }
181
231
}
182
232
183
233
class _GetterSpecializer extends _ProcedureSpecializer {
@@ -190,8 +240,11 @@ class _GetterSpecializer extends _ProcedureSpecializer {
190
240
bool get isSetter => false ;
191
241
192
242
@override
193
- String bodyString (String object, List <String > callArguments) =>
194
- '$object .$jsString ' ;
243
+ String bodyString (String receiver, List <String > callArguments) {
244
+ assert (receiver.isNotEmpty);
245
+ return _splitSelectorsAndRecombine (
246
+ fullDottedString: jsString, receiver: receiver);
247
+ }
195
248
}
196
249
197
250
class _SetterSpecializer extends _ProcedureSpecializer {
@@ -204,8 +257,12 @@ class _SetterSpecializer extends _ProcedureSpecializer {
204
257
bool get isSetter => true ;
205
258
206
259
@override
207
- String bodyString (String object, List <String > callArguments) =>
208
- '$object .$jsString = ${callArguments [0 ]}' ;
260
+ String bodyString (String receiver, List <String > callArguments) {
261
+ assert (receiver.isNotEmpty);
262
+ final setterStr = _splitSelectorsAndRecombine (
263
+ fullDottedString: jsString, receiver: receiver);
264
+ return '$setterStr = ${callArguments [0 ]}' ;
265
+ }
209
266
}
210
267
211
268
class _MethodSpecializer extends _ProcedureSpecializer {
@@ -218,8 +275,12 @@ class _MethodSpecializer extends _ProcedureSpecializer {
218
275
bool get isSetter => false ;
219
276
220
277
@override
221
- String bodyString (String object, List <String > callArguments) =>
222
- "$object .$jsString (${callArguments .join (',' )})" ;
278
+ String bodyString (String receiver, List <String > callArguments) {
279
+ assert (receiver.isNotEmpty);
280
+ final methodStr = _splitSelectorsAndRecombine (
281
+ fullDottedString: jsString, receiver: receiver);
282
+ return '$methodStr (${callArguments .join (',' )})' ;
283
+ }
223
284
}
224
285
225
286
class _OperatorSpecializer extends _ProcedureSpecializer {
@@ -238,10 +299,11 @@ class _OperatorSpecializer extends _ProcedureSpecializer {
238
299
};
239
300
240
301
@override
241
- String bodyString (String object, List <String > callArguments) {
302
+ String bodyString (String receiver, List <String > callArguments) {
303
+ assert (receiver.isNotEmpty);
242
304
return isSetter
243
- ? '$object [${callArguments [0 ]}] = ${callArguments [1 ]}'
244
- : '$object [${callArguments [0 ]}]' ;
305
+ ? '$receiver [${callArguments [0 ]}] = ${callArguments [1 ]}'
306
+ : '$receiver [${callArguments [0 ]}]' ;
245
307
}
246
308
}
247
309
@@ -302,8 +364,12 @@ class _ConstructorInvocationSpecializer
302
364
bool get isSetter => false ;
303
365
304
366
@override
305
- String bodyString (String object, List <String > callArguments) =>
306
- "new $jsString (${callArguments .join (',' )})" ;
367
+ String bodyString (String receiver, List <String > callArguments) {
368
+ assert (receiver.isEmpty);
369
+ final constructorStr = _splitSelectorsAndRecombine (
370
+ fullDottedString: jsString, receiver: receiver);
371
+ return "new $constructorStr (${callArguments .join (',' )})" ;
372
+ }
307
373
}
308
374
309
375
class _MethodInvocationSpecializer extends _PositionalInvocationSpecializer {
@@ -317,8 +383,12 @@ class _MethodInvocationSpecializer extends _PositionalInvocationSpecializer {
317
383
bool get isSetter => false ;
318
384
319
385
@override
320
- String bodyString (String object, List <String > callArguments) =>
321
- "$object .$jsString (${callArguments .join (',' )})" ;
386
+ String bodyString (String receiver, List <String > callArguments) {
387
+ assert (receiver.isNotEmpty);
388
+ final methodStr = _splitSelectorsAndRecombine (
389
+ fullDottedString: jsString, receiver: receiver);
390
+ return '$methodStr (${callArguments .join (',' )})' ;
391
+ }
322
392
}
323
393
324
394
/// Config class for object literals, which only use named arguments and are
@@ -361,7 +431,8 @@ class _ObjectLiteralSpecializer extends _InvocationSpecializer {
361
431
}
362
432
363
433
@override
364
- String bodyString (String object, List <String > callArguments) {
434
+ String bodyString (String receiver, List <String > callArguments) {
435
+ assert (receiver.isEmpty);
365
436
final keys = parameters.map (_jsKey).toList ();
366
437
final keyValuePairs = < String > [];
367
438
for (int i = 0 ; i < callArguments.length; i++ ) {
0 commit comments