blob: 6f2de75aa8ec58347f24841b5a083e423244f536 [file] [log] [blame]
// 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 edit.domain;
import 'dart:async';
import 'package:analysis_server/src/analysis_server.dart';
import 'package:analysis_server/src/constants.dart';
import 'package:analysis_server/src/protocol.dart' hide Element;
import 'package:analysis_server/src/services/correction/assist.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server/src/services/correction/status.dart';
import 'package:analysis_server/src/services/refactoring/refactoring.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/engine.dart' as engine;
import 'package:analyzer/src/generated/error.dart' as engine;
import 'package:analyzer/src/generated/source.dart';
/**
* Instances of the class [EditDomainHandler] implement a [RequestHandler]
* that handles requests in the edit domain.
*/
class EditDomainHandler implements RequestHandler {
/**
* The analysis server that is using this handler to process requests.
*/
final AnalysisServer server;
/**
* The [SearchEngine] for this server.
*/
SearchEngine searchEngine;
_RefactoringManager refactoringManager;
/**
* Initialize a newly created handler to handle requests for the given [server].
*/
EditDomainHandler(this.server) {
searchEngine = server.searchEngine;
refactoringManager = new _RefactoringManager(server, searchEngine);
}
Response getAssists(Request request) {
var params = new EditGetAssistsParams.fromRequest(request);
List<SourceChange> changes = <SourceChange>[];
List<CompilationUnit> units =
server.getResolvedCompilationUnits(params.file);
if (units.isNotEmpty) {
CompilationUnit unit = units[0];
List<Assist> assists =
computeAssists(searchEngine, unit, params.offset, params.length);
assists.forEach((Assist assist) {
changes.add(assist.change);
});
}
// respond
return new EditGetAssistsResult(changes).toResponse(request.id);
}
Response getAvailableRefactorings(Request request) {
var params = new EditGetAvailableRefactoringsParams.fromRequest(request);
String file = params.file;
int offset = params.offset;
int length = params.length;
// add refactoring kinds
List<RefactoringKind> kinds = <RefactoringKind>[];
// try EXTRACT_*
if (length != 0) {
kinds.add(RefactoringKind.EXTRACT_LOCAL_VARIABLE);
kinds.add(RefactoringKind.EXTRACT_METHOD);
}
// try RENAME
{
List<Element> elements = server.getElementsAtOffset(file, offset);
if (elements.isNotEmpty) {
Element element = elements[0];
RenameRefactoring renameRefactoring =
new RenameRefactoring(searchEngine, element);
if (renameRefactoring != null) {
kinds.add(RefactoringKind.RENAME);
}
}
}
// respond
return new EditGetAvailableRefactoringsResult(kinds).toResponse(request.id);
}
Response getFixes(Request request) {
var params = new EditGetFixesParams.fromRequest(request);
String file = params.file;
int offset = params.offset;
// add fixes
List<ErrorFixes> errorFixesList = <ErrorFixes>[];
List<CompilationUnit> units = server.getResolvedCompilationUnits(file);
for (CompilationUnit unit in units) {
engine.AnalysisErrorInfo errorInfo = server.getErrors(file);
if (errorInfo != null) {
LineInfo lineInfo = errorInfo.lineInfo;
int requestLine = lineInfo.getLocation(offset).lineNumber;
for (engine.AnalysisError error in errorInfo.errors) {
int errorLine = lineInfo.getLocation(error.offset).lineNumber;
if (errorLine == requestLine) {
List<Fix> fixes = computeFixes(searchEngine, unit, error);
if (fixes.isNotEmpty) {
AnalysisError serverError =
new AnalysisError.fromEngine(lineInfo, error);
ErrorFixes errorFixes = new ErrorFixes(serverError);
errorFixesList.add(errorFixes);
fixes.forEach((fix) {
errorFixes.addFix(fix);
});
}
}
}
}
}
// respond
return new EditGetFixesResult(errorFixesList).toResponse(request.id);
}
@override
Response handleRequest(Request request) {
try {
String requestName = request.method;
if (requestName == EDIT_GET_ASSISTS) {
return getAssists(request);
} else if (requestName == EDIT_GET_AVAILABLE_REFACTORINGS) {
return getAvailableRefactorings(request);
} else if (requestName == EDIT_GET_FIXES) {
return getFixes(request);
} else if (requestName == EDIT_GET_REFACTORING) {
refactoringManager.getRefactoring(request);
return Response.DELAYED_RESPONSE;
}
} on RequestFailure catch (exception) {
return exception.response;
}
return null;
}
}
/**
* An object managing a single [Refactoring] instance.
*
* The instance is identified by its kind, file, offset and length.
* It is initialized when the a set of parameters is given for the first time.
* All subsequent requests are performed on this [Refactoring] instance.
*
* Once new set of parameters is received, the previous [Refactoring] instance
* is invalidated and a new one is created and initialized.
*/
class _RefactoringManager {
final AnalysisServer server;
final SearchEngine searchEngine;
RefactoringKind kind;
String file;
int offset;
int length;
Refactoring refactoring;
Object feedback;
RefactoringStatus initStatus;
RefactoringStatus optionsStatus;
RefactoringStatus finalStatus;
String requestId;
EditGetRefactoringResult result;
_RefactoringManager(this.server, this.searchEngine) {
_reset();
}
bool get _hasFatalError {
return initStatus.hasFatalError ||
optionsStatus.hasFatalError ||
finalStatus.hasFatalError;
}
void getRefactoring(Request request) {
// prepare for processing the request
requestId = request.id;
result = new EditGetRefactoringResult(<RefactoringProblem>[]);
// process the request
var params = new EditGetRefactoringParams.fromRequest(request);
_init(params.kind, params.file, params.offset, params.length).then((_) {
if (_hasFatalError) {
return _sendResultResponse();
}
// set options
if (params.options == null) {
return _sendResultResponse();
}
optionsStatus = _setOptions(params.options);
if (_hasFatalError) {
return _sendResultResponse();
}
// done if just validation
if (params.validateOnly) {
return _sendResultResponse();
}
// validation and create change
refactoring.checkFinalConditions().then((_finalStatus) {
finalStatus = _finalStatus;
if (_hasFatalError) {
return _sendResultResponse();
}
return refactoring.createChange().then((change) {
result.change = new SourceChange(change.message, edits: change.edits);
return _sendResultResponse();
});
});
});
}
/**
* Initializes this context to perform a refactoring with the specified
* parameters. The existing [Refactoring] is reused or created as needed.
*/
Future<RefactoringStatus> _init(RefactoringKind kind, String file, int offset,
int length) {
List<RefactoringProblem> problems = <RefactoringProblem>[];
// check if we can continue with the existing Refactoring instance
if (this.kind == kind &&
this.file == file &&
this.offset == offset &&
this.length == length) {
return new Future.value(initStatus);
}
_reset();
this.kind = kind;
this.file = file;
this.offset = offset;
this.length = length;
// create a new Refactoring instance
if (kind == RefactoringKind.RENAME) {
List<AstNode> nodes = server.getNodesAtOffset(file, offset);
List<Element> elements = server.getElementsAtOffset(file, offset);
if (nodes.isNotEmpty && elements.isNotEmpty) {
AstNode node = nodes[0];
Element element = elements[0];
refactoring = new RenameRefactoring(searchEngine, element);
feedback = new RenameFeedback(node.offset, node.length);
}
}
if (refactoring == null) {
initStatus =
new RefactoringStatus.fatal('Unable to create a refactoring');
return new Future.value(initStatus);
}
// check initial conditions
return refactoring.checkInitialConditions().then((status) {
initStatus = status;
return initStatus;
});
}
void _reset() {
refactoring = null;
feedback = null;
initStatus = new RefactoringStatus();
optionsStatus = new RefactoringStatus();
finalStatus = new RefactoringStatus();
}
void _sendResultResponse() {
result.feedback = feedback;
// set problems
{
RefactoringStatus status = new RefactoringStatus();
status.addStatus(initStatus);
status.addStatus(optionsStatus);
status.addStatus(finalStatus);
result.problems = status.problems;
}
// send the response
server.sendResponse(result.toResponse(requestId));
// done with this request
requestId = null;
result = null;
}
RefactoringStatus _setOptions(Object options) {
if (refactoring is RenameRefactoring) {
RenameRefactoring renameRefactoring = refactoring;
RenameOptions renameOptions = options;
String newName = renameOptions.newName;
renameRefactoring.newName = newName;
return renameRefactoring.checkNewName();
}
return new RefactoringStatus();
}
}