add "extension" built-in keyword for extension methods

Change-Id: I6529fff31f681fd24c22f8c7579d2a35097f4d07
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/102940
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
diff --git a/pkg/analyzer/lib/src/dart/scanner/scanner.dart b/pkg/analyzer/lib/src/dart/scanner/scanner.dart
index 12599db..b446a32 100644
--- a/pkg/analyzer/lib/src/dart/scanner/scanner.dart
+++ b/pkg/analyzer/lib/src/dart/scanner/scanner.dart
@@ -199,6 +199,8 @@
       featureSet == null
           ? fasta.ScannerConfiguration()
           : fasta.ScannerConfiguration(
+              enableExtensionMethods:
+                  featureSet.isEnabled(Feature.extension_methods),
               enableTripleShift: featureSet.isEnabled(Feature.triple_shift),
               enableNonNullable: featureSet.isEnabled(Feature.non_nullable));
 }
diff --git a/pkg/front_end/lib/src/fasta/scanner/abstract_scanner.dart b/pkg/front_end/lib/src/fasta/scanner/abstract_scanner.dart
index 637bbc1..e59d613 100644
--- a/pkg/front_end/lib/src/fasta/scanner/abstract_scanner.dart
+++ b/pkg/front_end/lib/src/fasta/scanner/abstract_scanner.dart
@@ -51,15 +51,18 @@
   /// based upon the specified language version.
   final LanguageVersionChanged languageVersionChanged;
 
-  /// Experimental flag for enabling scanning of `>>>`.
-  /// See https://github.com/dart-lang/language/issues/61
-  /// and https://github.com/dart-lang/language/issues/60
-  bool _enableTripleShift = false;
+  /// Experimental flag for enabling scanning of the `extension` token.
+  bool _enableExtensionMethods = false;
 
   /// Experimental flag for enabling scanning of NNBD tokens
   /// such as 'required' and 'late'.
   bool _enableNonNullable = false;
 
