Skip to content

Fix Dart formatting in JSON strings, standardized strings, etc #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 2, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## 0.2.2

* Enable support for `enum` values.
* Added `asConst` to `JsonLiteral`.
* Improved the handling of Dart-specific characters in JSON strings.

## 0.2.1

Expand Down
3 changes: 1 addition & 2 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ linter:
- only_throw_errors
- prefer_final_fields
- prefer_is_not_empty
# Waiting for linter 0.1.33 to land in SDK
#- prefer_single_quotes
- prefer_single_quotes
- slash_for_doc_comments
- type_init_formals
32 changes: 16 additions & 16 deletions example/example.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions lib/src/json_literal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@
// 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.

// TODO: option to make the literal a const
class JsonLiteral {
/// The relative path from the Dart file with the annotation to the file
/// containing the source JSON.
final String path;

const JsonLiteral(this.path);
/// `true` if the JSON literal should be written as a constant.
final bool asConst;

const JsonLiteral(this.path, {bool asConst: false})
: this.asConst = asConst ?? false;
}
83 changes: 79 additions & 4 deletions lib/src/json_literal_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,89 @@ class JsonLiteralGenerator extends GeneratorForAnnotation<JsonLiteral> {
p.join(sourcePathDir, annotation.read('path').stringValue));
var content = JSON.decode(await buildStep.readAsString(fileId));

var thing = JSON.encode(content);
var asConst = annotation.read('asConst').boolValue;

var marked = _isConstType(content) ? 'const' : 'final';
var thing = _jsonLiteralAsDart(content, asConst).toString();
var marked = asConst ? 'const' : 'final';

return '$marked _\$${element.displayName}JsonLiteral = $thing;';
}
}

bool _isConstType(value) {
return value == null || value is String || value is num || value is bool;
/// Returns a [String] representing a valid Dart literal for [value].
///
/// If [asConst] is `true`, the returned [String] is encoded as a `const`
/// literal.
String _jsonLiteralAsDart(dynamic value, bool asConst) {
if (value == null) return 'null';

if (value is String) return _jsonStringAsDart(value);

if (value is bool || value is num) return value.toString();

if (value is List) {
var listItems =
value.map((v) => _jsonLiteralAsDart(v, asConst)).join(',\n');
return '${asConst ? 'const' : ''}[$listItems]';
}

if (value is Map) return _jsonMapAsDart(value, asConst);

throw new StateError(
'Should never get here – with ${value.runtimeType} - `$value`.');
}

String _jsonMapAsDart(Map value, bool asConst) {
var buffer = new StringBuffer();
if (asConst) {
buffer.write('const ');
}
buffer.write('{');

var first = true;
value.forEach((String k, v) {
if (first) {
first = false;
} else {
buffer.writeln(',');
}
buffer.write(_jsonStringAsDart(k));
buffer.write(':');
buffer.write(_jsonLiteralAsDart(v, asConst));
});

buffer.write('}');

return buffer.toString();
}

String _jsonStringAsDart(String value) {
var containsSingleQuote = value.contains("'");
var contains$ = value.contains(r'$');

if (containsSingleQuote) {
if (value.contains('"')) {
// `value` contains both single and double quotes as well as `$`.
// The only safe way to wrap the content is to escape all of the
// problematic characters.
var string = value
.replaceAll('\$', '\\\$')
.replaceAll('"', '\\"')
.replaceAll("'", "\\'");
return "'$string'";
} else if (contains$) {
// `value` contains "'" and "$", but not '"'.
// Safely wrap it in a raw string within double-quotes.
return 'r"$value"';
}
return '"$value"';
} else if (contains$) {
// `value` contains "$", but no "'"
// wrap it in a raw string using single quotes
return "r'$value'";
}

// `value` contains no problematic characters - except for '"' maybe.
// Wrap it in standard single-quotes.
return "'$value'";
}
43 changes: 21 additions & 22 deletions lib/src/json_serializable_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class JsonSerializableGenerator
fieldsList.where((fe) => fe.type.isUndefined).toList();
if (undefinedFields.isNotEmpty) {
var description =
undefinedFields.map((fe) => "`${fe.displayName}`").join(', ');
undefinedFields.map((fe) => '`${fe.displayName}`').join(', ');

throw new InvalidGenerationSourceError(
'At least one field has an invalid type: $description.',
Expand Down Expand Up @@ -153,13 +153,13 @@ class JsonSerializableGenerator

// TODO(kevmoo) We could write all values up to the null-excluded value
// directly in this literal.
buffer.writeln("var $toJsonMapVarName = <String, dynamic>{};");
buffer.writeln('var $toJsonMapVarName = <String, dynamic>{};');

buffer.writeln("""void $toJsonMapHelperName(String key, dynamic value) {
buffer.writeln('''void $toJsonMapHelperName(String key, dynamic value) {
if (value != null) {
$toJsonMapVarName[key] = value;
}
}""");
}''');

fields.forEach((fieldName, field) {
try {
Expand All @@ -173,21 +173,21 @@ class JsonSerializableGenerator
}

if (_includeIfNull(field, includeIfNull)) {
buffer.writeln("$toJsonMapVarName[$safeJsonKeyString] = "
"${_serialize(field.type, fieldName, _nullable(field))};");
buffer.writeln('$toJsonMapVarName[$safeJsonKeyString] = '
'${_serialize(field.type, fieldName, _nullable(field))};');
} else {
buffer.writeln("$toJsonMapHelperName($safeJsonKeyString, "
"${_serialize(field.type, fieldName, _nullable(field))});");
buffer.writeln('$toJsonMapHelperName($safeJsonKeyString, '
'${_serialize(field.type, fieldName, _nullable(field))});');
}
} on UnsupportedTypeError {
throw new InvalidGenerationSourceError(
"Could not generate `toJson` code for `${friendlyNameForElement(
field)}`.",
todo: "Make sure all of the types are serializable.");
'Could not generate `toJson` code for `${friendlyNameForElement(
field)}`.',
todo: 'Make sure all of the types are serializable.');
}
});

buffer.writeln(r"return $map;");
buffer.writeln(r'return $map;');

buffer.writeln('}');
}
Expand All @@ -200,12 +200,11 @@ class JsonSerializableGenerator
fields.forEach((fieldName, field) {
try {
pairs.add("'${_fieldToJsonMapKey(field, fieldName)}': "
"${_serialize(field.type, fieldName, _nullable(field))}");
'${_serialize(field.type, fieldName, _nullable(field))}');
} on UnsupportedTypeError {
throw new InvalidGenerationSourceError(
"Could not generate `toJson` code for `${friendlyNameForElement(
field)}`.",
todo: "Make sure all of the types are serializable.");
'Could not generate `toJson` code for `${friendlyNameForElement(field)}`.',
todo: 'Make sure all of the types are serializable.');
}
});
buffer.writeAll(pairs, ', ');
Expand Down Expand Up @@ -257,7 +256,7 @@ class JsonSerializableGenerator
.toList();
if (undefinedArgs.isNotEmpty) {
var description =
undefinedArgs.map((fe) => "`${fe.displayName}`").join(', ');
undefinedArgs.map((fe) => '`${fe.displayName}`').join(', ');

throw new InvalidGenerationSourceError(
'At least one constructor argument has an invalid type: $description.',
Expand Down Expand Up @@ -301,7 +300,7 @@ class JsonSerializableGenerator
} else {
fieldsToSet.forEach((name, field) {
buffer.writeln();
buffer.write(" ..${name} = ");
buffer.write(' ..${name} = ');
buffer.write(_deserializeForField(name, field));
});
buffer.writeln(';');
Expand Down Expand Up @@ -331,11 +330,11 @@ class JsonSerializableGenerator

try {
var safeName = _safeNameAccess(name);
return _deserialize(targetType, "json[$safeName]", _nullable(field));
return _deserialize(targetType, 'json[$safeName]', _nullable(field));
} on UnsupportedTypeError {
throw new InvalidGenerationSourceError(
"Could not generate fromJson code for `${friendlyNameForElement(field)}`.",
todo: "Make sure all of the types are serializable.");
'Could not generate fromJson code for `${friendlyNameForElement(field)}`.',
todo: 'Make sure all of the types are serializable.');
}
}

Expand Down Expand Up @@ -369,7 +368,7 @@ JsonKey _jsonKeyFor(FieldElement element) {
var key = _jsonKeyExpando[element];

if (key == null) {
// If an annotation exists on `element` the source is a "real" field.
// If an annotation exists on `element` the source is a 'real' field.
// If the result is `null`, check the getter – it is a property.
// TODO(kevmoo) setters: github.com/dart-lang/json_serializable/issues/24
var obj = _jsonKeyChecker.firstAnnotationOfExact(element) ??
Expand Down
4 changes: 2 additions & 2 deletions lib/src/type_helpers/date_time_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class DateTimeHelper extends TypeHelper {
buffer.write('?');
}

buffer.write(".toIso8601String()");
buffer.write('.toIso8601String()');

return buffer.toString();
}
Expand All @@ -30,7 +30,7 @@ class DateTimeHelper extends TypeHelper {
}

return commonNullPrefix(
nullable, expression, "DateTime.parse($expression as String)");
nullable, expression, 'DateTime.parse($expression as String)');
}
}

Expand Down
2 changes: 1 addition & 1 deletion lib/src/type_helpers/enum_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class EnumHelper extends TypeHelper {
TypeHelperGenerator serializeNested) {
if (targetType is InterfaceType && targetType.element.isEnum) {
return commonNullPrefix(
nullable, expression, '$expression.toString().split(".")[1]');
nullable, expression, "$expression.toString().split('.')[1]");
}

return null;
Expand Down
10 changes: 5 additions & 5 deletions lib/src/type_helpers/iterable_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'package:source_gen/source_gen.dart' show TypeChecker;
import '../type_helper.dart';

/// Name used for closure argument when generating calls to `map`.
final _closureArg = "e";
final _closureArg = 'e';

class IterableHelper extends TypeHelper {
const IterableHelper();
Expand All @@ -30,7 +30,7 @@ class IterableHelper extends TypeHelper {
if (subFieldValue != _closureArg) {
// TODO: the type could be imported from a library with a prefix!
expression =
"${expression}${optionalQuestion}.map(($_closureArg) => $subFieldValue)";
'${expression}${optionalQuestion}.map(($_closureArg) => $subFieldValue)';

// expression now represents an Iterable (even if it started as a List
// ...resetting `isList` to `false`.
Expand All @@ -39,7 +39,7 @@ class IterableHelper extends TypeHelper {

if (!isList) {
// If the static type is not a List, generate one.
expression += "${optionalQuestion}.toList()";
expression += '${optionalQuestion}.toList()';
}

return expression;
Expand All @@ -65,10 +65,10 @@ class IterableHelper extends TypeHelper {
var optionalQuestion = nullable ? '?' : '';

var output =
"($expression as List)${optionalQuestion}.map(($_closureArg) => $itemSubVal)";
'($expression as List)${optionalQuestion}.map(($_closureArg) => $itemSubVal)';

if (_coreListChecker.isAssignableFromType(targetType)) {
output += "${optionalQuestion}.toList()";
output += '${optionalQuestion}.toList()';
}

return output;
Expand Down
2 changes: 1 addition & 1 deletion lib/src/type_helpers/json_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class JsonHelper extends TypeHelper {

// TODO: the type could be imported from a library with a prefix!
// github.com/dart-lang/json_serializable/issues/19
var result = "new ${targetType.name}.fromJson($expression$asCast)";
var result = 'new ${targetType.name}.fromJson($expression$asCast)';

return commonNullPrefix(nullable, expression, result);
}
Expand Down
Loading