Specify analysis rules and conform to them

This commit is contained in:
Simon Binder 2019-02-04 21:01:50 +01:00
parent 9090ded541
commit 75b3b25117
22 changed files with 190 additions and 90 deletions

80
analysis_options.yaml Normal file
View File

@ -0,0 +1,80 @@
analyzer:
strong-mode:
implicit-casts: false
errors:
unused_element: error
unused_import: error
unused_local_variable: error
dead_code: error
override_on_non_overriding_method: error
linter:
rules:
- annotate_overrides
- avoid_empty_else
- avoid_function_literals_in_foreach_calls
- avoid_init_to_null
- avoid_null_checks_in_equality_operators
- avoid_relative_lib_imports
- avoid_return_types_on_setters
- avoid_returning_null
- avoid_types_as_parameter_names
- avoid_unused_constructor_parameters
- await_only_futures
- camel_case_types
- cancel_subscriptions
- cascade_invocations
- comment_references
- constant_identifier_names
- control_flow_in_finally
- directives_ordering
- empty_catches
- empty_constructor_bodies
- empty_statements
- hash_and_equals
- implementation_imports
- invariant_booleans
- iterable_contains_unrelated_type
- library_names
- library_prefixes
- list_remove_unrelated_type
- no_adjacent_strings_in_list
- no_duplicate_case_values
- non_constant_identifier_names
- null_closures
- omit_local_variable_types
- only_throw_errors
- overridden_fields
- package_api_docs
- package_names
- package_prefixed_library_names
- prefer_adjacent_string_concatenation
- prefer_collection_literals
- prefer_conditional_assignment
- prefer_const_constructors
- prefer_contains
- prefer_equal_for_default_values
- prefer_final_fields
- prefer_initializing_formals
- prefer_interpolation_to_compose_strings
- prefer_is_empty
- prefer_is_not_empty
- prefer_single_quotes
- prefer_typing_uninitialized_variables
- recursive_getters
- slash_for_doc_comments
- super_goes_last
- test_types_in_equals
- throw_in_finally
- type_init_formals
- unawaited_futures
- unnecessary_brace_in_string_interps
- unnecessary_getters_setters
- unnecessary_lambdas
- unnecessary_new
- unnecessary_null_aware_assignments
- unnecessary_statements
- unnecessary_this
- unrelated_type_equality_checks
- use_rethrow_when_possible
- valid_regexps

1
sally/analysis_options.yaml Symbolic link
View File

@ -0,0 +1 @@
../analysis_options.yaml

View File

@ -9,7 +9,7 @@ class Where extends Component {
@override
void writeInto(GenerationContext context) {
context.buffer.write("WHERE ");
context.buffer.write('WHERE ');
predicate.writeInto(context);
}
}

View File

@ -11,6 +11,6 @@ class SqlTypeSystem {
/// Returns the appropriate sql type for the dart type provided as the
/// generic parameter.
SqlType<T> forDartType<T>() {
return types.singleWhere((t) => t is SqlType<T>);
return types.singleWhere((t) => t is SqlType<T>) as SqlType<T>;
}
}

View File

@ -8,17 +8,21 @@ Expression<BoolType> and(Expression<BoolType> a, Expression<BoolType> b) =>
Expression<BoolType> not(Expression<BoolType> a) => NotExpression(a);
class AndExpression extends Expression<BoolType> with InfixOperator<BoolType> {
@override
Expression<BoolType> left, right;
final String operator = "AND";
@override
final String operator = 'AND';
AndExpression(this.left, this.right);
}
class OrExpression extends Expression<BoolType> with InfixOperator<BoolType> {
@override
Expression<BoolType> left, right;
final String operator = "AND";
@override
final String operator = 'AND';
OrExpression(this.left, this.right);
}

View File

