blob: b943eda7a0d8197f9e321bf3a3289dc510924769 [file] [log] [blame]
// 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/util.dart';
import 'package:analysis_server/src/services/linter/lint_names.dart';
import 'package:analysis_server/src/services/snippets/dart/snippet_manager.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/src/dart/analysis/session_helper.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/lint/linter.dart' show LinterContextImpl;
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
/// Produces a [Snippet] that creates a Class definition.
class DartClassSnippetProducer extends DartSnippetProducer {
static const prefix = 'class';
static const label = 'class';
DartClassSnippetProducer._(super.request);
@override
Future<Snippet> compute() async {
final builder = ChangeBuilder(session: request.analysisSession);
final indent = utils.getLinePrefix(request.offset);
await builder.addDartFileEdit(request.filePath, (builder) {
builder.addReplacement(request.replacementRange, (builder) {
void writeIndented(String string) => builder.write('$indent$string');
builder.write('class ');
builder.addSimpleLinkedEdit('className', 'ClassName');
builder.writeln(' {');
writeIndented(' ');
builder.selectHere();
builder.writeln();
writeIndented('}');
});
});
return Snippet(
prefix,
label,
'Insert a class definition.',
builder.sourceChange,
);
}
static DartClassSnippetProducer newInstance(DartSnippetRequest request) =>
DartClassSnippetProducer._(request);
}
/// Produces a [Snippet] that creates a `do while` loop.
class DartDoWhileLoopSnippetProducer extends DartSnippetProducer {
static const prefix = 'do';
static const label = 'do while';
DartDoWhileLoopSnippetProducer._(super.request);
@override
Future<Snippet> compute() async {
final builder = ChangeBuilder(session: request.analysisSession);
final indent = utils.getLinePrefix(request.offset);
await builder.addDartFileEdit(request.filePath, (builder) {
builder.addReplacement(request.replacementRange, (builder) {
void writeIndented(String string) => builder.write('$indent$string');
builder.writeln('do {');
writeIndented(' ');
builder.selectHere();
builder.writeln();
writeIndented('} while (');
builder.addSimpleLinkedEdit('condition', 'condition');
builder.write(');');
});
});
return Snippet(
prefix,
label,
'Insert a do-while loop.',
builder.sourceChange,
);
}
static DartDoWhileLoopSnippetProducer newInstance(
DartSnippetRequest request) =>
DartDoWhileLoopSnippetProducer._(request);
}
/// Produces a [Snippet] that creates a `for in` loop.
class DartForInLoopSnippetProducer extends DartSnippetProducer {
static const prefix = 'forin';
static const label = 'for in';
DartForInLoopSnippetProducer._(super.request);
@override
Future<Snippet> compute() async {
final builder = ChangeBuilder(session: request.analysisSession);
final indent = utils.getLinePrefix(request.offset);
final varOrFinal =
isLintEnabled(LintNames.prefer_final_locals) ? 'final' : 'var';
await builder.addDartFileEdit(request.filePath, (builder) {
builder.addReplacement(request.replacementRange, (builder) {
void writeIndented(String string) => builder.write('$indent$string');
builder.write('for ($varOrFinal ');
builder.addSimpleLinkedEdit('elementName', 'element');
builder.write(' in ');
builder.addSimpleLinkedEdit('collectionName', 'collection');
builder.writeln(') {');
writeIndented(' ');
builder.selectHere();
builder.writeln();
writeIndented('}');
});
});
return Snippet(
prefix,
label,
'Insert a for-in loop.',
builder.sourceChange,
);
}
static DartForInLoopSnippetProducer newInstance(DartSnippetRequest request) =>
DartForInLoopSnippetProducer._(request);
}
/// Produces a [Snippet] that creates a `for` loop.
class DartForLoopSnippetProducer extends DartSnippetProducer {
static const prefix = 'for';
static const label = 'for';
DartForLoopSnippetProducer._(super.request);
@override
Future<Snippet> compute() async {
final builder = ChangeBuilder(session: request.analysisSession);
final indent = utils.getLinePrefix(request.offset);
await builder.addDartFileEdit(request.filePath, (builder) {
builder.addReplacement(request.replacementRange, (builder) {
void writeIndented(String string) => builder.write('$indent$string');
builder.write('for (var i = 0; i < ');
builder.addSimpleLinkedEdit('count', 'count');
builder.writeln('; i++) {');
writeIndented(' ');
builder.selectHere();
builder.writeln();
writeIndented('}');
});
});
return Snippet(
prefix,
label,
'Insert a for loop.',
builder.sourceChange,
);
}
static DartForLoopSnippetProducer newInstance(DartSnippetRequest request) =>
DartForLoopSnippetProducer._(request);
}
/// Produces a [Snippet] that creates a function definition.
class DartFunctionSnippetProducer extends DartSnippetProducer {
static const prefix = 'fun';
static const label = 'fun';
DartFunctionSnippetProducer._(super.request);
@override
Future<Snippet> compute() async {
final builder = ChangeBuilder(session: request.analysisSession);
final indent = utils.getLinePrefix(request.offset);
await builder.addDartFileEdit(request.filePath, (builder) {
builder.addReplacement(request.replacementRange, (builder) {
void writeIndented(String string) => builder.write('$indent$string');
builder.addSimpleLinkedEdit('returnType', 'void');
builder.write(' ');
builder.addSimpleLinkedEdit('name', 'name');
builder.write('(');
builder.addSimpleLinkedEdit('params', 'params');
builder.writeln(') {');
writeIndented(' ');
builder.selectHere();
builder.writeln();
writeIndented('}');
});
});
return Snippet(
prefix,
label,
'Insert a function definition.',
builder.sourceChange,
);
}
static DartFunctionSnippetProducer newInstance(DartSnippetRequest request) =>
DartFunctionSnippetProducer._(request);
}
/// Produces a [Snippet] that creates an if/else statement.
class DartIfElseSnippetProducer extends DartSnippetProducer {
static const prefix = 'ife';
static const label = 'ife';
DartIfElseSnippetProducer._(super.request);
@override
Future<Snippet> compute() async {
final builder = ChangeBuilder(session: request.analysisSession);
final indent = utils.getLinePrefix(request.offset);
await builder.addDartFileEdit(request.filePath, (builder) {
builder.addReplacement(request.replacementRange, (builder) {
void writeIndented(String string) => builder.write('$indent$string');
void writeIndentedln(String string) =>
builder.writeln('$indent$string');
builder.write('if (');
builder.addSimpleLinkedEdit('condition', 'condition');
builder.writeln(') {');
writeIndented(' ');
builder.selectHere();
builder.writeln();
writeIndentedln('} else {');
writeIndentedln(' ');
writeIndented('}');
});
});
return Snippet(
prefix,
label,
'Insert an if/else statement.',
builder.sourceChange,
);
}
static DartIfElseSnippetProducer newInstance(DartSnippetRequest request) =>
DartIfElseSnippetProducer._(request);
}
/// Produces a [Snippet] that creates an if statement.
class DartIfSnippetProducer extends DartSnippetProducer {
static const prefix = 'if';
static const label = 'if';
DartIfSnippetProducer._(super.request);
@override
Future<Snippet> compute() async {
final builder = ChangeBuilder(session: request.analysisSession);
final indent = utils.getLinePrefix(request.offset);
await builder.addDartFileEdit(request.filePath, (builder) {
builder.addReplacement(request.replacementRange, (builder) {
void writeIndented(String string) => builder.write('$indent$string');
builder.write('if (');
builder.addSimpleLinkedEdit('condition', 'condition');
builder.writeln(') {');
writeIndented(' ');
builder.selectHere();
builder.writeln();
writeIndented('}');
});
});
return Snippet(
prefix,
label,
'Insert an if statement.',
builder.sourceChange,
);
}
static DartIfSnippetProducer newInstance(DartSnippetRequest request) =>
DartIfSnippetProducer._(request);
}
/// Produces a [Snippet] that creates a top-level `main` function.
///
/// A `List<String> args` parameter will be included when generating inside a
/// file in `bin` or `tool` folders.
class DartMainFunctionSnippetProducer extends DartSnippetProducer {
static const prefix = 'main';
static const label = 'main()';
DartMainFunctionSnippetProducer._(super.request);
/// Whether to insert a `List<String> args` parameter in the generated
/// function.
///
/// The parameter is suppressed for any known test directories.
bool get _insertArgsParameter => !isInTestDirectory;
@override
Future<Snippet> compute() async {
final builder = ChangeBuilder(session: request.analysisSession);
final typeProvider = request.unit.typeProvider;
final listString = typeProvider.listType(typeProvider.stringType);
await builder.addDartFileEdit(request.filePath, (builder) {
builder.addReplacement(request.replacementRange, (builder) {
builder.writeFunctionDeclaration(
'main',
returnType: VoidTypeImpl.instance,
parameterWriter: _insertArgsParameter
? () => builder.writeParameter('args', type: listString)
: null,
bodyWriter: () {
builder.writeln('{');
builder.write(' ');
builder.selectHere();
builder.writeln();
builder.write('}');
},
);
});
});
return Snippet(
prefix,
label,
'Insert a main function, used as an entry point.',
builder.sourceChange,
);
}
static DartMainFunctionSnippetProducer newInstance(
DartSnippetRequest request) =>
DartMainFunctionSnippetProducer._(request);
}
abstract class DartSnippetProducer extends SnippetProducer {
final AnalysisSessionHelper sessionHelper;
final CorrectionUtils utils;
final LibraryElement libraryElement;
final bool useSuperParams;
DartSnippetProducer(super.request)
: sessionHelper = AnalysisSessionHelper(request.analysisSession),
utils = CorrectionUtils(request.unit),
libraryElement = request.unit.libraryElement,
useSuperParams = request.unit.libraryElement.featureSet
.isEnabled(Feature.super_parameters);
bool get isInTestDirectory {
final path = request.unit.path;
return LinterContextImpl.testDirectories
.any((testDir) => path.contains(testDir));
}
/// The nullable suffix to use in this library.
NullabilitySuffix get nullableSuffix => libraryElement.isNonNullableByDefault
? NullabilitySuffix.question
: NullabilitySuffix.none;
bool isLintEnabled(String name) {
var analysisOptions = sessionHelper.session.analysisContext.analysisOptions;
return analysisOptions.isLintEnabled(name);
}
}
/// Produces a [Snippet] that creates an if statement.
class DartSwitchSnippetProducer extends DartSnippetProducer {
static const prefix = 'switch';
static const label = 'switch case';
DartSwitchSnippetProducer._(super.request);
@override
Future<Snippet> compute() async {
final builder = ChangeBuilder(session: request.analysisSession);
final indent = utils.getLinePrefix(request.offset);
await builder.addDartFileEdit(request.filePath, (builder) {
builder.addReplacement(request.replacementRange, (builder) {
void writeIndented(String string) => builder.write('$indent$string');
void writeIndentedln(String string) =>
builder.writeln('$indent$string');
builder.write('switch (');
builder.addSimpleLinkedEdit('expression', 'expression');
builder.writeln(') {');
writeIndented(' case ');
builder.addSimpleLinkedEdit('value', 'value');
builder.writeln(':');
writeIndented(' ');
builder.selectHere();
builder.writeln();
writeIndentedln(' break;');
writeIndentedln(' default:');
writeIndented('}');
});
});
return Snippet(
prefix,
label,
'Insert a switch statement.',
builder.sourceChange,
);
}
static DartSwitchSnippetProducer newInstance(DartSnippetRequest request) =>
DartSwitchSnippetProducer._(request);
}
/// Produces a [Snippet] that creates a `test()` block.
class DartTestBlockSnippetProducer extends DartSnippetProducer {
static const prefix = 'test';
static const label = 'test';
DartTestBlockSnippetProducer._(super.request);
@override
Future<Snippet> compute() async {
final builder = ChangeBuilder(session: request.analysisSession);
final indent = utils.getLinePrefix(request.offset);
await builder.addDartFileEdit(request.filePath, (builder) {
builder.addReplacement(request.replacementRange, (builder) {
void writeIndented(String string) => builder.write('$indent$string');
builder.write("test('");
builder.addSimpleLinkedEdit('testName', 'test name');
builder.writeln("', () {");
writeIndented(' ');
builder.selectHere();
builder.writeln();
writeIndented('});');
});
});
return Snippet(
prefix,
label,
'Insert a test block.',
builder.sourceChange,
);
}
@override
Future<bool> isValid() async {
if (!await super.isValid()) {
return false;
}
return isInTestDirectory;
}
static DartTestBlockSnippetProducer newInstance(DartSnippetRequest request) =>
DartTestBlockSnippetProducer._(request);
}
/// Produces a [Snippet] that creates a `test()` block.
class DartTestGroupBlockSnippetProducer extends DartSnippetProducer {
static const prefix = 'group';
static const label = 'group';
DartTestGroupBlockSnippetProducer._(super.request);
@override
Future<Snippet> compute() async {
final builder = ChangeBuilder(session: request.analysisSession);
final indent = utils.getLinePrefix(request.offset);
await builder.addDartFileEdit(request.filePath, (builder) {
builder.addReplacement(request.replacementRange, (builder) {
void writeIndented(String string) => builder.write('$indent$string');
builder.write("group('");
builder.addSimpleLinkedEdit('groupName', 'group name');
builder.writeln("', () {");
writeIndented(' ');
builder.selectHere();
builder.writeln();
writeIndented('});');
});
});
return Snippet(
prefix,
label,
'Insert a test group block.',
builder.sourceChange,
);
}
@override
Future<bool> isValid() async {
if (!await super.isValid()) {
return false;
}
return isInTestDirectory;
}
static DartTestGroupBlockSnippetProducer newInstance(
DartSnippetRequest request) =>
DartTestGroupBlockSnippetProducer._(request);
}
/// Produces a [Snippet] that creates a try/catch statement.
class DartTryCatchSnippetProducer extends DartSnippetProducer {
static const prefix = 'try';
static const label = 'try';
DartTryCatchSnippetProducer._(super.request);
@override
Future<Snippet> compute() async {
final builder = ChangeBuilder(session: request.analysisSession);
final indent = utils.getLinePrefix(request.offset);
await builder.addDartFileEdit(request.filePath, (builder) {
builder.addReplacement(request.replacementRange, (builder) {
void writeIndented(String string) => builder.write('$indent$string');
void writeIndentedln(String string) =>
builder.writeln('$indent$string');
builder.writeln('try {');
writeIndented(' ');
builder.selectHere();
builder.writeln();
writeIndented('} catch (');
builder.addLinkedEdit('exceptionName', (builder) {
builder.write('e');
});
builder.writeln(') {');
writeIndentedln(' ');
writeIndented('}');
});
});
return Snippet(
prefix,
label,
'Insert a try/catch statement.',
builder.sourceChange,
);
}
static DartTryCatchSnippetProducer newInstance(DartSnippetRequest request) =>
DartTryCatchSnippetProducer._(request);
}
/// Produces a [Snippet] that creates a `while` loop.
class DartWhileLoopSnippetProducer extends DartSnippetProducer {
static const prefix = 'while';
static const label = 'while';
DartWhileLoopSnippetProducer._(super.request);
@override
Future<Snippet> compute() async {
final builder = ChangeBuilder(session: request.analysisSession);
final indent = utils.getLinePrefix(request.offset);
await builder.addDartFileEdit(request.filePath, (builder) {
builder.addReplacement(request.replacementRange, (builder) {
void writeIndented(String string) => builder.write('$indent$string');
builder.write('while (');
builder.addSimpleLinkedEdit('condition', 'condition');
builder.writeln(') {');
writeIndented(' ');
builder.selectHere();
builder.writeln();
writeIndented('}');
});
});
return Snippet(
prefix,
label,
'Insert a while loop.',
builder.sourceChange,
);
}
static DartWhileLoopSnippetProducer newInstance(DartSnippetRequest request) =>
DartWhileLoopSnippetProducer._(request);
}