Version 2.10.0-150.0.dev

Merge commit 'a8586206f836417ec67eeedd3a1a003335367a22' into 'dev'
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/add_type_parameter.dart b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/add_type_parameter.dart
index 7ff448e..38ffa68 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/add_type_parameter.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/add_type_parameter.dart
@@ -4,7 +4,7 @@
 
 import 'package:analysis_server/src/services/correction/dart/data_driven.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/change.dart';
-import 'package:analysis_server/src/services/correction/fix/data_driven/value_extractor.dart';
+import 'package:analysis_server/src/services/correction/fix/data_driven/code_template.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
 import 'package:meta/meta.dart';
@@ -22,8 +22,8 @@
   /// type parameter doesn't have a bound.
   final String extendedType;
 
-  /// The value extractor used to compute the value of the type argument.
-  final ValueExtractor argumentValue;
+  /// The code template used to compute the value of the type argument.
+  final CodeTemplate argumentValue;
 
   /// Initialize a newly created change to describe adding a type parameter to a
   /// type or a function.
@@ -53,7 +53,7 @@
     if (node is NamedType) {
       // wrong_number_of_type_arguments
       // wrong_number_of_type_arguments_constructor
-      var argument = argumentValue.from(node, fix.utils);
+      var argument = argumentValue.generate(node, fix.utils);
       if (argument == null) {
         return null;
       }
@@ -66,7 +66,7 @@
     var parent = node.parent;
     if (parent is InvocationExpression) {
       // wrong_number_of_type_arguments_method
-      var argument = argumentValue.from(parent, fix.utils);
+      var argument = argumentValue.generate(parent, fix.utils);
       if (argument == null) {
         return null;
       }
@@ -85,7 +85,7 @@
       return _TypeParameterData(typeParameters, parent.name.end);
     } else if (node is TypeArgumentList && parent is ExtensionOverride) {
       // wrong_number_of_type_arguments_extension
-      var argument = argumentValue.from(node, fix.utils);
+      var argument = argumentValue.generate(node, fix.utils);
       if (argument == null) {
         return null;
       }
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/code_template.dart b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/code_template.dart
new file mode 100644
index 0000000..86b797e
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/code_template.dart
@@ -0,0 +1,95 @@
+// Copyright (c) 2020, 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.
+
+import 'package:analysis_server/src/services/correction/fix/data_driven/value_extractor.dart';
+import 'package:analysis_server/src/services/correction/util.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+
+/// An object used to generate code to be inserted.
+class CodeTemplate {
+  /// The kind of code that will be generated by this template.
+  final CodeTemplateKind kind;
+
+  /// The components of the template.
+  final List<TemplateComponent> components;
+
+  /// Initialize a newly generated code template with the given [kind] and
+  /// [components].
+  CodeTemplate(this.kind, this.components);
+
+  String generate(AstNode node, CorrectionUtils utils) {
+    var context = _TemplateContext(node, utils);
+    var buffer = StringBuffer();
+    for (var component in components) {
+      component.appendTo(buffer, context);
+    }
+    return buffer.toString();
+  }
+}
+
+/// The kinds of code that can be generated by a template.
+enum CodeTemplateKind {
+  expression,
+  statements,
+}
+
+/// An object used to compute some portion of a template.
+abstract class TemplateComponent {
+  /// Append the text contributed by this component to the given [sink], using
+  /// the [context] to access needed information that isn't already known to
+  /// this component.
+  void appendTo(StringSink sink, _TemplateContext context);
+}
+
+/// Literal text within a template.
+class TemplateText extends TemplateComponent {
+  /// The literal text to be included in the resulting code.
+  final String text;
+
+  /// Initialize a newly create template text with the given [text].
+  TemplateText(this.text);
+
+  @override
+  void appendTo(StringSink sink, _TemplateContext context) {
+    sink.write(text);
+  }
+}
+
+/// A reference to a variable within a template.
+class TemplateVariable extends TemplateComponent {
+  /// The extractor used to compute the value of the variable.
+  final ValueExtractor extractor;
+
+  /// Initialize a newly created template variable with the given [name].
+  TemplateVariable(this.extractor);
+
+  @override
+  void appendTo(StringSink sink, _TemplateContext context) {
+    sink.write(context.valueOf(extractor));
+  }
+}
+
+/// The context in which a template is being evaluated.
+class _TemplateContext {
+  /// The node in the AST that is being transformed.
+  final AstNode node;
+
+  /// The utilities used to help extract the code associated with various nodes.
+  final CorrectionUtils utils;
+
+  /// A table mapping variable names to the values of those variables after they
+  /// have been computed. Used to prevent computing the same value multiple
+  /// times.
+  final Map<ValueExtractor, String> variableValues = {};
+
+  /// Initialize a newly created variable support.
+  _TemplateContext(this.node, this.utils);
+
+  /// Return the value of the variable with the given [name].
+  String valueOf(ValueExtractor extractor) {
+    return variableValues.putIfAbsent(extractor, () {
+      return extractor.from(node, utils);
+    });
+  }
+}
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/modify_parameters.dart b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/modify_parameters.dart
index 20e8a0c..857a509 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/modify_parameters.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/modify_parameters.dart
@@ -4,8 +4,8 @@
 
 import 'package:analysis_server/src/services/correction/dart/data_driven.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/change.dart';
+import 'package:analysis_server/src/services/correction/fix/data_driven/code_template.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/parameter_reference.dart';
-import 'package:analysis_server/src/services/correction/fix/data_driven/value_extractor.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
 import 'package:analyzer_plugin/utilities/range_factory.dart';
@@ -26,12 +26,12 @@
   /// A flag indicating whether the parameter is a positional parameter.
   final bool isPositional;
 
-  /// The value of the new argument in invocations of the function, or `null` if
-  /// the parameter is optional and no argument needs to be added. The only time
-  /// an argument needs to be added for an optional parameter is if the
-  /// parameter is positional and there are pre-existing optional positional
-  /// parameters after the ones being added.
-  final ValueExtractor argumentValue;
+  /// The code template used to compute the value of the new argument in
+  /// invocations of the function, or `null` if the parameter is optional and no
+  /// argument needs to be added. The only time an argument needs to be added
+  /// for an optional parameter is if the parameter is positional and there are
+  /// pre-existing optional positional parameters after the ones being added.
+  final CodeTemplate argumentValue;
 
   /// Initialize a newly created parameter modification to represent the
   /// addition of a parameter. If provided, the [argumentValue] will be used as
@@ -91,7 +91,7 @@
     /// Write to the [builder] the argument associated with a single
     /// [parameter].
     void writeArgument(DartEditBuilder builder, AddParameter parameter) {
-      var value = parameter.argumentValue.from(argumentList, fix.utils);
+      var value = parameter.argumentValue.generate(argumentList, fix.utils);
       if (!parameter.isPositional) {
         builder.write(parameter.name);
         builder.write(': ');
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_error_code.dart b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_error_code.dart
index 2cdeb92..260102c 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_error_code.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_error_code.dart
@@ -26,6 +26,19 @@
       TransformSetErrorCode('missingKey', "Missing the required key '{0}'.");
 
   /**
+   * No parameters.
+   */
+  static const TransformSetErrorCode missingTemplateEnd = TransformSetErrorCode(
+      'missingTemplateEnd', "Missing the end brace for the template.");
+
+  /**
+   * Parameters:
+   * 0: the missing key
+   */
+  static const TransformSetErrorCode undefinedVariable = TransformSetErrorCode(
+      'undefinedVariable', "The variable '{0}' is not defined.");
+
+  /**
    * Parameters:
    * 0: the unsupported key
    */
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart
index 0175537..cf09db0 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart
@@ -4,6 +4,7 @@
 
 import 'package:analysis_server/src/services/correction/fix/data_driven/add_type_parameter.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/change.dart';
+import 'package:analysis_server/src/services/correction/fix/data_driven/code_template.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/element_descriptor.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/modify_parameters.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/parameter_reference.dart';
@@ -41,6 +42,7 @@
   static const String _dateKey = 'date';
   static const String _elementKey = 'element';
   static const String _enumKey = 'enum';
+  static const String _expressionKey = 'expression';
   static const String _extensionKey = 'extension';
   static const String _fieldKey = 'field';
   static const String _functionKey = 'function';
@@ -56,11 +58,13 @@
   static const String _nameKey = 'name';
   static const String _newNameKey = 'newName';
   static const String _setterKey = 'setter';
+  static const String _statementsKey = 'statements';
   static const String _styleKey = 'style';
   static const String _titleKey = 'title';
   static const String _transformsKey = 'transforms';
   static const String _typedefKey = 'typedef';
   static const String _urisKey = 'uris';
+  static const String _variablesKey = 'variables';
   static const String _versionKey = 'version';
 
   /// A table mapping top-level keys for member elements to the list of keys for
@@ -88,6 +92,9 @@
     'required_positional'
   ];
 
+  static const String _openComponent = '{%';
+  static const String _closeComponent = '%}';
+
   /// The highest file version supported by this parser. The version needs to be
   /// incremented any time the parser is updated to disallow input that would
   /// have been valid in the most recently published version of server. This
@@ -117,6 +124,50 @@
     return _translateTransformSet(map);
   }
 
+  /// Convert the given [template] into a list of components. Variable
+  /// references in the template are looked up in the map of [extractors].
+  List<TemplateComponent> _extractTemplateComponents(String template,
+      Map<String, ValueExtractor> extractors, int templateOffset) {
+    var components = <TemplateComponent>[];
+    var textStart = 0;
+    var variableStart = template.indexOf(_openComponent);
+    while (variableStart >= 0) {
+      if (textStart < variableStart) {
+        // TODO(brianwilkerson) Check for an end brace without a start brace.
+        components
+            .add(TemplateText(template.substring(textStart, variableStart)));
+      }
+      var endIndex = template.indexOf(_closeComponent, variableStart + 2);
+      if (endIndex < 0) {
+        errorReporter.reportErrorForOffset(
+            TransformSetErrorCode.missingTemplateEnd,
+            templateOffset + variableStart,
+            2);
+        return null;
+      } else {
+        var name = template.substring(variableStart + 2, endIndex).trim();
+        var extractor = extractors[name];
+        if (extractor == null) {
+          errorReporter.reportErrorForOffset(
+              TransformSetErrorCode.undefinedVariable,
+              templateOffset + template.indexOf(name, variableStart),
+              name.length,
+              [name]);
+          return null;
+        } else {
+          components.add(TemplateVariable(extractor));
+        }
+      }
+      textStart = endIndex + 2;
+      variableStart = template.indexOf(_openComponent, textStart);
+    }
+    if (textStart < template.length) {
+      // TODO(brianwilkerson) Check for an end brace without a start brace.
+      components.add(TemplateText(template.substring(textStart)));
+    }
+    return components;
+  }
+
   /// Return a textual description of the type of value represented by the
   /// [node].
   String _nodeType(YamlNode node) {
@@ -173,14 +224,9 @@
   /// Report any keys in the [map] whose values are not in [validKeys].
   void _reportUnsupportedKeys(YamlMap map, Set<String> validKeys) {
     for (var keyNode in map.nodes.keys) {
-      if (keyNode is YamlScalar && keyNode.value is String) {
-        var key = keyNode.value as String;
-        if (key != null && !validKeys.contains(key)) {
-          _reportError(TransformSetErrorCode.unsupportedKey, keyNode, [key]);
-        }
-      } else {
-        // TODO(brianwilkerson) Report the invalidKey.
-        //  "Keys must be of type 'String' but found the type '{0}'."
+      var key = _translateKey(keyNode);
+      if (key != null && !validKeys.contains(key)) {
+        _reportError(TransformSetErrorCode.unsupportedKey, keyNode, [key]);
       }
     }
   }
@@ -236,8 +282,7 @@
     }
     var isRequired = style.startsWith('required_');
     var isPositional = style.endsWith('_positional');
-    var argumentValue = _translateValueExtractor(
-        node.valueAt(_argumentValueKey),
+    var argumentValue = _translateCodeTemplate(node.valueAt(_argumentValueKey),
         ErrorContext(key: _argumentValueKey, parentNode: node));
     // TODO(brianwilkerson) We really ought to require an argument value for
     //  optional positional parameters too for the case where the added
@@ -269,8 +314,7 @@
     //  we might need to introduce a `TypeParameterModification` change, similar
     //  to `ParameterModification`. That becomes more likely if we add support
     //  for removing type parameters.
-    var argumentValue = _translateValueExtractor(
-        node.valueAt(_argumentValueKey),
+    var argumentValue = _translateCodeTemplate(node.valueAt(_argumentValueKey),
         ErrorContext(key: _argumentValueKey, parentNode: node));
     if (index == null || name == null || argumentValue == null) {
       // The error has already been reported.
@@ -336,6 +380,52 @@
     }
   }
 
+  /// Translate the [node] into a code template. Return the resulting template,
+  /// or `null` if the [node] does not represent a valid code template. If the
+  /// [node] is not valid, use the [context] to report the error.
+  CodeTemplate _translateCodeTemplate(YamlNode node, ErrorContext context) {
+    if (node is YamlMap) {
+      CodeTemplateKind kind;
+      int templateOffset;
+      String template;
+      var expressionNode = node.valueAt(_expressionKey);
+      if (expressionNode != null) {
+        _reportUnsupportedKeys(node, const {_expressionKey, _variablesKey});
+        kind = CodeTemplateKind.expression;
+        // TODO(brianwilkerson) We add 1 to account for the quotes around the
+        //  string, but quotes aren't required, so we need to find out what
+        //  style of node [expressionNode] is to get the right offset.
+        templateOffset = expressionNode.span.start.offset + 1;
+        template = _translateString(expressionNode,
+            ErrorContext(key: _expressionKey, parentNode: node));
+      } else {
+        var statementsNode = node.valueAt(_statementsKey);
+        if (statementsNode != null) {
+          _reportUnsupportedKeys(node, const {_statementsKey, _variablesKey});
+          kind = CodeTemplateKind.statements;
+          // TODO(brianwilkerson) We add 1 to account for the quotes around the
+          //  string, but quotes aren't required, so we need to find out what
+          //  style of node [expressionNode] is to get the right offset.
+          templateOffset = statementsNode.span.start.offset + 1;
+          template = _translateString(statementsNode,
+              ErrorContext(key: _statementsKey, parentNode: node));
+        } else {
+          // TODO(brianwilkerson) Report the missing key.
+          return null;
+        }
+      }
+      var extractors = _translateTemplateVariables(node.valueAt(_variablesKey),
+          ErrorContext(key: _variablesKey, parentNode: node));
+      var components =
+          _extractTemplateComponents(template, extractors, templateOffset);
+      return CodeTemplate(kind, components);
+    } else if (node == null) {
+      return _reportMissingKey(context);
+    } else {
+      return _reportInvalidValue(node, context, 'Map');
+    }
+  }
+
   /// Translate the [node] into a date. Return the resulting date, or `null`
   /// if the [node] does not represent a valid date. If the [node] is not
   /// valid, use the [context] to report the error.
@@ -435,6 +525,16 @@
     }
   }
 
+  /// Translate the given [node] as a key.
+  String _translateKey(YamlNode node) {
+    if (node is YamlScalar && node.value is String) {
+      return node.value as String;
+    }
+    // TODO(brianwilkerson) Report the invalidKey.
+    //  "Keys must be of type 'String' but found the type '{0}'."
+    return null;
+  }
+
   /// Translate the [node] into a list of objects using the [elementTranslator].
   /// Return the resulting list, or `null` if the [node] does not represent a
   /// valid list. If any of the elements of the list can't be translated, they
@@ -521,6 +621,32 @@
     }
   }
 
