mirror of https://github.com/AMT-Cheif/drift.git
Fix generated code with $ in identifier names
This commit is contained in:
parent
99bb9e0fe0
commit
5740eb8721
|
@ -6,6 +6,16 @@ import 'package:drift/native.dart';
|
|||
|
||||
part 'main.g.dart';
|
||||
|
||||
class Todo extends Table {
|
||||
TextColumn get id => text()();
|
||||
|
||||
TextColumn get listid => text().nullable()();
|
||||
|
||||
TextColumn get text$ => text().named('text').nullable()();
|
||||
|
||||
BoolColumn get completed => boolean()();
|
||||
}
|
||||
|
||||
@DataClassName('TodoCategory')
|
||||
class TodoCategories extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
|
@ -57,6 +67,7 @@ abstract class TodoItemWithCategoryNameView extends View {
|
|||
}
|
||||
|
||||
@DriftDatabase(tables: [
|
||||
Todo,
|
||||
TodoItems,
|
||||
TodoCategories,
|
||||
], views: [
|
||||
|
|
|
@ -3,6 +3,270 @@
|
|||
part of 'main.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
class $TodoTable extends Todo with TableInfo<$TodoTable, TodoData> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$TodoTable(this.attachedDatabase, [this._alias]);
|
||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
@override
|
||||
late final GeneratedColumn<String> id = GeneratedColumn<String>(
|
||||
'id', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||
static const VerificationMeta _listidMeta = const VerificationMeta('listid');
|
||||
@override
|
||||
late final GeneratedColumn<String> listid = GeneratedColumn<String>(
|
||||
'listid', aliasedName, true,
|
||||
type: DriftSqlType.string, requiredDuringInsert: false);
|
||||
static const VerificationMeta _text$Meta = const VerificationMeta('text\$');
|
||||
@override
|
||||
late final GeneratedColumn<String> text$ = GeneratedColumn<String>(
|
||||
'text', aliasedName, true,
|
||||
type: DriftSqlType.string, requiredDuringInsert: false);
|
||||
static const VerificationMeta _completedMeta =
|
||||
const VerificationMeta('completed');
|
||||
@override
|
||||
late final GeneratedColumn<bool> completed = GeneratedColumn<bool>(
|
||||
'completed', aliasedName, false,
|
||||
type: DriftSqlType.bool,
|
||||
requiredDuringInsert: true,
|
||||
defaultConstraints:
|
||||
GeneratedColumn.constraintIsAlways('CHECK ("completed" IN (0, 1))'));
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [id, listid, text$, completed];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'todo';
|
||||
@override
|
||||
VerificationContext validateIntegrity(Insertable<TodoData> instance,
|
||||
{bool isInserting = false}) {
|
||||
final context = VerificationContext();
|
||||
final data = instance.toColumns(true);
|
||||
if (data.containsKey('id')) {
|
||||
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_idMeta);
|
||||
}
|
||||
if (data.containsKey('listid')) {
|
||||
context.handle(_listidMeta,
|
||||
listid.isAcceptableOrUnknown(data['listid']!, _listidMeta));
|
||||
}
|
||||
if (data.containsKey('text')) {
|
||||
context.handle(
|
||||
_text$Meta, text$.isAcceptableOrUnknown(data['text']!, _text$Meta));
|
||||
}
|
||||
if (data.containsKey('completed')) {
|
||||
context.handle(_completedMeta,
|
||||
completed.isAcceptableOrUnknown(data['completed']!, _completedMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_completedMeta);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => const {};
|
||||
@override
|
||||
TodoData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return TodoData(
|
||||
id: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}id'])!,
|
||||
listid: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}listid']),
|
||||
text$: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}text']),
|
||||
completed: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.bool, data['${effectivePrefix}completed'])!,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
$TodoTable createAlias(String alias) {
|
||||
return $TodoTable(attachedDatabase, alias);
|
||||
}
|
||||
}
|
||||
|
||||
class TodoData extends DataClass implements Insertable<TodoData> {
|
||||
final String id;
|
||||
final String? listid;
|
||||
final String? text$;
|
||||
final bool completed;
|
||||
const TodoData(
|
||||
{required this.id, this.listid, this.text$, required this.completed});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['id'] = Variable<String>(id);
|
||||
if (!nullToAbsent || listid != null) {
|
||||
map['listid'] = Variable<String>(listid);
|
||||
}
|
||||
if (!nullToAbsent || text$ != null) {
|
||||
map['text'] = Variable<String>(text$);
|
||||
}
|
||||
map['completed'] = Variable<bool>(completed);
|
||||
return map;
|
||||
}
|
||||
|
||||
TodoCompanion toCompanion(bool nullToAbsent) {
|
||||
return TodoCompanion(
|
||||
id: Value(id),
|
||||
listid:
|
||||
listid == null && nullToAbsent ? const Value.absent() : Value(listid),
|
||||
text$:
|
||||
text$ == null && nullToAbsent ? const Value.absent() : Value(text$),
|
||||
completed: Value(completed),
|
||||
);
|
||||
}
|
||||
|
||||
factory TodoData.fromJson(Map<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return TodoData(
|
||||
id: serializer.fromJson<String>(json['id']),
|
||||
listid: serializer.fromJson<String?>(json['listid']),
|
||||
text$: serializer.fromJson<String?>(json['text\$']),
|
||||
completed: serializer.fromJson<bool>(json['completed']),
|
||||
);
|
||||
}
|
||||
factory TodoData.fromJsonString(String encodedJson,
|
||||
{ValueSerializer? serializer}) =>
|
||||
TodoData.fromJson(
|
||||
DataClass.parseJson(encodedJson) as Map<String, dynamic>,
|
||||
serializer: serializer);
|
||||
@override
|
||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<String>(id),
|
||||
'listid': serializer.toJson<String?>(listid),
|
||||
'text\$': serializer.toJson<String?>(text$),
|
||||
'completed': serializer.toJson<bool>(completed),
|
||||
};
|
||||
}
|
||||
|
||||
TodoData copyWith(
|
||||
{String? id,
|
||||
Value<String?> listid = const Value.absent(),
|
||||
Value<String?> text$ = const Value.absent(),
|
||||
bool? completed}) =>
|
||||
TodoData(
|
||||
id: id ?? this.id,
|
||||
listid: listid.present ? listid.value : this.listid,
|
||||
text$: text$.present ? text$.value : this.text$,
|
||||
completed: completed ?? this.completed,
|
||||
);
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('TodoData(')
|
||||
..write('id: $id, ')
|
||||
..write('listid: $listid, ')
|
||||
..write('text\$: ${text$}, ')
|
||||
..write('completed: $completed')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(id, listid, text$, completed);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is TodoData &&
|
||||
other.id == this.id &&
|
||||
other.listid == this.listid &&
|
||||
other.text$ == this.text$ &&
|
||||
other.completed == this.completed);
|
||||
}
|
||||
|
||||
class TodoCompanion extends UpdateCompanion<TodoData> {
|
||||
final Value<String> id;
|
||||
final Value<String?> listid;
|
||||
final Value<String?> text$;
|
||||
final Value<bool> completed;
|
||||
final Value<int> rowid;
|
||||
const TodoCompanion({
|
||||
this.id = const Value.absent(),
|
||||
this.listid = const Value.absent(),
|
||||
this.text$ = const Value.absent(),
|
||||
this.completed = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
});
|
||||
TodoCompanion.insert({
|
||||
required String id,
|
||||
this.listid = const Value.absent(),
|
||||
this.text$ = const Value.absent(),
|
||||
required bool completed,
|
||||
this.rowid = const Value.absent(),
|
||||
}) : id = Value(id),
|
||||
completed = Value(completed);
|
||||
static Insertable<TodoData> custom({
|
||||
Expression<String>? id,
|
||||
Expression<String>? listid,
|
||||
Expression<String>? text$,
|
||||
Expression<bool>? completed,
|
||||
Expression<int>? rowid,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
if (listid != null) 'listid': listid,
|
||||
if (text$ != null) 'text': text$,
|
||||
if (completed != null) 'completed': completed,
|
||||
if (rowid != null) 'rowid': rowid,
|
||||
});
|
||||
}
|
||||
|
||||
TodoCompanion copyWith(
|
||||
{Value<String>? id,
|
||||
Value<String?>? listid,
|
||||
Value<String?>? text$,
|
||||
Value<bool>? completed,
|
||||
Value<int>? rowid}) {
|
||||
return TodoCompanion(
|
||||
id: id ?? this.id,
|
||||
listid: listid ?? this.listid,
|
||||
text$: text$ ?? this.text$,
|
||||
completed: completed ?? this.completed,
|
||||
rowid: rowid ?? this.rowid,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (id.present) {
|
||||
map['id'] = Variable<String>(id.value);
|
||||
}
|
||||
if (listid.present) {
|
||||
map['listid'] = Variable<String>(listid.value);
|
||||
}
|
||||
if (text$.present) {
|
||||
map['text'] = Variable<String>(text$.value);
|
||||
}
|
||||
if (completed.present) {
|
||||
map['completed'] = Variable<bool>(completed.value);
|
||||
}
|
||||
if (rowid.present) {
|
||||
map['rowid'] = Variable<int>(rowid.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('TodoCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('listid: $listid, ')
|
||||
..write('text\$: ${text$}, ')
|
||||
..write('completed: $completed, ')
|
||||
..write('rowid: $rowid')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class $TodoCategoriesTable extends TodoCategories
|
||||
with TableInfo<$TodoCategoriesTable, TodoCategory> {
|
||||
@override
|
||||
|
@ -685,6 +949,7 @@ class $TodoItemWithCategoryNameViewView extends ViewInfo<
|
|||
|
||||
abstract class _$Database extends GeneratedDatabase {
|
||||
_$Database(QueryExecutor e) : super(e);
|
||||
late final $TodoTable todo = $TodoTable(this);
|
||||
late final $TodoCategoriesTable todoCategories = $TodoCategoriesTable(this);
|
||||
late final $TodoItemsTable todoItems = $TodoItemsTable(this);
|
||||
late final $TodoCategoryItemCountView todoCategoryItemCount =
|
||||
|
@ -696,5 +961,5 @@ abstract class _$Database extends GeneratedDatabase {
|
|||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities =>
|
||||
[todoCategories, todoItems, todoCategoryItemCount, customViewName];
|
||||
[todo, todoCategories, todoItems, todoCategoryItemCount, customViewName];
|
||||
}
|
||||
|
|
|
@ -143,7 +143,7 @@ class DataClassWriter {
|
|||
|
||||
for (final column in columns) {
|
||||
final getter = column.nameInDart;
|
||||
final jsonKey = column.getJsonKey(scope.options);
|
||||
final jsonKeyString = asDartLiteral(column.getJsonKey(scope.options));
|
||||
String deserialized;
|
||||
|
||||
final typeConverter = column.typeConverter;
|
||||
|
@ -154,13 +154,14 @@ class DataClassWriter {
|
|||
type = '$type?';
|
||||
}
|
||||
|
||||
final fromConverter = "serializer.fromJson<$type>(json['$jsonKey'])";
|
||||
final fromConverter =
|
||||
"serializer.fromJson<$type>(json[$jsonKeyString])";
|
||||
final converterField = _converter(column);
|
||||
deserialized = '$converterField.fromJson($fromConverter)';
|
||||
} else {
|
||||
final type = _columnType(column);
|
||||
|
||||
deserialized = "serializer.fromJson<$type>(json['$jsonKey'])";
|
||||
deserialized = "serializer.fromJson<$type>(json[$jsonKeyString])";
|
||||
}
|
||||
|
||||
_buffer.write('$getter: $deserialized,');
|
||||
|
@ -186,7 +187,7 @@ class DataClassWriter {
|
|||
'return <String, dynamic>{\n');
|
||||
|
||||
for (final column in columns) {
|
||||
final name = column.getJsonKey(scope.options);
|
||||
final nameLiteral = asDartLiteral(column.getJsonKey(scope.options));
|
||||
final getter = column.nameInDart;
|
||||
final needsThis = getter == 'serializer';
|
||||
var value = needsThis ? 'this.$getter' : getter;
|
||||
|
@ -199,7 +200,7 @@ class DataClassWriter {
|
|||
dartType = _jsonType(column);
|
||||
}
|
||||
|
||||
_buffer.write("'$name': serializer.toJson<$dartType>($value),");
|
||||
_buffer.write("$nameLiteral: serializer.toJson<$dartType>($value),");
|
||||
}
|
||||
|
||||
_buffer.write('};}');
|
||||
|
|
|
@ -454,10 +454,11 @@ class TableWriter extends TableOrViewWriter {
|
|||
void _writeColumnVerificationMeta(DriftColumn column) {
|
||||
if (!_skipVerification) {
|
||||
final meta = emitter.drift('VerificationMeta');
|
||||
final arg = asDartLiteral(column.nameInDart);
|
||||
|
||||
buffer
|
||||
..write('static const $meta ${_fieldNameForColumnMeta(column)} = ')
|
||||
..writeln("const $meta('${column.nameInDart}');");
|
||||
..writeln("const $meta($arg);");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,13 @@ void overrideToString(
|
|||
for (var i = 0; i < properties.length; i++) {
|
||||
final property = properties[i];
|
||||
|
||||
into.write("..write('$property: \$$property");
|
||||
if (property.contains(r'$')) {
|
||||
final asKey = property.replaceAll('\$', '\\\$');
|
||||
into.write("..write('$asKey: \${$property}");
|
||||
} else {
|
||||
into.write("..write('$property: \$$property");
|
||||
}
|
||||
|
||||
if (i != properties.length - 1) into.write(', ');
|
||||
|
||||
into.write("')");
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:sqlparser/sqlparser.dart' as sql;
|
|||
|
||||
import '../analysis/options.dart';
|
||||
import '../analysis/results/results.dart';
|
||||
import '../utils/string_escaper.dart';
|
||||
import 'import_manager.dart';
|
||||
import 'queries/sql_writer.dart';
|
||||
|
||||
|
@ -408,6 +409,10 @@ class TextEmitter extends _Node {
|
|||
void writeSqlByDialectMap(sql.AstNode node) {
|
||||
_writeSqlByDialectMap(node, buffer);
|
||||
}
|
||||
|
||||
void stringLiteral(String contents) {
|
||||
return buffer.write(asDartLiteral(contents));
|
||||
}
|
||||
}
|
||||
|
||||
/// Options that are specific to code-generation.
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:analyzer/dart/analysis/features.dart';
|
||||
import 'package:analyzer/dart/analysis/utilities.dart';
|
||||
import 'package:analyzer/file_system/memory_file_system.dart';
|
||||
import 'package:build/build.dart';
|
||||
import 'package:build/experiments.dart';
|
||||
import 'package:build_resolvers/build_resolvers.dart';
|
||||
|
@ -10,6 +13,7 @@ import 'package:drift_dev/integrations/build.dart';
|
|||
import 'package:glob/glob.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:package_config/package_config.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
|
@ -190,3 +194,34 @@ class _TrackingAssetReader implements AssetReader {
|
|||
return _inner.readAsString(id, encoding: encoding);
|
||||
}
|
||||
}
|
||||
|
||||
class IsValidDartFile extends CustomMatcher {
|
||||
IsValidDartFile(valueOrMatcher)
|
||||
: super(
|
||||
'A syntactically-valid Dart source file',
|
||||
'parsed unit',
|
||||
valueOrMatcher,
|
||||
);
|
||||
|
||||
@override
|
||||
Object? featureValueOf(actual) {
|
||||
final resourceProvider = MemoryResourceProvider();
|
||||
if (actual is List<int>) {
|
||||
resourceProvider.newFileWithBytes('/foo.dart', actual);
|
||||
} else if (actual is String) {
|
||||
resourceProvider.newFile('/foo.dart', actual);
|
||||
} else {
|
||||
throw 'Not a String or a List<int>';
|
||||
}
|
||||
|
||||
return parseFile(
|
||||
path: '/foo.dart',
|
||||
featureSet: FeatureSet.fromEnableFlags2(
|
||||
sdkLanguageVersion: Version(3, 0, 0),
|
||||
flags: const [],
|
||||
),
|
||||
resourceProvider: resourceProvider,
|
||||
throwIfDiagnostics: true,
|
||||
).unit;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import 'package:analyzer/dart/analysis/features.dart';
|
||||
import 'package:analyzer/dart/analysis/utilities.dart';
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/file_system/memory_file_system.dart';
|
||||
import 'package:build/build.dart';
|
||||
import 'package:build_test/build_test.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../utils.dart';
|
||||
|
@ -35,10 +31,10 @@ class Database extends _$Database {}
|
|||
}, options: options);
|
||||
|
||||
checkOutputs(
|
||||
const {
|
||||
'a|lib/main.drift.dart': _GeneratesWithoutFinalFields(
|
||||
{
|
||||
'a|lib/main.drift.dart': IsValidDartFile(_WithoutFinalFields(
|
||||
{'User', 'UsersCompanion', 'SomeQueryResult'},
|
||||
),
|
||||
)),
|
||||
},
|
||||
writer.dartOutputs,
|
||||
writer.writer,
|
||||
|
@ -46,10 +42,10 @@ class Database extends _$Database {}
|
|||
}, tags: 'analyzer');
|
||||
}
|
||||
|
||||
class _GeneratesWithoutFinalFields extends Matcher {
|
||||
class _WithoutFinalFields extends Matcher {
|
||||
final Set<String> expectedWithoutFinals;
|
||||
|
||||
const _GeneratesWithoutFinalFields(this.expectedWithoutFinals);
|
||||
const _WithoutFinalFields(this.expectedWithoutFinals);
|
||||
|
||||
@override
|
||||
Description describe(Description description) {
|
||||
|
@ -58,28 +54,15 @@ class _GeneratesWithoutFinalFields extends Matcher {
|
|||
}
|
||||
|
||||
@override
|
||||
bool matches(dynamic desc, Map matchState) {
|
||||
bool matches(Object? desc, Map matchState) {
|
||||
// Parse the file, assure we don't have final fields in data classes.
|
||||
final resourceProvider = MemoryResourceProvider();
|
||||
if (desc is List<int>) {
|
||||
resourceProvider.newFileWithBytes('/foo.dart', desc);
|
||||
} else if (desc is String) {
|
||||
resourceProvider.newFile('/foo.dart', desc);
|
||||
} else {
|
||||
desc['desc'] = 'Neither a List<int> or String - cannot be parsed';
|
||||
final parsed = desc;
|
||||
|
||||
if (parsed is! CompilationUnit) {
|
||||
matchState['desc'] = 'Could not be parsed';
|
||||
return false;
|
||||
}
|
||||
|
||||
final parsed = parseFile(
|
||||
path: '/foo.dart',
|
||||
featureSet: FeatureSet.fromEnableFlags2(
|
||||
sdkLanguageVersion: Version(2, 12, 0),
|
||||
flags: const [],
|
||||
),
|
||||
resourceProvider: resourceProvider,
|
||||
throwIfDiagnostics: true,
|
||||
).unit;
|
||||
|
||||
final remaining = expectedWithoutFinals.toSet();
|
||||
|
||||
final definedClasses = parsed.declarations.whereType<ClassDeclaration>();
|
||||
|
|
|
@ -120,4 +120,33 @@ CREATE VIEW a AS SELECT nullif(bar, '') FROM foo;
|
|||
}, result.dartOutputs, result.writer);
|
||||
},
|
||||
);
|
||||
|
||||
test('generates valid code for columns containing dollar signs', () async {
|
||||
final result = await emulateDriftBuild(
|
||||
inputs: {
|
||||
'a|lib/a.dart': r'''
|
||||
import 'package:drift/drift.dart';
|
||||
|
||||
class Todo extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get listid => text().nullable()();
|
||||
TextColumn get text$ => text().named('text').nullable()();
|
||||
BoolColumn get completed => boolean()();
|
||||
}
|
||||
|
||||
@DriftDatabase(tables: [Todo])
|
||||
class MyDatabase {}
|
||||
''',
|
||||
},
|
||||
logger: loggerThat(neverEmits(anything)),
|
||||
);
|
||||
|
||||
// Make sure we don't generate invalid code in string literals for dollar
|
||||
// signs in names - https://github.com/simolus3/drift/issues/2761.
|
||||
checkOutputs(
|
||||
{'a|lib/a.drift.dart': IsValidDartFile(anything)},
|
||||
result.dartOutputs,
|
||||
result.writer,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue