| // 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/expression.dart' |
| as data_driven; |
| import 'package:analysis_server/src/services/correction/fix/data_driven/value_generator.dart'; |
| import 'package:analysis_server/src/services/correction/util.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.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; |
| |
| /// The expression used to determine whether the template is required to be |
| /// used, or `null` if the template is not required to be used. |
| final data_driven.Expression requiredIfCondition; |
| |
| /// Initialize a newly generated code template with the given [kind] and |
| /// [components]. |
| CodeTemplate(this.kind, this.components, this.requiredIfCondition) |
| : assert(kind != null), |
| assert(components != null); |
| |
| /// Use the [context] to validate that this template will be able to generate |
| /// a value. |
| bool validate(TemplateContext context) { |
| for (var component in components) { |
| if (!component.validate(context)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| void writeOn(DartEditBuilder builder, TemplateContext context) { |
| for (var component in components) { |
| component.writeOn(builder, context); |
| } |
| } |
| } |
| |
| /// The kinds of code that can be generated by a template. |
| enum CodeTemplateKind { |
| // We currently only support expressions, but we will likely need to support |
| // statements at some point. |
| expression, |
| } |
| |
| /// An object used to compute some portion of a template. |
| abstract class TemplateComponent { |
| /// Use the [context] to validate that this component will be able to generate |
| /// a value. |
| bool validate(TemplateContext context); |
| |
| /// Write the text contributed by this component to the given [builder], using |
| /// the [context] to access needed information that isn't already known to |
| /// this component. |
| void writeOn(DartEditBuilder builder, TemplateContext context); |
| } |
| |
| /// 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; |
| |
| /// Initialize a newly created template context with the [node] and [utils]. |
| TemplateContext(this.node, this.utils); |
| |
| /// Initialize a newly created template context that uses the invocation |
| /// containing the [node] and the [utils]. |
| factory TemplateContext.forInvocation(AstNode node, CorrectionUtils utils) => |
| TemplateContext(_getInvocation(node), utils); |
| |
| /// Return the invocation containing the given [node]. The invocation will be |
| /// either an instance creation expression, function invocation, method |
| /// invocation, or an extension override. |
| static AstNode _getInvocation(AstNode node) { |
| if (node is ArgumentList) { |
| return node.parent; |
| } else if (node is InstanceCreationExpression || |
| node is InvocationExpression) { |
| return node; |
| } else if (node is SimpleIdentifier) { |
| var parent = node.parent; |
| if (parent is ConstructorName) { |
| var grandparent = parent.parent; |
| if (grandparent is InstanceCreationExpression) { |
| return grandparent; |
| } |
| } else if (parent is Label && parent.parent is NamedExpression) { |
| return parent.parent.parent.parent; |
| } else if (parent is MethodInvocation && parent.methodName == node) { |
| return parent; |
| } |
| } else if (node is TypeArgumentList) { |
| var parent = node.parent; |
| if (parent is InvocationExpression) { |
| return parent; |
| } else if (parent is ExtensionOverride) { |
| return parent; |
| } |
| } |
| return null; |
| } |
| } |
| |
| /// 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 |
| bool validate(TemplateContext context) { |
| return true; |
| } |
| |
| @override |
| void writeOn(DartEditBuilder builder, TemplateContext context) { |
| builder.write(text); |
| } |
| } |
| |
| /// A reference to a variable within a template. |
| class TemplateVariable extends TemplateComponent { |
| /// The generator used to compute the value of the variable. |
| final ValueGenerator generator; |
| |
| /// Initialize a newly created template variable with the given [generator]. |
| TemplateVariable(this.generator); |
| |
| @override |
| bool validate(TemplateContext context) { |
| return generator.validate(context); |
| } |
| |
| @override |
| void writeOn(DartEditBuilder builder, TemplateContext context) { |
| generator.writeOn(builder, context); |
| } |
| } |