@@ -12,6 +12,7 @@ import 'package:_fe_analyzer_shared/src/messages/codes.dart'
1212 messageJsInteropAnonymousFactoryPositionalParameters,
1313 messageJsInteropEnclosingClassJSAnnotation,
1414 messageJsInteropEnclosingClassJSAnnotationContext,
15+ messageJsInteropExternalExtensionMemberNotOnJSClass,
1516 messageJsInteropExternalMemberNotJSAnnotated,
1617 messageJsInteropIndexNotSupported,
1718 messageJsInteropNamedParameters,
@@ -30,6 +31,7 @@ class JsInteropChecks extends RecursiveVisitor {
3031 bool _classHasJSAnnotation = false ;
3132 bool _classHasAnonymousAnnotation = false ;
3233 bool _libraryHasJSAnnotation = false ;
34+ Map <Reference , Extension >? _libraryExtensionsIndex;
3335
3436 /// Libraries that use `external` to exclude from checks on external.
3537 static final Iterable <String > _pathsWithAllowedDartExternalUsage = < String > [
@@ -86,7 +88,8 @@ class JsInteropChecks extends RecursiveVisitor {
8688
8789 @override
8890 void defaultMember (Member member) {
89- _checkJSInteropAnnotation (member);
91+ _checkInstanceMemberJSAnnotation (member);
92+ if (! _isJSInteropMember (member)) _checkDisallowedExternal (member);
9093 // TODO(43530): Disallow having JS interop annotations on non-external
9194 // members (class members or otherwise). Currently, they're being ignored.
9295 super .defaultMember (member);
@@ -165,11 +168,12 @@ class JsInteropChecks extends RecursiveVisitor {
165168 super .visitLibrary (lib);
166169 _libraryIsGlobalNamespace = false ;
167170 _libraryHasJSAnnotation = false ;
171+ _libraryExtensionsIndex = null ;
168172 }
169173
170174 @override
171175 void visitProcedure (Procedure procedure) {
172- _checkJSInteropAnnotation (procedure);
176+ _checkInstanceMemberJSAnnotation (procedure);
173177 if (_classHasJSAnnotation && ! procedure.isExternal) {
174178 // If not one of few exceptions, member is not allowed to exclude
175179 // `external` inside of a JS interop class.
@@ -183,38 +187,45 @@ class JsInteropChecks extends RecursiveVisitor {
183187 procedure.fileUri);
184188 }
185189 }
186- if (! _isJSInteropMember (procedure)) return ;
187190
188- if (! procedure.isStatic &&
189- (procedure.name.text == '[]=' || procedure.name.text == '[]' )) {
190- _diagnosticsReporter.report (messageJsInteropIndexNotSupported,
191- procedure.fileOffset, procedure.name.text.length, procedure.fileUri);
192- }
193-
194- var isAnonymousFactory =
195- _classHasAnonymousAnnotation && procedure.isFactory;
196-
197- if (isAnonymousFactory) {
198- // ignore: unnecessary_null_comparison
199- if (procedure.function != null &&
200- ! procedure.function.positionalParameters.isEmpty) {
201- var firstPositionalParam = procedure.function.positionalParameters[0 ];
191+ if (! _isJSInteropMember (procedure)) {
192+ _checkDisallowedExternal (procedure);
193+ } else {
194+ // Check JS interop indexing.
195+ if (! procedure.isStatic &&
196+ (procedure.name.text == '[]=' || procedure.name.text == '[]' )) {
202197 _diagnosticsReporter.report (
203- messageJsInteropAnonymousFactoryPositionalParameters,
204- firstPositionalParam.fileOffset,
205- firstPositionalParam.name! .length,
206- firstPositionalParam.location! .file);
198+ messageJsInteropIndexNotSupported,
199+ procedure.fileOffset,
200+ procedure.name.text.length,
201+ procedure.fileUri);
202+ }
203+
204+ // Check JS Interop positional and named parameters.
205+ var isAnonymousFactory =
206+ _classHasAnonymousAnnotation && procedure.isFactory;
207+ if (isAnonymousFactory) {
208+ // ignore: unnecessary_null_comparison
209+ if (procedure.function != null &&
210+ ! procedure.function.positionalParameters.isEmpty) {
211+ var firstPositionalParam = procedure.function.positionalParameters[0 ];
212+ _diagnosticsReporter.report (
213+ messageJsInteropAnonymousFactoryPositionalParameters,
214+ firstPositionalParam.fileOffset,
215+ firstPositionalParam.name! .length,
216+ firstPositionalParam.location! .file);
217+ }
218+ } else {
219+ // Only factory constructors for anonymous classes are allowed to have
220+ // named parameters.
221+ _checkNoNamedParameters (procedure.function);
207222 }
208- } else {
209- // Only factory constructors for anonymous classes are allowed to have
210- // named parameters.
211- _checkNoNamedParameters (procedure.function);
212223 }
213224 }
214225
215226 @override
216227 void visitConstructor (Constructor constructor) {
217- _checkJSInteropAnnotation (constructor);
228+ _checkInstanceMemberJSAnnotation (constructor);
218229 if (_classHasJSAnnotation &&
219230 ! constructor.isExternal &&
220231 ! constructor.isSynthetic) {
@@ -225,9 +236,12 @@ class JsInteropChecks extends RecursiveVisitor {
225236 constructor.name.text.length,
226237 constructor.fileUri);
227238 }
228- if (! _isJSInteropMember (constructor)) return ;
229239
230- _checkNoNamedParameters (constructor.function);
240+ if (! _isJSInteropMember (constructor)) {
241+ _checkDisallowedExternal (constructor);
242+ } else {
243+ _checkNoNamedParameters (constructor.function);
244+ }
231245 }
232246
233247 /// Reports an error if [functionNode] has named parameters.
@@ -243,9 +257,9 @@ class JsInteropChecks extends RecursiveVisitor {
243257 }
244258 }
245259
246- /// Reports an error if [member] does not correctly use the JS interop
247- /// annotation or the keyword `external` .
248- void _checkJSInteropAnnotation (Member member) {
260+ /// Reports an error if given instance [member] is JS interop, but inside a
261+ /// non JS interop class .
262+ void _checkInstanceMemberJSAnnotation (Member member) {
249263 var enclosingClass = member.enclosingClass;
250264
251265 if (! _classHasJSAnnotation &&
@@ -262,13 +276,24 @@ class JsInteropChecks extends RecursiveVisitor {
262276 enclosingClass.name.length)
263277 ]);
264278 }
279+ }
265280
266- // Check for correct `external` usage.
267- if (member.isExternal &&
268- ! _isAllowedExternalUsage (member) &&
269- ! hasJSInteropAnnotation (member)) {
270- if (member.enclosingClass != null && ! _classHasJSAnnotation ||
271- member.enclosingClass == null && ! _libraryHasJSAnnotation) {
281+ /// Assumes given [member] is not JS interop, and reports an error if
282+ /// [member] is `external` and not an allowed `external` usage.
283+ void _checkDisallowedExternal (Member member) {
284+ if (member.isExternal) {
285+ // TODO(rileyporter): Allow extension members on some Native classes.
286+ if (member.isExtensionMember) {
287+ _diagnosticsReporter.report (
288+ messageJsInteropExternalExtensionMemberNotOnJSClass,
289+ member.fileOffset,
290+ member.name.text.length,
291+ member.fileUri);
292+ } else if (! hasJSInteropAnnotation (member) &&
293+ ! _isAllowedExternalUsage (member)) {
294+ // Member could be JS annotated and not considered a JS interop member
295+ // if inside a non-JS interop class. Should not report an error in this
296+ // case, since a different error will already be produced.
272297 _diagnosticsReporter.report (
273298 messageJsInteropExternalMemberNotJSAnnotated,
274299 member.fileOffset,
@@ -289,18 +314,39 @@ class JsInteropChecks extends RecursiveVisitor {
289314 }
290315
291316 /// Returns whether [member] is considered to be a JS interop member.
317+ ///
318+ /// A JS interop member is `external` , and is in a valid JS interop context,
319+ /// which can be:
320+ /// - inside a JS interop class
321+ /// - inside an extension on a JS interop class
322+ /// - a top level member that is JS interop annotated or in a JS interop
323+ /// library
324+ /// If a member belongs to a class, the class must be JS interop annotated.
292325 bool _isJSInteropMember (Member member) {
293326 if (member.isExternal) {
294327 if (_classHasJSAnnotation) return true ;
328+ if (member.isExtensionMember) return _isJSExtensionMember (member);
295329 if (member.enclosingClass == null ) {
296- // In the case where the member does not belong to any class, a JS
297- // annotation is not needed on the library to be considered JS interop
298- // as long as the member has an annotation.
299330 return hasJSInteropAnnotation (member) || _libraryHasJSAnnotation;
300331 }
301332 }
302333
303334 // Otherwise, not JS interop.
304335 return false ;
305336 }
337+
338+ /// Returns whether given extension [member] is in an extension that is on a
339+ /// JS interop class.
340+ bool _isJSExtensionMember (Member member) {
341+ assert (member.isExtensionMember);
342+ if (_libraryExtensionsIndex == null ) {
343+ _libraryExtensionsIndex = {};
344+ member.enclosingLibrary.extensions.forEach ((extension ) =>
345+ extension .members.forEach ((memberDescriptor) =>
346+ _libraryExtensionsIndex! [memberDescriptor.member] = extension ));
347+ }
348+
349+ var onType = _libraryExtensionsIndex! [member.reference]! .onType;
350+ return onType is InterfaceType && hasJSInteropAnnotation (onType.classNode);
351+ }
306352}
0 commit comments