+  /// Experimental flag for enabling scanning of `>>>`.
+  /// See https://github.com/dart-lang/language/issues/61
+  /// and https://github.com/dart-lang/language/issues/60
+  bool _enableTripleShift = false;
+
   /**
    * The string offset for the next token that will be created.
    *
@@ -108,6 +111,7 @@
   @override
   set configuration(ScannerConfiguration config) {
     if (config != null) {
+      _enableExtensionMethods = config.enableExtensionMethods;
       _enableNonNullable = config.enableNonNullable;
       _enableTripleShift = config.enableTripleShift;
     }
@@ -1135,6 +1139,9 @@
     if (state == null || state.keyword == null) {
       return tokenizeIdentifier(next, start, allowDollar);
     }
+    if (!_enableExtensionMethods && state.keyword == Keyword.EXTENSION) {
+      return tokenizeIdentifier(next, start, allowDollar);
+    }
     if (!_enableNonNullable &&
         (state.keyword == Keyword.LATE || state.keyword == Keyword.REQUIRED)) {
       return tokenizeIdentifier(next, start, allowDollar);
@@ -1533,6 +1540,9 @@
   static const classic = ScannerConfiguration();
   static const nonNullable = ScannerConfiguration(enableNonNullable: true);
 
+  /// Experimental flag for enabling scanning of the `extension` keyword.
+  final bool enableExtensionMethods;
+
   /// Experimental flag for enabling scanning of NNBD tokens
   /// such as 'required' and 'late'
   final bool enableNonNullable;
@@ -1543,8 +1553,10 @@
   final bool enableTripleShift;
 
   const ScannerConfiguration({
-    bool enableTripleShift,
+    bool enableExtensionMethods,
     bool enableNonNullable,
-  })  : this.enableTripleShift = enableTripleShift ?? false,
-        this.enableNonNullable = enableNonNullable ?? false;
+    bool enableTripleShift,
+  })  : this.enableExtensionMethods = enableExtensionMethods ?? false,
+        this.enableNonNullable = enableNonNullable ?? false,
+        this.enableTripleShift = enableTripleShift ?? false;
 }
diff --git a/pkg/front_end/lib/src/scanner/token.dart b/pkg/front_end/lib/src/scanner/token.dart
index 9f1ba8a..5947f80 100644
--- a/pkg/front_end/lib/src/scanner/token.dart
+++ b/pkg/front_end/lib/src/scanner/token.dart
@@ -173,6 +173,9 @@
 
   static const Keyword EXTENDS = const Keyword("extends", "EXTENDS");
 
+  static const Keyword EXTENSION = const Keyword("extension", "EXTENSION",
+      isBuiltIn: true, isTopLevelKeyword: true);
+
   static const Keyword EXTERNAL =
       const Keyword("external", "EXTERNAL", isBuiltIn: true, isModifier: true);
 
@@ -303,6 +306,7 @@
     ENUM,
     EXPORT,
     EXTENDS,
+    EXTENSION,
     EXTERNAL,
     FACTORY,
     FALSE,
diff --git a/pkg/front_end/test/scanner_fasta_test.dart b/pkg/front_end/test/scanner_fasta_test.dart
index abe0879..210e851 100644
--- a/pkg/front_end/test/scanner_fasta_test.dart
+++ b/pkg/front_end/test/scanner_fasta_test.dart
@@ -52,9 +52,11 @@
 @reflectiveTest
 class ScannerTest_Fasta_UTF8 extends ScannerTest_Fasta {
   @override
-  Token scanWithListener(String source, ErrorListener listener) {
+  Token scanWithListener(String source, ErrorListener listener,
+      {ScannerConfiguration configuration}) {
     var bytes = utf8.encode(source).toList()..add(0);
-    var result = scan(bytes, includeComments: true);
+    var result =
+        scan(bytes, configuration: configuration, includeComments: true);
     var token = result.tokens;
 
     // Translate error tokens
@@ -106,8 +108,10 @@
 @reflectiveTest
 class ScannerTest_Fasta extends ScannerTestBase {
   @override
-  Token scanWithListener(String source, ErrorListener listener) {
-    var result = scanString(source, includeComments: true);
+  Token scanWithListener(String source, ErrorListener listener,
+      {ScannerConfiguration configuration}) {
+    var result =
+        scanString(source, configuration: configuration, includeComments: true);
     var token = result.tokens;
 
     // Translate error tokens
diff --git a/pkg/front_end/test/scanner_replacement_test.dart b/pkg/front_end/test/scanner_replacement_test.dart
index 041821a..b0a979a 100644
--- a/pkg/front_end/test/scanner_replacement_test.dart
+++ b/pkg/front_end/test/scanner_replacement_test.dart
@@ -28,13 +28,14 @@
 @reflectiveTest
 class ScannerTest_Replacement extends ScannerTestBase {
   @override
-  analyzer.Token scanWithListener(String source, ErrorListener listener) {
+  analyzer.Token scanWithListener(String source, ErrorListener listener,
+      {fasta.ScannerConfiguration configuration}) {
     // Process the source similar to
     // pkg/analyzer/lib/src/dart/scanner/scanner.dart
     // to simulate replacing the analyzer scanner
 
-    fasta.ScannerResult result =
-        fasta.scanString(source, includeComments: true);
+    fasta.ScannerResult result = fasta.scanString(source,
+        configuration: configuration, includeComments: true);
 
     fasta.Token tokens = result.tokens;
     assertValidTokenStream(tokens, errorsFirst: true);
diff --git a/pkg/front_end/test/scanner_test.dart b/pkg/front_end/test/scanner_test.dart
index 36b1477..932d67a 100644
--- a/pkg/front_end/test/scanner_test.dart
+++ b/pkg/front_end/test/scanner_test.dart
@@ -4,7 +4,7 @@
 
 import 'package:front_end/src/base/errors.dart';
 import 'package:front_end/src/fasta/scanner/abstract_scanner.dart'
-    show AbstractScanner;
+    show AbstractScanner, ScannerConfiguration;
 import 'package:front_end/src/scanner/errors.dart';
 import 'package:front_end/src/scanner/reader.dart';
 import 'package:front_end/src/scanner/token.dart';
@@ -79,7 +79,8 @@
 }
 
 abstract class ScannerTestBase {
-  Token scanWithListener(String source, ErrorListener listener);
+  Token scanWithListener(String source, ErrorListener listener,
+      {ScannerConfiguration configuration});
 
   void test_ampersand() {
     _assertToken(TokenType.AMPERSAND, "&");
@@ -442,6 +443,16 @@
     _assertKeywordToken("extends");
   }
 
+  void test_keyword_extension() {
+    _assertKeywordToken("extension",
+        configuration: ScannerConfiguration(enableExtensionMethods: true));
+  }
+
+  void test_keyword_extension_old() {
+    _assertNotKeywordToken("extension",
+        configuration: ScannerConfiguration(enableExtensionMethods: false));
+  }
+
   void test_keyword_factory() {
     _assertKeywordToken("factory");
   }
@@ -494,6 +505,16 @@
     _assertKeywordToken("is");
   }
 
+  void test_keyword_late() {
+    _assertKeywordToken("late",
+        configuration: ScannerConfiguration(enableNonNullable: true));
+  }
+
+  void test_keyword_late_old() {
+    _assertNotKeywordToken("late",
+        configuration: ScannerConfiguration(enableNonNullable: false));
+  }
+
   void test_keyword_library() {
     _assertKeywordToken("library");
   }
@@ -534,6 +555,16 @@
     _assertKeywordToken("patch");
   }
 
+  void test_keyword_required() {
+    _assertKeywordToken("required",
+        configuration: ScannerConfiguration(enableNonNullable: true));
+  }
+
+  void test_keyword_required_disabled() {
+    _assertNotKeywordToken("required",
+        configuration: ScannerConfiguration(enableNonNullable: false));
+  }
+
   void test_keyword_rethrow() {
     _assertKeywordToken("rethrow");
   }
@@ -1298,8 +1329,9 @@
    * Assert that when scanned the given [source] contains a single keyword token
    * with the same lexeme as the original source.
    */
-  void _assertKeywordToken(String source) {
-    Token token = _scan(source);
+  void _assertKeywordToken(String source,
+      {ScannerConfiguration configuration}) {
+    Token token = _scan(source, configuration: configuration);
     expect(token, isNotNull);
     expect(token.type.isKeyword, true);
     expect(token.offset, 0);
@@ -1308,7 +1340,7 @@
     Object value = token.value();
     expect(value is Keyword, isTrue);
     expect((value as Keyword).lexeme, source);
-    token = _scan(" $source ");
+    token = _scan(" $source ", configuration: configuration);
     expect(token, isNotNull);
     expect(token.type.isKeyword, true);
     expect(token.offset, 1);
@@ -1321,6 +1353,27 @@
   }
 
   /**
+   * Assert that when scanned the given [source] contains a single identifier token
+   * with the same lexeme as the original source.
+   */
+  void _assertNotKeywordToken(String source,
+      {ScannerConfiguration configuration}) {
+    Token token = _scan(source, configuration: configuration);
+    expect(token, isNotNull);
+    expect(token.type.isKeyword, false);
+    expect(token.offset, 0);
+    expect(token.length, source.length);
+    expect(token.lexeme, source);
+    token = _scan(" $source ", configuration: configuration);
+    expect(token, isNotNull);
+    expect(token.type.isKeyword, false);
+    expect(token.offset, 1);
+    expect(token.length, source.length);
+    expect(token.lexeme, source);
+    expect(token.next.type, TokenType.EOF);
+  }
+
+  /**
    * Assert that the token scanned from the given [source] has the
    * [expectedType].
    */
@@ -1399,9 +1452,11 @@
     expect(token.type, TokenType.EOF);
   }
 
-  Token _scan(String source, {bool ignoreErrors: false}) {
+  Token _scan(String source,
+      {ScannerConfiguration configuration, bool ignoreErrors: false}) {
     ErrorListener listener = new ErrorListener();
-    Token token = scanWithListener(source, listener);
+    Token token =
+        scanWithListener(source, listener, configuration: configuration);
     if (!ignoreErrors) {
       listener.assertNoErrors();
     }
diff --git a/pkg/front_end/test/token_test.dart b/pkg/front_end/test/token_test.dart
index fb9f1ae..fa62c06 100644
--- a/pkg/front_end/test/token_test.dart
+++ b/pkg/front_end/test/token_test.dart
@@ -91,6 +91,7 @@
       Keyword.DEFERRED,
       Keyword.DYNAMIC,
       Keyword.EXPORT,
+      Keyword.EXTENSION,
       Keyword.EXTERNAL,
       Keyword.FACTORY,
       Keyword.GET,
@@ -142,6 +143,7 @@
       Keyword.CLASS,
       Keyword.ENUM,
       Keyword.EXPORT,
+      //Keyword.EXTENSION, <-- when "extension methods" is enabled by default
       Keyword.IMPORT,
       Keyword.LIBRARY,
       Keyword.MIXIN,