Skip to content

Commit

Permalink
[analysis server] add fix for dangling_library_doc_comments
Browse files Browse the repository at this point in the history
Change-Id: I5bd3ce04fc85a698983cbbd04bab61fedadfca6c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/272481
Reviewed-by: Brian Wilkerson <[email protected]>
Commit-Queue: Samuel Rawlins <[email protected]>
  • Loading branch information
srawlins authored and Commit Queue committed Nov 29, 2022
1 parent 8b48d78 commit e1acab8
Show file tree
Hide file tree
Showing 7 changed files with 284 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright (c) 2022, 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:_fe_analyzer_shared/src/scanner/token.dart';
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';
import 'package:collection/collection.dart';

class MoveDocCommentToLibraryDirective extends CorrectionProducer {
@override
FixKind get fixKind => DartFixKind.MOVE_DOC_COMMENT_TO_LIBRARY_DIRECTIVE;

@override
Future<void> compute(ChangeBuilder builder) async {
var comment = node.thisOrAncestorOfType<Comment>();
if (comment == null) {
return;
}
var compilationUnit = comment.root;
if (compilationUnit is! CompilationUnit) {
return;
}

var firstDirective = compilationUnit.directives.firstOrNull;
if (firstDirective is LibraryDirective) {
await _moveToExistingLibraryDirective(builder, comment, firstDirective);
} else if (libraryElement.featureSet.isEnabled(Feature.unnamedLibraries)) {
await _moveToNewLibraryDirective(builder, comment, compilationUnit);
}

// If the library doesn't support unnamed libraries, then we cannot add
// a new library directive; we don't know what to name it.
}

Future<void> _moveToExistingLibraryDirective(ChangeBuilder builder,
Comment comment, LibraryDirective libraryDirective) async {
// Just move the annotation to the existing library directive.
var commentRange = utils.getLinesRange(range.node(comment));
await builder.addDartFileEdit(file, (builder) {
builder.addDeletion(commentRange);
var commentText = utils.getRangeText(commentRange);
builder.addSimpleInsertion(
libraryDirective.firstTokenAfterCommentAndMetadata.offset,
commentText);
});
}

Future<void> _moveToNewLibraryDirective(ChangeBuilder builder,
Comment comment, CompilationUnit compilationUnit) async {
var commentRange = _rangeOfFirstBlock(comment, compilationUnit.lineInfo);

// Create a new, unnamed library directive, and move the comment to just
// above the directive.
var token = compilationUnit.beginToken;

if (token.type == TokenType.SCRIPT_TAG) {
// TODO(srawlins): Handle this case.
return;
}

if (token.precedingComments == comment.beginToken) {
// Do not "move" the comment. Just slip a library directive below it.
await builder.addDartFileEdit(file, (builder) {
builder.addSimpleInsertion(commentRange.end, 'library;$eol');
});
return;
}

int insertionOffset;
String prefix;
Token? commentOnFirstToken = token.precedingComments;
if (commentOnFirstToken != null) {
while (commentOnFirstToken!.next != null) {
commentOnFirstToken = commentOnFirstToken.next!;

if (commentOnFirstToken == comment.beginToken) {
// Do not "move" the comment. Just slip a library directive below it.
await builder.addDartFileEdit(file, (builder) {
builder.addSimpleInsertion(commentRange.end, 'library;$eol$eol');
});
return;
}
}
// `token` is now the last of the leading comments (perhaps a Copyight
// notice, a Dart language version, etc.)
insertionOffset = commentOnFirstToken.end;
prefix = '$eol$eol';
} else {
insertionOffset = 0;
prefix = '';
}

await builder.addDartFileEdit(file, (builder) {
builder.addDeletion(commentRange);
var commentText = utils.getRangeText(commentRange);
builder.addSimpleInsertion(
insertionOffset, '$prefix${commentText}library;$eol$eol');
});
}

/// The range of the first "block" in [comment].
///
/// A [Comment] can contain blank lines (even an end-of-line comment, and an
/// end-of-line doc comment). But for the purpose of this fix, we interpret
/// only the first "block" or "paragraph" of text as what was intented to be
/// the library comment.
SourceRange _rangeOfFirstBlock(Comment comment, LineInfo lineInfo) {
for (var token in comment.tokens) {
var next = token.next;
if (next != null &&
lineInfo.getLocation(next.offset).lineNumber >
lineInfo.getLocation(token.end).lineNumber + 1) {
// There is a blank line. Interpret this as two separate doc comments.
return utils.getLinesRange(range.startEnd(comment.tokens.first, token));
}
}
return utils.getLinesRange(range.node(comment));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1819,7 +1819,7 @@ LintCode.control_flow_in_finally:
LintCode.curly_braces_in_flow_control_structures:
status: hasFix
LintCode.dangling_library_doc_comments:
status: needsEvaluation
status: hasFix
LintCode.depend_on_referenced_packages:
status: needsFix
LintCode.deprecated_consistency:
Expand Down
5 changes: 5 additions & 0 deletions pkg/analysis_server/lib/src/services/correction/fix.dart
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,11 @@ class DartFixKind {
DartFixKindPriority.DEFAULT,
"Move this annotation to a library directive",
);
static const MOVE_DOC_COMMENT_TO_LIBRARY_DIRECTIVE = FixKind(
'dart.fix.moveDocCommentToLibraryDirective',
DartFixKindPriority.DEFAULT,
"Move this doc comment to a library directive",
);
static const ORGANIZE_IMPORTS = FixKind(
'dart.fix.organize.imports',
DartFixKindPriority.DEFAULT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ import 'package:analysis_server/src/services/correction/dart/make_return_type_nu
import 'package:analysis_server/src/services/correction/dart/make_variable_not_final.dart';
import 'package:analysis_server/src/services/correction/dart/make_variable_nullable.dart';
import 'package:analysis_server/src/services/correction/dart/move_annotation_to_library_directive.dart';
import 'package:analysis_server/src/services/correction/dart/move_doc_comment_to_library_directive.dart';
import 'package:analysis_server/src/services/correction/dart/move_type_arguments_to_class.dart';
import 'package:analysis_server/src/services/correction/dart/organize_imports.dart';
import 'package:analysis_server/src/services/correction/dart/qualify_reference.dart';
Expand Down Expand Up @@ -461,6 +462,9 @@ class FixProcessor extends BaseProcessor {
LintNames.curly_braces_in_flow_control_structures: [
UseCurlyBraces.new,
],
LintNames.dangling_library_doc_comments: [
MoveDocCommentToLibraryDirective.new,
],
LintNames.diagnostic_describe_all_properties: [
AddDiagnosticPropertyReference.new,
],
Expand Down
2 changes: 2 additions & 0 deletions pkg/analysis_server/lib/src/services/linter/lint_names.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class LintNames {
static const String combinators_ordering = 'combinators_ordering';
static const String curly_braces_in_flow_control_structures =
'curly_braces_in_flow_control_structures';
static const String dangling_library_doc_comments =
'dangling_library_doc_comments';
static const String diagnostic_describe_all_properties =
'diagnostic_describe_all_properties';
static const String directives_ordering = 'directives_ordering';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright (c) 2022, 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/services/correction/fix.dart';
import 'package:analysis_server/src/services/linter/lint_names.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import 'fix_processor.dart';

void main() {
defineReflectiveSuite(() {
defineReflectiveTests(MoveDocCommentToLibraryDirectiveTest);
});
}

@reflectiveTest
class MoveDocCommentToLibraryDirectiveTest extends FixProcessorLintTest {
@override
FixKind get kind => DartFixKind.MOVE_DOC_COMMENT_TO_LIBRARY_DIRECTIVE;

@override
String get lintCode => LintNames.dangling_library_doc_comments;

Future<void> test_commentsAreFirst() async {
await resolveTestCode('''
// Comment 1.
// Comment 2.
/// Doc comment.
import 'dart:async';
void f(Completer c) {}
''');
await assertHasFix('''
// Comment 1.
// Comment 2.
/// Doc comment.
library;
import 'dart:async';
void f(Completer c) {}
''');
}

Future<void> test_commentsAreFirst_andAnnotations() async {
await resolveTestCode('''
// Comment 1.
// Comment 2.
@deprecated
/// Doc comment.
import 'dart:async';
void f(Completer c) {}
''');
// TODO(srawlins): Fix the 4 newlines below; should be 2.
await assertHasFix('''
// Comment 1.
// Comment 2.
/// Doc comment.
library;
@deprecated
import 'dart:async';
void f(Completer c) {}
''');
}

Future<void> test_docCommentIsFirst_aboveDeclaration() async {
await resolveTestCode('''
/// Doc comment.
void f() {}
''');
await assertHasFix('''
/// Doc comment.
library;
void f() {}
''');
}

Future<void> test_docCommentIsFirst_aboveDeclarationWithComment() async {
await resolveTestCode('''
/// Library comment.
// Regular comment.
class C {}
''');
await assertHasFix('''
/// Library comment.
library;
// Regular comment.
class C {}
''');
}

Future<void> test_docCommentIsFirst_aboveDeclarationWithDocComment() async {
await resolveTestCode('''
/// Library comment.
/// Class comment.
class C {}
''');
await assertHasFix('''
/// Library comment.
library;
/// Class comment.
class C {}
''');
}

Future<void> test_docCommentIsFirst_aboveDirective() async {
await resolveTestCode('''
/// Doc comment.
import 'dart:async';
void f(Completer c) {}
''');
await assertHasFix('''
/// Doc comment.
library;
import 'dart:async';
void f(Completer c) {}
''');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ import 'make_variable_not_final_test.dart' as make_variable_not_final;
import 'make_variable_nullable_test.dart' as make_variable_nullable;
import 'move_annotation_to_library_directive_test.dart'
as move_annotation_to_library_directive;
import 'move_doc_comment_to_library_directive_test.dart'
as move_doc_comment_to_library_directive;
import 'move_type_arguments_to_class_test.dart' as move_type_arguments_to_class;
import 'organize_imports_test.dart' as organize_imports;
import 'pubspec/test_all.dart' as pubspec;
Expand Down Expand Up @@ -364,6 +366,7 @@ void main() {
make_variable_not_final.main();
make_variable_nullable.main();
move_annotation_to_library_directive.main();
move_doc_comment_to_library_directive.main();
move_type_arguments_to_class.main();
organize_imports.main();
pubspec.main();
Expand Down

0 comments on commit e1acab8

Please sign in to comment.