[cfe] Support patching of extension methods

Change-Id: I453e17e63f97a0ca2477371541c6a9602bee2404
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/119322
Reviewed-by: Jens Johansen <jensj@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/front_end/lib/src/api_prototype/memory_file_system.dart b/pkg/front_end/lib/src/api_prototype/memory_file_system.dart
index fd091bb..3618c93 100644
--- a/pkg/front_end/lib/src/api_prototype/memory_file_system.dart
+++ b/pkg/front_end/lib/src/api_prototype/memory_file_system.dart
@@ -26,7 +26,9 @@
   Uri currentDirectory;
 
   MemoryFileSystem(Uri currentDirectory)
-      : currentDirectory = _addTrailingSlash(currentDirectory);
+      : currentDirectory = _addTrailingSlash(currentDirectory) {
+    _directories.add(currentDirectory);
+  }
 
   @override
   MemoryFileSystemEntity entityForUri(Uri uri) {
diff --git a/pkg/front_end/lib/src/base/common.dart b/pkg/front_end/lib/src/base/common.dart
new file mode 100644
index 0000000..e511a04
--- /dev/null
+++ b/pkg/front_end/lib/src/base/common.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2019, 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.
+
+/// If `true`, data that would not otherwise be kept is stored for testing.
+bool retainDataForTesting = false;
diff --git a/pkg/front_end/lib/src/fasta/builder/class_builder.dart b/pkg/front_end/lib/src/fasta/builder/class_builder.dart
index c4a1632..1ccb68b 100644
--- a/pkg/front_end/lib/src/fasta/builder/class_builder.dart
+++ b/pkg/front_end/lib/src/fasta/builder/class_builder.dart
@@ -53,6 +53,8 @@
 
 import 'package:kernel/type_environment.dart' show TypeEnvironment;
 
+import '../../base/common.dart';
+
 import '../dill/dill_member_builder.dart' show DillMemberBuilder;
 
 import 'builder.dart'
@@ -166,6 +168,8 @@
 
   ClassBuilder actualOrigin;
 
+  ClassBuilder patchForTesting;
+
   ClassBuilder(
       List<MetadataBuilder> metadata,
       int modifiers,
@@ -1523,6 +1527,9 @@
   void applyPatch(Builder patch) {
     if (patch is ClassBuilder) {
       patch.actualOrigin = this;
+      if (retainDataForTesting) {
+        patchForTesting = patch;
+      }
       // TODO(ahe): Complain if `patch.supertype` isn't null.
       scope.local.forEach((String name, Builder member) {
         Builder memberPatch = patch.scope.local[name];
diff --git a/pkg/front_end/lib/src/fasta/builder/modifier_builder.dart b/pkg/front_end/lib/src/fasta/builder/modifier_builder.dart
index 6357975..592ecf2 100644
--- a/pkg/front_end/lib/src/fasta/builder/modifier_builder.dart
+++ b/pkg/front_end/lib/src/fasta/builder/modifier_builder.dart
@@ -74,5 +74,6 @@
     return buffer..write(name ?? fullNameForErrors);
   }
 
-  String toString() => "$debugName(${printOn(new StringBuffer())})";
+  String toString() =>
+      "${isPatch ? 'patch ' : ''}$debugName(${printOn(new StringBuffer())})";
 }
diff --git a/pkg/front_end/lib/src/fasta/builder/procedure_builder.dart b/pkg/front_end/lib/src/fasta/builder/procedure_builder.dart
index d818e4d..0335f23 100644
--- a/pkg/front_end/lib/src/fasta/builder/procedure_builder.dart
+++ b/pkg/front_end/lib/src/fasta/builder/procedure_builder.dart
@@ -11,6 +11,8 @@
 
 import 'package:kernel/type_algebra.dart';
 
+import '../../base/common.dart';
+
 import 'builder.dart'
     show
         Builder,
@@ -459,6 +461,7 @@
   final Procedure _procedure;
   final int charOpenParenOffset;
   final ProcedureKind kind;
+  ProcedureBuilder patchForTesting;
 
   AsyncMarker actualAsyncModifier = AsyncMarker.Sync;
 
@@ -553,8 +556,8 @@
       _procedure.isConst = isConst;
       if (isExtensionMethod) {
         ExtensionBuilder extensionBuilder = parent;
-        procedure.isExtensionMember = true;
-        procedure.isStatic = true;
+        _procedure.isExtensionMember = true;
+        _procedure.isStatic = true;
         String kindInfix = '';
         if (isExtensionInstanceMember) {
           // Instance getter and setter are converted to methods so we use an
@@ -574,18 +577,18 @@
               throw new UnsupportedError(
                   'Unexpected extension method kind ${kind}');
           }
-          procedure.kind = ProcedureKind.Method;
+          _procedure.kind = ProcedureKind.Method;
         }
-        procedure.name = new Name(
+        _procedure.name = new Name(
             '${extensionBuilder.name}|${kindInfix}${name}',
             libraryBuilder.library);
       } else {
         _procedure.isStatic = isStatic;
         _procedure.name = new Name(name, libraryBuilder.library);
       }
-    }
-    if (extensionTearOff != null) {
-      _buildExtensionTearOff(libraryBuilder, parent);
+      if (extensionTearOff != null) {
+        _buildExtensionTearOff(libraryBuilder, parent);
+      }
     }
     return _procedure;
   }
@@ -698,7 +701,7 @@
 
     Statement closureBody = new ReturnStatement(
         new StaticInvocation(
-            procedure,
+            _procedure,
             new Arguments(closurePositionalArguments,
                 types: typeArguments, named: closureNamedArguments))
           ..fileOffset = fileOffset)
@@ -709,10 +712,10 @@
         typeParameters: closureTypeParameters,
         positionalParameters: closurePositionalParameters,
         namedParameters: closureNamedParameters,
-        requiredParameterCount: procedure.function.requiredParameterCount - 1,
+        requiredParameterCount: _procedure.function.requiredParameterCount - 1,
         returnType: closureReturnType,
-        asyncMarker: procedure.function.asyncMarker,
-        dartAsyncMarker: procedure.function.dartAsyncMarker))
+        asyncMarker: _procedure.function.asyncMarker,
+        dartAsyncMarker: _procedure.function.dartAsyncMarker))
       ..fileOffset = fileOffset;
 
     _extensionTearOff