+  /// Translate the [node] into a list of template components. Return the
+  /// resulting list, or `null` if the [node] does not represent a valid
+  /// variables map. If the [node] is not valid, use the [context] to report the
+  /// error.
+  Map<String, ValueExtractor> _translateTemplateVariables(
+      YamlNode node, ErrorContext context) {
+    if (node is YamlMap) {
+      var extractors = <String, ValueExtractor>{};
+      for (var entry in node.nodes.entries) {
+        var name = _translateKey(entry.key);
+        if (name != null) {
+          var value = _translateValueExtractor(
+              entry.value, ErrorContext(key: name, parentNode: node));
+          if (value != null) {
+            extractors[name] = value;
+          }
+        }
+      }
+      return extractors;
+    } else if (node == null) {
+      return const {};
+    } else {
+      return _reportInvalidValue(node, context, 'Map');
+    }
+  }
+
   /// Translate the [node] into a transform. Return the resulting transform, or
   /// `null` if the [node] does not represent a valid transform. If the [node]
   /// is not valid, use the [context] to report the error.
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/value_extractor.dart b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/value_extractor.dart
index cc9b79f..cec9e04 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/value_extractor.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/value_extractor.dart
@@ -42,20 +42,6 @@
   }
 }
 
-/// A value extractor that returns a pre-computed piece of code.
-class LiteralExtractor extends ValueExtractor {
-  /// The code to be returned.
-  final String code;
-
-  /// Initialize a newly created extractor to return the given [code].
-  LiteralExtractor(this.code);
-
-  @override
-  String from(AstNode node, CorrectionUtils utils) {
-    return code;
-  }
-}
-
 /// An object used to extract an expression from an AST node.
 abstract class ValueExtractor {
   /// Return code extracted from the given [node], or `null` if no code could be
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/add_type_parameter_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/add_type_parameter_test.dart
index 3ac8426..b2e939d 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/add_type_parameter_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/add_type_parameter_test.dart
@@ -5,7 +5,6 @@
 import 'package:analysis_server/src/services/correction/fix/data_driven/add_type_parameter.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/element_descriptor.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/transform.dart';
-import 'package:analysis_server/src/services/correction/fix/data_driven/value_extractor.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'data_driven_test_support.dart';
@@ -468,6 +467,6 @@
                 extendedType: extendedType,
                 index: index,
                 name: 'T',
-                argumentValue: LiteralExtractor('String')),
+                argumentValue: codeTemplate('String')),
           ]);
 }
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/code_template_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/code_template_test.dart
new file mode 100644
index 0000000..22b1e74
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/code_template_test.dart
@@ -0,0 +1,87 @@
+// Copyright (c) 2020, 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.
+
+import 'package:analysis_server/src/services/correction/fix/data_driven/code_template.dart';
+import 'package:analysis_server/src/services/correction/fix/data_driven/parameter_reference.dart';
+import 'package:analysis_server/src/services/correction/fix/data_driven/value_extractor.dart';
+import 'package:analysis_server/src/services/correction/util.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'data_driven_test_support.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(CodeTemplateTest);
+  });
+}
+
+@reflectiveTest
+class CodeTemplateTest extends DataDrivenFixProcessorTest {
+  Future<void> test_text_variable() async {
+    await _assertTemplateResult('a0', [
+      _text('a'),
+      _variable(0),
+    ]);
+  }
+
+  Future<void> test_text_variable_text() async {
+    await _assertTemplateResult('a0b', [
+      _text('a'),
+      _variable(0),
+      _text('b'),
+    ]);
+  }
+
+  Future<void> test_textOnly() async {
+    await _assertTemplateResult('a', [
+      _text('a'),
+    ]);
+  }
+
+  Future<void> test_variable_text() async {
+    await _assertTemplateResult('0a', [
+      _variable(0),
+      _text('a'),
+    ]);
+  }
+
+  Future<void> test_variable_text_variable() async {
+    await _assertTemplateResult('0a1', [
+      _variable(0),
+      _text('a'),
+      _variable(1),
+    ]);
+  }
+
+  Future<void> test_variableOnly() async {
+    await _assertTemplateResult('0', [
+      _variable(0),
+    ]);
+  }
+
+  Future<void> _assertTemplateResult(
+      String expectedResult, List<TemplateComponent> components) async {
+    await resolveTestUnit('''
+void f() {
+  g(0, 1);
+}
+void g(int x, int y) {}
+''');
+    var unit = testAnalysisResult.unit;
+    var function = unit.declarations[0] as FunctionDeclaration;
+    var body = function.functionExpression.body as BlockFunctionBody;
+    var statement = body.block.statements[0] as ExpressionStatement;
+    var node = statement.expression;
+    var template = CodeTemplate(CodeTemplateKind.expression, components);
+    var result = template.generate(node, CorrectionUtils(testAnalysisResult));
+    expect(result, expectedResult);
+  }
+
+  TemplateText _text(String text) => TemplateText(text);
+
+  TemplateVariable _variable(int index) =>
+      TemplateVariable(ArgumentExtractor(PositionalParameterReference(index)));
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test_support.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test_support.dart
index 4e48e50..fe452ec 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test_support.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test_support.dart
@@ -4,6 +4,7 @@
 
 import 'package:analysis_server/src/services/correction/dart/data_driven.dart';
 import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analysis_server/src/services/correction/fix/data_driven/code_template.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/transform.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/transform_set.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/transform_set_manager.dart';
@@ -28,6 +29,11 @@
     addPackageFile('p', TransformSetManager.dataFileName, content);
   }
 
+  /// Return a code template that will produce the given [text].
+  CodeTemplate codeTemplate(String text) {
+    return CodeTemplate(CodeTemplateKind.expression, [TemplateText(text)]);
+  }
+
   /// A method that can be used as an error filter to ignore any unused_import
   /// diagnostics.
   bool ignoreUnusedImport(AnalysisError error) =>
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/diagnostics/missing_template_end_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/diagnostics/missing_template_end_test.dart
new file mode 100644
index 0000000..0301220
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/diagnostics/missing_template_end_test.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2020, 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.
+
+import 'package:analysis_server/src/services/correction/fix/data_driven/transform_set_error_code.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../transform_set_parser_test_support.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(MissingTemplateEndTest);
+  });
+}
+
+@reflectiveTest
+class MissingTemplateEndTest extends AbstractTransformSetParserTest {
+  void test_missing() {
+    assertErrors('''
+version: 1
+transforms:
+- date: 2020-09-19
+  element:
+    uris: ['test.dart']
+    function: 'f'
+  title: ''
+  changes:
+    - kind: 'addParameter'
+      index: 0
+      name: 'a'
+      style: optional_positional
+      argumentValue:
+        expression: 'a{% x b'
+''', [
+      error(TransformSetErrorCode.missingTemplateEnd, 252, 2),
+    ]);
+  }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/diagnostics/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/diagnostics/test_all.dart
index dc7a598..76bb9b4 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/diagnostics/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/diagnostics/test_all.dart
@@ -6,6 +6,8 @@
 
 import 'invalid_value_test.dart' as invalid_value;
 import 'missing_key_test.dart' as missing_key;
+import 'missing_template_end_test.dart' as missing_template_end;
+import 'undefined_variable_test.dart' as undefined_variable;
 import 'unsupported_key_test.dart' as unsupported_key;
 import 'yaml_syntax_error_test.dart' as yaml_syntax_error;
 
@@ -13,6 +15,8 @@
   defineReflectiveSuite(() {
     invalid_value.main();
     missing_key.main();
+    missing_template_end.main();
+    undefined_variable.main();
     unsupported_key.main();
     yaml_syntax_error.main();
   });
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/diagnostics/undefined_variable_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/diagnostics/undefined_variable_test.dart
new file mode 100644
index 0000000..19334c8
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/diagnostics/undefined_variable_test.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2020, 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.
+
+import 'package:analysis_server/src/services/correction/fix/data_driven/transform_set_error_code.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../transform_set_parser_test_support.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(UndefinedVariableTest);
+  });
+}
+
+@reflectiveTest
+class UndefinedVariableTest extends AbstractTransformSetParserTest {
+  void test_missingVariable() {
+    assertErrors('''
+version: 1
+transforms:
+- date: 2020-09-19
+  element:
+    uris: ['test.dart']
+    function: 'f'
+  title: ''
+  changes:
+    - kind: 'addParameter'
+      index: 0
+      name: 'a'
+      style: optional_positional
+      argumentValue:
+        expression: '{%xyz%}'
+        variables:
+          zyx:
+            kind: 'argument'
+            index: 0
+''', [
+      error(TransformSetErrorCode.undefinedVariable, 253, 3),
+    ]);
+  }
+
+  void test_noVariables() {
+    assertErrors('''
+version: 1
+transforms:
+- date: 2020-09-19
+  element:
+    uris: ['test.dart']
+    function: 'f'
+  title: ''
+  changes:
+    - kind: 'addParameter'
+      index: 0
+      name: 'a'
+      style: optional_positional
+      argumentValue:
+        expression: '{%xyz%}'
+''', [
+      error(TransformSetErrorCode.undefinedVariable, 253, 3),
+    ]);
+  }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/end_to_end_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/end_to_end_test.dart
index 0608b29..aad57fa 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/end_to_end_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/end_to_end_test.dart
@@ -35,8 +35,11 @@
       name: 'y'
       style: required_positional
       argumentValue:
-        kind: 'argument'
-        index: 0
+        expression: '{% y %}'
+        variables:
+          y:
+            kind: 'argument'
+            index: 0
 ''');
     await resolveTestUnit('''
 import '$importUri';
@@ -75,8 +78,11 @@
       index: 1
       name: 'T'
       argumentValue:
-        kind: 'argument'
-        index: 0
+        expression: '{% t %}'
+        variables:
+          t:
+            kind: 'argument'
+            index: 0
 ''');
     await resolveTestUnit('''
 import '$importUri';
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/modify_parameters_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/modify_parameters_test.dart
index 4dd1eb4..f0adabd 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/modify_parameters_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/modify_parameters_test.dart
@@ -7,7 +7,6 @@
 import 'package:analysis_server/src/services/correction/fix/data_driven/parameter_reference.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/rename.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/transform.dart';
-import 'package:analysis_server/src/services/correction/fix/data_driven/value_extractor.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'data_driven_test_support.dart';
@@ -57,7 +56,7 @@
 }
 ''');
     setPackageData(_modify(
-        ['C', 'm'], [AddParameter(0, 'a', false, true, LiteralExtractor('0'))],
+        ['C', 'm'], [AddParameter(0, 'a', false, true, codeTemplate('0'))],
         newName: 'm2'));
     await resolveTestUnit('''
 import '$importUri';
@@ -84,7 +83,7 @@
 }
 ''');
     setPackageData(_modify(
-        ['C', 'm'], [AddParameter(0, 'a', true, false, LiteralExtractor('0'))],
+        ['C', 'm'], [AddParameter(0, 'a', true, false, codeTemplate('0'))],
         newName: 'm2'));
     await resolveTestUnit('''
 import '$importUri';
@@ -111,7 +110,7 @@
 }
 ''');
     setPackageData(_modify(
-        ['C', 'm'], [AddParameter(0, 'a', true, true, LiteralExtractor('0'))],
+        ['C', 'm'], [AddParameter(0, 'a', true, true, codeTemplate('0'))],
         newName: 'm2'));
     await resolveTestUnit('''
 import '$importUri';
@@ -165,7 +164,7 @@
 }
 ''');
     setPackageData(_modify(
-        ['C', 'm'], [AddParameter(1, 'b', false, true, LiteralExtractor('1'))],
+        ['C', 'm'], [AddParameter(1, 'b', false, true, codeTemplate('1'))],
         newName: 'm2'));
     await resolveTestUnit('''
 import '$importUri';
@@ -192,7 +191,7 @@
 }
 ''');
     setPackageData(_modify(
-        ['C', 'm'], [AddParameter(1, 'b', true, false, LiteralExtractor('1'))],
+        ['C', 'm'], [AddParameter(1, 'b', true, false, codeTemplate('1'))],
         newName: 'm2'));
     await resolveTestUnit('''
 import '$importUri';
@@ -219,7 +218,7 @@
 }
 ''');
     setPackageData(_modify(
-        ['C', 'm'], [AddParameter(1, 'b', true, true, LiteralExtractor('1'))],
+        ['C', 'm'], [AddParameter(1, 'b', true, true, codeTemplate('1'))],
         newName: 'm2'));
     await resolveTestUnit('''
 import '$importUri';
@@ -249,9 +248,9 @@
       'C',
       'm'
     ], [
-      AddParameter(1, 'b', true, true, LiteralExtractor('1')),
-      AddParameter(2, 'c', true, true, LiteralExtractor('2')),
-      AddParameter(4, 'e', true, true, LiteralExtractor('4')),
+      AddParameter(1, 'b', true, true, codeTemplate('1')),
+      AddParameter(2, 'c', true, true, codeTemplate('2')),
+      AddParameter(4, 'e', true, true, codeTemplate('4')),
     ], newName: 'm2'));
     await resolveTestUnit('''
 import '$importUri';
@@ -276,7 +275,7 @@
 }
 ''');
     setPackageData(_modify(
-        ['C', 'm'], [AddParameter(0, 'a', true, true, LiteralExtractor('0'))],
+        ['C', 'm'], [AddParameter(0, 'a', true, true, codeTemplate('0'))],
         newName: 'm2'));
     await resolveTestUnit('''
 import '$importUri';
@@ -301,7 +300,7 @@
 }
 ''');
     setPackageData(_modify(
-        ['C', 'm'], [AddParameter(0, 'a', true, true, LiteralExtractor('0'))]));
+        ['C', 'm'], [AddParameter(0, 'a', true, true, codeTemplate('0'))]));
     await resolveTestUnit('''
 import '$importUri';
 
@@ -331,7 +330,7 @@
       'm'
     ], [
       RemoveParameter(PositionalParameterReference(0)),
-      AddParameter(2, 'c', true, true, LiteralExtractor('2'))
+      AddParameter(2, 'c', true, true, codeTemplate('2'))
     ], newName: 'm2'));
     await resolveTestUnit('''
 import '$importUri';
@@ -362,7 +361,7 @@
       'm'
     ], [
       RemoveParameter(PositionalParameterReference(1)),
-      AddParameter(0, 'a', true, true, LiteralExtractor('0'))
+      AddParameter(0, 'a', true, true, codeTemplate('0'))
     ], newName: 'm2'));
     await resolveTestUnit('''
 import '$importUri';
@@ -394,7 +393,7 @@
     ], [
       RemoveParameter(PositionalParameterReference(0)),
       RemoveParameter(PositionalParameterReference(1)),
-      AddParameter(0, 'c', true, true, LiteralExtractor('2')),
+      AddParameter(0, 'c', true, true, codeTemplate('2')),
     ], newName: 'm2'));
     await resolveTestUnit('''
 import '$importUri';
@@ -426,7 +425,7 @@
     ], [
       RemoveParameter(PositionalParameterReference(1)),
       RemoveParameter(PositionalParameterReference(2)),
-      AddParameter(1, 'd', true, true, LiteralExtractor('3')),
+      AddParameter(1, 'd', true, true, codeTemplate('3')),
     ], newName: 'm2'));
     await resolveTestUnit('''
 import '$importUri';
@@ -456,10 +455,10 @@
       'C',
       'm1'
     ], [
-      AddParameter(0, 'a', true, true, LiteralExtractor('0')),
+      AddParameter(0, 'a', true, true, codeTemplate('0')),
       RemoveParameter(PositionalParameterReference(1)),
       RemoveParameter(PositionalParameterReference(3)),
-      AddParameter(2, 'd', true, true, LiteralExtractor('3')),
+      AddParameter(2, 'd', true, true, codeTemplate('3')),
     ], newName: 'm2'));
     await resolveTestUnit('''
 import '$importUri';
@@ -827,7 +826,7 @@
     setPackageData(_modify([
       'f'
     ], [
-      AddParameter(0, 'a', true, true, LiteralExtractor('0')),
+      AddParameter(0, 'a', true, true, codeTemplate('0')),
     ], newName: 'g'));
     await resolveTestUnit('''
 import '$importUri';
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart
index 6b9b51a..bd85ebe 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart
@@ -5,6 +5,7 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'add_type_parameter_test.dart' as add_type_parameter_change;
+import 'code_template_test.dart' as code_template;
 import 'diagnostics/test_all.dart' as diagnostics;
 import 'end_to_end_test.dart' as end_to_end;
 import 'modify_parameters_test.dart' as modify_parameters;
@@ -15,6 +16,7 @@
 void main() {
   defineReflectiveSuite(() {
     add_type_parameter_change.main();
+    code_template.main();
     diagnostics.main();
     end_to_end.main();
     modify_parameters.main();
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_parser_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_parser_test.dart
index e944316..d366700 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_parser_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_parser_test.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analysis_server/src/services/correction/fix/data_driven/add_type_parameter.dart';
+import 'package:analysis_server/src/services/correction/fix/data_driven/code_template.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/modify_parameters.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/parameter_reference.dart';
 import 'package:analysis_server/src/services/correction/fix/data_driven/rename.dart';
@@ -37,8 +38,11 @@
       name: 'p'
       style: optional_named
       argumentValue:
-        kind: 'argument'
-        index: 1
+        expression: '{% p %}'
+        variables:
+          p:
+            kind: 'argument'
+            index: 1
 ''');
     var transforms = result.transformsFor('f', ['test.dart']);
     expect(transforms, hasLength(1));
@@ -53,7 +57,10 @@
     expect(modification.name, 'p');
     expect(modification.isRequired, false);
     expect(modification.isPositional, false);
-    var value = modification.argumentValue as ArgumentExtractor;
+    var components = modification.argumentValue.components;
+    expect(components, hasLength(1));
+    var value =
+        (components[0] as TemplateVariable).extractor as ArgumentExtractor;
     var parameter = value.parameter as PositionalParameterReference;
     expect(parameter.index, 1);
   }
