Add a minimal implementation of ExtensionDeclaration

Change-Id: I5a556ea1819b38a3130e801a3cbc4bcbad98dcdd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/103380
Reviewed-by: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/dart/ast/ast.dart b/pkg/analyzer/lib/dart/ast/ast.dart
index 7667168..2e5f1ae 100644
--- a/pkg/analyzer/lib/dart/ast/ast.dart
+++ b/pkg/analyzer/lib/dart/ast/ast.dart
@@ -500,6 +500,8 @@
 
   R visitExtendsClause(ExtendsClause node);
 
+  R visitExtensionDeclaration(ExtensionDeclaration node);
+
   R visitFieldDeclaration(FieldDeclaration node);
 
   R visitFieldFormalParameter(FieldFormalParameter node);
diff --git a/pkg/analyzer/lib/dart/ast/ast_factory.dart b/pkg/analyzer/lib/dart/ast/ast_factory.dart
index 815ad03..bf624d8 100644
--- a/pkg/analyzer/lib/dart/ast/ast_factory.dart
+++ b/pkg/analyzer/lib/dart/ast/ast_factory.dart
@@ -339,6 +339,20 @@
   /// Returns a newly created extends clause.
   ExtendsClause extendsClause(Token extendsKeyword, TypeName superclass);
 
+  /// Return a newly created extention declaration. The list of [typeParameters]
+  /// can be `null` if there are no type parameters.
+  ExtensionDeclaration extensionDeclaration(
+      {Comment comment,
+      List<Annotation> metadata,
+      Token extensionKeyword,
+      @required SimpleIdentifier name,
+      TypeParameterList typeParameters,
+      Token onKeyword,
+      @required TypeAnnotation extendedType,
+      Token leftBracket,
+      List<ClassMember> members,
+      Token rightBracket});
+
   /// Returns a newly created field declaration. Either or both of the [comment]
   /// and [metadata] can be `null` if the declaration does not have the
   /// corresponding attribute. The [staticKeyword] can be `null` if the field is
diff --git a/pkg/analyzer/lib/dart/ast/visitor.dart b/pkg/analyzer/lib/dart/ast/visitor.dart
index 6646631..68f4621 100644
--- a/pkg/analyzer/lib/dart/ast/visitor.dart
+++ b/pkg/analyzer/lib/dart/ast/visitor.dart
@@ -277,6 +277,10 @@
   R visitExtendsClause(ExtendsClause node) => visitNode(node);
 
   @override
+  R visitExtensionDeclaration(ExtensionDeclaration node) =>
+      visitNamedCompilationUnitMember(node);
+
+  @override
   R visitFieldDeclaration(FieldDeclaration node) => visitClassMember(node);
 
   @override
@@ -839,6 +843,12 @@
   }
 
   @override
+  R visitExtensionDeclaration(ExtensionDeclaration node) {
+    node.visitChildren(this);
+    return null;
+  }
+
+  @override
   R visitFieldDeclaration(FieldDeclaration node) {
     node.visitChildren(this);
     return null;
@@ -1447,6 +1457,9 @@
   R visitExtendsClause(ExtendsClause node) => null;
 
   @override
+  R visitExtensionDeclaration(ExtensionDeclaration node) => null;
+
+  @override
   R visitFieldDeclaration(FieldDeclaration node) => null;
 
   @override
@@ -1822,6 +1835,9 @@
   R visitExtendsClause(ExtendsClause node) => _throw(node);
 
   @override
+  R visitExtensionDeclaration(ExtensionDeclaration node) => _throw(node);
+
+  @override
   R visitFieldDeclaration(FieldDeclaration node) => _throw(node);
 
   @override
@@ -2406,6 +2422,14 @@
   }
 
   @override
+  T visitExtensionDeclaration(ExtensionDeclaration node) {
+    stopwatch.start();
+    T result = _baseVisitor.visitExtensionDeclaration(node);
+    stopwatch.stop();
+    return result;
+  }
+
+  @override
   T visitFieldDeclaration(FieldDeclaration node) {
     stopwatch.start();
     T result = _baseVisitor.visitFieldDeclaration(node);
@@ -3181,6 +3205,9 @@
   R visitExtendsClause(ExtendsClause node) => visitNode(node);
 
   @override
+  R visitExtensionDeclaration(ExtensionDeclaration node) => visitNode(node);
+
+  @override
   R visitFieldDeclaration(FieldDeclaration node) => visitNode(node);
 
   @override
diff --git a/pkg/analyzer/lib/src/dart/ast/ast.dart b/pkg/analyzer/lib/src/dart/ast/ast.dart
index e3064ce..8a28fb8 100644
--- a/pkg/analyzer/lib/src/dart/ast/ast.dart
+++ b/pkg/analyzer/lib/src/dart/ast/ast.dart
@@ -3821,6 +3821,109 @@
   }
 }
 
+/// The declaration of an extension of a type.
+///
+///    extension ::=
+///        'extension' [SimpleIdentifier] [TypeParameterList]?
+///        'on' [TypeAnnotation] '{' [ClassMember]* '}'
+///
+/// Clients may not extend, implement or mix-in this class.
+class ExtensionDeclarationImpl extends NamedCompilationUnitMemberImpl
+    implements ExtensionDeclaration {
+  @override
+  Token extensionKeyword;
+
+  /// The type parameters for the extension, or `null` if the extension
+  /// does not have any type parameters.
+  TypeParameterListImpl _typeParameters;
+
+  @override
+  Token onKeyword;
+
+  /// The type that is being extended.
+  TypeAnnotationImpl _extendedType;
+
+  @override
+  Token leftBracket;
+
+  /// The members being added to the extended class.
+  NodeList<ClassMember> _members;
+
+  @override
+  Token rightBracket;
+
+  ExtensionDeclarationImpl(
+      CommentImpl comment,
+      List<Annotation> metadata,
+      this.extensionKeyword,
+      SimpleIdentifierImpl name,
+      TypeParameterListImpl typeParameters,
+      this.onKeyword,
+      TypeAnnotationImpl extendedType,
+      this.leftBracket,
+      List<ClassMember> members,
+      this.rightBracket)
+      : super(comment, metadata, name) {
+    _typeParameters = _becomeParentOf(typeParameters);
+    _extendedType = _becomeParentOf(extendedType);
+    _members = new NodeListImpl<ClassMember>(this, members);
+  }
+
+  @override
+  Token get beginToken => extensionKeyword;
+
+  @override
+  Iterable<SyntacticEntity> get childEntities => new ChildEntities()
+    ..add(extensionKeyword)
+    ..add(name)
+    ..add(typeParameters)
+    ..add(onKeyword)
+    ..add(extendedType)
+    ..add(leftBracket)
+    ..addAll(members)
+    ..add(rightBracket);
+
+  @override
+  Element get declaredElement => name.staticElement;
+
+  @override
+  Element get element => name.staticElement;
+
+  @override
+  Token get endToken => rightBracket;
+
+  @override
+  TypeAnnotation get extendedType => _extendedType;
+
+  void set extendedType(TypeAnnotation extendedClass) {
+    _extendedType = _becomeParentOf(extendedClass as TypeAnnotationImpl);
+  }
+
+  @override
+  Token get firstTokenAfterCommentAndMetadata => name.beginToken;
+
+  @override
+  NodeList<ClassMember> get members => _members;
+
+  @override
+  TypeParameterList get typeParameters => _typeParameters;
+
+  void set typeParameters(TypeParameterList typeParameters) {
+    _typeParameters = _becomeParentOf(typeParameters as TypeParameterListImpl);
+  }
+
+  @override
+  E accept<E>(AstVisitor<E> visitor) => visitor.visitExtensionDeclaration(this);
+
+  @override
+  void visitChildren(AstVisitor visitor) {
+    name?.accept(visitor);
+    _typeParameters?.accept(visitor);
+    _extendedType?.accept(visitor);
+    _members.accept(visitor);
+  }
+}
+
 /// The declaration of one or more fields of the same type.
 ///
 ///    fieldDeclaration ::=
