mirror of
https://github.com/dart-lang/sdk
synced 2024-10-06 13:08:01 +00:00
Copy the file header comment when creating a new file
Change-Id: I5d864c0d138f6d9389c56a312c111da1f9671081 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/265186 Commit-Queue: Brian Wilkerson <brianwilkerson@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
parent
b8ffc08d5b
commit
8d925319ca
|
@ -4,6 +4,7 @@
|
|||
|
||||
import 'package:analysis_server/lsp_protocol/protocol_custom_generated.dart';
|
||||
import 'package:analysis_server/src/services/refactoring/framework/refactoring_producer.dart';
|
||||
import 'package:analysis_server/src/utilities/extensions/ast.dart';
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
|
||||
import 'package:analyzer_plugin/utilities/range_factory.dart';
|
||||
|
@ -96,8 +97,18 @@ class MoveTopLevelToFile extends RefactoringProducer {
|
|||
if (importUri == null) {
|
||||
return;
|
||||
}
|
||||
var destinationExists =
|
||||
result.session.resourceProvider.getFile(destinationFilePath).exists;
|
||||
String? fileHeader;
|
||||
if (!destinationExists) {
|
||||
var headerTokens = result.unit.fileHeader;
|
||||
if (headerTokens.isNotEmpty) {
|
||||
var offset = headerTokens.first.offset;
|
||||
var end = headerTokens.last.end;
|
||||
fileHeader = utils.getText(offset, end - offset);
|
||||
}
|
||||
}
|
||||
await builder.addDartFileEdit(destinationFilePath, (builder) {
|
||||
// TODO(brianwilkerson) Copy the file header to the new file.
|
||||
// TODO(brianwilkerson) Use `ImportedElementsComputer` to add imports
|
||||
// required by the newly copied code. Better yet, combine that with the
|
||||
// import analysis used to find unused and unnecessary imports so that we
|
||||
|
@ -106,6 +117,10 @@ class MoveTopLevelToFile extends RefactoringProducer {
|
|||
// TODO(dantup): Ensure the range inserted and deleted match (allowing for
|
||||
// whitespace), including handling of leading/trailing comments etc.
|
||||
builder.addInsertion(0, (builder) {
|
||||
if (fileHeader != null) {
|
||||
builder.writeln(fileHeader);
|
||||
builder.writeln();
|
||||
}
|
||||
builder.writeln(utils.getNodeText(member.node));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -123,9 +123,54 @@ extension AstNodeExtensions on AstNode {
|
|||
}
|
||||
|
||||
extension CompilationUnitExtension on CompilationUnit {
|
||||
/// Is `true` if library being analyzed is non-nullable by default.
|
||||
/// Return the list of tokens that comprise the file header comment for this
|
||||
/// compilation unit.
|
||||
///
|
||||
/// Will return false if the AST structure has not been resolved.
|
||||
/// If there is no file comment the list will be empty. If the file comment is
|
||||
/// a block comment the list will contain a single token. If the file comment
|
||||
/// is comprised of one or more single line comments, then the list will
|
||||
/// contain all of the tokens, and the comment is assumed to stop at the first
|
||||
/// line that contains anything other than a single line comment (either a
|
||||
/// blank line, a directive, a declaration, or a multi-line comment). The list
|
||||
/// will never include a documentation comment.
|
||||
List<Token> get fileHeader {
|
||||
final lineInfo = this.lineInfo;
|
||||
var firstToken = beginToken;
|
||||
if (firstToken.type == TokenType.SCRIPT_TAG) {
|
||||
firstToken = firstToken.next!;
|
||||
}
|
||||
var firstComment = firstToken.precedingComments;
|
||||
if (firstComment == null ||
|
||||
firstComment.lexeme.startsWith('/**') ||
|
||||
firstComment.lexeme.startsWith('///')) {
|
||||
return const [];
|
||||
} else if (firstComment.lexeme.startsWith('/*')) {
|
||||
return [firstComment];
|
||||
} else if (!firstComment.lexeme.startsWith('//')) {
|
||||
return const [];
|
||||
}
|
||||
var header = <Token>[firstComment];
|
||||
var previousLine = lineInfo.getLocation(firstComment.offset).lineNumber;
|
||||
var currentToken = firstComment.next;
|
||||
while (currentToken != null) {
|
||||
if (!currentToken.lexeme.startsWith('//') ||
|
||||
currentToken.lexeme.startsWith('///')) {
|
||||
return header;
|
||||
}
|
||||
var currentLine = lineInfo.getLocation(currentToken.offset).lineNumber;
|
||||
if (currentLine != previousLine + 1) {
|
||||
return header;
|
||||
}
|
||||
header.add(currentToken);
|
||||
currentToken = currentToken.next;
|
||||
previousLine = currentLine;
|
||||
}
|
||||
return header;
|
||||
}
|
||||
|
||||
/// Return `true` if library being analyzed is non-nullable by default.
|
||||
///
|
||||
/// Will return `false` if the AST structure has not been resolved.
|
||||
bool get isNonNullableByDefault =>
|
||||
declaredElement?.library.isNonNullableByDefault ?? false;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,8 @@ class MoveTopLevelToFileTest extends RefactoringTest {
|
|||
|
||||
Future<void> test_class() async {
|
||||
var originalSource = '''
|
||||
// File header.
|
||||
|
||||
class A {}
|
||||
|
||||
class ClassToMove^ {}
|
||||
|
@ -57,6 +59,8 @@ class ClassToMove^ {}
|
|||
class B {}
|
||||
''';
|
||||
var modifiedSource = '''
|
||||
// File header.
|
||||
|
||||
class A {}
|
||||
|
||||
class B {}
|
||||
|
@ -64,6 +68,8 @@ class B {}
|
|||
var declarationName = 'ClassToMove';
|
||||
var newFileName = 'class_to_move.dart';
|
||||
var newFileContent = '''
|
||||
// File header.
|
||||
|
||||
class ClassToMove {}
|
||||
''';
|
||||
await _singleDeclaration(
|
||||
|
|
181
pkg/analysis_server/test/src/utilities/extensions/ast_test.dart
Normal file
181
pkg/analysis_server/test/src/utilities/extensions/ast_test.dart
Normal file
|
@ -0,0 +1,181 @@
|
|||
// 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/utilities/extensions/ast.dart';
|
||||
import 'package:test/expect.dart';
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
import '../../../services/search/search_engine_test.dart';
|
||||
|
||||
void main() {
|
||||
defineReflectiveSuite(() {
|
||||
defineReflectiveTests(CompilationUnitFileHeaderTest);
|
||||
});
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class CompilationUnitFileHeaderTest extends PubPackageResolutionTest {
|
||||
Future<void> test_afterScriptTag_multiLine() async {
|
||||
await resolveTestCode('''
|
||||
#! x
|
||||
/* a */
|
||||
|
||||
class C {}
|
||||
''');
|
||||
_assertTokens(['/* a */']);
|
||||
}
|
||||
|
||||
Future<void> test_afterScriptTag_singleLine() async {
|
||||
await resolveTestCode('''
|
||||
#! x
|
||||
// a
|
||||
// b
|
||||
|
||||
class C {}
|
||||
''');
|
||||
_assertTokens(['// a', '// b']);
|
||||
}
|
||||
|
||||
Future<void> test_afterScriptTag_withDocComment_multiLine() async {
|
||||
await resolveTestCode('''
|
||||
#! x
|
||||
/* a */
|
||||
|
||||
/// b
|
||||
class C {}
|
||||
''');
|
||||
_assertTokens(['/* a */']);
|
||||
}
|
||||
|
||||
Future<void> test_afterScriptTag_withDocComment_singleLine() async {
|
||||
await resolveTestCode('''
|
||||
#! x
|
||||
// a
|
||||
// b
|
||||
|
||||
/// c
|
||||
class C {}
|
||||
''');
|
||||
_assertTokens(['// a', '// b']);
|
||||
}
|
||||
|
||||
Future<void> test_none() async {
|
||||
await resolveTestCode('''
|
||||
class C {}
|
||||
''');
|
||||
expect(result.unit.fileHeader, isEmpty);
|
||||
}
|
||||
|
||||
Future<void> test_only_multiLine() async {
|
||||
await resolveTestCode('''
|
||||
/* a */
|
||||
|
||||
class C {}
|
||||
''');
|
||||
_assertTokens(['/* a */']);
|
||||
}
|
||||
|
||||
Future<void> test_only_singleLine() async {
|
||||
await resolveTestCode('''
|
||||
// a
|
||||
// b
|
||||
|
||||
class C {}
|
||||
''');
|
||||
_assertTokens(['// a', '// b']);
|
||||
}
|
||||
|
||||
Future<void> test_onlyDocComment_multiLine() async {
|
||||
await resolveTestCode('''
|
||||
/** a */
|
||||
class C {}
|
||||
''');
|
||||
expect(result.unit.fileHeader, isEmpty);
|
||||
}
|
||||
|
||||
Future<void> test_onlyDocComment_singleLine() async {
|
||||
await resolveTestCode('''
|
||||
/// a
|
||||
class C {}
|
||||
''');
|
||||
expect(result.unit.fileHeader, isEmpty);
|
||||
}
|
||||
|
||||
Future<void> test_withDocComment_multiLine() async {
|
||||
await resolveTestCode('''
|
||||
/* a
|
||||
*/
|
||||
|
||||
/// b
|
||||
class C {}
|
||||
''');
|
||||
_assertTokens(['/* a\n*/']);
|
||||
}
|
||||
|
||||
Future<void> test_withDocComment_singleLine() async {
|
||||
await resolveTestCode('''
|
||||
// a
|
||||
// b
|
||||
|
||||
/// c
|
||||
class C {}
|
||||
''');
|
||||
_assertTokens(['// a', '// b']);
|
||||
}
|
||||
|
||||
Future<void> test_withDocComment_singleLine_noBlankLine() async {
|
||||
await resolveTestCode('''
|
||||
// a
|
||||
/// b
|
||||
class C {}
|
||||
''');
|
||||
_assertTokens(['// a']);
|
||||
}
|
||||
|
||||
Future<void> test_withNonDocComment_multiLine_multiLine() async {
|
||||
await resolveTestCode('''
|
||||
/* a */
|
||||
/* b */
|
||||
class C {}
|
||||
''');
|
||||
_assertTokens(['/* a */']);
|
||||
}
|
||||
|
||||
Future<void> test_withNonDocComment_multiLine_singleLine() async {
|
||||
await resolveTestCode('''
|
||||
/* a */
|
||||
// b
|
||||
class C {}
|
||||
''');
|
||||
_assertTokens(['/* a */']);
|
||||
}
|
||||
|
||||
Future<void> test_withNonDocComment_singleLine_multiLine() async {
|
||||
await resolveTestCode('''
|
||||
// a
|
||||
// b
|
||||
/* c */
|
||||
class C {}
|
||||
''');
|
||||
_assertTokens(['// a', '// b']);
|
||||
}
|
||||
|
||||
Future<void> test_withNonDocComment_singleLine_singleLine() async {
|
||||
await resolveTestCode('''
|
||||
// a
|
||||
// b
|
||||
|
||||
// c
|
||||
class C {}
|
||||
''');
|
||||
_assertTokens(['// a', '// b']);
|
||||
}
|
||||
|
||||
/// Assert that the returned tokens have lexemes that match the [expected]
|
||||
/// comments.
|
||||
void _assertTokens(List<String> expected) {
|
||||
expect(result.unit.fileHeader.map((token) => token.lexeme),
|
||||
orderedEquals(expected));
|
||||
}
|
||||
}
|
|
@ -4,11 +4,13 @@
|
|||
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
import 'ast_test.dart' as ast;
|
||||
import 'range_factory_test.dart' as range_factory;
|
||||
import 'string_test.dart' as string;
|
||||
|
||||
void main() {
|
||||
defineReflectiveSuite(() {
|
||||
ast.main();
|
||||
range_factory.main();
|
||||
string.main();
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue