| // Copyright (c) 2014, 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/protocol_server.dart' hide Element; |
| import 'package:analysis_server/src/services/correction/status.dart'; |
| import 'package:analysis_server/src/services/correction/util.dart'; |
| import 'package:analysis_server/src/services/refactoring/refactoring.dart'; |
| import 'package:analysis_server/src/services/refactoring/refactoring_internal.dart'; |
| import 'package:analysis_server/src/services/refactoring/visible_ranges_computer.dart'; |
| import 'package:analysis_server/src/services/search/hierarchy.dart'; |
| import 'package:analysis_server/src/services/search/search_engine.dart'; |
| import 'package:analysis_server/src/utilities/strings.dart'; |
| import 'package:analyzer/dart/analysis/results.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/precedence.dart'; |
| import 'package:analyzer/dart/ast/visitor.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/src/dart/analysis/session_helper.dart'; |
| import 'package:analyzer/src/dart/ast/extensions.dart'; |
| import 'package:analyzer/src/dart/ast/utilities.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:analyzer_plugin/utilities/range_factory.dart'; |
| |
| /// Returns the [SourceRange] to find conflicting locals in. |
| SourceRange _getLocalsConflictingRange(AstNode node) { |
| // maybe Block |
| var block = node.thisOrAncestorOfType<Block>(); |
| if (block != null) { |
| return range.startEnd(node, block); |
| } |
| // maybe whole executable |
| var executableNode = getEnclosingExecutableNode(node); |
| if (executableNode != null) { |
| return range.node(executableNode); |
| } |
| // not a part of a declaration with locals |
| return SourceRange.EMPTY; |
| } |
| |
| /// Returns the source which should replace given invocation with given |
| /// arguments. |
| String _getMethodSourceForInvocation( |
| RefactoringStatus status, |
| _SourcePart part, |
| CorrectionUtils utils, |
| AstNode contextNode, |
| Expression? targetExpression, |
| List<Expression> arguments) { |
| // prepare edits to replace parameters with arguments |
| var edits = <SourceEdit>[]; |
| part._parameters.forEach( |
| (ParameterElement parameter, List<_ParameterOccurrence> occurrences) { |
| // prepare argument |
| Expression? argument; |
| for (var arg in arguments) { |
| if (arg.staticParameterElement == parameter) { |
| argument = arg; |
| break; |
| } |
| } |
| if (argument is NamedExpression) { |
| argument = argument.expression; |
| } |
| // prepare argument properties |
| Precedence argumentPrecedence; |
| String? argumentSource; |
| if (argument != null) { |
| argumentPrecedence = getExpressionPrecedence(argument); |
| argumentSource = utils.getNodeText(argument); |
| } else { |
| // report about a missing required parameter |
| if (parameter.isRequiredPositional) { |
| status.addError('No argument for the parameter "${parameter.name}".', |
| newLocation_fromNode(contextNode)); |
| return; |
| } |
| // an optional parameter |
| argumentPrecedence = Precedence.none; |
| argumentSource = parameter.defaultValueCode; |
| argumentSource ??= 'null'; |
| } |
| // replace all occurrences of this parameter |
| for (var occurrence in occurrences) { |
| var range = occurrence.range; |
| // prepare argument source to apply at this occurrence |
| String occurrenceArgumentSource; |
| if (argumentPrecedence < occurrence.parentPrecedence) { |
| occurrenceArgumentSource = '($argumentSource)'; |
| } else { |
| occurrenceArgumentSource = argumentSource; |
| } |
| // do replace |
| edits.add(newSourceEdit_range(range, occurrenceArgumentSource)); |
| } |
| }); |
| // replace static field "qualifier" with invocation target |
| part._implicitClassNameOffsets.forEach((String className, List<int> offsets) { |
| for (var offset in offsets) { |
| // edits.add(newSourceEdit_range(range, className + '.')); |
| edits.add(SourceEdit(offset, 0, className + '.')); |
| } |
| }); |
| // replace "this" references with invocation target |
| if (targetExpression != null) { |
| var targetSource = utils.getNodeText(targetExpression); |
| // explicit "this" references |
| for (var offset in part._explicitThisOffsets) { |
| edits.add(SourceEdit(offset, 4, targetSource)); |
| } |
| // implicit "this" references |
| targetSource += '.'; |
| for (var offset in part._implicitThisOffsets) { |
| edits.add(SourceEdit(offset, 0, targetSource)); |
| } |
| } |
| // prepare edits to replace conflicting variables |
| var conflictingNames = _getNamesConflictingAt(contextNode); |
| part._variables.forEach((VariableElement variable, List<SourceRange> ranges) { |
| var originalName = variable.displayName; |
| // prepare unique name |
| String uniqueName; |
| { |
| uniqueName = originalName; |
| var uniqueIndex = 2; |
| while (conflictingNames.contains(uniqueName)) { |
| uniqueName = originalName + uniqueIndex.toString(); |
| uniqueIndex++; |
| } |
| } |
| // update references, if name was change |
| if (uniqueName != originalName) { |
| for (var range in ranges) { |
| edits.add(newSourceEdit_range(range, uniqueName)); |
| } |
| } |
| }); |
| // prepare source with applied arguments |
| edits.sort((SourceEdit a, SourceEdit b) => b.offset - a.offset); |
| return SourceEdit.applySequence(part._source, edits); |
| } |
| |
| /// Returns the names which will shadow or will be shadowed by any declaration |
| /// at [node]. |
| Set<String> _getNamesConflictingAt(AstNode node) { |
| var result = <String>{}; |
| // local variables and functions |
| { |
| var localsRange = _getLocalsConflictingRange(node); |
| var enclosingExecutable = getEnclosingExecutableNode(node); |
| if (enclosingExecutable != null) { |
| var visibleRangeMap = VisibleRangesComputer.forNode(enclosingExecutable); |
| visibleRangeMap.forEach((element, elementRange) { |
| if (elementRange.intersects(localsRange)) { |
| result.add(element.displayName); |
| } |
| }); |
| } |
| } |
| // fields |
| { |
| var enclosingClassElement = getEnclosingClassElement(node); |
| if (enclosingClassElement != null) { |
| var elements = <ClassElement>{}; |
| elements.add(enclosingClassElement); |
| elements.addAll(getSuperClasses(enclosingClassElement)); |
| for (var classElement in elements) { |
| var classMembers = getChildren(classElement); |
| for (var classMemberElement in classMembers) { |
| result.add(classMemberElement.displayName); |
| } |
| } |
| } |
| } |
| // done |
| return result; |
| } |
| |
| /// [InlineMethodRefactoring] implementation. |
| class InlineMethodRefactoringImpl extends RefactoringImpl |
| implements InlineMethodRefactoring { |
| final SearchEngine searchEngine; |
| final ResolvedUnitResult resolveResult; |
| final int offset; |
| final AnalysisSessionHelper sessionHelper; |
| late CorrectionUtils utils; |
| late SourceChange change; |
| |
| @override |
| bool isDeclaration = false; |
| bool deleteSource = false; |
| bool inlineAll = true; |
| |
| ExecutableElement? _methodElement; |
| late CompilationUnit _methodUnit; |
| late CorrectionUtils _methodUtils; |
| late AstNode _methodNode; |
| FormalParameterList? _methodParameters; |
| FunctionBody? _methodBody; |
| Expression? _methodExpression; |
| _SourcePart? _methodExpressionPart; |
| _SourcePart? _methodStatementsPart; |
| final List<_ReferenceProcessor> _referenceProcessors = []; |
| final Set<Element> _alreadyMadeAsync = <Element>{}; |
| |
| InlineMethodRefactoringImpl( |
| this.searchEngine, this.resolveResult, this.offset) |
| : sessionHelper = AnalysisSessionHelper(resolveResult.session) { |
| utils = CorrectionUtils(resolveResult); |
| } |
| |
| @override |
| String? get className { |
| var classElement = _methodElement?.enclosingElement; |
| if (classElement is ClassElement) { |
| return classElement.displayName; |
| } |
| return null; |
| } |
| |
| @override |
| String? get methodName { |
| return _methodElement?.displayName; |
| } |
| |
| @override |
| String get refactoringName { |
| if (_methodElement is MethodElement) { |
| return 'Inline Method'; |
| } else { |
| return 'Inline Function'; |
| } |
| } |
| |
| @override |
| Future<RefactoringStatus> checkFinalConditions() { |
| change = SourceChange(refactoringName); |
| var result = RefactoringStatus(); |
| // check for compatibility of "deleteSource" and "inlineAll" |
| if (deleteSource && !inlineAll) { |
| result.addError('All references must be inlined to remove the source.'); |
| } |
| // prepare changes |
| for (var processor in _referenceProcessors) { |
| processor._process(result); |
| } |
| // delete method |
| if (deleteSource && inlineAll) { |
| var methodRange = range.node(_methodNode); |
| var linesRange = |
| _methodUtils.getLinesRange(methodRange, skipLeadingEmptyLines: true); |
| doSourceChange_addElementEdit( |
| change, _methodElement!, newSourceEdit_range(linesRange, '')); |
| } |
| // done |
| return Future.value(result); |
| } |
| |
| @override |
| Future<RefactoringStatus> checkInitialConditions() async { |
| var result = RefactoringStatus(); |
| // prepare method information |
| result.addStatus(await _prepareMethod()); |
| if (result.hasFatalError) { |
| return Future<RefactoringStatus>.value(result); |
| } |
| // maybe operator |
| if (_methodElement!.isOperator) { |
| result = RefactoringStatus.fatal('Cannot inline operator.'); |
| return Future<RefactoringStatus>.value(result); |
| } |
| // maybe [a]sync* |
| if (_methodElement!.isGenerator) { |
| result = RefactoringStatus.fatal('Cannot inline a generator.'); |
| return Future<RefactoringStatus>.value(result); |
| } |
| // analyze method body |
| result.addStatus(_prepareMethodParts()); |
| // process references |
| var references = await searchEngine.searchReferences(_methodElement!); |
| _referenceProcessors.clear(); |
| for (var reference in references) { |
| var processor = _ReferenceProcessor(this, reference); |
| await processor.init(); |
| _referenceProcessors.add(processor); |
| } |
| return result; |
| } |
| |
| @override |
| Future<SourceChange> createChange() { |
| return Future.value(change); |
| } |
| |
| @override |
| bool isAvailable() { |
| return !_checkOffset().hasFatalError; |
| } |
| |
| /// Checks if [offset] is a method that can be inlined. |
| RefactoringStatus _checkOffset() { |
| var fatalStatus = RefactoringStatus.fatal( |
| 'Method declaration or reference must be selected to activate this refactoring.'); |
| |
| var identifier = NodeLocator(offset).searchWithin(resolveResult.unit); |
| if (identifier is! SimpleIdentifier) { |
| return fatalStatus; |
| } |
| var element = identifier.writeOrReadElement; |
| if (element is! ExecutableElement) { |
| return fatalStatus; |
| } |
| if (element.isSynthetic) { |
| return fatalStatus; |
| } |
| // maybe operator |
| if (element.isOperator) { |
| return RefactoringStatus.fatal('Cannot inline operator.'); |
| } |
| // maybe [a]sync* |
| if (element.isGenerator) { |
| return RefactoringStatus.fatal('Cannot inline a generator.'); |
| } |
| |
| return RefactoringStatus(); |
| } |
| |
| _SourcePart _createSourcePart(SourceRange range) { |
| var source = _methodUtils.getRangeText(range); |
| var prefix = getLinePrefix(source); |
| var result = _SourcePart(range.offset, source, prefix); |
| // remember parameters and variables occurrences |
| _methodUnit.accept(_VariablesVisitor(_methodElement!, range, result)); |
| // done |
| return result; |
| } |
| |
| /// Initializes [_methodElement] and related fields. |
| Future<RefactoringStatus> _prepareMethod() async { |
| _methodElement = null; |
| _methodParameters = null; |
| _methodBody = null; |
| deleteSource = false; |
| inlineAll = false; |
| // prepare for failure |
| var fatalStatus = RefactoringStatus.fatal( |
| 'Method declaration or reference must be selected to activate this refactoring.'); |
| // prepare selected SimpleIdentifier |
| var identifier = NodeLocator(offset).searchWithin(resolveResult.unit); |
| if (identifier is! SimpleIdentifier) { |
| return fatalStatus; |
| } |
| // prepare selected ExecutableElement |
| var element = identifier.writeOrReadElement; |
| if (element is! ExecutableElement) { |
| return fatalStatus; |
| } |
| if (element.isSynthetic) { |
| return fatalStatus; |
| } |
| _methodElement = element; |
| |
| var declaration = await sessionHelper.getElementDeclaration(element); |
| var methodNode = declaration!.node; |
| _methodNode = methodNode; |
| |
| var resolvedUnit = declaration.resolvedUnit!; |
| _methodUnit = resolvedUnit.unit; |
| _methodUtils = CorrectionUtils(resolvedUnit); |
| |
| if (methodNode is MethodDeclaration) { |
| _methodParameters = methodNode.parameters; |
| _methodBody = methodNode.body; |
| } else if (methodNode is FunctionDeclaration) { |
| _methodParameters = methodNode.functionExpression.parameters; |
| _methodBody = methodNode.functionExpression.body; |
| } else { |
| return fatalStatus; |
| } |
| |
| isDeclaration = resolveResult.uri == element.source.uri && |
| identifier.offset == element.nameOffset; |
| deleteSource = isDeclaration; |
| inlineAll = deleteSource; |
| return RefactoringStatus(); |
| } |
| |
| /// Analyze [_methodBody] to fill [_methodExpressionPart] and |
| /// [_methodStatementsPart]. |
| RefactoringStatus _prepareMethodParts() { |
| var result = RefactoringStatus(); |
| if (_methodBody is ExpressionFunctionBody) { |
| var body = _methodBody as ExpressionFunctionBody; |
| _methodExpression = body.expression; |
| var methodExpressionRange = range.node(_methodExpression!); |
| _methodExpressionPart = _createSourcePart(methodExpressionRange); |
| } else if (_methodBody is BlockFunctionBody) { |
| var body = (_methodBody as BlockFunctionBody).block; |
| List<Statement> statements = body.statements; |
| if (statements.isNotEmpty) { |
| var lastStatement = statements[statements.length - 1]; |
| // "return" statement requires special handling |
| if (lastStatement is ReturnStatement) { |
| _methodExpression = lastStatement.expression; |
| if (_methodExpression != null) { |
| var methodExpressionRange = range.node(_methodExpression!); |
| _methodExpressionPart = _createSourcePart(methodExpressionRange); |
| } |
| // exclude "return" statement from statements |
| statements = statements.sublist(0, statements.length - 1); |
| } |
| // if there are statements, process them |
| if (statements.isNotEmpty) { |
| var statementsRange = |
| _methodUtils.getLinesRangeStatements(statements); |
| _methodStatementsPart = _createSourcePart(statementsRange); |
| } |
| } |
| // check if more than one return |
| body.accept(_ReturnsValidatorVisitor(result)); |
| } else { |
| return RefactoringStatus.fatal('Cannot inline method without body.'); |
| } |
| return result; |
| } |
| } |
| |
| class _ParameterOccurrence { |
| final Precedence parentPrecedence; |
| final SourceRange range; |
| |
| _ParameterOccurrence(this.parentPrecedence, this.range); |
| } |
| |
| /// Processor for single [SearchMatch] reference to [methodElement]. |
| class _ReferenceProcessor { |
| final InlineMethodRefactoringImpl ref; |
| final SearchMatch reference; |
| |
| late Element refElement; |
| late CorrectionUtils _refUtils; |
| late SimpleIdentifier _node; |
| SourceRange? _refLineRange; |
| late String _refPrefix; |
| |
| _ReferenceProcessor(this.ref, this.reference); |
| |
| Future<void> init() async { |
| refElement = reference.element; |
| |
| // prepare CorrectionUtils |
| var result = await ref.sessionHelper.getResolvedUnitByElement(refElement); |
| _refUtils = CorrectionUtils(result!); |
| |
| // prepare node and environment |
| _node = |
| _refUtils.findNode(reference.sourceRange.offset) as SimpleIdentifier; |
| var refStatement = _node.thisOrAncestorOfType<Statement>(); |
| if (refStatement != null) { |
| _refLineRange = _refUtils.getLinesRangeStatements([refStatement]); |
| _refPrefix = _refUtils.getNodePrefix(refStatement); |
| } else { |
| _refLineRange = null; |
| _refPrefix = _refUtils.getLinePrefix(_node.offset); |
| } |
| } |
| |
| void _addRefEdit(SourceEdit edit) { |
| doSourceChange_addElementEdit(ref.change, refElement, edit); |
| } |
| |
| bool _canInlineBody(AstNode usage) { |
| // no statements, usually just expression |
| if (ref._methodStatementsPart == null) { |
| // empty method, inline as closure |
| if (ref._methodExpressionPart == null) { |
| return false; |
| } |
| // OK, just expression |
| return true; |
| } |
| // analyze point of invocation |
| var parent = usage.parent; |
| var parent2 = parent?.parent; |
| // OK, if statement in block |
| if (parent is Statement) { |
| return parent2 is Block; |
| } |
| // maybe assignment, in block |
| if (parent is AssignmentExpression) { |
| var assignment = parent; |
| // inlining setter |
| if (assignment.leftHandSide == usage) { |
| return parent2 is Statement && parent2.parent is Block; |
| } |
| // inlining initializer |
| return ref._methodExpressionPart != null; |
| } |
| // maybe value for variable initializer, in block |
| if (ref._methodExpressionPart != null) { |
| if (parent is VariableDeclaration) { |
| if (parent2 is VariableDeclarationList) { |
| var parent3 = parent2.parent; |
| return parent3 is VariableDeclarationStatement && |
| parent3.parent is Block; |
| } |
| } |
| } |
| // not in block, cannot inline body |
| return false; |
| } |
| |
| void _inlineMethodInvocation(RefactoringStatus status, Expression usage, |
| bool cascaded, Expression? target, List<Expression> arguments) { |
| // we don't support cascade |
| if (cascaded) { |
| status.addError( |
| 'Cannot inline cascade invocation.', newLocation_fromNode(usage)); |
| } |
| // can we inline method body into "methodUsage" block? |
| if (_canInlineBody(usage)) { |
| // insert non-return statements |
| if (ref._methodStatementsPart != null) { |
| // prepare statements source for invocation |
| var source = _getMethodSourceForInvocation(status, |
| ref._methodStatementsPart!, _refUtils, usage, target, arguments); |
| source = _refUtils.replaceSourceIndent( |
| source, ref._methodStatementsPart!._prefix, _refPrefix); |
| // do insert |
| var edit = |
| newSourceEdit_range(SourceRange(_refLineRange!.offset, 0), source); |
| _addRefEdit(edit); |
| } |
| // replace invocation with return expression |
| if (ref._methodExpressionPart != null) { |
| // prepare expression source for invocation |
| var source = _getMethodSourceForInvocation(status, |
| ref._methodExpressionPart!, _refUtils, usage, target, arguments); |
| if (getExpressionPrecedence(ref._methodExpression!) < |
| getExpressionParentPrecedence(usage)) { |
| source = '($source)'; |
| } |
| // do replace |
| var methodUsageRange = range.node(usage); |
| var edit = newSourceEdit_range(methodUsageRange, source); |
| _addRefEdit(edit); |
| } else { |
| var edit = newSourceEdit_range(_refLineRange!, ''); |
| _addRefEdit(edit); |
| } |
| return; |
| } |
| // inline as closure invocation |
| String source; |
| { |
| source = ref._methodUtils.getRangeText(range.startEnd( |
| ref._methodParameters!.leftParenthesis, ref._methodNode)); |
| var methodPrefix = ref._methodUtils.getLinePrefix(ref._methodNode.offset); |
| source = _refUtils.replaceSourceIndent(source, methodPrefix, _refPrefix); |
| source = source.trim(); |
| } |
| // do insert |
| var edit = newSourceEdit_range(range.node(_node), source); |
| _addRefEdit(edit); |
| } |
| |
| void _process(RefactoringStatus status) { |
| var nodeParent = _node.parent; |
| // may be only single place should be inlined |
| if (!_shouldProcess()) { |
| return; |
| } |
| // If the element being inlined is async, ensure that the function |
| // body that encloses the method is also async. |
| if (ref._methodElement!.isAsynchronous) { |
| var body = _node.thisOrAncestorOfType<FunctionBody>(); |
| if (body != null) { |
| if (body.isSynchronous) { |
| if (body.isGenerator) { |
| status.addFatalError( |
| 'Cannot inline async into sync*.', newLocation_fromNode(_node)); |
| return; |
| } |
| if (refElement is ExecutableElement) { |
| var executable = refElement as ExecutableElement; |
| if (!executable.returnType.isDartAsyncFuture) { |
| status.addFatalError( |
| 'Cannot inline async into a function that does not return a Future.', |
| newLocation_fromNode(_node)); |
| return; |
| } |
| } |
| if (ref._alreadyMadeAsync.add(refElement)) { |
| var bodyStart = range.startLength(body, 0); |
| _addRefEdit(newSourceEdit_range(bodyStart, 'async ')); |
| } |
| } |
| } |
| } |
| // may be invocation of inline method |
| if (nodeParent is MethodInvocation) { |
| var invocation = nodeParent; |
| var target = invocation.target; |
| List<Expression> arguments = invocation.argumentList.arguments; |
| _inlineMethodInvocation( |
| status, invocation, invocation.isCascaded, target, arguments); |
| } else { |
| // cannot inline reference to method: var v = new A().method; |
| if (ref._methodElement is MethodElement) { |
| status.addFatalError('Cannot inline class method reference.', |
| newLocation_fromNode(_node)); |
| return; |
| } |
| // PropertyAccessorElement |
| if (ref._methodElement is PropertyAccessorElement) { |
| Expression usage = _node; |
| Expression? target; |
| var cascade = false; |
| if (nodeParent is PrefixedIdentifier) { |
| var propertyAccess = nodeParent; |
| usage = propertyAccess; |
| target = propertyAccess.prefix; |
| cascade = false; |
| } |
| if (nodeParent is PropertyAccess) { |
| var propertyAccess = nodeParent; |
| usage = propertyAccess; |
| target = propertyAccess.realTarget; |
| cascade = propertyAccess.isCascaded; |
| } |
| // prepare arguments |
| var arguments = <Expression>[]; |
| if (_node.inSetterContext()) { |
| var assignment = _node.thisOrAncestorOfType<AssignmentExpression>()!; |
| arguments.add(assignment.rightHandSide); |
| } |
| // inline body |
| _inlineMethodInvocation(status, usage, cascade, target, arguments); |
| return; |
| } |
| // not invocation, just reference to function |
| String source; |
| { |
| source = ref._methodUtils.getRangeText(range.startEnd( |
| ref._methodParameters!.leftParenthesis, ref._methodNode)); |
| var methodPrefix = |
| ref._methodUtils.getLinePrefix(ref._methodNode.offset); |
| source = |
| _refUtils.replaceSourceIndent(source, methodPrefix, _refPrefix); |
| source = source.trim(); |
| source = removeEnd(source, ';')!; |
| } |
| // do insert |
| var edit = newSourceEdit_range(range.node(_node), source); |
| _addRefEdit(edit); |
| } |
| } |
| |
| bool _shouldProcess() { |
| if (!ref.inlineAll) { |
| var parentRange = range.node(_node); |
| return parentRange.contains(ref.offset); |
| } |
| return true; |
| } |
| } |
| |
| class _ReturnsValidatorVisitor extends RecursiveAstVisitor<void> { |
| final RefactoringStatus result; |
| int _numReturns = 0; |
| |
| _ReturnsValidatorVisitor(this.result); |
| |
| @override |
| void visitReturnStatement(ReturnStatement node) { |
| _numReturns++; |
| if (_numReturns == 2) { |
| result.addError('Ambiguous return value.', newLocation_fromNode(node)); |
| } |
| } |
| } |
| |
| /// Information about the source of a method being inlined. |
| class _SourcePart { |
| /// The base for all [SourceRange]s. |
| final int _base; |
| |
| /// The source of the method. |
| final String _source; |
| |
| /// The original prefix of the method. |
| final String _prefix; |
| |
| /// The occurrences of the method parameters. |
| final Map<ParameterElement, List<_ParameterOccurrence>> _parameters = {}; |
| |
| /// The occurrences of the method local variables. |
| final Map<VariableElement, List<SourceRange>> _variables = {}; |
| |
| /// The offsets of explicit `this` expression references. |
| final List<int> _explicitThisOffsets = []; |
| |
| /// The offsets of implicit `this` expression references. |
| final List<int> _implicitThisOffsets = []; |
| |
| /// The offsets of the implicit class references in static member references. |
| final Map<String, List<int>> _implicitClassNameOffsets = {}; |
| |
| _SourcePart(this._base, this._source, this._prefix); |
| |
| void addExplicitThisOffset(int offset) { |
| _explicitThisOffsets.add(offset - _base); |
| } |
| |
| void addImplicitClassNameOffset(String className, int offset) { |
| var offsets = _implicitClassNameOffsets[className]; |
| if (offsets == null) { |
| offsets = []; |
| _implicitClassNameOffsets[className] = offsets; |
| } |
| offsets.add(offset - _base); |
| } |
| |
| void addImplicitThisOffset(int offset) { |
| _implicitThisOffsets.add(offset - _base); |
| } |
| |
| void addParameterOccurrence(ParameterElement parameter, |
| SourceRange identifierRange, Precedence precedence) { |
| var occurrences = _parameters[parameter]; |
| if (occurrences == null) { |
| occurrences = []; |
| _parameters[parameter] = occurrences; |
| } |
| identifierRange = range.offsetBy(identifierRange, -_base); |
| occurrences.add(_ParameterOccurrence(precedence, identifierRange)); |
| } |
| |
| void addVariable(VariableElement element, SourceRange identifierRange) { |
| var ranges = _variables[element]; |
| if (ranges == null) { |
| ranges = []; |
| _variables[element] = ranges; |
| } |
| identifierRange = range.offsetBy(identifierRange, -_base); |
| ranges.add(identifierRange); |
| } |
| } |
| |
| /// A visitor that fills [_SourcePart] with fields, parameters and variables. |
| class _VariablesVisitor extends GeneralizingAstVisitor<void> { |
| /// The [ExecutableElement] being inlined. |
| final ExecutableElement methodElement; |
| |
| /// The [SourceRange] of the element body. |
| final SourceRange bodyRange; |
| |
| /// The [_SourcePart] to record reference into. |
| final _SourcePart result; |
| |
| _VariablesVisitor(this.methodElement, this.bodyRange, this.result); |
| |
| @override |
| void visitNode(AstNode node) { |
| var nodeRange = range.node(node); |
| if (!bodyRange.intersects(nodeRange)) { |
| return null; |
| } |
| super.visitNode(node); |
| } |
| |
| @override |
| void visitSimpleIdentifier(SimpleIdentifier node) { |
| var nodeRange = range.node(node); |
| if (bodyRange.covers(nodeRange)) { |
| _addMemberQualifier(node); |
| _addParameter(node); |
| _addVariable(node); |
| } |
| } |
| |
| @override |
| void visitThisExpression(ThisExpression node) { |
| var offset = node.offset; |
| if (bodyRange.contains(offset)) { |
| result.addExplicitThisOffset(offset); |
| } |
| } |
| |
| void _addMemberQualifier(SimpleIdentifier node) { |
| // should be unqualified |
| var qualifier = getNodeQualifier(node); |
| if (qualifier != null) { |
| return; |
| } |
| // should be a method or field reference |
| var element = node.writeOrReadElement; |
| if (element is ExecutableElement) { |
| if (element is MethodElement || element is PropertyAccessorElement) { |
| // OK |
| } else { |
| return; |
| } |
| } else { |
| return; |
| } |
| if (element.enclosingElement is! ClassElement) { |
| return; |
| } |
| // record the implicit static or instance reference |
| var offset = node.offset; |
| if (element.isStatic) { |
| var className = element.enclosingElement.displayName; |
| result.addImplicitClassNameOffset(className, offset); |
| } else { |
| result.addImplicitThisOffset(offset); |
| } |
| } |
| |
| void _addParameter(SimpleIdentifier node) { |
| var parameterElement = getParameterElement(node); |
| // not a parameter |
| if (parameterElement == null) { |
| return; |
| } |
| // not a parameter of the function being inlined |
| if (!methodElement.parameters.contains(parameterElement)) { |
| return; |
| } |
| // OK, add occurrence |
| var nodeRange = range.node(node); |
| var parentPrecedence = getExpressionParentPrecedence(node); |
| result.addParameterOccurrence( |
| parameterElement, nodeRange, parentPrecedence); |
| } |
| |
| void _addVariable(SimpleIdentifier node) { |
| var variableElement = getLocalVariableElement(node); |
| if (variableElement != null) { |
| var nodeRange = range.node(node); |
| result.addVariable(variableElement, nodeRange); |
| } |
| } |
| } |