Skip to content

Commit

Permalink
Add "convert to int literal" to dartfix
Browse files Browse the repository at this point in the history
This updates the analysis server edit.dartfix request
to find and convert double literals to int literals.
Under the covers it uses the prefer_int_literal lint
and gracefully degrades if the lint is not found
(e.g. third_party/linter needs to be rolled).

Change-Id: I0236af9c19667e543541fef18836bfeeba8c67d7
Reviewed-on: https://dart-review.googlesource.com/c/81302
Reviewed-by: Brian Wilkerson <[email protected]>
Commit-Queue: Dan Rubel <[email protected]>
  • Loading branch information
Dan Rubel authored and [email protected] committed Oct 24, 2018
1 parent 0009ca7 commit 4695b5f
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 72 deletions.
112 changes: 40 additions & 72 deletions pkg/analysis_server/lib/src/edit/edit_dartfix.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@
// 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/plugin/edit/fix/fix_core.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:analysis_server/src/edit/fix/prefer_int_literals_fix.dart';
import 'package:analysis_server/src/edit/fix/prefer_mixin_fix.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server/src/services/correction/fix_internal.dart';
import 'package:analyzer/analyzer.dart';
Expand Down Expand Up @@ -78,30 +77,36 @@ class EditDartFix {
}

// Get the desired lints
final LintRule preferMixin = Registry.ruleRegistry['prefer_mixin'];
if (preferMixin == null) {
return new Response.serverError(
request, 'Missing prefer_mixin lint', null);
}
final lintRules = Registry.ruleRegistry;

final preferMixin = lintRules['prefer_mixin'];
final preferMixinFix = new PreferMixinFix(this);
preferMixin.reporter = preferMixinFix;

final preferIntLiterals = lintRules['prefer_int_literals'];
final preferIntLiteralsFix = new PreferIntLiteralsFix(this);
preferIntLiterals?.reporter = preferIntLiteralsFix;

// Setup
final linters = <Linter>[
preferMixin,
preferIntLiterals,
];
final fixes = <LinterFix>[
preferMixinFix,
preferIntLiteralsFix,
];
final visitors = <AstVisitor>[];
final registry = new NodeLintRegistry(false);
for (Linter linter in linters) {
final visitor = linter.getVisitor();
if (visitor != null) {
visitors.add(visitor);
}
if (linter is NodeLintRule) {
(linter as NodeLintRule).registerNodeProcessors(registry);
if (linter != null) {
final visitor = linter.getVisitor();
if (visitor != null) {
visitors.add(visitor);
}
if (linter is NodeLintRule) {
(linter as NodeLintRule).registerNodeProcessors(registry);
}
}
}
final AstVisitor astVisitor = visitors.isNotEmpty
Expand Down Expand Up @@ -149,23 +154,36 @@ class EditDartFix {
}
Source source = result.sourceFactory.forUri2(result.uri);
for (Linter linter in linters) {
linter.reporter.source = source;
if (linter != null) {
linter.reporter.source = source;
}
}
if (astVisitor != null) {
unit.accept(astVisitor);
}
unit.accept(linterVisitor);
for (LinterFix fix in fixes) {
await fix.applyLocalFixes(result);
}
}
}

// Cleanup
for (Linter linter in linters) {
linter.reporter = null;
if (linter != null) {
linter.reporter.source = null;
linter.reporter = null;
}
}

// Apply distributed fixes
if (preferIntLiterals == null) {
// TODO(danrubel): Remove this once linter rolled into sdk/third_party.
addRecommendation('*** Convert double literal not available'
' because prefer_int_literal not found. May need to roll linter');
}
for (LinterFix fix in fixes) {
await fix.applyFix();
await fix.applyRemainingFixes();
}

