blob: 9f0f65416386349a8eff2e570bdfce5e115aa980 [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/snippets/dart/dart_snippet_producers.dart';
import 'package:analysis_server/src/services/snippets/dart/snippet_manager.dart';
import 'package:analysis_server/src/utilities/flutter.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
import 'package:meta/meta.dart';
abstract class FlutterSnippetProducer extends DartSnippetProducer {
final flutter = Flutter.instance;
late ClassElement? classWidget;
FlutterSnippetProducer(DartSnippetRequest request) : super(request);
@override
@mustCallSuper
Future<bool> isValid() async {
if ((classWidget = await _getClass('Widget')) == null) {
return false;
}
return super.isValid();
}
Future<ClassElement?> _getClass(String name) =>
sessionHelper.getClass(flutter.widgetsUri, name);
DartType _getType(
ClassElement classElement, [
NullabilitySuffix nullabilitySuffix = NullabilitySuffix.none,
]) =>
classElement.instantiate(
typeArguments: const [],
nullabilitySuffix: nullabilitySuffix,
);
}
/// Produces a [Snippet] that creates a Flutter StatefulWidget and related State
/// class.
class FlutterStatefulWidgetSnippetProducer extends FlutterSnippetProducer
with FlutterWidgetSnippetProducerMixin {
static const prefix = 'stful';
static const label = 'Flutter Stateful Widget';
late ClassElement? classStatefulWidget;
late ClassElement? classState;
@override
late ClassElement? classBuildContext;
@override
late ClassElement? classKey;
FlutterStatefulWidgetSnippetProducer._(DartSnippetRequest request)
: super(request);
@override
Future<Snippet> compute() async {
final builder = ChangeBuilder(session: request.analysisSession);
// Checked by isValid().
final classStatefulWidget = this.classStatefulWidget!;
final classState = this.classState!;
await builder.addDartFileEdit(request.filePath, (builder) {
builder.addReplacement(request.replacementRange, (builder) {
// Write the StatefulWidget class
builder.writeClassDeclaration(
widgetClassName,
nameGroupName: 'name',
superclass: _getType(classStatefulWidget),
membersWriter: () {
writeWidgetConstructor(builder);
builder.writeln();
builder.writeln();
writeCreateStateMethod(builder);
},
);
builder.writeln();
builder.writeln();
// Write the State class.
builder.write('class _');
builder.addSimpleLinkedEdit('name', widgetClassName);
builder.write('State extends ');
builder.writeReference(classState);
builder.write('<');
builder.addSimpleLinkedEdit('name', widgetClassName);
builder.writeln('> {');
{
writeBuildMethod(builder);
}
builder.write('}');
});
});
return Snippet(
prefix,
label,
'Insert a Flutter StatefulWidget.',
builder.sourceChange,
);
}
@override
Future<bool> isValid() async {
if (!await super.isValid()) {
return false;
}
if ((classStatefulWidget = await _getClass('StatefulWidget')) == null ||
(classState = await _getClass('State')) == null ||
(classBuildContext = await _getClass('BuildContext')) == null ||
(classKey = await _getClass('Key')) == null) {
return false;
}
return true;
}
static FlutterStatefulWidgetSnippetProducer newInstance(
DartSnippetRequest request) =>
FlutterStatefulWidgetSnippetProducer._(request);
}
/// Produces a [Snippet] that creates a Flutter StatefulWidget with a
/// AnimationController and related State class.
class FlutterStatefulWidgetWithAnimationControllerSnippetProducer
extends FlutterSnippetProducer with FlutterWidgetSnippetProducerMixin {
static const prefix = 'stanim';
static const label = 'Flutter Widget with AnimationController';
late ClassElement? classStatefulWidget;
late ClassElement? classState;
@override
late ClassElement? classBuildContext;
@override
late ClassElement? classKey;
late ClassElement? classAnimationController;
late ClassElement? classSingleTickerProviderStateMixin;
FlutterStatefulWidgetWithAnimationControllerSnippetProducer._(
DartSnippetRequest request)
: super(request);
@override
Future<Snippet> compute() async {
final builder = ChangeBuilder(session: request.analysisSession);
// Checked by isValid().
final classStatefulWidget = this.classStatefulWidget!;
final classState = this.classState!;
final classAnimationController = this.classAnimationController!;
final classSingleTickerProviderStateMixin =
this.classSingleTickerProviderStateMixin!;
await builder.addDartFileEdit(request.filePath, (builder) {
builder.addReplacement(request.replacementRange, (builder) {
// Write the StatefulWidget class
builder.writeClassDeclaration(
widgetClassName,
nameGroupName: 'name',
superclass: _getType(classStatefulWidget),
membersWriter: () {
writeWidgetConstructor(builder);
builder.writeln();
builder.writeln();
writeCreateStateMethod(builder);
},
);
builder.writeln();
builder.writeln();
// Write the State class.
builder.write('class _');
builder.addSimpleLinkedEdit('name', widgetClassName);
builder.write('State extends ');
builder.writeReference(classState);
builder.write('<');
builder.addSimpleLinkedEdit('name', widgetClassName);
builder.writeln('>');
builder.write(' with ');
builder.writeReference(classSingleTickerProviderStateMixin);
builder.writeln(' {');
builder.write(' late ');
builder.writeReference(classAnimationController);
builder.writeln(' _controller;');
builder.writeln();
{
// Add the initState method.
builder.writeln(' @override');
builder.write(' ');
builder.writeFunctionDeclaration(
'initState',
returnType: VoidTypeImpl.instance,
bodyWriter: () {
builder.writeln('{');
builder.writeln(' super.initState();');
builder.write(' _controller = ');
builder.writeReference(classAnimationController);
builder.writeln('(vsync: this);');
builder.writeln(' }');
},
);
}
builder.writeln();
{
// Add the dispose method.
builder.writeln(' @override');
builder.write(' ');
builder.writeFunctionDeclaration(
'dispose',
returnType: VoidTypeImpl.instance,
bodyWriter: () {
builder.writeln('{');
builder.writeln(' super.dispose();');
builder.writeln(' _controller.dispose();');
builder.writeln(' }');
},
);
}
builder.writeln();
{
writeBuildMethod(builder);
}
builder.write('}');
});
});
return Snippet(
prefix,
label,
'Insert a Flutter StatefulWidget with an AnimationController.',
builder.sourceChange,
);
}
@override
Future<bool> isValid() async {
if (!await super.isValid()) {
return false;
}
if ((classStatefulWidget = await _getClass('StatefulWidget')) == null ||
(classState = await _getClass('State')) == null ||
(classBuildContext = await _getClass('BuildContext')) == null ||
(classKey = await _getClass('Key')) == null ||
(classAnimationController = await _getClass('AnimationController')) ==
null ||
(classSingleTickerProviderStateMixin =
await _getClass('SingleTickerProviderStateMixin')) ==
null) {
return false;
}
return true;
}
static FlutterStatefulWidgetWithAnimationControllerSnippetProducer
newInstance(DartSnippetRequest request) =>
FlutterStatefulWidgetWithAnimationControllerSnippetProducer._(
request);
}
/// Produces a [Snippet] that creates a Flutter StatelessWidget.
class FlutterStatelessWidgetSnippetProducer extends FlutterSnippetProducer
with FlutterWidgetSnippetProducerMixin {
static const prefix = 'stless';
static const label = 'Flutter Stateless Widget';
late ClassElement? classStatelessWidget;
@override
late ClassElement? classBuildContext;
@override
late ClassElement? classKey;
FlutterStatelessWidgetSnippetProducer._(DartSnippetRequest request)
: super(request);
@override
Future<Snippet> compute() async {
final builder = ChangeBuilder(session: request.analysisSession);
// Checked by isValid().
final classStatelessWidget = this.classStatelessWidget!;
await builder.addDartFileEdit(request.filePath, (builder) {
builder.addReplacement(request.replacementRange, (builder) {
builder.writeClassDeclaration(
widgetClassName,
nameGroupName: 'name',
superclass: _getType(classStatelessWidget),
membersWriter: () {
writeWidgetConstructor(builder);
builder.writeln();
builder.writeln();
writeBuildMethod(builder);
},
);
});
});
return Snippet(
prefix,
label,
'Insert a Flutter StatelessWidget.',
builder.sourceChange,
);
}
@override
Future<bool> isValid() async {
if (!await super.isValid()) {
return false;
}
if ((classStatelessWidget = await _getClass('StatelessWidget')) == null ||
(classBuildContext = await _getClass('BuildContext')) == null ||
(classKey = await _getClass('Key')) == null) {
return false;
}
return true;
}
static FlutterStatelessWidgetSnippetProducer newInstance(
DartSnippetRequest request) =>
FlutterStatelessWidgetSnippetProducer._(request);
}
/// A mixin that provides some common methods for producers that build snippets
/// for Flutter widget classes.
mixin FlutterWidgetSnippetProducerMixin on FlutterSnippetProducer {
ClassElement? get classBuildContext;
ClassElement? get classKey;
String get widgetClassName => 'MyWidget';
void writeBuildMethod(DartEditBuilder builder) {
// Checked by isValid() before this will be called.
final classBuildContext = this.classBuildContext!;
final classWidget = this.classWidget!;
// Add the build method.
builder.writeln(' @override');
builder.write(' ');
builder.writeFunctionDeclaration(
'build',
returnType: _getType(classWidget),
parameterWriter: () {
builder.writeParameter(
'context',
type: _getType(classBuildContext),
);
},
bodyWriter: () {
builder.writeln('{');
builder.write(' ');
builder.selectHere();
builder.writeln();
builder.writeln(' }');
},
);
}
void writeCreateStateMethod(DartEditBuilder builder) {
builder.writeln(' @override');
builder.write(' State<');
builder.addSimpleLinkedEdit('name', widgetClassName);
builder.write('> createState() => _');
builder.addSimpleLinkedEdit('name', widgetClassName);
builder.writeln('State();');
}
void writeWidgetConstructor(DartEditBuilder builder) {
// Checked by isValid() before this will be called.
final classKey = this.classKey!;
String keyName;
DartType? keyType;
void Function()? keyInitializer;
if (useSuperParams) {
keyName = 'super.key';
} else {
keyName = 'key';
keyType = _getType(classKey, nullableSuffix);
keyInitializer = () => builder.write('super(key: key)');
}
builder.write(' ');
builder.writeConstructorDeclaration(
widgetClassName,
classNameGroupName: 'name',
isConst: true,
parameterWriter: () {
builder.write('{');
builder.writeParameter(keyName, type: keyType);
builder.write('}');
},
initializerWriter: keyInitializer,
);
}
}