@ -39,21 +39,24 @@ abstract class InfixOperator<T extends SqlType> implements Expression<T> {
}
}
enum ComparisonOperator { less, less_or_equal, equal, more_or_equal, more }
enum ComparisonOperator { less, lessOrEqual, equal, moreOrEqual, more }
class Comparison extends InfixOperator<BoolType> {
static const Map<ComparisonOperator, String> operatorNames = {
ComparisonOperator.less: '<',
ComparisonOperator.less_or_equal: '<=',
ComparisonOperator.lessOrEqual: '<=',
ComparisonOperator.equal: '=',
ComparisonOperator.more_or_equal: '>=',
ComparisonOperator.moreOrEqual: '>=',
ComparisonOperator.more: '>'
};
@override
final Expression left;
@override
final Expression right;
final ComparisonOperator op;
@override
final bool placeBrackets = false;
@override
@ -61,5 +64,5 @@ class Comparison extends InfixOperator<BoolType> {
Comparison(this.left, this.op, this.right);
Comparison.equal(this.left, this.right) : this.op = ComparisonOperator.equal;
Comparison.equal(this.left, this.right) : op = ComparisonOperator.equal;
}

View File

@ -10,7 +10,7 @@ class Variable<T, S extends SqlType<T>> extends Expression<S> {
@override
void writeInto(GenerationContext context) {
context.introduceVariable(value);
context.buffer.write("?");
context.buffer.write('?');
}
}

View File

@ -27,7 +27,7 @@ class BoolType extends SqlType<bool> {
@override
String mapToSqlConstant(bool content) {
return content ? "1" : "0";
return content ? '1' : '0';
}
@override
@ -40,7 +40,7 @@ class StringType extends SqlType<String> {
const StringType();
@override
String mapFromDatabaseResponse(response) => response;
String mapFromDatabaseResponse(response) => response as String;
@override
String mapToSqlConstant(String content) {
@ -56,7 +56,7 @@ class IntType extends SqlType<int> {
const IntType();
@override
int mapFromDatabaseResponse(response) => response;
int mapFromDatabaseResponse(response) => response as int;
@override
String mapToSqlConstant(int content) => content.toString();

View File

@ -5,6 +5,7 @@ import 'package:sally/src/runtime/structure/table_info.dart';
class SelectStatement<UserTable, DataType> extends Query<UserTable> {
@override
// ignore: overridden_fields
covariant TableInfo<UserTable, DataType> table;
SelectStatement(GeneratedDatabase database, this.table)

View File

@ -23,6 +23,7 @@ abstract class GeneratedColumn<T, S extends SqlType<T>> extends Column<T, S> {
class GeneratedTextColumn extends GeneratedColumn<String, StringType>
implements TextColumn {
@override
final String $name;
GeneratedTextColumn(this.$name);
@ -34,6 +35,7 @@ class GeneratedTextColumn extends GeneratedColumn<String, StringType>
class GeneratedBoolColumn extends GeneratedColumn<bool, BoolType>
implements BoolColumn {
@override
final String $name;
GeneratedBoolColumn(this.$name);
@ -48,6 +50,7 @@ class GeneratedBoolColumn extends GeneratedColumn<bool, BoolType>
class GeneratedIntColumn extends GeneratedColumn<int, IntType>
implements IntColumn {
@override
final String $name;
GeneratedIntColumn(this.$name);

View File

@ -19,13 +19,16 @@ class GeneratedUsersTable extends Users with TableInfo<Users, UserDataObject> {
GeneratedUsersTable(this.db);
IntColumn id = GeneratedIntColumn("id");
TextColumn name = GeneratedTextColumn("name");
BoolColumn isAwesome = GeneratedBoolColumn("is_awesome");
@override
IntColumn id = GeneratedIntColumn('id');
@override
TextColumn name = GeneratedTextColumn('name');
@override
BoolColumn isAwesome = GeneratedBoolColumn('is_awesome');
@override
List<Column<dynamic, SqlType>> get $columns => [id, name, isAwesome];
@override
String get $tableName => "users";
String get $tableName => 'users';
@override
Users get asDslTable => this;
@override
@ -36,7 +39,7 @@ class GeneratedUsersTable extends Users with TableInfo<Users, UserDataObject> {
class TestDatabase extends GeneratedDatabase {
TestDatabase(QueryExecutor executor)
: super(SqlTypeSystem.withDefaults(), executor);
: super(const SqlTypeSystem.withDefaults(), executor);
GeneratedUsersTable get users => GeneratedUsersTable(this);
}

View File

@ -17,40 +17,40 @@ void main() {
when(executor.runSelect(any, any)).thenAnswer((_) => Future.value([]));
});
group("Generates SELECT statements", () {
test("generates simple statements", () {
group('Generates SELECT statements', () {
test('generates simple statements', () {
db.select(db.users).get();
verify(executor.runSelect("SELECT * FROM users;", argThat(isEmpty)));
verify(executor.runSelect('SELECT * FROM users;', argThat(isEmpty)));
});
test("generates limit statements", () {
test('generates limit statements', () {
(db.select(db.users)..limit(10)).get();
verify(executor.runSelect(
"SELECT * FROM users LIMIT 10;", argThat(isEmpty)));
'SELECT * FROM users LIMIT 10;', argThat(isEmpty)));
});
test("generates like expressions", () {
(db.select(db.users)..where((u) => u.name.like("Dash%"))).get();
test('generates like expressions', () {
(db.select(db.users)..where((u) => u.name.like('Dash%'))).get();
verify(executor
.runSelect("SELECT * FROM users WHERE name LIKE ?;", ["Dash%"]));
.runSelect('SELECT * FROM users WHERE name LIKE ?;', ['Dash%']));
});
test("generates complex predicates", () {
test('generates complex predicates', () {
(db.select(db.users)
..where((u) =>
and(not(u.name.equalsVal("Dash")), (u.id.isBiggerThan(12)))))
and(not(u.name.equalsVal('Dash')), (u.id.isBiggerThan(12)))))
.get();
verify(executor.runSelect(
"SELECT * FROM users WHERE (NOT name = ?) AND (id > ?);",
["Dash", 12]));
'SELECT * FROM users WHERE (NOT name = ?) AND (id > ?);',
['Dash', 12]));
});
test("generates expressions from boolean fields", () {
test('generates expressions from boolean fields', () {
(db.select(db.users)..where((u) => u.isAwesome)).get();
verify(executor.runSelect(
"SELECT * FROM users WHERE (is_awesome = 1);", argThat(isEmpty)));
'SELECT * FROM users WHERE (is_awesome = 1);', argThat(isEmpty)));
});
});

View File

@ -0,0 +1 @@
../analysis_options.yaml

View File

@ -3,4 +3,4 @@ import 'package:source_gen/source_gen.dart';
import 'package:sally_generator/src/sally_generator.dart';
Builder sallyBuilder(BuilderOptions _) =>
new SharedPartBuilder([SallyGenerator()], "sally");
SharedPartBuilder([SallyGenerator()], 'sally');

View File

@ -6,7 +6,7 @@ class SallyError {
final Element affectedElement;
SallyError(
{this.critical = false, this.message, this.affectedElement = null});
{this.critical = false, this.message, this.affectedElement});
}
class ErrorStore {

View File

@ -54,6 +54,9 @@ class AutoIncrement extends ColumnFeature {
@override
bool operator ==(other) => other is AutoIncrement;
@override
int get hashCode => 1337420;
}
abstract class LimitingTextLength extends ColumnFeature

View File

@ -4,22 +4,22 @@ import 'package:sally_generator/src/model/specified_column.dart';
import 'package:sally_generator/src/parser/parser.dart';
import 'package:sally_generator/src/sally_generator.dart';
const String startInt = "integer";
const String startString = "text";
const String startBool = "boolean";
const String startInt = 'integer';
const String startString = 'text';
const String startBool = 'boolean';
// todo replace with set literal once dart supports it
final Set<String> starters = [startInt, startString, startBool].toSet();
const String functionNamed = "named";
const String functionPrimaryKey = "primaryKey";
const String functionReferences = "references";
const String functionAutoIncrement = "autoIncrement";
const String functionWithLength = "withLength";
const String functionNamed = 'named';
const String functionPrimaryKey = 'primaryKey';
const String functionReferences = 'references';
const String functionAutoIncrement = 'autoIncrement';
const String functionWithLength = 'withLength';
const String errorMessage = "This getter does not create a valid column that "
"can be parsed by sally. Please refer to the readme from sally to see how "
"columns are formed. If you have any questions, feel free to raise an issue.";
const String errorMessage = 'This getter does not create a valid column that '
'can be parsed by sally. Please refer to the readme from sally to see how '
'columns are formed. If you have any questions, feel free to raise an issue.';
class ColumnParser extends ParserBase {
ColumnParser(SallyGenerator generator) : super(generator);
@ -51,9 +51,9 @@ class ColumnParser extends ParserBase {
String foundStartMethod;
String foundExplicitName;
bool wasDeclaredAsPrimaryKey = false;
var wasDeclaredAsPrimaryKey = false;
// todo parse reference
List<ColumnFeature> foundFeatures = [];
final foundFeatures = <ColumnFeature>[];
while (true) {
final methodName = remainingExpr.methodName.name;
@ -71,7 +71,7 @@ class ColumnParser extends ParserBase {
affectedElement: getter.declaredElement,
message:
"You're setting more than one name here, the first will "
"be used"));
'be used'));
}
foundExplicitName =
@ -80,8 +80,8 @@ class ColumnParser extends ParserBase {
critical: false,
affectedElement: getter.declaredElement,
message:
"This table name is cannot be resolved! Please only use "
"a constant string as parameter for .named()."));
'This table name is cannot be resolved! Please only use '
'a constant string as parameter for .named().'));
});
break;
case functionPrimaryKey:
@ -91,8 +91,8 @@ class ColumnParser extends ParserBase {
break; // todo: parsing this is going to suck
case functionWithLength:
final args = remainingExpr.argumentList;
final minArg = findNamedArgument(args, "min");
final maxArg = findNamedArgument(args, "max");
final minArg = findNamedArgument(args, 'min');
final maxArg = findNamedArgument(args, 'max');
foundFeatures.add(LimitingTextLength.withLength(
min: readIntLiteral(minArg, () {}),

View File

@ -22,7 +22,7 @@ class ParserBase {
affectedElement: method.declaredElement,
critical: true,
message:
"This method must have an expression body (use => instead of {return ...})"));
'This method must have an expression body (use => instead of {return ...})'));
return null;
}
@ -33,7 +33,7 @@ class ParserBase {
if (!(expression is StringLiteral)) {
onError();
} else {
String value = (expression as StringLiteral).stringValue;
final value = (expression as StringLiteral).stringValue;
if (value == null)
onError();
else
@ -46,6 +46,7 @@ class ParserBase {
int readIntLiteral(Expression expression, void onError()) {
if (!(expression is IntegerLiteral)) {
onError();
// ignore: avoid_returning_null
return null;
} else {
return (expression as IntegerLiteral).value;

View File

@ -12,19 +12,19 @@ class TableParser extends ParserBase {
TableParser(SallyGenerator generator) : super(generator);
SpecifiedTable parse(ClassElement element) {
String sqlName = _parseTableName(element);
final sqlName = _parseTableName(element);
return SpecifiedTable(
fromClass: element,
columns: _parseColumns(element),
sqlName: sqlName,
dartTypeName:
"${element.name}_Data" // TODO better name for generated data classes
'${element.name}_Data' // TODO better name for generated data classes
);
}
String _parseTableName(ClassElement element) {
final tableNameGetter = element.getGetter("tableName");
final tableNameGetter = element.getGetter('tableName');
if (tableNameGetter == null) {
// class does not override tableName. So just use the dart class name
// instead. Will use placed_orders for a class called PlacedOrders
@ -38,11 +38,11 @@ class TableParser extends ParserBase {
final returnExpr = returnExpressionOfMethod(
tableNameDeclaration.node as MethodDeclaration);
String tableName = readStringLiteral(returnExpr, () {
final tableName = readStringLiteral(returnExpr, () {
generator.errors.add(SallyError(
critical: true,
message:
"This getter must return a string literal, and do nothing more",
'This getter must return a string literal, and do nothing more',
affectedElement: tableNameGetter));
});

View File

@ -8,8 +8,8 @@ import 'package:sally_generator/src/parser/table_parser.dart';
import 'package:source_gen/source_gen.dart';
class SallyGenerator extends Generator {
Map<String, ParsedLibraryResult> _astForLibs = Map();
ErrorStore errors = ErrorStore();
final Map<String, ParsedLibraryResult> _astForLibs = {};
final ErrorStore errors = ErrorStore();
TableParser tableParser;
ColumnParser columnParser;
@ -25,12 +25,12 @@ class SallyGenerator extends Generator {
@override
String generate(LibraryReader library, BuildStep buildStep) {
final testUsers = library.findType("Users");
final testUsers = library.findType('Users');
if (testUsers == null) return "";
if (testUsers == null) return '';
TableParser(this).parse(testUsers);
return "";
return '';
}
}

View File

@ -1,9 +1,9 @@
import 'package:analyzer/dart/element/type.dart';
bool isFromSally(DartType type) {
return type.element.library.location.components.first.contains("sally");
return type.element.library.location.components.first.contains('sally');
}
bool isColumn(DartType type) {
return isFromSally(type) && type.name.contains("Column");
return isFromSally(type) && type.name.contains('Column');
}

View File

@ -36,7 +36,7 @@ void main() async {
@override
String get tableName => constructTableName();"
}
''', (r) => r.findLibraryByName("test_parser"));
''', (r) => r.findLibraryByName('test_parser'));
});
setUp(() {
@ -44,57 +44,57 @@ void main() async {
generator.columnParser = ColumnParser(generator);
});
group("SQL table name", () {
test("should parse correctly when valid", () {
group('SQL table name', () {
test('should parse correctly when valid', () {
expect(
TableParser(generator)
.parse(testLib.getType("TableWithCustomName"))
.parse(testLib.getType('TableWithCustomName'))
.sqlName,
equals("my-fancy-table"));
equals('my-fancy-table'));
});
test("should use class name if table name is not specified", () {
expect(TableParser(generator).parse(testLib.getType("Users")).sqlName,
equals("users"));
test('should use class name if table name is not specified', () {
expect(TableParser(generator).parse(testLib.getType('Users')).sqlName,
equals('users'));
});
test("should not parse for complex methods", () async {
TableParser(generator).parse(testLib.getType("WrongName"));
test('should not parse for complex methods', () async {
TableParser(generator).parse(testLib.getType('WrongName'));
expect(generator.errors.errors, isNotEmpty);
});
});
group("Columns", () {
test("should use field name if no name has been set explicitely", () {
final table = TableParser(generator).parse(testLib.getType("Users"));
group('Columns', () {
test('should use field name if no name has been set explicitely', () {
final table = TableParser(generator).parse(testLib.getType('Users'));
final idColumn =
table.columns.singleWhere((col) => col.name.name == "id");
table.columns.singleWhere((col) => col.name.name == 'id');
expect(idColumn.name, equals(ColumnName.implicitly("id")));
expect(idColumn.name, equals(ColumnName.implicitly('id')));
});
test("should use explicit name, if it exists", () {
final table = TableParser(generator).parse(testLib.getType("Users"));
test('should use explicit name, if it exists', () {
final table = TableParser(generator).parse(testLib.getType('Users'));
final idColumn =
table.columns.singleWhere((col) => col.name.name == "user_name");
table.columns.singleWhere((col) => col.name.name == 'user_name');
expect(idColumn.name, equals(ColumnName.explicitly("user_name")));
expect(idColumn.name, equals(ColumnName.explicitly('user_name')));
});
test("should parse min and max length for text columns", () {
final table = TableParser(generator).parse(testLib.getType("Users"));
test('should parse min and max length for text columns', () {
final table = TableParser(generator).parse(testLib.getType('Users'));
final idColumn =
table.columns.singleWhere((col) => col.name.name == "user_name");
table.columns.singleWhere((col) => col.name.name == 'user_name');
expect(idColumn.features,
contains(LimitingTextLength.withLength(min: 6, max: 32)));
});
test("should only parse max length when relevant", () {
final table = TableParser(generator).parse(testLib.getType("Users"));
test('should only parse max length when relevant', () {
final table = TableParser(generator).parse(testLib.getType('Users'));
final idColumn =
table.columns.singleWhere((col) => col.name.name == "onlyMax");
table.columns.singleWhere((col) => col.name.name == 'onlyMax');
expect(
idColumn.features, contains(LimitingTextLength.withLength(max: 100)));