return new EditDartfixResult(descriptionOfFixes, otherRecommendations,
Expand Down Expand Up @@ -279,7 +297,11 @@ abstract class LinterFix implements ErrorReporter {

LinterFix(this.dartFix);

Future<void> applyFix();
/// Apply fixes for the current compilation unit.
Future<void> applyLocalFixes(AnalysisResult result);

/// Apply any fixes remaining after analysis is complete.
Future<void> applyRemainingFixes();

@override
void reportError(AnalysisError error) {
Expand Down Expand Up @@ -328,57 +350,3 @@ abstract class LinterFix implements ErrorReporter {
// ignored
}
}

class PreferMixinFix extends LinterFix {
final classesToConvert = new Set<Element>();

PreferMixinFix(EditDartFix dartFix) : super(dartFix);

@override
Future<void> applyFix() async {
for (Element elem in classesToConvert) {
await convertClassToMixin(elem);
}
}

Future<void> convertClassToMixin(Element elem) async {
AnalysisResult result =
await dartFix.server.getAnalysisResult(elem.source?.fullName);

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<Assist> assists = await processor
.computeAssist(DartAssistKind.CONVERT_CLASS_TO_MIXIN);
final location = dartFix.locationDescription(result, elem.nameOffset);
if (assists.isNotEmpty) {
for (Assist assist in assists) {
dartFix.addFix(
'Convert ${elem.displayName} to a mixin in $location',
assist.change);
}
} else {
// TODO(danrubel): If assists is empty, then determine why
// assist could not be performed and report that in the description.
dartFix.addRecommendation(
'Could not convert ${elem.displayName} to a mixin'
' because the class contains a constructor in $location');
}
}
}
}

@override
void reportErrorForNode(ErrorCode errorCode, AstNode node,
[List<Object> arguments]) {
TypeName type = node;
Element element = type.name.staticElement;
String filePath = element.source?.fullName;
if (filePath != null && dartFix.isIncluded(filePath)) {
classesToConvert.add(element);
}
}
}
56 changes: 56 additions & 0 deletions pkg/analysis_server/lib/src/edit/fix/prefer_int_literals_fix.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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/src/edit/edit_dartfix.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/src/dart/analysis/driver.dart';

class PreferIntLiteralsFix extends LinterFix {
final literalsToConvert = <DoubleLiteral>[];

PreferIntLiteralsFix(EditDartFix dartFix) : super(dartFix);

@override
Future<void> applyLocalFixes(AnalysisResult result) async {
while (literalsToConvert.isNotEmpty) {
DoubleLiteral literal = literalsToConvert.removeLast();
AssistProcessor processor = new AssistProcessor(
new EditDartFixAssistContext(dartFix, source, result.unit, literal));
List<Assist> assists =
await processor.computeAssist(DartAssistKind.CONVERT_TO_INT_LITERAL);
final location = dartFix.locationDescription(result, literal.offset);
if (assists.isNotEmpty) {
for (Assist assist in assists) {
dartFix.addFix(
'Replace a double literal with an int literal in $location',
assist.change);
}
} else {
// TODO(danrubel): If assists is empty, then determine why
// assist could not be performed and report that in the description.
dartFix.addRecommendation('Could not replace'
' a double literal with an int literal in $location');
}
}
}

@override
Future<void> applyRemainingFixes() {
// All fixes applied in [applyLocalFixes]
return null;
}

@override
void reportErrorForNode(ErrorCode errorCode, AstNode node,
[List<Object> arguments]) {
String filePath = source.fullName;
if (filePath != null && dartFix.isIncluded(filePath)) {
literalsToConvert.add(node);
}
}
}
72 changes: 72 additions & 0 deletions pkg/analysis_server/lib/src/edit/fix/prefer_mixin_fix.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// 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/src/edit/edit_dartfix.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';
import 'package:analyzer/src/dart/analysis/driver.dart';

class PreferMixinFix extends LinterFix {
final classesToConvert = new Set<Element>();

PreferMixinFix(EditDartFix dartFix) : super(dartFix);

@override
Future<void> applyLocalFixes(AnalysisResult result) {
// All fixes applied in [applyRemainingFixes]
return null;
}

@override
Future<void> applyRemainingFixes() async {
for (Element elem in classesToConvert) {
await convertClassToMixin(elem);
}
}

Future<void> convertClassToMixin(Element elem) async {
AnalysisResult result =
await dartFix.server.getAnalysisResult(elem.source?.fullName);

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<Assist> assists = await processor
.computeAssist(DartAssistKind.CONVERT_CLASS_TO_MIXIN);
final location = dartFix.locationDescription(result, elem.nameOffset);
if (assists.isNotEmpty) {
for (Assist assist in assists) {
dartFix.addFix(
'Convert ${elem.displayName} to a mixin in $location',
assist.change);
}
} else {
// TODO(danrubel): If assists is empty, then determine why
// assist could not be performed and report that in the description.
dartFix.addRecommendation(
'Could not convert ${elem.displayName} to a mixin'
' because the class contains a constructor in $location');
}
}
}
}

@override
void reportErrorForNode(ErrorCode errorCode, AstNode node,
[List<Object> arguments]) {
TypeName type = node;
Element element = type.name.staticElement;
String filePath = element.source?.fullName;
if (filePath != null && dartFix.isIncluded(filePath)) {
classesToConvert.add(element);
}
}
}

0 comments on commit 4695b5f

Please sign in to comment.