adding packages

This commit is contained in:
2026-01-15 14:38:46 -08:00
parent ef86e3ab6a
commit 6869cf47e5
5253 changed files with 726695 additions and 34 deletions
@@ -0,0 +1,6 @@
import 'package:build/build.dart';
import 'package:settings_gen/settings_gen.dart';
import 'package:source_gen/source_gen.dart';
Builder storeGenerator(BuilderOptions options) =>
SharedPartBuilder([StoreGenerator()], 'store_generator');
@@ -0,0 +1,5 @@
library settings_gen;
export 'src/settings_gen_base.dart';
const version = '1.0.1';
@@ -0,0 +1,204 @@
abstract class CodegenError {
bool get hasErrors;
String get message;
}
class StoreClassCodegenErrors implements CodegenError {
StoreClassCodegenErrors(this.name) {
_errorCategories = [
nonAbstractStoreMixinDeclarations,
invalidComputedAnnotations,
invalidObservableAnnotations,
invalidActionAnnotations,
staticObservables,
finalObservables,
];
}
final String name;
final NonAbstractStoreMixinDeclarations nonAbstractStoreMixinDeclarations =
NonAbstractStoreMixinDeclarations();
final PropertyErrors finalObservables = FinalObservableFields();
final PropertyErrors staticObservables = StaticObservableFields();
final PropertyErrors invalidObservableAnnotations =
InvalidObservableAnnotations();
final PropertyErrors invalidComputedAnnotations =
InvalidComputedAnnotations();
final PropertyErrors invalidActionAnnotations = InvalidActionAnnotations();
List<CodegenError> _errorCategories;
@override
String get message {
final errors = _errorCategories
.where((category) => category.hasErrors)
.toList(growable: false)
.asMap()
.map((i, category) => MapEntry(i, ' ${i + 1}. ${category.message}'))
.values
.join('\n');
return 'Could not make class "$name" observable. Changes needed:\n$errors';
}
@override
bool get hasErrors => _errorCategories.any((category) => category.hasErrors);
}
final _fieldPluralizer = Pluralize('the field', 'fields');
final _methodPluralizer = Pluralize('the method', 'methods');
final _memberPluralizer = Pluralize('the member', 'members');
abstract class _InvalidStoreDeclarations implements CodegenError {
final NameList _classNames = NameList();
// ignore: avoid_positional_boolean_parameters
bool addIf(bool condition, String className) {
if (condition) {
_classNames.add(className);
}
return condition;
}
@override
bool get hasErrors => _classNames.isNotEmpty;
}
class NonAbstractStoreMixinDeclarations extends _InvalidStoreDeclarations {
@override
String get message =>
'Classes that use the SettingsStore mixin must be marked abstract. Affected classes: $_classNames.';
}
abstract class PropertyErrors implements CodegenError {
final NameList _properties = NameList();
// ignore: avoid_positional_boolean_parameters
bool addIf(bool condition, String propertyName) {
if (condition) {
_properties.add(propertyName);
}
return condition;
}
String get propertyList => _properties.toString();
Pluralize propertyPlural = _fieldPluralizer;
String get property => propertyPlural(_properties.length);
@override
bool get hasErrors => _properties.isNotEmpty;
}
class FinalObservableFields extends PropertyErrors {
@override
String get message => 'Remove final modifier from $property $propertyList.';
}
class StaticObservableFields extends PropertyErrors {
@override
String get message => 'Remove static modifier from $property $propertyList.';
}
class AsyncGeneratorActionMethods extends PropertyErrors {
@override
// ignore: overridden_fields
Pluralize propertyPlural = _methodPluralizer;
@override
String get message =>
'Replace async* modifier with async from $property $propertyList.';
}
class NonAsyncMethods extends PropertyErrors {
@override
// ignore: overridden_fields
Pluralize propertyPlural = _methodPluralizer;
@override
String get message =>
'Return a Future or a Stream from $property $propertyList.';
}
class InvalidComputedAnnotations extends PropertyErrors {
@override
// ignore: overridden_fields
Pluralize propertyPlural = _memberPluralizer;
@override
String get message =>
'Remove @computed annotation for $property $propertyList. They only apply to property-getters.';
}
class InvalidObservableAnnotations extends PropertyErrors {
@override
// ignore: overridden_fields
Pluralize propertyPlural = _memberPluralizer;
@override
String get message =>
'Remove @observable annotation for $property $propertyList. They only apply to fields.';
}
class InvalidActionAnnotations extends PropertyErrors {
@override
// ignore: overridden_fields
Pluralize propertyPlural = _memberPluralizer;
@override
String get message =>
'Remove @action annotation for $property $propertyList. They only apply to methods.';
}
class InvalidStaticMethods extends PropertyErrors {
@override
// ignore: overridden_fields
Pluralize propertyPlural = _methodPluralizer;
@override
String get message => 'Remove static modifier from $property $propertyList.';
}
class NameList {
final List<String> _names = [];
void add(String name) => _names.add(name);
int get length => _names.length;
bool get isNotEmpty => _names.isNotEmpty;
@override
String toString() {
if (_names.length == 1) {
return '"${_names[0]}"';
}
final buf = StringBuffer();
for (var i = 0; i < _names.length; i++) {
final name = _names[i];
buf.write('"$name"');
if (i < _names.length - 2) {
buf.write(', ');
} else if (i == _names.length - 2) {
buf.write(' and ');
}
}
return buf.toString();
}
}
class Pluralize {
Pluralize(this._single, this._multiple);
final String _single;
final String _multiple;
String call(int count) => count == 1 ? _single : _multiple;
}
@@ -0,0 +1,71 @@
import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type_system.dart';
import 'package:build/build.dart';
import 'package:settings_gen/src/store_class_visitor.dart';
import 'package:settings_gen/src/template/store_file.dart';
import 'package:settings_gen/src/template/store.dart';
import 'package:settings_gen/src/type_names.dart';
import 'package:source_gen/source_gen.dart';
class StoreGenerator extends Generator {
@override
FutureOr<String> generate(LibraryReader library, BuildStep buildStep) async {
if (library.allElements.isEmpty) {
return '';
}
final typeSystem = await library.allElements.first.session.typeSystem;
final file = StoreFileTemplate()
..storeSources = _generateCodeForLibrary(library, typeSystem).toSet();
return file.toString();
}
Iterable<String> _generateCodeForLibrary(
LibraryReader library,
TypeSystem typeSystem,
) sync* {
for (final classElement in library.classes) {
if (isMixinStoreClass(classElement)) {
yield* _generateCodeForMixinStore(library, classElement, typeSystem);
}
}
}
Iterable<String> _generateCodeForMixinStore(
LibraryReader library,
ClassElement baseClass,
TypeSystem typeSystem,
) sync* {
final typeNameFinder = LibraryScopedNameFinder(library.element);
final otherClasses = library.classes.where((c) => c != baseClass);
final mixedClass = otherClasses.firstWhere((c) {
if (baseClass.typeParameters.length != c.supertype.typeArguments.length) {
return false;
}
return typeSystem.isSubtypeOf(
c.type, baseClass.type.instantiate(c.supertype.typeArguments));
}, orElse: () => null);
if (mixedClass != null) {
yield _generateCodeFromTemplate(
mixedClass.name, baseClass, MixinStoreTemplate(), typeNameFinder);
}
}
String _generateCodeFromTemplate(
String publicTypeName,
ClassElement userStoreClass,
StoreTemplate template,
LibraryScopedNameFinder typeNameFinder,
) {
final visitor = StoreClassVisitor(
publicTypeName, userStoreClass, template, typeNameFinder);
userStoreClass
..accept(visitor)
..visitChildren(visitor);
return visitor.source;
}
}
@@ -0,0 +1,166 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/visitor.dart';
import 'package:build/build.dart';
import 'package:settings_gen/src/template/double_setting.dart';
import 'package:settings_gen/src/template/int_setting.dart';
import 'package:settings_gen/src/template/string_list_setting.dart';
import 'package:settings_manager/settings_manager.dart';
import 'package:settings_manager/src/api/annotations.dart'
show BoolSetting, SettingsConfig;
import 'package:source_gen/source_gen.dart';
import 'errors.dart';
import 'template/bool_setting.dart';
import 'template/store.dart';
import 'template/string_setting.dart';
import 'template/util.dart';
import 'type_names.dart';
class StoreClassVisitor extends SimpleElementVisitor {
StoreClassVisitor(
String publicTypeName,
ClassElement userClass,
StoreTemplate template,
this.typeNameFinder,
) : _errors = StoreClassCodegenErrors(publicTypeName) {
_storeTemplate = template
..typeParams.templates.addAll(userClass.typeParameters
.map((type) => typeParamTemplate(type, typeNameFinder)))
..typeArgs.templates.addAll(userClass.typeParameters.map((t) => t.name))
..parentTypeName = userClass.name
..publicTypeName = publicTypeName;
}
final _boolSettingChecker = const TypeChecker.fromRuntime(BoolSetting);
final _stringSettingChecker = const TypeChecker.fromRuntime(StringSetting);
final _intSettingChecker = const TypeChecker.fromRuntime(IntSetting);
final _doubleSettingChecker = const TypeChecker.fromRuntime(DoubleSetting);
final _stringListSettingChecker =
const TypeChecker.fromRuntime(StringListSetting);
StoreTemplate _storeTemplate;
LibraryScopedNameFinder typeNameFinder;
final StoreClassCodegenErrors _errors;
String get source {
if (_errors.hasErrors) {
log.severe(_errors.message);
return '';
}
return _storeTemplate.toString();
}
@override
void visitClassElement(ClassElement element) {
if (isMixinStoreClass(element)) {
_errors.nonAbstractStoreMixinDeclarations
.addIf(!element.isAbstract, element.name);
}
// if the class is annotated to generate toString() method we add the information to the _storeTemplate
_storeTemplate.generateToString = hasGeneratedToString(element);
}
@override
void visitFieldElement(FieldElement element) {
if (_fieldIsNotValid(element)) {
return;
}
if (_boolSettingChecker.hasAnnotationOfExact(element)) {
final annotation = _boolSettingChecker.firstAnnotationOfExact(element);
final template = BoolSettingTemplate()
..defaultValue = annotation.getField('defaultValue').toBoolValue()
..addStream = annotation.getField('addStream').toBoolValue()
..addValueNotifier =
annotation.getField('addValueNotifier').toBoolValue()
..isPrivate = element.isPrivate
..name = element.name;
_storeTemplate.boolSettings.add(template);
}
if (_stringSettingChecker.hasAnnotationOfExact(element)) {
final annotation = _stringSettingChecker.firstAnnotationOfExact(element);
final template = StringSettingTemplate()
..defaultValue = annotation.getField('defaultValue').toStringValue()
..addStream = annotation.getField('addStream').toBoolValue()
..addValueNotifier =
annotation.getField('addValueNotifier').toBoolValue()
..isPrivate = element.isPrivate
..name = element.name;
_storeTemplate.stringSettings.add(template);
}
if (_intSettingChecker.hasAnnotationOfExact(element)) {
final annotation = _intSettingChecker.firstAnnotationOfExact(element);
final template = IntSettingTemplate()
..defaultValue = annotation.getField('defaultValue').toIntValue()
..addStream = annotation.getField('addStream').toBoolValue()
..addValueNotifier =
annotation.getField('addValueNotifier').toBoolValue()
..isPrivate = element.isPrivate
..name = element.name;
_storeTemplate.intSettings.add(template);
}
if (_doubleSettingChecker.hasAnnotationOfExact(element)) {
final annotation = _doubleSettingChecker.firstAnnotationOfExact(element);
final template = DoubleSettingTemplate()
..defaultValue = annotation.getField('defaultValue').toDoubleValue()
..addStream = annotation.getField('addStream').toBoolValue()
..addValueNotifier =
annotation.getField('addValueNotifier').toBoolValue()
..isPrivate = element.isPrivate
..name = element.name;
_storeTemplate.doubleSettings.add(template);
}
if (_stringListSettingChecker.hasAnnotationOfExact(element)) {
final annotation =
_stringListSettingChecker.firstAnnotationOfExact(element);
final template = StringListSettingTemplate()
..defaultValue = annotation
.getField('defaultValue')
.toListValue()
.map((item) => item.toStringValue())
.toList()
..addStream = annotation.getField('addStream').toBoolValue()
..addValueNotifier =
annotation.getField('addValueNotifier').toBoolValue()
..isPrivate = element.isPrivate
..name = element.name;
_storeTemplate.stringListSettings.add(template);
}
return;
}
bool _fieldIsNotValid(FieldElement element) => _any([
_errors.staticObservables.addIf(element.isStatic, element.name),
_errors.finalObservables.addIf(element.isFinal, element.name)
]);
}
const _storeMixinChecker = TypeChecker.fromRuntime(SettingsStore);
const _toStringAnnotationChecker = TypeChecker.fromRuntime(SettingsConfig);
bool isMixinStoreClass(ClassElement classElement) =>
classElement.mixins.any(_storeMixinChecker.isExactlyType);
// Checks if the class as a toString annotation
bool isStoreConfigAnnotatedStoreClass(ClassElement classElement) =>
_toStringAnnotationChecker.hasAnnotationOfExact(classElement);
bool hasGeneratedToString(ClassElement classElement) {
if (isStoreConfigAnnotatedStoreClass(classElement)) {
final annotation =
_toStringAnnotationChecker.firstAnnotationOfExact(classElement);
return annotation.getField('hasToString').toBoolValue();
}
return true;
}
bool _any(List<bool> list) => list.any(_identity);
T _identity<T>(T value) => value;
@@ -0,0 +1,77 @@
import 'setting_impl.dart';
class BoolSettingTemplate implements SettingsImpl {
bool defaultValue;
String name;
bool isPrivate;
bool addStream = true;
bool addValueNotifier = true;
@override
String preInit() {
return '${name}Notify($defaultValue);';
}
@override
String postInit() {
return '${name}Notify($name);';
}
@override
String dispose() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(' _${name}Controller.close();');
}
return sb.toString();
}
@override
String toString() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(
'final _${name}Controller = StreamController<bool>.broadcast();');
sb.writeln(
'Stream<bool> get ${name}Stream => _${name}Controller.stream;');
}
if (addValueNotifier) {
sb.writeln('final _${name}Notifier = ValueNotifier<bool>($defaultValue);');
sb.writeln('ValueListenable<bool> get ${name}Notifier => _${name}Notifier;');
}
sb.writeln("""
@override
bool get $name {
return prefs?.getBool('$name') ?? $defaultValue;
}
@override
set $name(bool value) {
${name}Async(value);
}
Future<bool> ${name}Async(bool value) async {
final success = await prefs.setBool('$name', value);
if (success) {
${name}Notify(value);
}
return success;
}
""");
sb.writeln('void ${name}Notify(bool value) {');
if (addStream) {
sb.writeln(' _${name}Controller.add(value);');
}
if (addValueNotifier) {
sb.writeln('_${name}Notifier.value = value;');
}
sb.writeln('_controller.add(this);');
sb.writeln('}');
return sb.toString();
}
}
@@ -0,0 +1,25 @@
import 'package:settings_gen/src/template/util.dart';
class CommaList<T> {
CommaList(this.templates) : assert(templates != null);
final List<T> templates;
@override
String toString() =>
templates.map((t) => t.toString()).where((s) => s.isNotEmpty).join(', ');
}
class SurroundedCommaList<T> {
SurroundedCommaList(this.prefix, this.suffix, this.templates)
: assert(prefix != null),
assert(suffix != null),
assert(templates != null);
final String prefix;
final String suffix;
final List<T> templates;
@override
String toString() => surroundNonEmpty(prefix, suffix, CommaList(templates));
}
@@ -0,0 +1,78 @@
import 'setting_impl.dart';
class DoubleSettingTemplate implements SettingsImpl {
double defaultValue;
String name;
bool isPrivate;
bool addStream = true;
bool addValueNotifier = true;
@override
String preInit() {
return '${name}Notify($defaultValue);';
}
@override
String postInit() {
return '${name}Notify($name);';
}
@override
String dispose() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(' _${name}Controller.close();');
}
return sb.toString();
}
@override
String toString() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(
'final _${name}Controller = StreamController<double>.broadcast();');
sb.writeln(
'Stream<double> get ${name}Stream => _${name}Controller.stream;');
}
if (addValueNotifier) {
sb.writeln('final _${name}Notifier = ValueNotifier<double>($defaultValue);');
sb.writeln(
'ValueListenable<double> get ${name}Notifier => _${name}Notifier;');
}
sb.writeln("""
@override
double get $name {
return prefs?.getDouble('$name') ?? $defaultValue;
}
@override
set $name(double value) {
${name}Async(value);
}
Future<bool> ${name}Async(double value) async {
final success = await prefs.setDouble('$name', value);
if (success) {
${name}Notify(value);
}
return success;
}
""");
sb.writeln('void ${name}Notify(double value) {');
if (addStream) {
sb.writeln(' _${name}Controller.add(value);');
}
if (addValueNotifier) {
sb.writeln('_${name}Notifier.value = value;');
}
sb.writeln('_controller.add(this);');
sb.writeln('}');
return sb.toString();
}
}
@@ -0,0 +1,76 @@
import 'setting_impl.dart';
class IntSettingTemplate implements SettingsImpl {
int defaultValue;
String name;
bool isPrivate;
bool addStream = true;
bool addValueNotifier = true;
@override
String preInit() {
return '${name}Notify($defaultValue);';
}
@override
String postInit() {
return '${name}Notify($name);';
}
@override
String dispose() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(' _${name}Controller.close();');
}
return sb.toString();
}
@override
String toString() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(
'final _${name}Controller = StreamController<int>.broadcast();');
sb.writeln('Stream<int> get ${name}Stream => _${name}Controller.stream;');
}
if (addValueNotifier) {
sb.writeln('final _${name}Notifier = ValueNotifier<int>($defaultValue);');
sb.writeln('ValueListenable<int> get ${name}Notifier => _${name}Notifier;');
}
sb.writeln("""
@override
int get $name {
return prefs?.getInt('$name') ?? $defaultValue;
}
@override
set $name(int value) {
${name}Async(value);
}
Future<bool> ${name}Async(int value) async {
final success = await prefs.setInt('$name', value);
if (success) {
${name}Notify(value);
}
return success;
}
""");
sb.writeln('void ${name}Notify(int value) {');
if (addStream) {
sb.writeln(' _${name}Controller.add(value);');
}
if (addValueNotifier) {
sb.writeln('_${name}Notifier.value = value;');
}
sb.writeln('_controller.add(this);');
sb.writeln('}');
return sb.toString();
}
}
@@ -0,0 +1,34 @@
class ParamTemplate {
String name;
String type;
String defaultValue;
bool hasRequiredAnnotation = false;
String get asArgument => name;
NamedArgTemplate get asNamedArgument => NamedArgTemplate()..name = name;
String get metadata => hasRequiredAnnotation ? '@required ' : '';
@override
String toString() => defaultValue == null
? '$metadata$type $name'
: '$type $name = $defaultValue';
}
class TypeParamTemplate {
String name;
String bound;
String get asArgument => name;
@override
String toString() => bound == null ? name : '$name extends $bound';
}
class NamedArgTemplate {
String name;
@override
String toString() => '$name: $name';
}
@@ -0,0 +1,12 @@
class Rows<T> {
final List<T> _templates = [];
void add(T template) => _templates.add(template);
bool get isEmpty => _templates.isEmpty;
List get templates => _templates;
@override
String toString() =>
_templates.map((t) => t.toString()).where((s) => s.isNotEmpty).join('\n');
}
@@ -0,0 +1,5 @@
abstract class SettingsImpl {
String preInit();
String postInit();
String dispose();
}
@@ -0,0 +1,140 @@
import 'bool_setting.dart';
import 'comma_list.dart';
import 'double_setting.dart';
import 'int_setting.dart';
import 'params.dart';
import 'rows.dart';
import 'setting_impl.dart';
import 'string_list_setting.dart';
import 'string_setting.dart';
class MixinStoreTemplate extends StoreTemplate {
String get typeName => '_\$$publicTypeName';
@override
String toString() => '''
mixin $typeName$typeParams on $parentTypeName$typeArgs, SettingsStore {
$storeBody
}''';
}
abstract class StoreTemplate {
final SurroundedCommaList<TypeParamTemplate> typeParams =
SurroundedCommaList('<', '>', []);
final SurroundedCommaList<String> typeArgs =
SurroundedCommaList('<', '>', []);
String publicTypeName;
String parentTypeName;
final Rows<BoolSettingTemplate> boolSettings = Rows();
final Rows<StringSettingTemplate> stringSettings = Rows();
final Rows<IntSettingTemplate> intSettings = Rows();
final Rows<DoubleSettingTemplate> doubleSettings = Rows();
final Rows<StringListSettingTemplate> stringListSettings = Rows();
final List<String> toStringList = [];
bool generateToString = false;
String _actionControllerName;
String get actionControllerName =>
_actionControllerName ??= '_\$${parentTypeName}ActionController';
String get storeBody {
var toStringMethod = '';
final sb = StringBuffer();
sb.writeln('SharedPreferences prefs;');
sb.writeln(
'final _controller = StreamController<$publicTypeName>.broadcast();');
sb.writeln('Stream<$publicTypeName> get stream => _controller.stream;');
sb.writeln();
List<SettingsImpl> _settingsImpl = [];
_settingsImpl.addAll(
boolSettings.templates.whereType<SettingsImpl>().map((t) => t).toList(),
);
_settingsImpl.addAll(
stringSettings.templates.whereType<SettingsImpl>().map((t) => t).toList(),
);
_settingsImpl.addAll(
intSettings.templates.whereType<SettingsImpl>().map((t) => t).toList(),
);
_settingsImpl.addAll(
doubleSettings.templates.whereType<SettingsImpl>().map((t) => t).toList(),
);
_settingsImpl.addAll(
stringListSettings.templates
.whereType<SettingsImpl>()
.map((t) => t)
.toList(),
);
sb.writeln(' Future<bool> init() async {');
for (final setting in _settingsImpl) {
sb.writeln(setting.preInit());
}
sb.writeln(' prefs = await SharedPreferences.getInstance();');
for (final setting in _settingsImpl) {
sb.writeln(setting.postInit());
}
sb.writeln(' return prefs != null;');
sb.writeln(' }');
sb.writeln();
sb.writeln('$boolSettings');
sb.writeln();
sb.writeln('$stringSettings');
sb.writeln();
sb.writeln('$intSettings');
sb.writeln();
sb.writeln('$doubleSettings');
sb.writeln();
sb.writeln('$stringListSettings');
sb.writeln();
sb.writeln(' void dispose() {');
for (final setting in _settingsImpl) {
sb.writeln(setting.dispose());
}
sb.writeln('_controller.close();');
sb.writeln(' }');
sb.writeln();
final baseBody = sb.toString();
if (generateToString) {
final publicBoolSettings = boolSettings.templates
..removeWhere((element) => element.isPrivate);
final publicStringSettings = stringSettings.templates
..removeWhere((element) => element.isPrivate);
final publicIntSettings = intSettings.templates
..removeWhere((element) => element.isPrivate);
final publicDoubleSettings = doubleSettings.templates
..removeWhere((element) => element.isPrivate);
final publicStringListSettings = doubleSettings.templates
..removeWhere((element) => element.isPrivate);
toStringList
..addAll(publicBoolSettings.map(
(current) => '${current.name}: \${${current.name}.toString()}'))
..addAll(publicStringSettings.map(
(current) => '${current.name}: \${${current.name}.toString()}'))
..addAll(publicIntSettings.map(
(current) => '${current.name}: \${${current.name}.toString()}'))
..addAll(publicDoubleSettings.map(
(current) => '${current.name}: \${${current.name}.toString()}'))
..addAll(publicStringListSettings.map(
(current) => '${current.name}: \${${current.name}.toString()}'));
toStringMethod = '''
@override
String toString() {
final string = \'${toStringList.join(',')}\';
return '{\$string}';
}
''';
}
return baseBody + toStringMethod;
}
@override
String toString();
}
@@ -0,0 +1,15 @@
const _analyzerIgnores =
'// ignore_for_file: non_constant_identifier_names, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic';
class StoreFileTemplate {
Iterable<String> storeSources;
@override
String toString() => storeSources.isEmpty
? ''
: '''
$_analyzerIgnores
${storeSources.join('\n\n')}
''';
}
@@ -0,0 +1,77 @@
import 'setting_impl.dart';
class StringListSettingTemplate implements SettingsImpl {
List<String> defaultValue;
String name;
bool isPrivate;
bool addStream = true;
bool addValueNotifier = true;
@override
String preInit() {
return "${name}Notify($defaultValue);";
}
@override
String postInit() {
return '${name}Notify($name);';
}
@override
String dispose() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(' _${name}Controller.close();');
}
return sb.toString();
}
@override
String toString() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(
'final _${name}Controller = StreamController<List<String>>.broadcast();');
sb.writeln(
'Stream<List<String>> get ${name}Stream => _${name}Controller.stream;');
}
if (addValueNotifier) {
sb.writeln('final _${name}Notifier = ValueNotifier<List<String>>($defaultValue);');
sb.writeln('ValueListenable<List<String>> get ${name}Notifier => _${name}Notifier;');
}
sb.writeln("""
@override
List<String> get $name {
return prefs?.getStringList('$name') ?? $defaultValue;
}
@override
set $name(List<String> value) {
${name}Async(value);
}
Future<bool> ${name}Async(List<String> value) async {
final success = await prefs.setStringList('$name', value);
if (success) {
${name}Notify(value);
}
return success;
}
""");
sb.writeln('void ${name}Notify(List<String> value) {');
if (addStream) {
sb.writeln(' _${name}Controller.add(value);');
}
if (addValueNotifier) {
sb.writeln('_${name}Notifier.value = value;');
}
sb.writeln('_controller.add(this);');
sb.writeln('}');
return sb.toString();
}
}
@@ -0,0 +1,77 @@
import 'setting_impl.dart';
class StringSettingTemplate implements SettingsImpl {
String defaultValue;
String name;
bool isPrivate;
bool addStream = true;
bool addValueNotifier = true;
@override
String preInit() {
return "${name}Notify('$defaultValue');";
}
@override
String postInit() {
return '${name}Notify($name);';
}
@override
String dispose() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(' _${name}Controller.close();');
}
return sb.toString();
}
@override
String toString() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(
'final _${name}Controller = StreamController<String>.broadcast();');
sb.writeln(
'Stream<String> get ${name}Stream => _${name}Controller.stream;');
}
if (addValueNotifier) {
sb.writeln("final _${name}Notifier = ValueNotifier<String>('$defaultValue');");
sb.writeln('ValueListenable<String> get ${name}Notifier => _${name}Notifier;');
}
sb.writeln("""
@override
String get $name {
return prefs?.getString('$name') ?? '$defaultValue';
}
@override
set $name(String value) {
${name}Async(value);
}
Future<bool> ${name}Async(String value) async {
final success = await prefs.setString('$name', value);
if (success) {
${name}Notify(value);
}
return success;
}
""");
sb.writeln('void ${name}Notify(String value) {');
if (addStream) {
sb.writeln(' _${name}Controller.add(value);');
}
if (addValueNotifier) {
sb.writeln('_${name}Notifier.value = value;');
}
sb.writeln('_controller.add(this);');
sb.writeln('}');
return sb.toString();
}
}
@@ -0,0 +1,42 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:settings_gen/src/template/params.dart';
import 'package:settings_gen/src/type_names.dart';
import 'package:source_gen/source_gen.dart';
// ignore: avoid_annotating_with_dynamic
String surroundNonEmpty(String prefix, String suffix, dynamic content) {
final contentStr = content.toString();
return contentStr.isEmpty ? '' : '$prefix$contentStr$suffix';
}
const _streamChecker = TypeChecker.fromRuntime(Stream);
class AsyncMethodChecker {
AsyncMethodChecker([TypeChecker checkStream]) {
_checkStream = checkStream ?? _streamChecker;
}
TypeChecker _checkStream;
bool returnsFuture(MethodElement method) =>
method.returnType.isDartAsyncFuture ||
(method.isAsynchronous &&
!method.isGenerator &&
method.returnType.isDynamic);
bool returnsStream(MethodElement method) =>
_checkStream.isAssignableFromType(method.returnType) ||
(method.isAsynchronous &&
method.isGenerator &&
method.returnType.isDynamic);
}
TypeParamTemplate typeParamTemplate(
TypeParameterElement param,
LibraryScopedNameFinder typeNameFinder,
) =>
TypeParamTemplate()
..name = param.name
..bound = param.bound != null
? typeNameFinder.findTypeParameterBoundsTypeName(param)
: null;
@@ -0,0 +1,140 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:settings_gen/src/template/comma_list.dart';
/// Determines the names of types within the context of a library, determining
/// prefixes if applicable.
///
/// For example, if a library has been imported with a name, references to types
/// contained or exported by that library must be prefixed by that name.
///
/// ```dart
/// import 'dart:io' as io;
///
/// io.File someFile;
/// ```
///
/// If we had a reference to `someFile`'s [Element], [findVariableTypeName]
/// would return `"io.File"`.
class LibraryScopedNameFinder {
LibraryScopedNameFinder(this.library);
final LibraryElement library;
Map<Element, String> _namesByElement;
Map<Element, String> get namesByElement {
if (_namesByElement != null) {
return _namesByElement;
}
_namesByElement = {};
// Add all of this library's type-defining elements to the name map
final libraryElements =
library.topLevelElements.whereType<TypeDefiningElement>();
for (final element in libraryElements) {
_namesByElement[element] = element.name;
}
// Reverse each import's export namespace so we can map elements to their
// library-local names. Note that the definedNames include a prefix if there
// is one.
for (final import in library.imports) {
for (final entry in import.namespace.definedNames.entries) {
_namesByElement[entry.value] = entry.key;
}
}
return _namesByElement;
}
String findVariableTypeName(VariableElement variable) =>
_getDartTypeName(variable.type);
String findGetterTypeName(PropertyAccessorElement getter) {
assert(getter.isGetter);
return findReturnTypeName(getter);
}
String findParameterTypeName(ParameterElement parameter) =>
_getDartTypeName(parameter.type);
String findReturnTypeName(FunctionTypedElement executable) =>
_getDartTypeName(executable.returnType);
List<String> findReturnTypeArgumentTypeNames(ExecutableElement executable) {
final returnType = executable.returnType;
return returnType is ParameterizedType
? returnType.typeArguments.map(_getDartTypeName).toList()
: [];
}
String findTypeParameterBoundsTypeName(TypeParameterElement typeParameter) {
assert(typeParameter.bound != null);
return _getDartTypeName(typeParameter.bound);
}
/// Calculates a type name, including its type arguments
///
/// The returned string will include import prefixes on all applicable types.
String _getDartTypeName(DartType type) {
var typeElement = type.element;
if (type is FunctionType) {
// If we're dealing with a typedef, we let it undergo the standard name
// lookup. Otherwise, we special case the function naming.
if (typeElement?.enclosingElement is GenericTypeAliasElement) {
typeElement = typeElement.enclosingElement;
} else {
return _getFunctionTypeName(type);
}
} else if (
// Some types don't have associated elements, like void
typeElement == null ||
// This is a bare type param, like "T"
type is TypeParameterType) {
// TODO(shyndman): This ignored deprecation can be removed when we
// increase the analyzer dependency's lower bound to 0.39.2, and
// migrate to using `DartType.getDisplayString`.
// ignore: deprecated_member_use
return type.displayName;
}
return _getNamedElementTypeName(typeElement, type);
}
String _getFunctionTypeName(FunctionType type) {
final returnTypeName = _getDartTypeName(type.returnType);
final normalParameterTypeNames =
CommaList(type.normalParameterTypes.map(_getDartTypeName).toList());
final optionalParameterTypeNames = SurroundedCommaList(
'[', ']', type.optionalParameterTypes.map(_getDartTypeName).toList());
final namedParameterPairs = type.namedParameterTypes.entries
.map((entry) => '${_getDartTypeName(entry.value)} ${entry.key}')
.toList();
final namedParameterTypeNames =
SurroundedCommaList('{', '}', namedParameterPairs);
final parameterTypeNames = CommaList([
normalParameterTypeNames,
optionalParameterTypeNames,
namedParameterTypeNames,
]);
return '$returnTypeName Function($parameterTypeNames)';
}
String _getNamedElementTypeName(Element typeElement, DartType type) {
// Determine the name of the type, without type arguments.
assert(namesByElement.containsKey(typeElement));
// If the type is parameterized, we recursively name its type arguments
if (type is ParameterizedType && type.typeArguments.isNotEmpty) {
final typeArgNames = SurroundedCommaList(
'<', '>', type.typeArguments.map(_getDartTypeName).toList());
return '${namesByElement[typeElement]}$typeArgNames';
}
return namesByElement[typeElement];
}
}