diff --git a/pkg/analysis_server/lib/protocol/protocol_generated.dart b/pkg/analysis_server/lib/protocol/protocol_generated.dart
index 947615a643c..0d1bf0ca169 100644
--- a/pkg/analysis_server/lib/protocol/protocol_generated.dart
+++ b/pkg/analysis_server/lib/protocol/protocol_generated.dart
@@ -6180,7 +6180,7 @@ class EditDartfixParams implements RequestParams {
/**
* A list of the files and directories for which edits should be suggested.
*
- * If a request is made with path that is invalid, e.g. is not absolute and
+ * If a request is made with a path that is invalid, e.g. is not absolute and
* normalized, an error of type INVALID_FILE_PATH_FORMAT will be generated.
* If a request is made for a file which does not exist, or which is not
* currently subject to analysis (e.g. because it is not associated with any
@@ -6192,7 +6192,7 @@ class EditDartfixParams implements RequestParams {
/**
* A list of the files and directories for which edits should be suggested.
*
- * If a request is made with path that is invalid, e.g. is not absolute and
+ * If a request is made with a path that is invalid, e.g. is not absolute and
* normalized, an error of type INVALID_FILE_PATH_FORMAT will be generated.
* If a request is made for a file which does not exist, or which is not
* currently subject to analysis (e.g. because it is not associated with any
diff --git a/pkg/analysis_server/lib/src/edit/edit_dartfix.dart b/pkg/analysis_server/lib/src/edit/edit_dartfix.dart
index 336cedde79e..0aa72a1d67e 100644
--- a/pkg/analysis_server/lib/src/edit/edit_dartfix.dart
+++ b/pkg/analysis_server/lib/src/edit/edit_dartfix.dart
@@ -1,10 +1,14 @@
-// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// Copyright (c) 2018, 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/plugin/edit/assist/assist_core.dart';
+import 'package:analysis_server/plugin/edit/assist/assist_dart.dart';
import 'package:analysis_server/protocol/protocol.dart';
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/services/correction/assist.dart';
+import 'package:analysis_server/src/services/correction/assist_internal.dart';
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
@@ -14,6 +18,8 @@ import 'package:analyzer/src/lint/linter.dart';
import 'package:analyzer/src/lint/linter_visitor.dart';
import 'package:analyzer/src/lint/registry.dart';
import 'package:analyzer/src/services/lint.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart'
+ show SourceChange, SourceEdit, SourceFileEdit;
import 'package:front_end/src/base/source.dart';
import 'package:front_end/src/scanner/token.dart';
import 'package:source_span/src/span.dart';
@@ -24,8 +30,23 @@ class EditDartFix {
final fixFolders = [];
final fixFiles = [];
+ List descriptions;
+ SourceChange sourceChange;
+
EditDartFix(this.server, this.request);
+ void addResult(String description, [SourceChange change]) {
+ assert(description != null && description.isNotEmpty);
+ descriptions.add(description);
+ if (change != null) {
+ for (SourceFileEdit fileEdit in change.edits) {
+ for (SourceEdit sourceEdit in fileEdit.edits) {
+ sourceChange.addEdit(fileEdit.file, fileEdit.fileStamp, sourceEdit);
+ }
+ }
+ }
+ }
+
Future compute() async {
final params = new EditDartfixParams.fromRequest(request);
@@ -53,7 +74,7 @@ class EditDartFix {
final LintRule preferMixin = Registry.ruleRegistry['prefer_mixin'];
if (preferMixin == null) {
return new Response.serverError(
- request, 'Missing PreferMixin lint', null);
+ request, 'Missing prefer_mixin lint', null);
}
final preferMixinFix = new PreferMixinFix(this);
preferMixin.reporter = preferMixinFix;
@@ -123,11 +144,13 @@ class EditDartFix {
}
// Reporting
- final descriptions = [];
+ descriptions = [];
+ sourceChange = new SourceChange('dartfix');
for (LinterFix fix in fixes) {
- fix.updateResponse(descriptions);
+ await fix.applyFix();
}
- return new EditDartfixResult(descriptions, []).toResponse(request.id);
+ return new EditDartfixResult(descriptions, sourceChange.edits)
+ .toResponse(request.id);
}
/// Return `true` if the path in within the set of `included` files
@@ -149,6 +172,29 @@ class EditDartFix {
}
}
+class EditDartFixAssistContext implements DartAssistContext {
+ @override
+ final AnalysisDriver analysisDriver;
+
+ @override
+ final int selectionLength;
+
+ @override
+ final int selectionOffset;
+
+ @override
+ final Source source;
+
+ @override
+ final CompilationUnit unit;
+
+ EditDartFixAssistContext(
+ EditDartFix dartFix, this.source, this.unit, AstNode node)
+ : analysisDriver = dartFix.server.getAnalysisDriver(source.fullName),
+ selectionOffset = node.offset,
+ selectionLength = 0;
+}
+
abstract class LinterFix implements ErrorReporter {
final EditDartFix dartFix;
@@ -198,7 +244,7 @@ abstract class LinterFix implements ErrorReporter {
// ignored
}
- void updateResponse(List descriptions);
+ void applyFix();
}
class PreferMixinFix extends LinterFix {
@@ -212,18 +258,44 @@ class PreferMixinFix extends LinterFix {
TypeName type = node;
Element element = type.name.staticElement;
String path = element.source?.fullName;
- if (dartFix.isIncluded(path)) {
- // Only report classes that are `included`
+ if (path != null && dartFix.isIncluded(path)) {
classesToConvert.add(element);
}
}
@override
- void updateResponse(List descriptions) {
- final sorted = classesToConvert.toList()
- ..sort((c1, c2) => c1.name.compareTo(c2.name));
- for (Element elem in sorted) {
- descriptions.add('Convert class to mixin: ${elem.name}');
+ void applyFix() async {
+ for (Element elem in classesToConvert) {
+ await convertClassToMixin(elem);
+ }
+ }
+
+ void convertClassToMixin(Element elem) async {
+ String path = elem.source?.fullName;
+ AnalysisResult result = await dartFix.server.getAnalysisResult(path);
+
+ // TODO(danrubel): Verify that class can be converted
+ for (CompilationUnitMember declaration in result.unit.declarations) {
+ if (declaration is ClassOrMixinDeclaration &&
+ declaration.name.name == elem.name) {
+ AssistProcessor processor = new AssistProcessor(
+ new EditDartFixAssistContext(
+ dartFix, elem.source, result.unit, declaration.name));
+ List assists =
+ await processor.compute(DartAssistKind.CONVERT_CLASS_TO_MIXIN);
+ if (assists.isNotEmpty) {
+ for (Assist assist in assists) {
+ dartFix.addResult(
+ 'Convert class to mixin: ${elem.name}', assist.change);
+ }
+ } else {
+ // TODO(danrubel): If assists is empty, then determine why
+ // assist could not be performed and report that in the description.
+ dartFix.addResult(
+ 'Could not automatically convert ${elem.name} to a mixin'
+ ' because the class contains a constructor.');
+ }
+ }
}
}
}
diff --git a/pkg/analysis_server/lib/src/services/correction/assist_internal.dart b/pkg/analysis_server/lib/src/services/correction/assist_internal.dart
index 2dad5df1bbd..5f083c57667 100644
--- a/pkg/analysis_server/lib/src/services/correction/assist_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/assist_internal.dart
@@ -109,7 +109,7 @@ class AssistProcessor {
return _typeProvider;
}
- Future> compute() async {
+ Future> compute([AssistKind assistKind]) async {
// TODO(brianwilkerson) Determine whether this await is necessary.
await null;
try {
@@ -123,6 +123,13 @@ class AssistProcessor {
return assists;
}
+ // Calculate only specific assists for edit.dartFix
+ if (assistKind == DartAssistKind.CONVERT_CLASS_TO_MIXIN) {
+ await _addProposal_convertClassToMixin();
+ return assists;
+ }
+
+ // Calculate all assists
await _addProposal_addTypeAnnotation_DeclaredIdentifier();
await _addProposal_addTypeAnnotation_SimpleFormalParameter();
await _addProposal_addTypeAnnotation_VariableDeclaration();
diff --git a/pkg/analysis_server/test/integration/support/integration_test_methods.dart b/pkg/analysis_server/test/integration/support/integration_test_methods.dart
index 9d91f7514ff..ae805f47707 100644
--- a/pkg/analysis_server/test/integration/support/integration_test_methods.dart
+++ b/pkg/analysis_server/test/integration/support/integration_test_methods.dart
@@ -1506,12 +1506,13 @@ abstract class IntegrationTestMixin {
*
* A list of the files and directories for which edits should be suggested.
*
- * If a request is made with path that is invalid, e.g. is not absolute and
- * normalized, an error of type INVALID_FILE_PATH_FORMAT will be generated.
- * If a request is made for a file which does not exist, or which is not
- * currently subject to analysis (e.g. because it is not associated with
- * any analysis root specified to analysis.setAnalysisRoots), an error of
- * type FILE_NOT_ANALYZED will be generated.
+ * If a request is made with a path that is invalid, e.g. is not absolute
+ * and normalized, an error of type INVALID_FILE_PATH_FORMAT will be
+ * generated. If a request is made for a file which does not exist, or
+ * which is not currently subject to analysis (e.g. because it is not
+ * associated with any analysis root specified to
+ * analysis.setAnalysisRoots), an error of type FILE_NOT_ANALYZED will be
+ * generated.
*
* Returns
*
diff --git a/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java b/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java
index b01778110d1..ed29103b55d 100644
--- a/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java
+++ b/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java
@@ -413,11 +413,11 @@ public interface AnalysisServer {
* if a change in a specified source requires it.
*
* @param included A list of the files and directories for which edits should be suggested. If a
- * request is made with path that is invalid, e.g. is not absolute and normalized, an error
- * of type INVALID_FILE_PATH_FORMAT will be generated. If a request is made for a file
- * which does not exist, or which is not currently subject to analysis (e.g. because it is
- * not associated with any analysis root specified to analysis.setAnalysisRoots), an error
- * of type FILE_NOT_ANALYZED will be generated.
+ * request is made with a path that is invalid, e.g. is not absolute and normalized, an
+ * error of type INVALID_FILE_PATH_FORMAT will be generated. If a request is made for a
+ * file which does not exist, or which is not currently subject to analysis (e.g. because
+ * it is not associated with any analysis root specified to analysis.setAnalysisRoots), an
+ * error of type FILE_NOT_ANALYZED will be generated.
*/
public void edit_dartfix(List included, DartfixConsumer consumer);
diff --git a/pkg/analysis_server/tool/spec/spec_input.html b/pkg/analysis_server/tool/spec/spec_input.html
index 7469fc5e767..c854f7baa5e 100644
--- a/pkg/analysis_server/tool/spec/spec_input.html
+++ b/pkg/analysis_server/tool/spec/spec_input.html
@@ -1949,7 +1949,7 @@
A list of the files and directories for which edits should be suggested.
- If a request is made with path that is invalid, e.g. is not absolute and normalized,
+ If a request is made with a path that is invalid, e.g. is not absolute and normalized,
an error of type INVALID_FILE_PATH_FORMAT will be generated.
If a request is made for a file which does not exist, or which is not currently subject to analysis
(e.g. because it is not associated with any analysis root specified to analysis.setAnalysisRoots),
diff --git a/pkg/analyzer_cli/lib/src/fix/driver.dart b/pkg/analyzer_cli/lib/src/fix/driver.dart
index 1e022a72ad2..0a03c13961c 100644
--- a/pkg/analyzer_cli/lib/src/fix/driver.dart
+++ b/pkg/analyzer_cli/lib/src/fix/driver.dart
@@ -18,9 +18,9 @@ class Driver {
Completer serverConnected;
Completer analysisComplete;
- bool processAnalysisErrors;
- int errorCount;
bool verbose;
+ static const progressThreshold = 10;
+ int progressCount = progressThreshold;
Future start(List args) async {
final options = Options.parse(args);
@@ -31,11 +31,12 @@ class Driver {
}
verbose = options.verbose;
+ EditDartfixResult result;
await startServer(options);
bool normalShutdown = false;
try {
- await performAnalysis(options);
- await requestFixes(options);
+ await setupAnalysis(options);
+ result = await requestFixes(options);
normalShutdown = true;
} finally {
try {
@@ -46,6 +47,9 @@ class Driver {
}
}
}
+ if (result != null) {
+ applyFixes(result);
+ }
}
Future startServer(Options options) async {
@@ -63,11 +67,7 @@ class Driver {
});
}
- Future performAnalysis(Options options) async {
- analysisComplete = new Completer();
- processAnalysisErrors = true;
- errorCount = 0;
- outSink.writeln('Analyzing...');
+ Future setupAnalysis(Options options) async {
verboseOut('Setup analysis');
await server.send(SERVER_REQUEST_SET_SUBSCRIPTIONS,
new ServerSetSubscriptionsParams([ServerService.STATUS]).toJson());
@@ -77,29 +77,19 @@ class Driver {
options.analysisRoots,
const [],
).toJson());
- await analysisComplete.future;
- analysisComplete = null;
- processAnalysisErrors = false;
- verboseOut('Analysis complete.');
- if (errorCount > 0) {
- // TODO: Ask "Do you want to continue given # of errors?"
- }
}
- Future requestFixes(Options options) async {
- outSink.writeln('Calculating fixes...');
+ Future requestFixes(Options options) async {
+ outSink.write('Calculating fixes...');
+ verboseOut('');
+ analysisComplete = new Completer();
Map json = await server.send(EDIT_REQUEST_DARTFIX,
new EditDartfixParams(options.analysisRoots).toJson());
+ await analysisComplete.future;
+ analysisComplete = null;
+ resetProgress();
ResponseDecoder decoder = new ResponseDecoder(null);
- final result = EditDartfixResult.fromJson(decoder, 'result', json);
- if (result.description.isNotEmpty) {
- outSink.writeln('Recommended changes:');
- for (String line in result.description) {
- outSink.writeln(line);
- }
- } else {
- outSink.writeln('No recommended changes.');
- }
+ return EditDartfixResult.fromJson(decoder, 'result', json);
}
Future stopServer(Server server) async {
@@ -114,6 +104,31 @@ class Driver {
});
}
+ Future applyFixes(EditDartfixResult result) async {
+ // TODO(danrubel): Add flag to dartfix result indicating whether there
+ // were any errors detected that would compromise the suggested edits.
+ // If this flag is true, then the changes should not be applied
+ // or the user should be asked if they want to apply the changes.
+ if (!result.description.isNotEmpty) {
+ outSink.writeln('No recommended changes.');
+ return;
+ }
+ outSink.writeln('Recommended changes:');
+ List sorted = new List.from(result.description)..sort();
+ for (String line in sorted) {
+ outSink.writeln(line);
+ }
+ // TODO(danrubel): Apply the fixes rather than displaying them.
+ outSink.writeln('=== Source edits:');
+ for (SourceFileEdit fileEdit in result.fixes) {
+ outSink.writeln(fileEdit.file);
+ for (SourceEdit sourceEdit in fileEdit.edits) {
+ outSink.writeln(
+ ' ${sourceEdit.offset} ${sourceEdit.length} ${sourceEdit.replacement}');
+ }
+ }
+ }
+
/**
* Dispatch the notification named [event], and containing parameters
* [params], to the appropriate stream.
@@ -144,10 +159,8 @@ class Driver {
// decoder, 'params', params));
// break;
case ANALYSIS_NOTIFICATION_ERRORS:
- if (processAnalysisErrors) {
- onAnalysisErrors(
- new AnalysisErrorsParams.fromJson(decoder, 'params', params));
- }
+ onAnalysisErrors(
+ new AnalysisErrorsParams.fromJson(decoder, 'params', params));
break;
// case ANALYSIS_NOTIFICATION_FLUSH_RESULTS:
// outOfTestExpect(params, isAnalysisFlushResultsParams);
@@ -223,15 +236,15 @@ class Driver {
void onAnalysisErrors(AnalysisErrorsParams params) {
List errors = params.errors;
if (errors.isNotEmpty) {
+ resetProgress();
outSink.writeln(params.file);
for (AnalysisError error in errors) {
- if (error.severity == AnalysisErrorSeverity.ERROR) {
- ++errorCount;
- }
Location loc = error.location;
outSink.writeln(' ${error.message}'
' at ${loc.startLine}:${loc.startColumn}');
}
+ } else {
+ showProgress();
}
}
@@ -260,6 +273,20 @@ class Driver {
}
}
+ void resetProgress() {
+ if (!verbose && progressCount >= progressThreshold) {
+ outSink.writeln();
+ }
+ progressCount = 0;
+ }
+
+ void showProgress() {
+ if (!verbose && progressCount % progressThreshold == 0) {
+ outSink.write('.');
+ }
+ ++progressCount;
+ }
+
void verboseOut(String message) {
if (verbose) {
outSink.writeln(message);