Check types for existing row classes

This commit is contained in:
Simon Binder 2021-04-20 22:34:11 +02:00
parent 885c63e66e
commit 8b5d5a9f6c
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
2 changed files with 168 additions and 0 deletions

View File

@ -1,5 +1,6 @@
// @dart=2.9
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:moor_generator/moor_generator.dart';
import 'package:moor_generator/src/analyzer/errors.dart';
@ -24,6 +25,7 @@ ExistingRowClass /*?*/ validateExistingClass(
final column = unmatchedColumnsByName.remove(parameter.name);
if (column != null) {
columnsToParameter[column] = parameter;
_checkType(parameter, column, errors);
} else if (!parameter.isOptional) {
errors.report(ErrorInDartCode(
affectedElement: parameter,
@ -35,3 +37,58 @@ ExistingRowClass /*?*/ validateExistingClass(
return ExistingRowClass(desiredClass, ctor, columnsToParameter);
}
void _checkType(ParameterElement element, MoorColumn column, ErrorSink errors) {
final type = element.type;
final typesystem = element.library.typeSystem;
void error(String message) {
errors.report(ErrorInDartCode(
affectedElement: element,
message: message,
));
}
if (element.library.isNonNullableByDefault &&
column.nullableInDart &&
!typesystem.isNullable(type) &&
element.isNotOptional) {
error('Expected this parameter to be nullable');
return;
}
// If there's a type converter, ensure the type matches
if (column.typeConverter != null) {
final mappedType = column.typeConverter.mappedType;
if (!typesystem.isAssignableTo(mappedType, type)) {
error('Parameter must accept '
'${mappedType.getDisplayString(withNullability: true)}');
}
} else {
// No type converter, check raw column type
if (!type.matches(column.type)) {
error('Invalid type, expected ${dartTypeNames[column.type]}');
}
}
}
extension on DartType {
bool matches(ColumnType type) {
switch (type) {
case ColumnType.integer:
return isDartCoreInt;
case ColumnType.text:
return isDartCoreString;
case ColumnType.boolean:
return isDartCoreBool;
case ColumnType.real:
return isDartCoreDouble;
case ColumnType.datetime:
return element.name == 'DateTime' && element.library.isDartCore;
case ColumnType.blob:
return isDartCoreList;
}
throw AssertionError('Unhandled moor type');
}
}

View File

@ -0,0 +1,111 @@
// @dart=2.9
@Tags(['analyzer'])
import 'package:moor_generator/src/analyzer/errors.dart';
import 'package:test/test.dart';
import '../utils.dart';
void main() {
TestState state;
setUpAll(() {
state = TestState.withContent({
'a|lib/invalid_no_unnamed_constructor.dart': '''
import 'package:moor/moor.dart';
class RowClass {
RowClass.create();
}
@UseRowClass(RowClass)
class TableClass extends Table {}
''',
'a|lib/mismatching_type.dart': '''
import 'package:moor/moor.dart';
class RowClass {
RowClass(int x);
}
@UseRowClass(RowClass)
class TableClass extends Table {
TextColumn get x => text()();
}
''',
'a|lib/mismatching_nullability.dart': '''
import 'package:moor/moor.dart';
class RowClass {
RowClass(int x);
}
@UseRowClass(RowClass)
class TableClass extends Table {
IntColumn get x => integer().nullable()();
}
''',
'a|lib/mismatching_type_converter.dart': '''
import 'package:moor/moor.dart';
class MyConverter extends TypeConverter<int, String> {
const MyConverter();
@override
int? mapToDart(String? fromDb) => throw 'stub';
@override
String? mapToSql(int? value) => throw 'stub';
}
class RowClass {
RowClass(String x);
}
@UseRowClass(RowClass)
class TableClass extends Table {
TextColumn get x => text().map(const MyConverter())();
}
''',
});
});
tearDownAll(() => state.close());
group('warns about misuse', () {
test('when the desired row class does not have an unnamed constructor',
() async {
final file =
await state.analyze('package:a/invalid_no_unnamed_constructor.dart');
expect(
file.errors.errors,
contains(isA<ErrorInDartCode>().having((e) => e.message, 'message',
contains('must have an unnamed constructor'))),
);
});
test('when a parameter has a mismatching type', () async {
final file = await state.analyze('package:a/mismatching_type.dart');
expect(
file.errors.errors,
contains(isA<ErrorInDartCode>().having((e) => e.message, 'message',
contains('Invalid type, expected String'))),
);
});
test('when a parameter should be nullable', () async {
final file =
await state.analyze('package:a/mismatching_nullability.dart');
expect(
file.errors.errors,
contains(isA<ErrorInDartCode>().having((e) => e.message, 'message',
'Expected this parameter to be nullable')),
);
});
test('when a parameter has a mismatching type converter', () async {
final file =
await state.analyze('package:a/mismatching_type_converter.dart');
expect(
file.errors.errors,
contains(isA<ErrorInDartCode>()
.having((e) => e.message, 'message', 'Parameter must accept int')),
);
});
});
}