Add support for formatting extension methods. (#842)

* Add support for formatting extension methods.

Fix #830.

* Revise from code review.

- Better name than "hasBody".
- Don't put a space in unnamed generic extensions.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 79f43ed..4e7679d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 1.3.0
+
+* Add support for formatting extension methods (#830).
+
 # 1.2.10
 
 * Format null assertion operators.
diff --git a/lib/src/dart_formatter.dart b/lib/src/dart_formatter.dart
index 478e6a7..fe0c1ca 100644
--- a/lib/src/dart_formatter.dart
+++ b/lib/src/dart_formatter.dart
@@ -93,12 +93,16 @@
     // version.
     // TODO(paulberry): consider plumbing in experiment enable flags from the
     // command line.
-    var featureSet = FeatureSet.fromEnableFlags(["non-nullable"]);
+    var featureSet = FeatureSet.fromEnableFlags([
+      "extension-methods",
+      "non-nullable",
+    ]);
 
     // Tokenize the source.
     var reader = CharSequenceReader(source.text);
     var stringSource = StringSource(source.text, source.uri);
     var scanner = Scanner(stringSource, reader, errorListener);
+    scanner.configureFeatures(featureSet);
     var startToken = scanner.tokenize();
     var lineInfo = LineInfo(scanner.lineStarts);
 
diff --git a/lib/src/source_visitor.dart b/lib/src/source_visitor.dart
index fb2f231..c66011b 100644
--- a/lib/src/source_visitor.dart
+++ b/lib/src/source_visitor.dart
@@ -761,8 +761,11 @@
 
     var needsDouble = true;
     for (var declaration in node.declarations) {
-      // Add a blank line before classes.
-      if (declaration is ClassDeclaration) needsDouble = true;
+      var hasClassBody = declaration is ClassDeclaration ||
+          declaration is ExtensionDeclaration;
+
+      // Add a blank line before declarations with class-like bodies.
+      if (hasClassBody) needsDouble = true;
 
       if (needsDouble) {
         twoNewlines();
@@ -775,8 +778,8 @@
       visit(declaration);
 
       needsDouble = false;
-      if (declaration is ClassDeclaration) {
-        // Add a blank line after classes.
+      if (hasClassBody) {
+        // Add a blank line after declarations with class-like bodies.
         needsDouble = true;
       } else if (declaration is FunctionDeclaration) {
         // Add a blank line after non-empty block functions.
@@ -1145,6 +1148,32 @@
     visit(node.superclass);
   }
 
+  void visitExtensionDeclaration(ExtensionDeclaration node) {
+    visitMetadata(node.metadata);
+
+    builder.nestExpression();
+    token(node.extensionKeyword);
+
+    // Don't put a space after `extension` if the extension is unnamed. That
+    // way, generic unnamed extensions format like `extension<T> on ...`.
+    if (node.name != null) {
+      space();
+      visit(node.name);
+    }
+
+    visit(node.typeParameters);
+    soloSplit();
+    token(node.onKeyword);
+    space();
+    visit(node.extendedType);
+    space();
+    builder.unnest();
+
+    _beginBody(node.leftBracket);
+    _visitMembers(node.members);
+    _endBody(node.rightBracket);
+  }
+
   visitFieldDeclaration(FieldDeclaration node) {
     visitMetadata(node.metadata);
 
diff --git a/pubspec.lock b/pubspec.lock
index d4d3620..b53d5f0 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -7,7 +7,7 @@
       name: analyzer
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.38.0"
+    version: "0.38.1"
   archive:
     dependency: transitive
     description:
@@ -70,7 +70,7 @@
       name: crypto
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.1.1+1"
+    version: "2.1.2"
   csslib:
     dependency: transitive
     description:
@@ -84,7 +84,7 @@
       name: front_end
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.1.22"
+    version: "0.1.23"
   glob:
     dependency: transitive
     description:
@@ -147,7 +147,7 @@
       name: kernel
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.3.22"
+    version: "0.3.23"
   matcher:
     dependency: transitive
     description:
@@ -350,7 +350,7 @@
       name: vm_service
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.1.0"
+    version: "1.1.1"
   watcher:
     dependency: transitive
     description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 6c4c50a..fc607c1 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
 name: dart_style
 # Note: See tool/grind.dart for how to bump the version.
-version: 1.2.10
+version: 1.3.0-dev
 author: Dart Team <misc@dartlang.org>
 description: >-
   Opinionated, automatic Dart source code formatter.
@@ -11,7 +11,7 @@
   sdk: '>=2.3.0 <3.0.0'
 
 dependencies:
-  analyzer: '>=0.37.0 <0.39.0'
+  analyzer: '>=0.38.0 <0.39.0'
   args: '>=0.12.1 <2.0.0'
   path: ^1.0.0
   source_span: ^1.4.0
diff --git a/test/comments/extensions.unit b/test/comments/extensions.unit
new file mode 100644
index 0000000..67123f3
--- /dev/null
+++ b/test/comments/extensions.unit
@@ -0,0 +1,185 @@
+40 columns                              |
+>>> indented line comment
+extension A on B {
+  // comment
+}
+<<<
+extension A on B {
+  // comment
+}
+>>> line comment on opening line
+extension A on B { // comment
+}
+<<<
+extension A on B {
+  // comment
+}
+>>> indented block comment
+extension A on B {
+  /* comment */
+}
+<<<
+extension A on B {
+  /* comment */
+}
+>>> block comment with trailing newline
+extension A on B {/* comment */
+}
+<<<
+extension A on B {
+  /* comment */
+}
+>>> block comment with leading newline
+extension A on B {
+  /* comment */}
+<<<
+extension A on B {
+  /* comment */
+}
+>>> inline block comment
+extension A on B {  /* comment */  }
+<<<
+extension A on B {/* comment */}
+>>> multiple comments on opening line
+extension A on B { /* first */ // second
+}
+<<<
+extension A on B {
+  /* first */ // second
+}
+>>> multiple inline block comments
+extension A on B {  /*1*/   /*2*/   /*3*/  }
+<<<
+extension A on B {/*1*/ /*2*/ /*3*/}
+>>> multiline trailing block comment
+extension A on B {  /* comment
+*/  }
+<<<
+extension A on B {
+  /* comment
+*/
+}
+>>> lines comments at the start of the line in body
+extension A on B {
+//  a() => 1;
+//  b() => 2;
+  c() => 3;
+}
+<<<
+extension A on B {
+//  a() => 1;
+//  b() => 2;
+  c() => 3;
+}
+>>> block comment
+extension A on B/* is cool */{
+  /* int */ foo(/* comment */) => 42;
+}
+<<<
+extension A on B /* is cool */ {
+  /* int */ foo(/* comment */) => 42;
+}
+>>> block comments in odd places
+extension/*1*/A/*2*/on B {}
+extension A on/*3*/B/*4*/{}
+<<<
+extension /*1*/ A /*2*/ on B {}
+
+extension A on /*3*/ B /*4*/ {}
+>>> line comments in odd places
+extension// 1
+A// 2
+on// 3
+B// 4
+{}
+<<<
+extension // 1
+    A // 2
+    on // 3
+    B // 4
+{}
+>>> block comment
+library foo;
+/* A long
+ * Comment
+*/
+extension A on B /* is cool */ {
+  /* int */ foo() => 42;
+}
+<<<
+library foo;
+
+/* A long
+ * Comment
+*/
+extension A on B /* is cool */ {
+  /* int */ foo() => 42;
+}
+>>> ensure blank line above doc comments
+extension A on B {a() => 1;
+/// doc
+b() => 2;}
+<<<
+extension A on B {
+  a() => 1;
+
+  /// doc
+  b() => 2;
+}
+>>> remove blank line before beginning of body
+extension A on B {
+
+
+
+  // comment
+}
+<<<
+extension A on B {
+  // comment
+}
+>>> nested flush left comment
+extension A on B {
+  method() {
+// flush
+  }
+}
+<<<
+extension A on B {
+  method() {
+// flush
+  }
+}
+>>> nested flush left after non-nested
+extension A on B {
+  method() {
+    // ...
+// flush
+  }
+}
+<<<
+extension A on B {
+  method() {
+    // ...
+// flush
+  }
+}
+>>> force doc comment between extensions to have two newlines before
+extension A on B {} /**
+*/
+extension A on B {}
+<<<
+extension A on B {}
+
+/**
+*/
+extension A on B {}
+>>> force doc comment between classes to have newline after
+extension A on B {}
+/**
+*/ extension A on B {}
+<<<
+extension A on B {}
+
+/**
+*/
+extension A on B {}
\ No newline at end of file
diff --git a/test/splitting/extensions.unit b/test/splitting/extensions.unit
new file mode 100644
index 0000000..9fe4226
--- /dev/null
+++ b/test/splitting/extensions.unit
@@ -0,0 +1,33 @@
+40 columns                              |
+>>>
+extension SomeExtension on SomeLongClass {}
+<<<
+extension SomeExtension
+    on SomeLongClass {}
+>>>
+extension Extension<VeryLongTypeParameterName> on C {}
+<<<
+extension Extension<
+    VeryLongTypeParameterName> on C {}
+>>>
+extension Extension<VeryLongTypeParameterName> on BaseClass {}
+<<<
+extension Extension<
+        VeryLongTypeParameterName>
+    on BaseClass {}
+>>>
+extension Extension on C<VeryLongTypeArgumentName> {}
+<<<
+extension Extension
+    on C<VeryLongTypeArgumentName> {}
+>>>
+extension Extension on BaseClass<VeryLongTypeArgumentName> {}
+<<<
+extension Extension on BaseClass<
+    VeryLongTypeArgumentName> {}
+>>>
+extension Extension on VeryLongBaseClass<VeryLongTypeArgumentName> {}
+<<<
+extension Extension
+    on VeryLongBaseClass<
+        VeryLongTypeArgumentName> {}
\ No newline at end of file
diff --git a/test/splitting/type_parameters.unit b/test/splitting/type_parameters.unit
index 920cb38..95f624f 100644
--- a/test/splitting/type_parameters.unit
+++ b/test/splitting/type_parameters.unit
@@ -46,4 +46,9 @@
 <<<
 class Foo {
   foo<A, B, C, D>() {}
-}
\ No newline at end of file
+}
+>>> split type parameters
+class Foo extends GenericBaseClass<TypeParameter> {}
+<<<
+class Foo extends GenericBaseClass<
+    TypeParameter> {}
\ No newline at end of file
diff --git a/test/whitespace/classes.unit b/test/whitespace/classes.unit
index 968e422..7ba866c 100644
--- a/test/whitespace/classes.unit
+++ b/test/whitespace/classes.unit
@@ -112,4 +112,14 @@
   covariant int baz;
   @wat
   covariant int zoop;
-}
\ No newline at end of file
+}
+>>> blank line before and after class
+var x = 1;
+class A {}
+var y = 2;
+<<<
+var x = 1;
+
+class A {}
+
+var y = 2;
\ No newline at end of file
diff --git a/test/whitespace/extensions.unit b/test/whitespace/extensions.unit
new file mode 100644
index 0000000..009bbdf
--- /dev/null
+++ b/test/whitespace/extensions.unit
@@ -0,0 +1,120 @@
+40 columns                              |
+>>> indentation
+extension A on B {
+z() => 0;
+inc(int x) => ++x;
+foo(int x) {
+if (x == 0) {
+return true;
+}}}
+<<<
+extension A on B {
+  z() => 0;
+  inc(int x) => ++x;
+  foo(int x) {
+    if (x == 0) {
+      return true;
+    }
+  }
+}
+>>> trailing space inside body
+extension A on B {
+  }
+<<<
+extension A on B {}
+>>> leading space before "extension"
+  extension A on B {
+}
+<<<
+extension A on B {}
+>>>
+extension A on B  { int meaningOfLife() => 42; }
+<<<
+extension A on B {
+  int meaningOfLife() => 42;
+}
+>>>
+extension     A      on      B    {
+  }
+<<<
+extension A on B {}
+>>>
+extension A on B{z() => 0;inc(int x) => ++x;}
+<<<
+extension A on B {
+  z() => 0;
+  inc(int x) => ++x;
+}
+>>> eats newlines
+extension
+
+A
+
+
+on
+
+
+B
+
+
+
+{}
+<<<
+extension A on B {}
+>>> require blank line after non-empty block-bodied members
+extension A on B {
+b() {;} c() => null; get d {;} get e => null; set f(value) {;
+} set g(value) => null;}
+<<<
+extension A on B {
+  b() {
+    ;
+  }
+
+  c() => null;
+  get d {
+    ;
+  }
+
+  get e => null;
+  set f(value) {
+    ;
+  }
+
+  set g(value) => null;
+}
+>>> no required blank line after empty block-bodied members
+extension A on B {
+b() {} c() => null; get d {} get e => null; set f(value) {
+} set g(value) => null;}
+<<<
+extension A on B {
+  b() {}
+  c() => null;
+  get d {}
+  get e => null;
+  set f(value) {}
+  set g(value) => null;
+}
+>>> blank line before and after extension
+var x = 1;
+extension A on B {}
+var y = 2;
+<<<
+var x = 1;
+
+extension A on B {}
+
+var y = 2;
+>>> generic extension
+extension   A    <  T  ,  S > on B{}
+<<<
+extension A<T, S> on B {}
+>>> unnamed extension
+extension    on   B{}
+<<<
+extension on B {}
+>>> unnamed generic extension
+extension   <  T  ,  S  >    on   B{}
+<<<
+extension<T, S> on B {}
\ No newline at end of file
diff --git a/test/whitespace/metadata.unit b/test/whitespace/metadata.unit
index fd4bde7..7b38052 100644
--- a/test/whitespace/metadata.unit
+++ b/test/whitespace/metadata.unit
@@ -320,4 +320,9 @@
 @meta   mixin M {}
 <<<
 @meta
-mixin M {}
\ No newline at end of file
+mixin M {}
+>>> metadata on extension
+@meta extension A on B {}
+<<<
+@meta
+extension A on B {}
\ No newline at end of file