Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(8)

Unified Diff: pkg/analysis_server/lib/src/services/refactoring/inline_method.dart

Issue 526583002: 'Inline Method' refactoring. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Fixes for review comments Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: pkg/analysis_server/lib/src/services/refactoring/inline_method.dart
diff --git a/pkg/analysis_server/lib/src/services/refactoring/inline_method.dart b/pkg/analysis_server/lib/src/services/refactoring/inline_method.dart
new file mode 100644
index 0000000000000000000000000000000000000000..227be794b2dda2c344a6dd99872aa4f5a15b21b1
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/refactoring/inline_method.dart
@@ -0,0 +1,814 @@
+// 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.
+
+library services.src.refactoring.inline_method;
+
+import 'dart:async';
+
+import 'package:analysis_server/src/protocol.dart' hide Element;
+import 'package:analysis_server/src/services/correction/source_range.dart';
+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/search/element_visitors.dart';
+import 'package:analysis_server/src/services/search/hierarchy.dart';
+import 'package:analysis_server/src/services/search/search_engine.dart';
+import 'package:analyzer/src/generated/ast.dart';
+import 'package:analyzer/src/generated/element.dart';
+import 'package:analyzer/src/generated/scanner.dart';
+import 'package:analyzer/src/generated/source.dart';
+
+
+/**
+ * Resolver sets [ParameterElement] for the most cases, but not for setter invocation.
+ *
+ * Returns the best available [ParameterElement] for which the given [Expression] is used.
+ */
+ParameterElement _getBestParameterElement(Expression expr) {
+ // TODO(scheglov) investigate why AssignmentExpression "f = 0" doesn't have
+ // an element.
+ // setter invocation
+ if (expr.parent is AssignmentExpression) {
+ AssignmentExpression assignment = expr.parent as AssignmentExpression;
+ if (assignment.rightHandSide == expr &&
+ assignment.operator.type == TokenType.EQ) {
+ Expression lhs = assignment.leftHandSide;
+ Element lhsElement = null;
+ if (lhs is Identifier) {
+ lhsElement = lhs.bestElement;
+ }
+ if (lhs is PropertyAccess) {
+ lhsElement = lhs.propertyName.bestElement;
+ }
+ if (lhsElement is PropertyAccessorElement) {
+ List<ParameterElement> parameters = lhsElement.parameters;
+ if (parameters.length != 0) {
+ return parameters[0];
+ }
+ }
+ }
+ }
+ // use resolver
+ return expr.bestParameterElement;
+}
+
+
+/**
+ * Returns the [SourceRange] to find conflicting locals in.
+ */
+SourceRange _getLocalsConflictingRange(AstNode node) {
+ // maybe Block
+ Block block = node.getAncestor((node) => node is Block);
+ if (block != null) {
+ int offset = node.offset;
+ int endOffset = block.end;
+ return rangeStartEnd(offset, endOffset);
+ }
+ // maybe whole executable
+ AstNode executableNode = getEnclosingExecutableNode(node);
+ if (executableNode != null) {
+ return rangeNode(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(_SourcePart part, CorrectionUtils utils,
+ AstNode contextNode, Expression targetExpression, List<Expression> arguments) {
+ // prepare edits to replace parameters with arguments
+ List<SourceEdit> edits = <SourceEdit>[];
+ part._parameters.forEach(
+ (ParameterElement parameter, List<_ParameterOccurrence> occurrences) {
+ // prepare argument
+ Expression argument = null;
+ for (Expression arg in arguments) {
+ if (_getBestParameterElement(arg) == parameter) {
+ argument = arg;
+ break;
+ }
+ }
+ if (argument is NamedExpression) {
+ argument = (argument as NamedExpression).expression;
+ }
+ int argumentPrecedence = getExpressionPrecedence(argument);
+ String argumentSource = utils.getNodeText(argument);
+ // replace all occurrences of this parameter
+ for (_ParameterOccurrence occurrence in occurrences) {
+ SourceRange 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(new SourceEdit.range(range, occurrenceArgumentSource));
+ }
+ });
+ // replace static field "qualifier" with invocation target
+ part._staticFieldQualifiers.forEach(
+ (String className, List<SourceRange> ranges) {
+ for (SourceRange range in ranges) {
+ edits.add(new SourceEdit.range(range, className + '.'));
+ }
+ });
+ // replace instance field "qualifier" with invocation target
+ if (targetExpression != null) {
+ String targetSource = utils.getNodeText(targetExpression) + '.';
+ for (SourceRange qualifierRange in part._instanceFieldQualifiers) {
+ edits.add(new SourceEdit.range(qualifierRange, targetSource));
+ }
+ }
+ // prepare edits to replace conflicting variables
+ Set<String> conflictingNames = _getNamesConflictingAt(contextNode);
+ part._variables.forEach((VariableElement variable, List<SourceRange> ranges) {
+ String originalName = variable.displayName;
+ // prepare unique name
+ String uniqueName;
+ {
+ uniqueName = originalName;
+ int uniqueIndex = 2;
+ while (conflictingNames.contains(uniqueName)) {
+ uniqueName = originalName + uniqueIndex.toString();
+ uniqueIndex++;
+ }
+ }
+ // update references, if name was change
+ if (uniqueName != originalName) {
+ for (SourceRange range in ranges) {
+ edits.add(new SourceEdit.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) {
+ Set<String> result = new Set<String>();
+ // local variables and functions
+ {
+ SourceRange localsRange = _getLocalsConflictingRange(node);
+ ExecutableElement enclosingExecutable = getEnclosingExecutableElement(node);
+ if (enclosingExecutable != null) {
+ visitChildren(enclosingExecutable, (element) {
+ if (element is LocalElement) {
+ SourceRange elementRange = element.visibleRange;
+ if (elementRange != null && elementRange.intersects(localsRange)) {
+ result.add(element.displayName);
+ }
+ }
+ return true;
+ });
+ }
+ }
+ // fields
+ {
+ ClassElement enclosingClassElement = getEnclosingClassElement(node);
+ if (enclosingClassElement != null) {
+ Set<ClassElement> elements = new Set<ClassElement>();
+ elements.add(enclosingClassElement);
+ elements.addAll(getSuperClasses(enclosingClassElement));
+ for (ClassElement classElement in elements) {
+ List<Element> classMembers = getChildren(classElement);
+ for (Element classMemberElement in classMembers) {
+ result.add(classMemberElement.displayName);
+ }
+ }
+ }
+ }
+ // done
+ return result;
+}
+
+
+/**
+ * [InlineMethodRefactoring] implementation.
+ */
+class InlineMethodRefactoringImpl extends RefactoringImpl implements
+ InlineMethodRefactoring {
+ final SearchEngine searchEngine;
+ final CompilationUnit unit;
+ final int offset;
+ String file;
+ CorrectionUtils utils;
+ SourceChange change;
+
+ bool deleteSource = false;
+ bool inlineAll = true;
+
+ ExecutableElement _methodElement;
+ String _methodFile;
+ CompilationUnit _methodUnit;
+ CorrectionUtils _methodUtils;
+ AstNode _methodNode;
+ FormalParameterList _methodParameters;
+ FunctionBody _methodBody;
+ Expression _methodExpression;
+ _SourcePart _methodExpressionPart;
+ _SourcePart _methodStatementsPart;
+ List<_ReferenceProcessor> _referenceProcessors = [];
+
+ InlineMethodRefactoringImpl(this.searchEngine, this.unit, this.offset) {
+ file = unit.element.source.fullName;
+ utils = new CorrectionUtils(unit);
+ }
+
+ @override
+ String get refactoringName {
+ if (_methodElement is MethodElement) {
+ return "Inline Method";
+ } else {
+ return "Inline Function";
+ }
+ }
+
+ @override
+ Future<RefactoringStatus> checkFinalConditions() {
+ change = new SourceChange(refactoringName);
+ RefactoringStatus result = new 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 (_ReferenceProcessor processor in _referenceProcessors) {
+ processor._process(result);
+ }
+ // delete method
+ if (deleteSource && inlineAll) {
+ SourceRange methodRange = rangeNode(_methodNode);
+ SourceRange linesRange = _methodUtils.getLinesRange(methodRange);
+ change.addEdit(_methodFile, new SourceEdit.range(linesRange, ""));
+ }
+ // done
+ return new Future.value(result);
+ }
+
+ @override
+ Future<RefactoringStatus> checkInitialConditions() {
+ RefactoringStatus result = new RefactoringStatus();
+ // prepare method information
+ result.addStatus(_prepareMethod());
+ if (result.hasFatalError) {
+ return new Future.value(result);
+ }
+ // maybe operator
+ if (_methodElement.isOperator) {
+ result = new RefactoringStatus.fatal('Cannot inline operator.');
+ return new Future.value(result);
+ }
+ // analyze method body
+ result.addStatus(_prepareMethodParts());
+ // process references
+ return searchEngine.searchReferences(_methodElement).then((references) {
+ _referenceProcessors.clear();
+ for (SearchMatch reference in references) {
+ _ReferenceProcessor processor =
+ new _ReferenceProcessor(this, reference);
+ _referenceProcessors.add(processor);
+ }
+ }).then((_) {
+ return result;
+ });
+ }
+
+ @override
+ Future<SourceChange> createChange() {
+ return new Future.value(change);
+ }
+
+ @override
+ bool requiresPreview() => false;
+
+ _SourcePart _createSourcePart(SourceRange range) {
+ String source = _methodUtils.getRangeText(range);
+ String prefix = getLinePrefix(source);
+ _SourcePart result = new _SourcePart(range.offset, source, prefix);
+ // remember parameters and variables occurrences
+ _methodUnit.accept(new _VariablesVisitor(_methodElement, range, result));
+ // done
+ return result;
+ }
+
+ /**
+ * Initializes [_methodElement] and related fields.
+ */
+ RefactoringStatus _prepareMethod() {
+ _methodElement = null;
+ _methodParameters = null;
+ _methodBody = null;
+ deleteSource = false;
+ inlineAll = false;
+ // prepare selected SimpleIdentifier
+ AstNode selectedNode = new NodeLocator.con1(offset).searchWithin(unit);
+ if (selectedNode is! SimpleIdentifier) {
+ return new RefactoringStatus.fatal(
+ 'Method declaration or reference must be selected to activate this refactoring.');
+ }
+ SimpleIdentifier selectedIdentifier = selectedNode as SimpleIdentifier;
+ // prepare selected ExecutableElement
+ Element selectedElement = selectedIdentifier.bestElement;
+ if (selectedElement is! ExecutableElement) {
+ return new RefactoringStatus.fatal(
+ 'Method declaration or reference must be selected to activate this refactoring.');
+ }
+ _methodElement = selectedElement as ExecutableElement;
+ _methodFile = _methodElement.source.fullName;
+ _methodUnit = selectedElement.unit;
+ _methodUtils = new CorrectionUtils(_methodUnit);
+ if (selectedElement is MethodElement ||
+ selectedElement is PropertyAccessorElement) {
+ MethodDeclaration methodDeclaration =
+ _methodElement.node as MethodDeclaration;
+ _methodNode = methodDeclaration;
+ _methodParameters = methodDeclaration.parameters;
+ _methodBody = methodDeclaration.body;
+ // prepare mode
+ deleteSource = selectedNode == methodDeclaration.name;
+ inlineAll = deleteSource;
+ }
+ if (selectedElement is FunctionElement) {
+ FunctionDeclaration functionDeclaration =
+ _methodElement.node as FunctionDeclaration;
+ _methodNode = functionDeclaration;
+ _methodParameters = functionDeclaration.functionExpression.parameters;
+ _methodBody = functionDeclaration.functionExpression.body;
+ // prepare mode
+ deleteSource = selectedNode == functionDeclaration.name;
+ inlineAll = deleteSource;
+ }
+ // OK
+ return new RefactoringStatus();
+ }
+
+ /**
+ * Analyze [_methodBody] to fill [_methodExpressionPart] and
+ * [_methodStatementsPart].
+ */
+ RefactoringStatus _prepareMethodParts() {
+ RefactoringStatus result = new RefactoringStatus();
+ if (_methodBody is ExpressionFunctionBody) {
+ ExpressionFunctionBody body = _methodBody as ExpressionFunctionBody;
+ _methodExpression = body.expression;
+ SourceRange methodExpressionRange = rangeNode(_methodExpression);
+ _methodExpressionPart = _createSourcePart(methodExpressionRange);
+ } else if (_methodBody is BlockFunctionBody) {
+ Block body = (_methodBody as BlockFunctionBody).block;
+ List<Statement> statements = body.statements;
+ if (statements.length >= 1) {
+ Statement lastStatement = statements[statements.length - 1];
+ // "return" statement requires special handling
+ if (lastStatement is ReturnStatement) {
+ _methodExpression = lastStatement.expression;
+ SourceRange methodExpressionRange = rangeNode(_methodExpression);
+ _methodExpressionPart = _createSourcePart(methodExpressionRange);
+ // exclude "return" statement from statements
+ statements = statements.sublist(0, statements.length - 1);
+ }
+ // if there are statements, process them
+ if (!statements.isEmpty) {
+ SourceRange statementsRange =
+ _methodUtils.getLinesRangeStatements(statements);
+ _methodStatementsPart = _createSourcePart(statementsRange);
+ }
+ }
+ // check if more than one return
+ body.accept(new _ReturnsValidatorVisitor(result));
+ } else {
+ return new RefactoringStatus.fatal('Cannot inline method without body.');
+ }
+ return result;
+ }
+}
+
+
+class _ParameterOccurrence {
+ final int parentPrecedence;
+ final SourceRange range;
+ _ParameterOccurrence(this.parentPrecedence, this.range);
+}
+
+
+/**
+ * Processor for single [SearchMatch] reference to [methodElement].
+ */
+class _ReferenceProcessor {
+ final InlineMethodRefactoringImpl ref;
+
+ String _refFile;
+ CorrectionUtils _refUtils;
+ AstNode _node;
+ SourceRange _refLineRange;
+ String _refPrefix;
+
+ _ReferenceProcessor(this.ref, SearchMatch reference) {
+ // prepare SourceChange to update
+ Element refElement = reference.element;
+ _refFile = refElement.source.fullName;
+ // prepare CorrectionUtils
+ CompilationUnit refUnit = refElement.unit;
+ _refUtils = new CorrectionUtils(refUnit);
+ // prepare node and environment
+ _node = _refUtils.findNode(reference.sourceRange.offset);
+ Statement refStatement = _node.getAncestor((node) => node is Statement);
+ if (refStatement != null) {
+ _refLineRange = _refUtils.getLinesRangeStatements([refStatement]);
+ _refPrefix = _refUtils.getNodePrefix(refStatement);
+ } else {
+ _refLineRange = null;
+ _refPrefix = _refUtils.getLinePrefix(_node.offset);
+ }
+ }
+
+ 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
+ AstNode parent = usage.parent;
+ AstNode parent2 = parent.parent;
+ // OK, if statement in block
+ if (parent is Statement) {
+ return parent2 is Block;
+ }
+ // maybe assignment, in block
+ if (parent is AssignmentExpression) {
+ AssignmentExpression 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) {
+ AstNode parent3 = parent2.parent;
+ return parent3 is VariableDeclarationStatement &&
+ parent3.parent is Block;
+ }
+ }
+ }
+ // not in block, cannot inline body
+ return false;
+ }
+
+ void _inlineMethodInvocation(RefactoringStatus status, Expression methodUsage,
+ bool cascaded, Expression target, List<Expression> arguments) {
+ // we don't support cascade
+ if (cascaded) {
+ status.addError(
+ 'Cannot inline cascade invocation.',
+ new Location.fromNode(methodUsage));
+ }
+ // can we inline method body into "methodUsage" block?
+ if (_canInlineBody(methodUsage)) {
+ // insert non-return statements
+ if (ref._methodStatementsPart != null) {
+ // prepare statements source for invocation
+ String source = _getMethodSourceForInvocation(
+ ref._methodStatementsPart,
+ _refUtils,
+ methodUsage,
+ target,
+ arguments);
+ source = _refUtils.replaceSourceIndent(
+ source,
+ ref._methodStatementsPart._prefix,
+ _refPrefix);
+ // do insert
+ SourceRange range = rangeStartLength(_refLineRange, 0);
+ SourceEdit edit = new SourceEdit.range(range, source);
+ ref.change.addEdit(_refFile, edit);
+ }
+ // replace invocation with return expression
+ if (ref._methodExpressionPart != null) {
+ // prepare expression source for invocation
+ String source = _getMethodSourceForInvocation(
+ ref._methodExpressionPart,
+ _refUtils,
+ methodUsage,
+ target,
+ arguments);
+ if (getExpressionPrecedence(ref._methodExpression) <
+ getExpressionParentPrecedence(methodUsage)) {
+ source = "(${source})";
+ }
+ // do replace
+ SourceRange methodUsageRange = rangeNode(methodUsage);
+ SourceEdit edit = new SourceEdit.range(methodUsageRange, source);
+ ref.change.addEdit(_refFile, edit);
+ } else {
+ SourceEdit edit = new SourceEdit.range(_refLineRange, "");
+ ref.change.addEdit(_refFile, edit);
+ }
+ return;
+ }
+ // inline as closure invocation
+ String source;
+ {
+ source = ref._methodUtils.getRangeText(
+ rangeStartEnd(ref._methodParameters.leftParenthesis, ref._methodNode));
+ String methodPrefix =
+ ref._methodUtils.getLinePrefix(ref._methodNode.offset);
+ source = _refUtils.replaceSourceIndent(source, methodPrefix, _refPrefix);
+ source = source.trim();
+ }
+ // do insert
+ SourceRange range = rangeNode(_node);
+ SourceEdit edit = new SourceEdit.range(range, source);
+ ref.change.addEdit(_refFile, edit);
+ }
+
+ void _process(RefactoringStatus status) {
+ AstNode nodeParent = _node.parent;
+ // may be only single place should be inlined
+ if (!_shouldProcess()) {
+ return;
+ }
+ // may be invocation of inline method
+ if (nodeParent is MethodInvocation) {
+ MethodInvocation invocation = nodeParent;
+ Expression 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.',
+ new Location.fromNode(_node));
+ return;
+ }
+ // PropertyAccessorElement
+ if (ref._methodElement is PropertyAccessorElement) {
+ Expression target = null;
+ bool cascade = false;
+ if (nodeParent is PrefixedIdentifier) {
+ PrefixedIdentifier propertyAccess = nodeParent;
+ target = propertyAccess.prefix;
+ cascade = false;
+ }
+ if (nodeParent is PropertyAccess) {
+ PropertyAccess propertyAccess = nodeParent;
+ target = propertyAccess.realTarget;
+ cascade = propertyAccess.isCascaded;
+ }
+ // prepare arguments
+ List<Expression> arguments = [];
+ if ((_node as SimpleIdentifier).inSetterContext()) {
+ arguments.add(
+ (nodeParent.parent as AssignmentExpression).rightHandSide);
+ }
+ // inline body
+ _inlineMethodInvocation(
+ status,
+ nodeParent as Expression,
+ cascade,
+ target,
+ arguments);
+ return;
+ }
+ // not invocation, just reference to function
+ String source;
+ {
+ source = ref._methodUtils.getRangeText(
+ rangeStartEnd(ref._methodParameters.leftParenthesis, ref._methodNode));
+ String methodPrefix =
+ ref._methodUtils.getLinePrefix(ref._methodNode.offset);
+ source =
+ _refUtils.replaceSourceIndent(source, methodPrefix, _refPrefix);
+ source = source.trim();
+ }
+ // do insert
+ SourceRange range = rangeNode(_node);
+ SourceEdit edit = new SourceEdit.range(range, source);
+ ref.change.addEdit(_refFile, edit);
+ }
+ }
+
+ bool _shouldProcess() {
+ if (!ref.inlineAll) {
+ SourceRange parentRange = rangeNode(_node);
+ return parentRange.contains(ref.offset);
+ }
+ return true;
+ }
+}
+
+class _ReturnsValidatorVisitor extends RecursiveAstVisitor {
+ final RefactoringStatus result;
+ int _numReturns = 0;
+
+ _ReturnsValidatorVisitor(this.result);
+
+ @override
+ visitReturnStatement(ReturnStatement node) {
+ _numReturns++;
+ if (_numReturns == 2) {
+ result.addError('Ambiguous return value.', new Location.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.
+ */
+ Map<ParameterElement, List<_ParameterOccurrence>> _parameters = {};
+
+ /**
+ * The occurrences of the method local variables.
+ */
+ Map<VariableElement, List<SourceRange>> _variables = {};
+
+ /**
+ * The source ranges of the qualifiers in instance field references.
+ * Some of them have length `0`.
+ */
+ List<SourceRange> _instanceFieldQualifiers = [];
+
+ /**
+ * The source ranges of the qualifiers in instance field references.
+ * Some of them have length `0`.
+ */
+ Map<String, List<SourceRange>> _staticFieldQualifiers = {};
+
+ _SourcePart(this._base, this._source, this._prefix);
+
+ void addInstanceFieldQualifier(SourceRange range) {
+ range = rangeFromBase(range, _base);
+ _instanceFieldQualifiers.add(range);
+ }
+
+ void addParameterOccurrence(ParameterElement parameter, SourceRange range,
+ int precedence) {
+ if (parameter != null) {
+ List<_ParameterOccurrence> occurrences = _parameters[parameter];
+ if (occurrences == null) {
+ occurrences = [];
+ _parameters[parameter] = occurrences;
+ }
+ range = rangeFromBase(range, _base);
+ occurrences.add(new _ParameterOccurrence(precedence, range));
+ }
+ }
+
+ void addStaticFieldQualifier(String className, SourceRange range) {
+ List<SourceRange> ranges = _staticFieldQualifiers[className];
+ if (ranges == null) {
+ ranges = [];
+ _staticFieldQualifiers[className] = ranges;
+ }
+ range = rangeFromBase(range, _base);
+ ranges.add(range);
+ }
+
+ void addVariable(VariableElement element, SourceRange range) {
+ List<SourceRange> ranges = _variables[element];
+ if (ranges == null) {
+ ranges = [];
+ _variables[element] = ranges;
+ }
+ range = rangeFromBase(range, _base);
+ ranges.add(range);
+ }
+}
+
+/**
+ * A visitor that fills [_SourcePart] with fields, parameters and variables.
+ */
+class _VariablesVisitor extends GeneralizingAstVisitor {
+ /**
+ * The [ExecutableElement] being inlined.
+ */
+ final ExecutableElement methodElement;
+
+ /**
+ * The [SourceRange] of the element body.
+ */
+ SourceRange bodyRange;
+
+ /**
+ * The [_SourcePart] to record reference into.
+ */
+ _SourcePart result;
+
+ _VariablesVisitor(this.methodElement, this.bodyRange, this.result);
+
+ @override
+ visitNode(AstNode node) {
+ SourceRange nodeRange = rangeNode(node);
+ if (!bodyRange.intersects(nodeRange)) {
+ return null;
+ }
+ super.visitNode(node);
+ }
+
+ @override
+ visitSimpleIdentifier(SimpleIdentifier node) {
+ SourceRange nodeRange = rangeNode(node);
+ if (bodyRange.covers(nodeRange)) {
+ _addInstanceFieldQualifier(node);
+ _addParameter(node);
+ _addVariable(node);
+ }
+ }
+
+ void _addInstanceFieldQualifier(SimpleIdentifier node) {
+ PropertyAccessorElement accessor = getPropertyAccessorElement(node);
+ if (isClassFieldAccessorElement(accessor)) {
+ AstNode qualifier = getNodeQualifier(node);
+ if (qualifier == null || qualifier is ThisExpression) {
+ if (accessor.isStatic) {
+ String className = accessor.enclosingElement.displayName;
+ if (qualifier == null) {
+ SourceRange qualifierRange = rangeStartLength(node, 0);
+ result.addStaticFieldQualifier(className, qualifierRange);
+ }
+ } else {
+ SourceRange qualifierRange;
+ if (qualifier != null) {
+ qualifierRange = rangeStartStart(qualifier, node);
+ } else {
+ qualifierRange = rangeStartLength(node, 0);
+ }
+ result.addInstanceFieldQualifier(qualifierRange);
+ }
+ }
+ }
+ }
+
+ void _addParameter(SimpleIdentifier node) {
+ ParameterElement 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
+ SourceRange nodeRange = rangeNode(node);
+ int parentPrecedence = getExpressionParentPrecedence(node);
+ result.addParameterOccurrence(
+ parameterElement,
+ nodeRange,
+ parentPrecedence);
+ }
+
+ void _addVariable(SimpleIdentifier node) {
+ VariableElement variableElement = getLocalVariableElement(node);
+ if (variableElement != null) {
+ SourceRange nodeRange = rangeNode(node);
+ result.addVariable(variableElement, nodeRange);
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698