@@ -783,6 +786,9 @@
     if (patch is ProcedureBuilder) {
       if (checkPatch(patch)) {
         patch.actualOrigin = this;
+        if (retainDataForTesting) {
+          patchForTesting = patch;
+        }
       }
     } else {
       reportPatchMismatch(patch);
@@ -807,6 +813,8 @@
   @override
   ConstructorBuilder actualOrigin;
 
+  ConstructorBuilder patchForTesting;
+
   Constructor get actualConstructor => _constructor;
 
   ConstructorBuilder(
@@ -998,6 +1006,9 @@
     if (patch is ConstructorBuilder) {
       if (checkPatch(patch)) {
         patch.actualOrigin = this;
+        if (retainDataForTesting) {
+          patchForTesting = patch;
+        }
       }
     } else {
       reportPatchMismatch(patch);
diff --git a/pkg/front_end/lib/src/fasta/dill/dill_extension_member_builder.dart b/pkg/front_end/lib/src/fasta/dill/dill_extension_member_builder.dart
index b46cd36..d45a8c7 100644
--- a/pkg/front_end/lib/src/fasta/dill/dill_extension_member_builder.dart
+++ b/pkg/front_end/lib/src/fasta/dill/dill_extension_member_builder.dart
@@ -26,7 +26,7 @@
   bool get isStatic => _descriptor.isStatic;
 
   @override
-  bool get isExternal => _descriptor.isExternal;
+  bool get isExternal => member.isExternal;
 
   @override
   Procedure get procedure {
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index f2753bd..9e548fb 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -3444,7 +3444,7 @@
               beforeInitializers?.next, token);
           break;
         case DeclarationKind.Extension:
-          if (optional(';', bodyStart)) {
+          if (optional(';', bodyStart) && externalToken == null) {
             reportRecoverableError(isOperator ? name.next : name,
                 fasta.messageExtensionDeclaresAbstractMember);
           }
diff --git a/pkg/front_end/lib/src/fasta/source/source_extension_builder.dart b/pkg/front_end/lib/src/fasta/source/source_extension_builder.dart
index fd7df17..ea3c839 100644
--- a/pkg/front_end/lib/src/fasta/source/source_extension_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_extension_builder.dart
@@ -4,6 +4,7 @@
 
 import 'dart:core' hide MapEntry;
 import 'package:kernel/ast.dart';
+import '../../base/common.dart';
 import '../builder/declaration.dart';
 import '../builder/extension_builder.dart';
 import '../builder/library_builder.dart';
@@ -12,22 +13,25 @@
 import '../builder/type_builder.dart';
 import '../builder/type_variable_builder.dart';
 import '../scope.dart';
-import 'source_library_builder.dart';
 import '../kernel/kernel_builder.dart';
-
 import '../problems.dart';
-
 import '../fasta_codes.dart'
     show
+        messagePatchDeclarationMismatch,
+        messagePatchDeclarationOrigin,
         noLength,
         templateConflictsWithMember,
         templateConflictsWithMemberWarning,
         templateConflictsWithSetter,
         templateConflictsWithSetterWarning;
+import 'source_library_builder.dart';
 
 class SourceExtensionBuilder extends ExtensionBuilder {
   final Extension _extension;
 
+  SourceExtensionBuilder _origin;
+  SourceExtensionBuilder patchForTesting;
+
   SourceExtensionBuilder(
       List<MetadataBuilder> metadata,
       int modifiers,
@@ -48,7 +52,10 @@
         super(metadata, modifiers, name, parent, nameOffset, scope,
             typeParameters, onType);
 
-  Extension get extension => _extension;
+  @override
+  SourceExtensionBuilder get origin => _origin ?? this;
+
+  Extension get extension => isPatch ? origin._extension : _extension;
 
   /// Builds the [Extension] for this extension build and inserts the members
   /// into the [Library] of [libraryBuilder].
@@ -75,7 +82,7 @@
           Field field = declaration.build(libraryBuilder);
           if (addMembersToLibrary && declaration.next == null) {
             libraryBuilder.library.addMember(field);
-            _extension.members.add(new ExtensionMemberDescriptor(
+            extension.members.add(new ExtensionMemberDescriptor(
                 name: new Name(declaration.name, libraryBuilder.library),
                 member: field.reference,
                 isStatic: declaration.isStatic,
@@ -83,7 +90,9 @@
           }
         } else if (declaration is ProcedureBuilder) {
           Member function = declaration.build(libraryBuilder);
-          if (addMembersToLibrary && declaration.next == null) {
+          if (addMembersToLibrary &&
+              !declaration.isPatch &&
+              declaration.next == null) {
             libraryBuilder.library.addMember(function);
             ExtensionMemberKind kind;
             switch (declaration.kind) {
@@ -103,11 +112,10 @@
                 unsupported("Extension method kind: ${declaration.kind}",
                     declaration.charOffset, declaration.fileUri);
             }
-            _extension.members.add(new ExtensionMemberDescriptor(
+            extension.members.add(new ExtensionMemberDescriptor(
                 name: new Name(declaration.name, libraryBuilder.library),
                 member: function.reference,
                 isStatic: declaration.isStatic,
-                isExternal: declaration.isExternal,
                 kind: kind));
             Procedure tearOff = declaration.extensionTearOff;
             if (tearOff != null) {
@@ -116,7 +124,6 @@
                   name: new Name(declaration.name, libraryBuilder.library),
                   member: tearOff.reference,
                   isStatic: false,
-                  isExternal: false,
                   kind: ExtensionMemberKind.TearOff));
             }
           }
@@ -157,4 +164,46 @@
 
     return _extension;
   }
+
+  @override
+  void applyPatch(Builder patch) {
+    if (patch is SourceExtensionBuilder) {
+      patch._origin = this;
+      if (retainDataForTesting) {
+        patchForTesting = patch;
+      }
+      scope.local.forEach((String name, Builder member) {
+        Builder memberPatch = patch.scope.local[name];
+        if (memberPatch != null) {
+          member.applyPatch(memberPatch);
+        }
+      });
+      scope.setters.forEach((String name, Builder member) {
+        Builder memberPatch = patch.scope.setters[name];
+        if (memberPatch != null) {
+          member.applyPatch(memberPatch);
+        }
+      });
+
+      // TODO(johnniwinther): Check that type parameters and on-type match
+      // with origin declaration.
+    } else {
+      library.addProblem(messagePatchDeclarationMismatch, patch.charOffset,
+          noLength, patch.fileUri, context: [
+        messagePatchDeclarationOrigin.withLocation(
+            fileUri, charOffset, noLength)
+      ]);
+    }
+  }
+
+  @override
+  int finishPatch() {
+    if (!isPatch) return 0;
+
+    int count = 0;
+    scope.forEach((String name, Builder declaration) {
+      count += declaration.finishPatch();
+    });
+    return count;
+  }
 }
diff --git a/pkg/front_end/lib/src/testing/id_extractor.dart b/pkg/front_end/lib/src/testing/id_extractor.dart
index 0afbb82..a7be604 100644
--- a/pkg/front_end/lib/src/testing/id_extractor.dart
+++ b/pkg/front_end/lib/src/testing/id_extractor.dart
@@ -61,9 +61,8 @@
 
   DataExtractor(this.actualMap);
 
-  void computeForLibrary(Library library, {bool useFileUri: false}) {
-    LibraryId id =
-        new LibraryId(useFileUri ? library.fileUri : library.importUri);
+  void computeForLibrary(Library library) {
+    LibraryId id = new LibraryId(library.fileUri);
     T value = computeLibraryValue(id, library);
     registerValue(library.fileUri, null, id, value, library);
   }
diff --git a/pkg/front_end/lib/src/testing/id_testing.dart b/pkg/front_end/lib/src/testing/id_testing.dart
index 6aba9fb..3c49ea0 100644
--- a/pkg/front_end/lib/src/testing/id_testing.dart
+++ b/pkg/front_end/lib/src/testing/id_testing.dart
@@ -214,6 +214,8 @@
             entry;
       }
     }
+    assert(
+        mainTestFile != null, "No 'main.dart' test file found for $testFile.");
   }
 
   String annotatedCode = new File.fromUri(mainTestFile.uri).readAsStringSync();
diff --git a/pkg/front_end/lib/src/testing/id_testing_helper.dart b/pkg/front_end/lib/src/testing/id_testing_helper.dart
index ec92375..68aa64d 100644
--- a/pkg/front_end/lib/src/testing/id_testing_helper.dart
+++ b/pkg/front_end/lib/src/testing/id_testing_helper.dart
@@ -8,6 +8,7 @@
 import '../api_prototype/experimental_flags.dart' show ExperimentalFlag;
 import '../api_prototype/terminal_color_support.dart'
     show printDiagnosticMessage;
+import '../base/common.dart';
 import '../fasta/messages.dart' show FormattedMessage;
 import '../fasta/severity.dart' show Severity;
 import '../kernel_generator_impl.dart' show InternalCompilerResult;
@@ -42,8 +43,10 @@
   final String marker;
   final String name;
   final Map<ExperimentalFlag, bool> experimentalFlags;
+  final Uri librariesSpecificationUri;
 
-  const TestConfig(this.marker, this.name, {this.experimentalFlags = const {}});
+  const TestConfig(this.marker, this.name,
+      {this.experimentalFlags = const {}, this.librariesSpecificationUri});
 
   void customizeCompilerOptions(CompilerOptions options) {}
 }
@@ -180,6 +183,7 @@
 /// Creates a test runner for [dataComputer] on [testedConfigs].
 RunTestFunction runTestFor<T>(
     DataComputer<T> dataComputer, List<TestConfig> testedConfigs) {
+  retainDataForTesting = true;
   return (TestData testData,
       {bool testAfterFailures, bool verbose, bool succinct, bool printCode}) {
     return runTest(testData, dataComputer, testedConfigs,
@@ -242,6 +246,13 @@
   };
   options.debugDump = printCode;
   options.experimentalFlags.addAll(config.experimentalFlags);
+  if (config.librariesSpecificationUri != null) {
+    Set<Uri> testFiles =
+        testData.memorySourceFiles.keys.map(createUriForFileName).toSet();
+    if (testFiles.contains(config.librariesSpecificationUri)) {
+      options.librariesSpecificationUri = config.librariesSpecificationUri;
+    }
+  }
   config.customizeCompilerOptions(options);
   InternalCompilerResult compilerResult = await compileScript(
       testData.memorySourceFiles,
diff --git a/pkg/front_end/lib/src/testing/id_testing_utils.dart b/pkg/front_end/lib/src/testing/id_testing_utils.dart
index d32d8f5..0c98653 100644
--- a/pkg/front_end/lib/src/testing/id_testing_utils.dart
+++ b/pkg/front_end/lib/src/testing/id_testing_utils.dart
@@ -62,7 +62,7 @@
       (Extension extension) => extension.name == extensionName, orElse: () {
     if (required) {
       throw new ArgumentError(
-          "Extension '$extensionName' not found in '$library'.");
+          "Extension '$extensionName' not found in '${library.importUri}'.");
     }
     return null;
   });
@@ -151,7 +151,7 @@
       lookupClassBuilder(compilerResult, cls, required: required);
   MemberBuilder memberBuilder;
   if (classBuilder != null) {
-    if (member is Constructor) {
+    if (member is Constructor || member is Procedure && member.isFactory) {
       memberBuilder = classBuilder.constructors.local[memberName];
     } else if (member is Procedure && member.isSetter) {
       memberBuilder = classBuilder.scope.setters[memberName];
@@ -527,9 +527,6 @@
 /// Returns a textual representation of [descriptor] to be used in testing.
 String extensionMethodDescriptorToText(ExtensionMemberDescriptor descriptor) {
   StringBuffer sb = new StringBuffer();
-  if (descriptor.isExternal) {
-    sb.write('external ');
-  }
   if (descriptor.isStatic) {
     sb.write('static ');
   }
diff --git a/pkg/front_end/test/extensions/data/as_show/lib.dart b/pkg/front_end/test/extensions/data/as_show/lib.dart
new file mode 100644
index 0000000..e89ef0c
--- /dev/null
+++ b/pkg/front_end/test/extensions/data/as_show/lib.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2019, 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.
+
+/*library: scope=[Extension1]*/
+
+/*class: Extension1:
+ builder-name=Extension1,
+ builder-onType=String,
+ extension-members=[
+  method1=Extension1|method1,
+  tearoff method1=Extension1|get#method1],
+ extension-name=Extension1,
+ extension-onType=String
+*/
+extension Extension1 on String {
+  /*member: Extension1|method1:
+   builder-name=method1,
+   builder-params=[#this],
+   member-name=Extension1|method1,
+   member-params=[#this]
+  */
+  method1() {}
+
+  /*member: Extension1|get#method1:
+   builder-name=method1,
+   builder-params=[#this],
+   member-name=Extension1|get#method1,
+   member-params=[#this]*/
+}
\ No newline at end of file
diff --git a/pkg/front_end/test/extensions/data/as_show/libraries.json b/pkg/front_end/test/extensions/data/as_show/libraries.json
new file mode 100644
index 0000000..7a584cb
--- /dev/null
+++ b/pkg/front_end/test/extensions/data/as_show/libraries.json
@@ -0,0 +1,9 @@
+{
+  "none": {
+    "libraries": {
+      "test": {
+        "uri": "origin.dart"
+      }
+    }
+  }
+}
diff --git a/pkg/front_end/test/extensions/data/as_show/main.dart b/pkg/front_end/test/extensions/data/as_show/main.dart
new file mode 100644
index 0000000..2a7d2d1
--- /dev/null
+++ b/pkg/front_end/test/extensions/data/as_show/main.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2019, 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.
+
+/*library: scope=[lib.dart.Extension1,origin.dart.Extension2]*/
+
+import 'lib.dart' as lib1;
+import 'lib.dart' show Extension1;
+
+// ignore: uri_does_not_exist
+import 'dart:test' as lib2;
+// ignore: uri_does_not_exist
+import 'dart:test' show Extension2;
+
+main() {
+  "".method1();
+  Extension1("").method1();
+  "".method2();
+  Extension2("").method2();
+}
diff --git a/pkg/front_end/test/extensions/data/as_show/origin.dart b/pkg/front_end/test/extensions/data/as_show/origin.dart
new file mode 100644
index 0000000..1b36bb9
--- /dev/null
+++ b/pkg/front_end/test/extensions/data/as_show/origin.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2019, 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.
+
+/*library: scope=[Extension2]*/
+
+/*class: Extension2:
+ builder-name=Extension2,
+ builder-onType=String,
+ extension-members=[
+  method2=Extension2|method2,
+  tearoff method2=Extension2|get#method2],
+ extension-name=Extension2,
+ extension-onType=String
+*/
+extension Extension2 on String {
+/*member: Extension2|method2:
+   builder-name=method2,
+   builder-params=[#this],
+   member-name=Extension2|method2,
+   member-params=[#this]
+  */
+method2() {}
+
+/*member: Extension2|get#method2:
+   builder-name=method2,
+   builder-params=[#this],
+   member-name=Extension2|get#method2,
+   member-params=[#this]*/
+}
\ No newline at end of file
diff --git a/pkg/front_end/test/extensions/data/patching/libraries.json b/pkg/front_end/test/extensions/data/patching/libraries.json
new file mode 100644
index 0000000..a697508
--- /dev/null
+++ b/pkg/front_end/test/extensions/data/patching/libraries.json
@@ -0,0 +1,12 @@
+{
+  "none": {
+    "libraries": {
+      "test": {
+        "patches": [
+          "patch.dart"
+        ],
+        "uri": "origin.dart"
+      }
+    }
+  }
+}
diff --git a/pkg/front_end/test/extensions/data/patching/main.dart b/pkg/front_end/test/extensions/data/patching/main.dart
new file mode 100644
index 0000000..9599059
--- /dev/null
+++ b/pkg/front_end/test/extensions/data/patching/main.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2019, 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.
+
+/*library: scope=[origin.dart.Extension,origin.dart.GenericExtension]*/
+
+// ignore: uri_does_not_exist
+import 'dart:test';
+
+main() {
+  "".instanceMethod();
+  "".genericInstanceMethod<int>(0);
+  "".instanceProperty = "".instanceProperty;
+  Extension.staticMethod();
+  Extension.genericStaticMethod<int>(0);
+  Extension.staticProperty = Extension.staticProperty;
+  true.instanceMethod();
+  true.genericInstanceMethod<int>(0);
+  true.instanceProperty = true.instanceProperty;
+  GenericExtension.staticMethod();
+  GenericExtension.genericStaticMethod<int>(0);
+  GenericExtension.staticProperty = GenericExtension.staticProperty;
+}
diff --git a/pkg/front_end/test/extensions/data/patching/origin.dart b/pkg/front_end/test/extensions/data/patching/origin.dart
new file mode 100644
index 0000000..1a5b69e
--- /dev/null
+++ b/pkg/front_end/test/extensions/data/patching/origin.dart
@@ -0,0 +1,108 @@
+// Copyright (c) 2019, 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.
+
+/*library: scope=[Extension,GenericExtension]*/
+
+/*class: Extension:
+ builder-name=Extension,
+ builder-onType=String,
+ extension-members=[
+  genericInstanceMethod=Extension|genericInstanceMethod,
+  getter instanceProperty=Extension|get#instanceProperty,
+  instanceMethod=Extension|instanceMethod,
+  setter instanceProperty=Extension|set#instanceProperty,
+  static genericStaticMethod=Extension|genericStaticMethod,
+  static getter staticProperty=Extension|staticProperty,
+  static setter staticProperty=Extension|staticProperty=,
+  static staticMethod=Extension|staticMethod,
+  tearoff genericInstanceMethod=Extension|get#genericInstanceMethod,
+  tearoff instanceMethod=Extension|get#instanceMethod,
+ ],
+ extension-name=Extension,
+ extension-onType=String
+*/
+extension Extension on String {
+  /*member: Extension|get#instanceMethod:
+   builder-name=instanceMethod,
+   builder-params=[#this],
+   member-name=Extension|get#instanceMethod,
+   member-params=[#this]
+  */
+  external int instanceMethod();
+
+  /*member: Extension|get#genericInstanceMethod:
+   builder-name=genericInstanceMethod,
+   builder-params=[#this,t],
+   builder-type-params=[T],
+   member-name=Extension|get#genericInstanceMethod,
+   member-params=[#this]
+  */
+  external T genericInstanceMethod<T>(T t);
+
+  external static int staticMethod();
+
+  external static T genericStaticMethod<T>(T t);
+
+  external int get instanceProperty;
+
+  external void set instanceProperty(int value);
+
+  external static int get staticProperty;
+
+  external static void set staticProperty(int value);
+}
+
+/*class: GenericExtension:
+ builder-name=GenericExtension,
+ builder-onType=T,
+ builder-type-params=[T],
+ extension-members=[
+  genericInstanceMethod=GenericExtension|genericInstanceMethod,
+  getter instanceProperty=GenericExtension|get#instanceProperty,
+  instanceMethod=GenericExtension|instanceMethod,
+  setter instanceProperty=GenericExtension|set#instanceProperty,
+  static genericStaticMethod=GenericExtension|genericStaticMethod,
+  static getter staticProperty=GenericExtension|staticProperty,
+  static setter staticProperty=GenericExtension|staticProperty=,
+  static staticMethod=GenericExtension|staticMethod,
+  tearoff genericInstanceMethod=GenericExtension|get#genericInstanceMethod,
+  tearoff instanceMethod=GenericExtension|get#instanceMethod
+  ],
+ extension-name=GenericExtension,
+ extension-onType=T,
+ extension-type-params=[T]
+*/
+extension GenericExtension<T> on T {
+  /*member: GenericExtension|get#instanceMethod:
+   builder-name=instanceMethod,
+   builder-params=[#this],
+   builder-type-params=[T],
+   member-name=GenericExtension|get#instanceMethod,
+   member-params=[#this],
+   member-type-params=[T]
+  */
+  external int instanceMethod();
+
+  /*member: GenericExtension|get#genericInstanceMethod:
+   builder-name=genericInstanceMethod,
+   builder-params=[#this,t],
+   builder-type-params=[T,T],
+   member-name=GenericExtension|get#genericInstanceMethod,
+   member-params=[#this],
+   member-type-params=[#T]
+  */
+  external T genericInstanceMethod<T>(T t);
+
+  external static int staticMethod();
+
+  external static T genericStaticMethod<T>(T t);
+
+  external int get instanceProperty;
+
+  external void set instanceProperty(int value);
+
+  external static int get staticProperty;
+
+  external static void set staticProperty(int value);
+}
\ No newline at end of file
diff --git a/pkg/front_end/test/extensions/data/patching/patch.dart b/pkg/front_end/test/extensions/data/patching/patch.dart
new file mode 100644
index 0000000..32edfc4
--- /dev/null
+++ b/pkg/front_end/test/extensions/data/patching/patch.dart
@@ -0,0 +1,163 @@
+// Copyright (c) 2019, 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.
+
+// ignore: import_internal_library
+import 'dart:_internal';
+
+@patch
+extension Extension on String {
+  /*member: Extension|instanceMethod:
+   builder-name=instanceMethod,
+   builder-params=[#this],
+   member-name=Extension|instanceMethod,
+   member-params=[#this]
+  */
+  @patch
+  int instanceMethod() => 42;
+
+  /*member: Extension|genericInstanceMethod:
+   builder-name=genericInstanceMethod,
+   builder-params=[#this,t],
+   builder-type-params=[T],
+   member-name=Extension|genericInstanceMethod,
+   member-params=[#this,t],
+   member-type-params=[T]
+  */
+  @patch
+  T genericInstanceMethod<T>(T t) => t;
+
+  /*member: Extension|staticMethod:
+   builder-name=staticMethod,
+   member-name=Extension|staticMethod
+  */
+  @patch
+  static int staticMethod() => 87;
+
+  /*member: Extension|genericStaticMethod:
+   builder-name=genericStaticMethod,
+   builder-params=[t],
+   builder-type-params=[T],
+   member-name=Extension|genericStaticMethod,
+   member-params=[t],
+   member-type-params=[T]
+  */
+  @patch
+  static T genericStaticMethod<T>(T t) => t;
+
+  /*member: Extension|get#instanceProperty:
+   builder-name=instanceProperty,
+   builder-params=[#this],
+   member-name=Extension|get#instanceProperty,
+   member-params=[#this]
+  */
+  @patch
+  int get instanceProperty => 123;
+
+  /*member: Extension|set#instanceProperty:
+   builder-name=instanceProperty,
+   builder-params=[#this,value],
+   member-name=Extension|set#instanceProperty,
+   member-params=[#this,value]
+  */
+  @patch
+  void set instanceProperty(int value) {}
+
+  /*member: Extension|staticProperty:
+   builder-name=staticProperty,
+   member-name=Extension|staticProperty
+  */
+  @patch
+  static int get staticProperty => 237;
+
+  /*member: Extension|staticProperty=:
+   builder-name=staticProperty,
+   builder-params=[value],
+   member-name=Extension|staticProperty=,
+   member-params=[value]
+  */
+  @patch
+  static void set staticProperty(int value) {}
+}
+
+
+@patch
+extension GenericExtension<T> on T {
+  /*member: GenericExtension|instanceMethod:
+   builder-name=instanceMethod,
+   builder-params=[#this],
+   builder-type-params=[T],
+   member-name=GenericExtension|instanceMethod,
+   member-params=[#this],
+   member-type-params=[T]
+  */
+  @patch
+  int instanceMethod() => 42;
+
+  /*member: GenericExtension|genericInstanceMethod:
+   builder-name=genericInstanceMethod,
+   builder-params=[#this,t],
+   builder-type-params=[T,T],
+   member-name=GenericExtension|genericInstanceMethod,
+   member-params=[#this,t],
+   member-type-params=[#T,T]
+  */
+  @patch
+  T genericInstanceMethod<T>(T t) => t;
+
+  /*member: GenericExtension|staticMethod:
+   builder-name=staticMethod,
+   member-name=GenericExtension|staticMethod
+  */
+  @patch
+  static int staticMethod() => 87;
+
+  /*member: GenericExtension|genericStaticMethod:
+   builder-name=genericStaticMethod,
+   builder-params=[t],
+   builder-type-params=[T],
+   member-name=GenericExtension|genericStaticMethod,
+   member-params=[t],
+   member-type-params=[T]
+  */
+  @patch
+  static T genericStaticMethod<T>(T t) => t;
+
+  /*member: GenericExtension|get#instanceProperty:
+   builder-name=instanceProperty,
+   builder-params=[#this],
+   builder-type-params=[T],
+   member-name=GenericExtension|get#instanceProperty,
+   member-params=[#this],
+   member-type-params=[T]
+  */
+  @patch
+  int get instanceProperty => 123;
+
+  /*member: GenericExtension|set#instanceProperty:
+   builder-name=instanceProperty,
+   builder-params=[#this,value],
+   builder-type-params=[T],
+   member-name=GenericExtension|set#instanceProperty,
+   member-params=[#this,value],
+   member-type-params=[T]
+  */
+  @patch
+  void set instanceProperty(int value) {}
+
+  /*member: GenericExtension|staticProperty:
+   builder-name=staticProperty,
+   member-name=GenericExtension|staticProperty
+  */
+  @patch
+  static int get staticProperty => 237;
+
+  /*member: GenericExtension|staticProperty=:
+   builder-name=staticProperty,
+   builder-params=[value],
+   member-name=GenericExtension|staticProperty=,
+   member-params=[value]
+  */
+  @patch
+  static void set staticProperty(int value) {}
+}
\ No newline at end of file
diff --git a/pkg/front_end/test/extensions/extensions_test.dart b/pkg/front_end/test/extensions/extensions_test.dart
index 33e8fb4..880d249 100644
--- a/pkg/front_end/test/extensions/extensions_test.dart
+++ b/pkg/front_end/test/extensions/extensions_test.dart
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:io' show Directory, Platform;
+import 'package:front_end/src/api_prototype/experimental_flags.dart'
+    show ExperimentalFlag;
 import 'package:front_end/src/fasta/builder/builder.dart';
 import 'package:front_end/src/fasta/builder/extension_builder.dart';
 import 'package:front_end/src/fasta/kernel/kernel_builder.dart';
@@ -22,8 +24,11 @@
       supportedMarkers: sharedMarkers,
       createUriForFileName: createUriForFileName,
       onFailure: onFailure,
-      runTest: runTestFor(
-          const ExtensionsDataComputer(), [cfeExtensionMethodsConfig]));
+      runTest: runTestFor(const ExtensionsDataComputer(), [
+        new TestConfig(cfeMarker, 'cfe with extension methods',
+            experimentalFlags: const {ExperimentalFlag.extensionMethods: true},
+            librariesSpecificationUri: createUriForFileName('libraries.json'))
+      ]));
 }
 
 class ExtensionsDataComputer extends DataComputer<Features> {
diff --git a/pkg/front_end/test/id_testing/id_testing_test.dart b/pkg/front_end/test/id_testing/id_testing_test.dart
index 07d99ba..92a4fb5 100644
--- a/pkg/front_end/test/id_testing/id_testing_test.dart
+++ b/pkg/front_end/test/id_testing/id_testing_test.dart
@@ -58,7 +58,7 @@
       Library library, Map<Id, ActualData<String>> actualMap,
       {bool verbose}) {
     new IdTestingDataExtractor(compilerResult, actualMap)
-        .computeForLibrary(library, useFileUri: true);
+        .computeForLibrary(library);
   }
 
   @override
diff --git a/pkg/front_end/test/language_versioning/language_versioning_test.dart b/pkg/front_end/test/language_versioning/language_versioning_test.dart
index 691df94..a387f9a 100644
--- a/pkg/front_end/test/language_versioning/language_versioning_test.dart
+++ b/pkg/front_end/test/language_versioning/language_versioning_test.dart
@@ -52,7 +52,7 @@
       Library library, Map<Id, ActualData<String>> actualMap,
       {bool verbose}) {
     new LanguageVersioningDataExtractor(compilerResult, actualMap)
-        .computeForLibrary(library, useFileUri: true);
+        .computeForLibrary(library);
   }
 
   @override
diff --git a/pkg/kernel/binary.md b/pkg/kernel/binary.md
index d8a353e..333a741e 100644
--- a/pkg/kernel/binary.md
+++ b/pkg/kernel/binary.md
@@ -353,7 +353,7 @@
 type ExtensionMemberDescriptor {
   StringReference name;
   ExtensionMemberKind kind;
-  Byte flags (isStatic, isExternal);
+  Byte flags (isStatic);
   MemberReference member;
 }
 
diff --git a/pkg/kernel/lib/ast.dart b/pkg/kernel/lib/ast.dart
index cd777e3..6fb554d 100644
--- a/pkg/kernel/lib/ast.dart
+++ b/pkg/kernel/lib/ast.dart
@@ -1350,7 +1350,6 @@
 /// Information about an member declaration in an extension.
 class ExtensionMemberDescriptor {
   static const int FlagStatic = 1 << 0; // Must match serialized bit positions.
-  static const int FlagExternal = 1 << 1;
 
   /// The name of the extension member.
   ///
@@ -1387,28 +1386,16 @@
   Reference member;
 
   ExtensionMemberDescriptor(
-      {this.name,
-      this.kind,
-      bool isStatic: false,
-      bool isExternal: false,
-      this.member}) {
+      {this.name, this.kind, bool isStatic: false, this.member}) {
     this.isStatic = isStatic;
-    this.isExternal = isExternal;
   }
 
   /// Return `true` if the extension method was declared as `static`.
   bool get isStatic => flags & FlagStatic != 0;
 
-  /// Return `true` if the extension method was declared as `external`.
-  bool get isExternal => flags & FlagExternal != 0;
-
   void set isStatic(bool value) {
     flags = value ? (flags | FlagStatic) : (flags & ~FlagStatic);
   }
-
-  void set isExternal(bool value) {
-    flags = value ? (flags | FlagExternal) : (flags & ~FlagExternal);
-  }
 }
 
 // ------------------------------------------------------------------------
diff --git a/pkg/kernel/lib/text/ast_to_text.dart b/pkg/kernel/lib/text/ast_to_text.dart
index 707b3a1..d3a9b1a 100644
--- a/pkg/kernel/lib/text/ast_to_text.dart
+++ b/pkg/kernel/lib/text/ast_to_text.dart
@@ -1189,7 +1189,6 @@
     ++indentation;
     node.members.forEach((ExtensionMemberDescriptor descriptor) {
       writeIndentation();
-      writeModifier(descriptor.isExternal, 'external');
       writeModifier(descriptor.isStatic, 'static');
       switch (descriptor.kind) {
         case ExtensionMemberKind.Method:
diff --git a/tests/compiler/dart2js/equivalence/id_testing_test.dart b/tests/compiler/dart2js/equivalence/id_testing_test.dart
index a24168a..c40ff6f 100644
--- a/tests/compiler/dart2js/equivalence/id_testing_test.dart
+++ b/tests/compiler/dart2js/equivalence/id_testing_test.dart
@@ -61,7 +61,7 @@
     KernelToElementMapImpl elementMap = frontendStrategy.elementMap;
     ir.Library node = elementMap.getLibraryNode(library);
     new IdTestingDataExtractor(compiler.reporter, actualMap, elementMap)
-        .computeForLibrary(node, useFileUri: true);
+        .computeForLibrary(node);
   }
 
   @override