| // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'package:kernel/kernel.dart'; |
| import 'package:kernel/target/targets.dart'; |
| import 'package:_fe_analyzer_shared/src/messages/codes.dart' |
| show |
| Message, |
| LocatedMessage, |
| messageJsInteropAnonymousFactoryPositionalParameters, |
| messageJsInteropEnclosingClassJSAnnotation, |
| messageJsInteropEnclosingClassJSAnnotationContext, |
| messageJsInteropIndexNotSupported, |
| messageJsInteropNamedParameters, |
| messageJsInteropNonExternalConstructor; |
| |
| import 'src/js_interop.dart'; |
| |
| class JsInteropChecks extends RecursiveVisitor<void> { |
| final DiagnosticReporter<Message, LocatedMessage> _diagnosticsReporter; |
| |
| JsInteropChecks(this._diagnosticsReporter); |
| |
| @override |
| void defaultMember(Member member) { |
| _checkMemberJSInteropAnnotation(member); |
| super.defaultMember(member); |
| } |
| |
| @override |
| void visitProcedure(Procedure procedure) { |
| _checkMemberJSInteropAnnotation(procedure); |
| |
| if (!procedure.isExternal || !isJSInteropMember(procedure)) return; |
| |
| if (!procedure.isStatic && |
| (procedure.name.name == '[]=' || procedure.name.name == '[]')) { |
| _diagnosticsReporter.report( |
| messageJsInteropIndexNotSupported, |
| procedure.fileOffset, |
| procedure.name.name.length, |
| procedure.location.file); |
| } |
| |
| var isAnonymousFactory = |
| isAnonymousClassMember(procedure) && procedure.isFactory; |
| |
| if (isAnonymousFactory) { |
| if (procedure.function != null && |
| !procedure.function.positionalParameters.isEmpty) { |
| var firstPositionalParam = procedure.function.positionalParameters[0]; |
| _diagnosticsReporter.report( |
| messageJsInteropAnonymousFactoryPositionalParameters, |
| firstPositionalParam.fileOffset, |
| firstPositionalParam.name.length, |
| firstPositionalParam.location.file); |
| } |
| } else { |
| // Only factory constructors for anonymous classes are allowed to have |
| // named parameters. |
| _checkNoNamedParameters(procedure.function); |
| } |
| } |
| |
| @override |
| void visitConstructor(Constructor constructor) { |
| _checkMemberJSInteropAnnotation(constructor); |
| |
| if (!isJSInteropMember(constructor)) return; |
| |
| if (!constructor.isExternal && !constructor.isSynthetic) { |
| _diagnosticsReporter.report( |
| messageJsInteropNonExternalConstructor, |
| constructor.fileOffset, |
| constructor.name.name.length, |
| constructor.location.file); |
| } |
| |
| _checkNoNamedParameters(constructor.function); |
| } |
| |
| /// Reports an error if [functionNode] has named parameters. |
| void _checkNoNamedParameters(FunctionNode functionNode) { |
| if (functionNode != null && !functionNode.namedParameters.isEmpty) { |
| var firstNamedParam = functionNode.namedParameters[0]; |
| _diagnosticsReporter.report( |
| messageJsInteropNamedParameters, |
| firstNamedParam.fileOffset, |
| firstNamedParam.name.length, |
| firstNamedParam.location.file); |
| } |
| } |
| |
| /// Reports an error if [m] has a JS interop annotation and is part of a class |
| /// that does not. |
| void _checkMemberJSInteropAnnotation(Member m) { |
| if (!hasJSInteropAnnotation(m)) return; |
| var enclosingClass = m.enclosingClass; |
| if (enclosingClass != null && !hasJSInteropAnnotation(enclosingClass)) { |
| _diagnosticsReporter.report(messageJsInteropEnclosingClassJSAnnotation, |
| m.fileOffset, m.name.name.length, m.location.file, |
| context: <LocatedMessage>[ |
| messageJsInteropEnclosingClassJSAnnotationContext.withLocation( |
| enclosingClass.location.file, |
| enclosingClass.fileOffset, |
| enclosingClass.name.length) |
| ]); |
| } |
| } |
| } |