diff --git a/pkg/analyzer/lib/src/dart/ast/ast_factory.dart b/pkg/analyzer/lib/src/dart/ast/ast_factory.dart
index 95b5867..ded10fd 100644
--- a/pkg/analyzer/lib/src/dart/ast/ast_factory.dart
+++ b/pkg/analyzer/lib/src/dart/ast/ast_factory.dart
@@ -370,6 +370,30 @@
       new ExtendsClauseImpl(extendsKeyword, superclass);
 
   @override
+  ExtensionDeclaration extensionDeclaration(
+          {Comment comment,
+          List<Annotation> metadata,
+          Token extensionKeyword,
+          @required SimpleIdentifier name,
+          TypeParameterList typeParameters,
+          Token onKeyword,
+          @required TypeAnnotation extendedType,
+          Token leftBracket,
+          List<ClassMember> members,
+          Token rightBracket}) =>
+      new ExtensionDeclarationImpl(
+          comment,
+          metadata,
+          extensionKeyword,
+          name,
+          typeParameters,
+          onKeyword,
+          extendedType,
+          leftBracket,
+          members,
+          rightBracket);
+
+  @override
   FieldDeclaration fieldDeclaration(
           Comment comment,
           List<Annotation> metadata,
diff --git a/pkg/analyzer/lib/src/dart/ast/utilities.dart b/pkg/analyzer/lib/src/dart/ast/utilities.dart
index 5ac34d1..1a26ff2 100644
--- a/pkg/analyzer/lib/src/dart/ast/utilities.dart
+++ b/pkg/analyzer/lib/src/dart/ast/utilities.dart
@@ -450,6 +450,20 @@
           cloneToken(node.extendsKeyword), cloneNode(node.superclass));
 
   @override
+  ExtensionDeclaration visitExtensionDeclaration(ExtensionDeclaration node) =>
+      astFactory.extensionDeclaration(
+          comment: cloneNode(node.documentationComment),
+          metadata: cloneNodeList(node.metadata),
+          extensionKeyword: cloneToken(node.extensionKeyword),
+          name: cloneNode(node.name),
+          typeParameters: cloneNode(node.typeParameters),
+          onKeyword: cloneToken(node.onKeyword),
+          extendedType: cloneNode(node.extendedType),
+          leftBracket: cloneToken(node.leftBracket),
+          members: cloneNodeList(node.members),
+          rightBracket: cloneToken(node.rightBracket));
+
+  @override
   FieldDeclaration visitFieldDeclaration(FieldDeclaration node) =>
       astFactory.fieldDeclaration2(
           comment: cloneNode(node.documentationComment),
@@ -1577,6 +1591,22 @@
   }
 
   @override
