| // 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. |
| |
| import 'dart:math' as math; |
| |
| import 'package:analysis_server/src/services/correction/util.dart'; |
| import 'package:analysis_server/src/utilities/flutter.dart'; |
| import 'package:analyzer/dart/analysis/results.dart'; |
| import 'package:analyzer/dart/analysis/session.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/diagnostic/diagnostic.dart'; |
| import 'package:analyzer/source/source_range.dart'; |
| import 'package:analyzer/src/dart/analysis/session_helper.dart'; |
| import 'package:analyzer/src/dart/ast/utilities.dart'; |
| import 'package:analyzer/src/generated/resolver.dart'; |
| import 'package:analyzer_plugin/utilities/assist/assist.dart'; |
| import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart'; |
| import 'package:analyzer_plugin/utilities/change_builder/change_workspace.dart'; |
| import 'package:analyzer_plugin/utilities/fixes/fixes.dart'; |
| import 'package:meta/meta.dart'; |
| |
| abstract class CorrectionProducer { |
| CorrectionProducerContext _context; |
| |
| /// The most deeply nested node that completely covers the highlight region of |
| /// the diagnostic, or `null` if there is no diagnostic, such a node does not |
| /// exist, or if it hasn't been computed yet. Use [coveredNode] to access this |
| /// field. |
| AstNode _coveredNode; |
| |
| /// Return the arguments that should be used when composing the message for an |
| /// assist, or `null` if the assist message has no parameters or if this |
| /// producer doesn't support assists. |
| List<Object> get assistArguments => null; |
| |
| /// Return the assist kind that should be used to build an assist, or `null` |
| /// if this producer doesn't support assists. |
| AssistKind get assistKind => null; |
| |
| /// The most deeply nested node that completely covers the highlight region of |
| /// the diagnostic, or `null` if there is no diagnostic or if such a node does |
| /// not exist. |
| AstNode get coveredNode { |
| // TODO(brianwilkerson) Consider renaming this to `coveringNode`. |
| if (_coveredNode == null) { |
| var diagnostic = this.diagnostic; |
| if (diagnostic == null) { |
| return null; |
| } |
| var errorOffset = diagnostic.problemMessage.offset; |
| var errorLength = diagnostic.problemMessage.length; |
| _coveredNode = |
| NodeLocator2(errorOffset, math.max(errorOffset + errorLength - 1, 0)) |
| .searchWithin(unit); |
| } |
| return _coveredNode; |
| } |
| |
| /// Return the diagnostic being fixed, or `null` if this producer is being |
| /// used to produce an assist. |
| Diagnostic get diagnostic => _context.diagnostic; |
| |
| /// Returns the EOL to use for this [CompilationUnit]. |
| String get eol => utils.endOfLine; |
| |
| String get file => _context.file; |
| |
| /// Return the arguments that should be used when composing the message for a |
| /// fix, or `null` if the fix message has no parameters or if this producer |
| /// doesn't support fixes. |
| List<Object> get fixArguments => null; |
| |
| /// Return the fix kind that should be used to build a fix, or `null` if this |
| /// producer doesn't support fixes. |
| FixKind get fixKind => null; |
| |
| Flutter get flutter => _context.flutter; |
| |
| AstNode get node => _context.node; |
| |
| ResolvedUnitResult get resolvedResult => _context.resolvedResult; |
| |
| int get selectionLength => _context.selectionLength; |
| |
| int get selectionOffset => _context.selectionOffset; |
| |
| TypeProvider get typeProvider => _context.typeProvider; |
| |
| CompilationUnit get unit => _context.unit; |
| |
| CorrectionUtils get utils => _context.utils; |
| |
| Future<void> compute(DartChangeBuilder builder); |
| |
| void configure(CorrectionProducerContext context) { |
| _context = context; |
| } |
| |
| /// Return the function body of the most deeply nested method or function that |
| /// encloses the [node], or `null` if the node is not in a method or function. |
| FunctionBody getEnclosingFunctionBody() { |
| var closure = node.thisOrAncestorOfType<FunctionExpression>(); |
| if (closure != null) { |
| return closure.body; |
| } |
| var function = node.thisOrAncestorOfType<FunctionDeclaration>(); |
| if (function != null) { |
| return function.functionExpression.body; |
| } |
| var constructor = node.thisOrAncestorOfType<ConstructorDeclaration>(); |
| if (constructor != null) { |
| return constructor.body; |
| } |
| var method = node.thisOrAncestorOfType<MethodDeclaration>(); |
| if (method != null) { |
| return method.body; |
| } |
| return null; |
| } |
| |
| /// Return the text of the given [range] in the unit. |
| String getRangeText(SourceRange range) { |
| return utils.getRangeText(range); |
| } |
| |
| /// Return `true` the lint with the given [name] is enabled. |
| bool isLintEnabled(String name) { |
| return _context.isLintEnabled(name); |
| } |
| |
| /// Return `true` if the selection covers an operator of the given |
| /// [binaryExpression]. |
| bool isOperatorSelected(BinaryExpression binaryExpression) { |
| AstNode left = binaryExpression.leftOperand; |
| AstNode right = binaryExpression.rightOperand; |
| // between the nodes |
| if (selectionOffset >= left.end && |
| selectionOffset + selectionLength <= right.offset) { |
| return true; |
| } |
| // or exactly select the node (but not with infix expressions) |
| if (selectionOffset == left.offset && |
| selectionOffset + selectionLength == right.end) { |
| if (left is BinaryExpression || right is BinaryExpression) { |
| return false; |
| } |
| return true; |
| } |
| // invalid selection (part of node, etc) |
| return false; |
| } |
| } |
| |
| class CorrectionProducerContext { |
| final int selectionOffset; |
| final int selectionLength; |
| final int selectionEnd; |
| |
| final CompilationUnit unit; |
| final CorrectionUtils utils; |
| final String file; |
| |
| final TypeProvider typeProvider; |
| final Flutter flutter; |
| |
| final AnalysisSession session; |
| final AnalysisSessionHelper sessionHelper; |
| final ResolvedUnitResult resolvedResult; |
| final ChangeWorkspace workspace; |
| |
| final Diagnostic diagnostic; |
| |
| AstNode _node; |
| |
| CorrectionProducerContext({ |
| @required this.resolvedResult, |
| @required this.workspace, |
| this.diagnostic, |
| this.selectionOffset = -1, |
| this.selectionLength = 0, |
| }) : file = resolvedResult.path, |
| flutter = Flutter.of(resolvedResult), |
| session = resolvedResult.session, |
| sessionHelper = AnalysisSessionHelper(resolvedResult.session), |
| typeProvider = resolvedResult.typeProvider, |
| selectionEnd = (selectionOffset ?? 0) + (selectionLength ?? 0), |
| unit = resolvedResult.unit, |
| utils = CorrectionUtils(resolvedResult); |
| |
| AstNode get node => _node; |
| |
| /// Return `true` the lint with the given [name] is enabled. |
| bool isLintEnabled(String name) { |
| var analysisOptions = session.analysisContext.analysisOptions; |
| return analysisOptions.isLintEnabled(name); |
| } |
| |
| bool setupCompute() { |
| final locator = NodeLocator(selectionOffset, selectionEnd); |
| _node = locator.searchWithin(resolvedResult.unit); |
| return _node != null; |
| } |
| } |