blob: 52c1307ce0841244115ab7ee27d79061717c7846 [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/snippets/dart_snippet_request.dart';
import 'package:analysis_server/src/services/snippets/snippet.dart';
import 'package:analysis_server/src/utilities/flutter.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/dart/element/type.dart';
import 'package:analyzer/src/dart/analysis/session_helper.dart';
import 'package:analyzer/src/lint/linter.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
import 'package:meta/meta.dart';
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);
}
}
abstract class FlutterSnippetProducer extends DartSnippetProducer {
final flutter = Flutter.instance;
late ClassElement? classWidget;
late ClassElement? classContainer;
FlutterSnippetProducer(super.request);
Future<ClassElement?> getClass(String name) =>
sessionHelper.getClass(flutter.widgetsUri, name);
DartType getType(
ClassElement classElement, [
NullabilitySuffix nullabilitySuffix = NullabilitySuffix.none,
]) =>
classElement.instantiate(
typeArguments: const [],
nullabilitySuffix: nullabilitySuffix,
);
@override
@mustCallSuper
Future<bool> isValid() async {
if ((classWidget = await getClass('Widget')) == null) {
return false;
}
if ((classContainer = await getClass('Container')) == null) {
return false;
}
return super.isValid();
}
}
/// 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!;
final classContainer = this.classContainer!;
// 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(' return ');
builder.selectAll(() {
builder.writeType(getType(classContainer));
builder.write('()');
});
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,
);
}
}
abstract class SnippetProducer {
final DartSnippetRequest request;
SnippetProducer(this.request);
Future<Snippet> compute();
Future<bool> isValid() async {
// File edit builders will not produce edits for files outside of the
// analysis roots so we should not try to produce any snippets.
final analysisContext = request.analysisSession.analysisContext;
return analysisContext.contextRoot.isAnalyzed(request.filePath);
}
}