@@ -103,8 +110,11 @@
       name: 'p'
       style: required_named
       argumentValue:
-        kind: 'argument'
-        index: 1
+        expression: '{% p %}'
+        variables:
+          p:
+            kind: 'argument'
+            index: 1
 ''');
     var transforms = result.transformsFor('f', ['test.dart']);
     expect(transforms, hasLength(1));
@@ -119,7 +129,10 @@
     expect(modification.name, 'p');
     expect(modification.isRequired, true);
     expect(modification.isPositional, false);
-    var value = modification.argumentValue as ArgumentExtractor;
+    var components = modification.argumentValue.components;
+    expect(components, hasLength(1));
+    var value =
+        (components[0] as TemplateVariable).extractor as ArgumentExtractor;
     var parameter = value.parameter as PositionalParameterReference;
     expect(parameter.index, 1);
   }
@@ -139,8 +152,11 @@
       name: 'p'
       style: required_positional
       argumentValue:
-        kind: 'argument'
-        index: 1
+        expression: '{% p %}'
+        variables:
+          p:
+            kind: 'argument'
+            index: 1
 ''');
     var transforms = result.transformsFor('f', ['test.dart']);
     expect(transforms, hasLength(1));
@@ -155,11 +171,65 @@
     expect(modification.name, 'p');
     expect(modification.isRequired, true);
     expect(modification.isPositional, true);
