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