+  bool visitExtensionDeclaration(ExtensionDeclaration node) {
+    ExtensionDeclaration other = _other as ExtensionDeclaration;
+    return isEqualNodes(
+            node.documentationComment, other.documentationComment) &&
+        _isEqualNodeLists(node.metadata, other.metadata) &&
+        isEqualTokens(node.extensionKeyword, other.extensionKeyword) &&
+        isEqualNodes(node.name, other.name) &&
+        isEqualNodes(node.typeParameters, other.typeParameters) &&
+        isEqualTokens(node.onKeyword, other.onKeyword) &&
+        isEqualNodes(node.extendedType, other.extendedType) &&
+        isEqualTokens(node.leftBracket, other.leftBracket) &&
+        _isEqualNodeLists(node.members, other.members) &&
+        isEqualTokens(node.rightBracket, other.rightBracket);
+  }
+
+  @override
   bool visitFieldDeclaration(FieldDeclaration node) {
     FieldDeclaration other = _other as FieldDeclaration;
     return isEqualNodes(
@@ -2817,6 +2847,20 @@
           _mapToken(node.extendsKeyword), _cloneNode(node.superclass));
 
   @override
+  ExtensionDeclaration visitExtensionDeclaration(ExtensionDeclaration node) =>
+      astFactory.extensionDeclaration(
+          comment: _cloneNode(node.documentationComment),
+          metadata: _cloneNodeList(node.metadata),
+          extensionKeyword: _mapToken(node.extensionKeyword),
+          name: _cloneNode(node.name),
+          typeParameters: _cloneNode(node.typeParameters),
+          onKeyword: _mapToken(node.onKeyword),
+          extendedType: _cloneNode(node.extendedType),
+          leftBracket: _mapToken(node.leftBracket),
+          members: _cloneNodeList(node.members),
+          rightBracket: _mapToken(node.rightBracket));
+
+  @override
   FieldDeclaration visitFieldDeclaration(FieldDeclaration node) =>
       astFactory.fieldDeclaration2(
           comment: _cloneNode(node.documentationComment),
@@ -4230,6 +4274,29 @@
     return visitNode(node);
   }
 
+  bool visitExtensionDeclaration(ExtensionDeclaration node) {
+    if (identical(node.documentationComment, _oldNode)) {
+      node.documentationComment = _newNode as Comment;
+      return true;
+    } else if (_replaceInList(node.metadata)) {
+      return true;
+    } else if (identical(node.name, _oldNode)) {
+      (node as ExtensionDeclarationImpl).name = _newNode as SimpleIdentifier;
+      return true;
+    } else if (identical(node.typeParameters, _oldNode)) {
+      (node as ExtensionDeclarationImpl).typeParameters =
+          _newNode as TypeParameterList;
+      return true;
+    } else if (identical(node.extendedType, _oldNode)) {
+      (node as ExtensionDeclarationImpl).extendedType =
+          _newNode as TypeAnnotation;
+      return true;
+    } else if (_replaceInList(node.members)) {
+      return true;
+    }
+    return visitNode(node);
+  }
+
   @override
   bool visitFieldDeclaration(FieldDeclaration node) {
     if (identical(node.fields, _oldNode)) {
@@ -5585,6 +5652,25 @@
   }
 
   @override
+  bool visitExtensionDeclaration(ExtensionDeclaration node) {
+    ExtensionDeclaration toNode = this._toNode as ExtensionDeclaration;
+    if (_and(
+        _isEqualNodes(node.documentationComment, toNode.documentationComment),
+        _isEqualNodeLists(node.metadata, toNode.metadata),
+        _isEqualTokens(node.extensionKeyword, toNode.extensionKeyword),
+        _isEqualNodes(node.name, toNode.name),
+        _isEqualNodes(node.typeParameters, toNode.typeParameters),
+        _isEqualTokens(node.onKeyword, toNode.onKeyword),
+        _isEqualNodes(node.extendedType, toNode.extendedType),
+        _isEqualTokens(node.leftBracket, toNode.leftBracket),
+        _isEqualNodeLists(node.members, toNode.members),
+        _isEqualTokens(node.rightBracket, toNode.rightBracket))) {
+      return true;
+    }
+    return false;
+  }
+
+  @override
   bool visitFieldDeclaration(FieldDeclaration node) {
     FieldDeclaration toNode = this._toNode as FieldDeclaration;
     return _and(
@@ -7155,6 +7241,21 @@
   }
 
   @override
+  void visitExtensionDeclaration(ExtensionDeclaration node) {
+    _visitNodeListWithSeparatorAndSuffix(node.metadata, ' ', ' ');
+    _visitTokenWithSuffix(node.extensionKeyword, ' ');
+    _visitNode(node.name);
+    _visitNode(node.typeParameters);
+    _writer.print(' ');
+    _visitToken(node.onKeyword);
+    _writer.print(' ');
+    _visitNodeWithSuffix(node.extendedType, ' ');
+    _visitToken(node.leftBracket);
+    _visitNodeListWithSeparator(node.members, ' ');
+    _visitToken(node.rightBracket);
+  }
+
+  @override
   void visitFieldDeclaration(FieldDeclaration node) {
     _visitNodeListWithSeparatorAndSuffix(node.metadata, " ", " ");
     _visitTokenWithSuffix(node.staticKeyword, " ");
@@ -7927,6 +8028,15 @@
   }
 
   /**
+   * Safely visit the given [token].
+   */
+  void _visitToken(Token token) {
+    if (token != null) {
+      _writer.print(token.lexeme);
+    }
+  }
+
+  /**
    * Safely visit the given [token], printing the [suffix] after the token if it
    * is non-`null`.
    */
@@ -8076,6 +8186,16 @@
   }
 
   /**
+   * Safely visit the given [token].
+   */
+  @protected
+  void safelyVisitToken(Token token) {
+    if (token != null) {
+      sink.write(token.lexeme);
+    }
+  }
+
+  /**
    * Safely visit the given [token], printing the [suffix] after the token if it
    * is non-`null`.
    */
@@ -8420,6 +8540,21 @@
   }
 
   @override
+  void visitExtensionDeclaration(ExtensionDeclaration node) {
+    safelyVisitNodeListWithSeparatorAndSuffix(node.metadata, ' ', ' ');
+    safelyVisitTokenWithSuffix(node.extensionKeyword, ' ');
+    safelyVisitNode(node.name);
+    safelyVisitNode(node.typeParameters);
+    sink.write(' ');
+    safelyVisitToken(node.onKeyword);
+    sink.write(' ');
+    safelyVisitNodeWithSuffix(node.extendedType, ' ');
+    safelyVisitToken(node.leftBracket);
+    safelyVisitNodeListWithSeparator(node.members, ' ');
+    safelyVisitToken(node.rightBracket);
+  }
+
+  @override
   void visitFieldDeclaration(FieldDeclaration node) {
     safelyVisitNodeListWithSeparatorAndSuffix(node.metadata, " ", " ");
     safelyVisitTokenWithSuffix(node.staticKeyword, " ");
diff --git a/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart b/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart
index ab12e27..469e29c 100644
--- a/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart
+++ b/pkg/analyzer/lib/src/generated/testing/ast_test_factory.dart
@@ -8,6 +8,7 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/src/generated/testing/token_factory.dart';
 import 'package:analyzer/src/generated/utilities_dart.dart';
+import 'package:meta/meta.dart';
 
 /**
  * The class `AstTestFactory` defines utility methods that can be used to create AST nodes. The
@@ -469,6 +470,24 @@
   static ExtendsClause extendsClause(TypeName type) => astFactory.extendsClause(
       TokenFactory.tokenFromKeyword(Keyword.EXTENDS), type);
 
+  static ExtensionDeclaration extensionDeclaration(
+          {@required String name,
+          TypeParameterList typeParameters,
+          @required TypeAnnotation extendedType,
+          List<ClassMember> members}) =>
+      astFactory.extensionDeclaration(
+          comment: null,
+          metadata: null,
+          extensionKeyword: TokenFactory.tokenFromKeyword(Keyword.EXTENSION),
+          name: identifier3(name),
+          typeParameters: typeParameters,
+          onKeyword: TokenFactory.tokenFromKeyword(Keyword.ON),
+          extendedType: extendedType,
+          leftBracket: TokenFactory.tokenFromType(TokenType.OPEN_CURLY_BRACKET),
+          members: members,
+          rightBracket:
+              TokenFactory.tokenFromType(TokenType.CLOSE_CURLY_BRACKET));
+
   static FieldDeclaration fieldDeclaration(bool isStatic, Keyword keyword,
           TypeAnnotation type, List<VariableDeclaration> variables) =>
       astFactory.fieldDeclaration2(
diff --git a/pkg/analyzer/test/src/dart/ast/utilities_test.dart b/pkg/analyzer/test/src/dart/ast/utilities_test.dart
index 5771180..bcdc6f3 100644
--- a/pkg/analyzer/test/src/dart/ast/utilities_test.dart
+++ b/pkg/analyzer/test/src/dart/ast/utilities_test.dart
@@ -1713,6 +1713,48 @@
         AstTestFactory.extendsClause(AstTestFactory.typeName4("C")));
   }
 
+  void test_visitExtensionDeclaration_empty() {
+    _assertSource(
+        'extension E on C {}',
+        AstTestFactory.extensionDeclaration(
+            name: 'E', extendedType: AstTestFactory.typeName4('C')));
+  }
+
+  void test_visitExtensionDeclaration_multipleMember() {
+    _assertSource(
+        'extension E on C {var a; var b;}',
+        AstTestFactory.extensionDeclaration(
+            name: 'E',
+            extendedType: AstTestFactory.typeName4('C'),
+            members: [
+              AstTestFactory.fieldDeclaration2(false, Keyword.VAR,
+                  [AstTestFactory.variableDeclaration('a')]),
+              AstTestFactory.fieldDeclaration2(
+                  false, Keyword.VAR, [AstTestFactory.variableDeclaration('b')])
+            ]));
+  }
+
+  void test_visitExtensionDeclaration_parameters() {
+    _assertSource(
+        'extension E<T> on C {}',
+        AstTestFactory.extensionDeclaration(
+            name: 'E',
+            typeParameters: AstTestFactory.typeParameterList(['T']),
+            extendedType: AstTestFactory.typeName4('C')));
+  }
+
+  void test_visitExtensionDeclaration_singleMember() {
+    _assertSource(
+        'extension E on C {var a;}',
+        AstTestFactory.extensionDeclaration(
+            name: 'E',
+            extendedType: AstTestFactory.typeName4('C'),
+            members: [
+              AstTestFactory.fieldDeclaration2(
+                  false, Keyword.VAR, [AstTestFactory.variableDeclaration('a')])
+            ]));
+  }
+
   void test_visitFieldDeclaration_instance() {
     _assertSource(
         "var a;",
@@ -4427,6 +4469,48 @@
         AstTestFactory.extendsClause(AstTestFactory.typeName4("C")));
   }
 
+  void test_visitExtensionDeclaration_empty() {
+    _assertSource(
+        'extension E on C {}',
+        AstTestFactory.extensionDeclaration(
+            name: 'E', extendedType: AstTestFactory.typeName4('C')));
+  }
+
+  void test_visitExtensionDeclaration_multipleMember() {
+    _assertSource(
+        'extension E on C {var a; var b;}',
+        AstTestFactory.extensionDeclaration(
+            name: 'E',
+            extendedType: AstTestFactory.typeName4('C'),
+            members: [
+              AstTestFactory.fieldDeclaration2(false, Keyword.VAR,
+                  [AstTestFactory.variableDeclaration('a')]),
+              AstTestFactory.fieldDeclaration2(
+                  false, Keyword.VAR, [AstTestFactory.variableDeclaration('b')])
+            ]));
+  }
+
+  void test_visitExtensionDeclaration_parameters() {
+    _assertSource(
+        'extension E<T> on C {}',
+        AstTestFactory.extensionDeclaration(
+            name: 'E',
+            typeParameters: AstTestFactory.typeParameterList(['T']),
+            extendedType: AstTestFactory.typeName4('C')));
+  }
+
+  void test_visitExtensionDeclaration_singleMember() {
+    _assertSource(
+        'extension E on C {var a;}',
+        AstTestFactory.extensionDeclaration(
+            name: 'E',
+            extendedType: AstTestFactory.typeName4('C'),
+            members: [
+              AstTestFactory.fieldDeclaration2(
+                  false, Keyword.VAR, [AstTestFactory.variableDeclaration('a')])
+            ]));
+  }
+
   void test_visitFieldDeclaration_instance() {
     _assertSource(
         "var a;",
diff --git a/pkg/dev_compiler/lib/src/analyzer/code_generator.dart b/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
index 33fff86..9e1ecef 100644
--- a/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
+++ b/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
@@ -6673,6 +6673,9 @@
   @override
   visitForPartsWithExpression(ForPartsWithExpression node) =>
       _unreachable(node);
+
+  @override
+  visitExtensionDeclaration(ExtensionDeclaration node) => _unreachable(node);
 }
 
 // TODO(jacobr): we would like to do something like the following