-    var value = modification.argumentValue as ArgumentExtractor;
+    var components = modification.argumentValue.components;
+    expect(components, hasLength(1));
+    var value =
+        (components[0] as TemplateVariable).extractor as ArgumentExtractor;
     var parameter = value.parameter as PositionalParameterReference;
     expect(parameter.index, 1);
   }
 
+  void test_addParameter_requiredPositional_complexTemplate() {
+    parse('''
+version: 1
+transforms:
+- title: 'Add'
+  date: 2020-09-09
+  element:
+    uris: ['test.dart']
+    function: 'f'
+  changes:
+    - kind: 'addParameter'
+      index: 0
+      name: 'p'
+      style: required_positional
+      argumentValue:
+        expression: '{% a %}({% b %})'
+        variables:
+          a:
+            kind: 'argument'
+            index: 1
+          b:
+            kind: 'argument'
+            index: 2
+''');
+    var transforms = result.transformsFor('f', ['test.dart']);
+    expect(transforms, hasLength(1));
+    var transform = transforms[0];
+    expect(transform.title, 'Add');
+    expect(transform.changes, hasLength(1));
+    var change = transform.changes[0] as ModifyParameters;
+    var modifications = change.modifications;
+    expect(modifications, hasLength(1));
+    var modification = modifications[0] as AddParameter;
+    expect(modification.index, 0);
+    expect(modification.name, 'p');
+    expect(modification.isRequired, true);
+    expect(modification.isPositional, true);
+    var components = modification.argumentValue.components;
+    expect(components, hasLength(4));
+    var extractorA =
+        (components[0] as TemplateVariable).extractor as ArgumentExtractor;
+    var parameterA = extractorA.parameter as PositionalParameterReference;
+    expect(parameterA.index, 1);
+    expect((components[1] as TemplateText).text, '(');
+    var extractorB =
+        (components[2] as TemplateVariable).extractor as ArgumentExtractor;
+    var parameterB = extractorB.parameter as PositionalParameterReference;
+    expect(parameterB.index, 2);
+    expect((components[3] as TemplateText).text, ')');
+  }
+
   void test_addTypeParameter_fromNamedArgument() {
     parse('''
 version: 1
@@ -175,8 +245,11 @@
       index: 0
       name: 'T'
       argumentValue:
-        kind: 'argument'
-        name: 'p'
+        expression: '{% t %}'
+        variables:
+          t:
+            kind: 'argument'
+            name: 'p'
 ''');
     var transforms = result.transformsFor('A', ['test.dart']);
     expect(transforms, hasLength(1));
@@ -186,7 +259,10 @@
     var change = transform.changes[0] as AddTypeParameter;
     expect(change.index, 0);
     expect(change.name, 'T');
-    var value = change.argumentValue as ArgumentExtractor;
+    var components = change.argumentValue.components;
+    expect(components, hasLength(1));
+    var value =
+        (components[0] as TemplateVariable).extractor as ArgumentExtractor;
     var parameter = value.parameter as NamedParameterReference;
     expect(parameter.name, 'p');
   }
@@ -206,8 +282,11 @@
       index: 0
       name: 'T'
       argumentValue:
-        kind: 'argument'
-        index: 2
+        expression: '{% t %}'
+        variables:
+          t:
+            kind: 'argument'
+            index: 2
 ''');
     var transforms = result.transformsFor('A', ['test.dart']);
     expect(transforms, hasLength(1));
@@ -217,7 +296,10 @@
     var change = transform.changes[0] as AddTypeParameter;
     expect(change.index, 0);
     expect(change.name, 'T');
-    var value = change.argumentValue as ArgumentExtractor;
+    var components = change.argumentValue.components;
+    expect(components, hasLength(1));
+    var value =
+        (components[0] as TemplateVariable).extractor as ArgumentExtractor;
     var parameter = value.parameter as PositionalParameterReference;
     expect(parameter.index, 2);
   }
diff --git a/pkg/analyzer/lib/src/dart/element/least_upper_bound.dart b/pkg/analyzer/lib/src/dart/element/least_upper_bound.dart
index c713b70..27eceab 100644
--- a/pkg/analyzer/lib/src/dart/element/least_upper_bound.dart
+++ b/pkg/analyzer/lib/src/dart/element/least_upper_bound.dart
@@ -439,8 +439,6 @@
     );
   }
 
-  InterfaceType get _objectType => _typeSystem.typeProvider.objectType;
-
   /// Compute the least upper bound of two types.
   ///
   /// https://github.com/dart-lang/language
@@ -808,9 +806,16 @@
     return _typeSystem.getGreatestLowerBound(a.type, b.type);
   }
 
+  /// TODO(scheglov) Use greatest closure.
+  /// See https://github.com/dart-lang/language/pull/1195
   DartType _typeParameterResolveToObjectBounds(DartType type) {
     var element = type.element;
-    type = type.resolveToBound(_objectType);
-    return Substitution.fromMap({element: _objectType}).substituteType(type);
+
+    var objectType = _typeSystem.isNonNullableByDefault
+        ? _typeSystem.objectQuestion
+        : _typeSystem.objectStar;
+
+    type = type.resolveToBound(objectType);
+    return Substitution.fromMap({element: objectType}).substituteType(type);
   }
 }
diff --git a/pkg/analyzer/test/src/dart/element/upper_lower_bound_test.dart b/pkg/analyzer/test/src/dart/element/upper_lower_bound_test.dart
index 6454cda..cda9e13 100644
--- a/pkg/analyzer/test/src/dart/element/upper_lower_bound_test.dart
+++ b/pkg/analyzer/test/src/dart/element/upper_lower_bound_test.dart
@@ -2943,7 +2943,7 @@
     _checkLeastUpperBound(
       S_none,
       typeParameterTypeNone(U),
-      interfaceTypeNone(A, typeArguments: [objectNone]),
+      interfaceTypeNone(A, typeArguments: [objectQuestion]),
     );
   }
 
@@ -2983,14 +2983,22 @@
     _checkLeastUpperBound(typeT, C_none, A_none);
   }
 
-  void test_typeParameter_interface_noBound() {
+  void test_typeParameter_interface_bounded_objectQuestion() {
     var T = typeParameter('T', bound: objectQuestion);
 
-    var A = class_(name: 'A');
-
     _checkLeastUpperBound(
       typeParameterTypeNone(T),
-      interfaceTypeNone(A),
+      intNone,
+      objectQuestion,
+    );
+  }
+
+  void test_typeParameter_interface_noBound() {
+    var T = typeParameter('T');
+
+    _checkLeastUpperBound(
+      typeParameterTypeNone(T),
+      intNone,
       objectQuestion,
     );
   }
diff --git a/tools/VERSION b/tools/VERSION
index 640a299..e4f21fd 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 10
 PATCH 0
-PRERELEASE 149
+PRERELEASE 150
 PRERELEASE_PATCH 0
\ No newline at end of file