-
Notifications
You must be signed in to change notification settings - Fork 198
Tweak uses of RegExp and parsing using them. #1097
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -168,7 +168,7 @@ String _typeNameFromUrl(String typeUrl) { | |
| } | ||
|
|
||
| mixin TimestampMixin { | ||
| static final RegExp finalGroupsOfThreeZeroes = RegExp(r'(?:000)*$'); | ||
| static final RegExp finalGroupsOfThreeZeroes = RegExp(r'(?:000)+$'); | ||
|
|
||
| Int64 get seconds; | ||
| set seconds(Int64 value); | ||
|
|
@@ -192,9 +192,15 @@ mixin TimestampMixin { | |
| /// | ||
| /// Time zone information will not be preserved. | ||
| static void setFromDateTime(TimestampMixin target, DateTime dateTime) { | ||
| final micros = dateTime.microsecondsSinceEpoch; | ||
| target.seconds = Int64((micros / Duration.microsecondsPerSecond).floor()); | ||
| target.nanos = (micros % Duration.microsecondsPerSecond).toInt() * 1000; | ||
| var micros = dateTime.microsecondsSinceEpoch; | ||
| var seconds = micros ~/ Duration.microsecondsPerSecond; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Going through doubles loses precision for large/small dates. Tests added. |
||
| micros = micros.remainder(Duration.microsecondsPerSecond); | ||
| if (micros < 0) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Usual way to handle |
||
| seconds -= 1; | ||
| micros += Duration.microsecondsPerSecond; | ||
| } | ||
| target.seconds = Int64(seconds); | ||
| target.nanos = micros * 1000; | ||
| } | ||
|
|
||
| static String _twoDigits(int n) { | ||
|
|
@@ -268,10 +274,10 @@ mixin TimestampMixin { | |
| JsonParsingContext context, | ||
| ) { | ||
| if (json is String) { | ||
| var jsonWithoutFracSec = json; | ||
| var nanos = 0; | ||
| final Match? fracSecsMatch = RegExp(r'\.(\d+)').firstMatch(json); | ||
| if (fracSecsMatch != null) { | ||
| var jsonWithoutFracSec = json.replaceFirstMapped(RegExp(r'\.(\d+)'), ( | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Could combine match and replace.) |
||
| fracSecsMatch, | ||
| ) { | ||
| final fracSecs = fracSecsMatch[1]!; | ||
| if (fracSecs.length > 9) { | ||
| throw context.parseException( | ||
|
|
@@ -280,12 +286,8 @@ mixin TimestampMixin { | |
| ); | ||
| } | ||
| nanos = int.parse(fracSecs.padRight(9, '0')); | ||
| jsonWithoutFracSec = json.replaceRange( | ||
| fracSecsMatch.start, | ||
| fracSecsMatch.end, | ||
| '', | ||
| ); | ||
| } | ||
| return ''; | ||
| }); | ||
| final dateTimeWithoutFractionalSeconds = | ||
| DateTime.tryParse(jsonWithoutFracSec) ?? | ||
| (throw context.parseException( | ||
|
|
@@ -319,16 +321,30 @@ mixin DurationMixin { | |
| TypeRegistry typeRegistry, | ||
| ) { | ||
| final duration = message as DurationMixin; | ||
| final secFrac = duration.nanos | ||
| // nanos and seconds should always have the same sign. | ||
| final seconds = duration.seconds; | ||
| final nanos = duration.nanos; | ||
| // Extra sign needed if `seconds` is zero and nanos is negative. | ||
| final sign = seconds.isZero && nanos < 0 ? '-' : ''; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was a bug. It wrote 0 seconds and -500000000 nanos as |
||
|
|
||
| return '$sign$seconds${_toNanosString(nanos)}s'; | ||
| } | ||
|
|
||
| /// Nanoseconds as fractional seconds. | ||
| /// | ||
| /// Is empty if [nanos] is zero, otherwise the digits of | ||
| /// (absolute value) of `nanos` as billionths of seconds | ||
| /// with no trailing zeros. | ||
| static String _toNanosString(int nanos) { | ||
| if (nanos == 0) return ''; | ||
| final digits = nanos | ||
| .abs() | ||
| .toString() | ||
| .padLeft(9, '0') | ||
| .replaceFirst(finalZeroes, ''); | ||
| final secPart = secFrac == '' ? '' : '.$secFrac'; | ||
| return '${duration.seconds}${secPart}s'; | ||
| return '.$digits'; | ||
| } | ||
|
|
||
| // Matches `-?\d*\.?\d*s$`, grouping is only to control captures. | ||
| static final RegExp durationPattern = RegExp(r'(-?\d*)(?:\.(\d*))?s$'); | ||
|
|
||
| static void fromProto3JsonHelper( | ||
|
|
@@ -340,25 +356,33 @@ mixin DurationMixin { | |
| final duration = message as DurationMixin; | ||
| if (json is String) { | ||
| final match = durationPattern.matchAsPrefix(json); | ||
| if (match == null) { | ||
| throw context.parseException( | ||
| 'Expected a String of the form `<seconds>.<nanos>s`', | ||
| json, | ||
| ); | ||
| } else { | ||
| if (match != null) { | ||
| final secondsString = match[1]!; | ||
| final seconds = | ||
| secondsString == '' ? Int64.ZERO : Int64.parseInt(secondsString); | ||
| duration.seconds = seconds; | ||
| final nanos = int.parse((match[2] ?? '').padRight(9, '0')); | ||
| duration.nanos = seconds < 0 ? -nanos : nanos; | ||
| final isNegative = secondsString.startsWith('-'); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was a bug in the opposite direction. |
||
| duration.seconds = | ||
| (secondsString.length == (isNegative ? 1 : 0)) | ||
| ? Int64.ZERO | ||
| : Int64.parseInt(secondsString); | ||
| var fractionalSeconds = match[2]; | ||
| var nanos = 0; | ||
| if (fractionalSeconds != null) { | ||
| if (fractionalSeconds.length > 9) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The original code would parse I assume the input would be correctly generated and not have more than 9 digits, but better to be safe. |
||
| // Shouldn't happen for valid input. | ||
| fractionalSeconds = fractionalSeconds.substring(0, 9); | ||
| } else { | ||
| fractionalSeconds = fractionalSeconds.padRight(9, '0'); | ||
| } | ||
| nanos = int.parse(fractionalSeconds); | ||
| if (isNegative) nanos = -nanos; | ||
| } | ||
| duration.nanos = nanos; | ||
| return; | ||
| } | ||
| } else { | ||
| throw context.parseException( | ||
| 'Expected a String of the form `<seconds>.<nanos>s`', | ||
| json, | ||
| ); | ||
| } | ||
| throw context.parseException( | ||
| 'Expected a String of the form `<seconds>.<nanos>s`', | ||
| json, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -565,7 +589,7 @@ mixin FieldMaskMixin { | |
| ) { | ||
| final fieldMask = message as FieldMaskMixin; | ||
| for (final path in fieldMask.paths) { | ||
| if (path.contains(RegExp('[A-Z]|_[^a-z]'))) { | ||
| if (path.contains(RegExp(r'[A-Z]|_[^a-z]'))) { | ||
| throw ArgumentError( | ||
| 'Bad fieldmask $path. Does not round-trip to json.', | ||
| ); | ||
|
|
@@ -587,7 +611,7 @@ mixin FieldMaskMixin { | |
| json, | ||
| ); | ||
| } | ||
| if (json == '') { | ||
| if (json.isEmpty) { | ||
| // The empty string splits to a single value. So this is a special case. | ||
| return; | ||
| } | ||
|
|
@@ -604,15 +628,15 @@ mixin FieldMaskMixin { | |
|
|
||
| static String _toCamelCase(String name) { | ||
| return name.replaceAllMapped( | ||
| RegExp('_([a-z])'), | ||
| (Match m) => m.group(1)!.toUpperCase(), | ||
| RegExp(r'_[a-z]'), | ||
| (Match m) => name[m.start + 1].toUpperCase(), | ||
| ); | ||
| } | ||
|
|
||
| static String _fromCamelCase(String name) { | ||
| return name.replaceAllMapped( | ||
| RegExp('[A-Z]'), | ||
| (Match m) => '_${m.group(0)!.toLowerCase()}', | ||
| RegExp(r'[A-Z]'), | ||
| (Match m) => '_${m[0]!.toLowerCase()}', | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -145,7 +145,7 @@ Iterable<String> extensionSuffixes() sync* { | |
| /// | ||
| /// This function does not take care of leading underscores. | ||
| String legalDartIdentifier(String input) { | ||
| return input.replaceAll(RegExp(r'[^a-zA-Z0-9$_]'), '_'); | ||
| return input.replaceAll(RegExp(r'[^\w$]'), '_'); | ||
| } | ||
|
|
||
| /// Chooses the name of the Dart class holding top-level extensions. | ||
|
|
@@ -592,7 +592,7 @@ String? _nameOption(FieldDescriptorProto field) => | |
|
|
||
| bool _isDartFieldName(String name) => name.startsWith(_dartFieldNameExpr); | ||
|
|
||
| final _dartFieldNameExpr = RegExp(r'^[a-z]\w+$'); | ||
| final _dartFieldNameExpr = RegExp(r'^[a-z]\w*$'); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Seems unlikely that single-letter fields are not allowed, but two-letter ones are.) |
||
|
|
||
| /// Names that would collide as top-level identifiers. | ||
| final List<String> forbiddenTopLevelNames = <String>[ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,7 @@ | |
|
|
||
| part of '../protoc.dart'; | ||
|
|
||
| final RegExp _dartIdentifier = RegExp(r'^\w+$'); | ||
| final RegExp _dartIdentifier = RegExp(r'^[a-zA-Z_]\w*$'); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That didn't match Dart identifiers, it allowed starting with |
||
|
|
||
| const String _asyncImportUrl = 'dart:async'; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,20 +6,10 @@ part of '../protoc.dart'; | |
|
|
||
| class ProtobufField { | ||
| static final RegExp _hexLiteralRegex = RegExp( | ||
| r'^0x[0-9a-f]+$', | ||
| multiLine: false, | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| caseSensitive: false, | ||
| r'^0x[\da-fA-F]+$', | ||
| ); | ||
| static final RegExp _integerLiteralRegex = RegExp(r'^[+-]?[0-9]+$'); | ||
| static final RegExp _decimalLiteralRegexA = RegExp( | ||
| r'^[+-]?([0-9]*)\.[0-9]+(e[+-]?[0-9]+)?$', | ||
| multiLine: false, | ||
| caseSensitive: false, | ||
| ); | ||
| static final RegExp _decimalLiteralRegexB = RegExp( | ||
| r'^[+-]?[0-9]+e[+-]?[0-9]+$', | ||
| multiLine: false, | ||
| caseSensitive: false, | ||
| static final RegExp _decimalLiteralRegex = RegExp( | ||
| r'^[+\-]?(\d*\.)?\d+([eE][+\-]?\d+)?$', | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of three regexpsm just use one and switch on whether it has double-literal parts. |
||
| ); | ||
|
|
||
| final FieldDescriptorProto descriptor; | ||
|
|
@@ -411,10 +401,12 @@ class ProtobufField { | |
| return '$coreImportPrefix.double.nan'; | ||
| } else if (_hexLiteralRegex.hasMatch(descriptor.defaultValue)) { | ||
| return '(${descriptor.defaultValue}).toDouble()'; | ||
| } else if (_integerLiteralRegex.hasMatch(descriptor.defaultValue)) { | ||
| return '${descriptor.defaultValue}.0'; | ||
| } else if (_decimalLiteralRegexA.hasMatch(descriptor.defaultValue) || | ||
| _decimalLiteralRegexB.hasMatch(descriptor.defaultValue)) { | ||
| } else if (_decimalLiteralRegex.matchAsPrefix(descriptor.defaultValue) | ||
| case final match?) { | ||
| if (match[1] == null && match[2] == null) { | ||
| // Integer literal, no `.` or `e` parts. | ||
| return '${descriptor.defaultValue}.0'; | ||
| } | ||
| return descriptor.defaultValue; | ||
| } | ||
| throw _invalidDefaultValue; | ||
|
|
@@ -502,7 +494,7 @@ class ProtobufField { | |
| static String _unCamelCase(String name) { | ||
| return name.replaceAllMapped( | ||
| _upperCase, | ||
| (match) => '_${match.group(0)!.toLowerCase()}', | ||
| (match) => '_${match[0]!.toLowerCase()}', | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -71,25 +71,25 @@ String? toDartComment(String value) { | |
|
|
||
| if (value.isEmpty) return null; | ||
|
|
||
| var lines = LineSplitter.split(value).toList(); | ||
| var lines = LineSplitter.split(value) | ||
| .map((line) => line.trimRight()) | ||
| .toList(); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doing |
||
|
|
||
| // Find any leading spaces in the first line. If all of the lines have the | ||
| // same leading spaces, remove them all. | ||
| final leadingSpaces = _leadingSpaces.firstMatch(lines.first); | ||
| if (leadingSpaces != null) { | ||
| final prefix = leadingSpaces.group(0)!; | ||
| final prefix = leadingSpaces[0]!; | ||
| if (lines.every((line) => line.isEmpty || line.startsWith(prefix))) { | ||
| lines = | ||
| lines | ||
| .map( | ||
| (line) => line.isEmpty ? line : line.substring(prefix.length), | ||
| ) | ||
| .toList(); | ||
| for (var i = 0; i < lines.length; i++) { | ||
| var line = lines[i]; | ||
| if (line.isNotEmpty) lines[i] = line.substring(prefix.length); | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just update the list in-place instead of building a new one. |
||
| } | ||
| } | ||
|
|
||
| // Remove empty, trailing lines. | ||
| while (lines.isNotEmpty && lines.last.trim().isEmpty) { | ||
| while (lines.isNotEmpty && lines.last.isEmpty) { | ||
| lines.removeLast(); | ||
| } | ||
|
|
||
|
|
@@ -98,7 +98,7 @@ String? toDartComment(String value) { | |
| return null; | ||
| } | ||
|
|
||
| return lines.map((e) => '/// $e'.trimRight()).join('\n'); | ||
| return lines.map((e) => e.isEmpty ? '///' : '/// $e').join('\n'); | ||
| } | ||
|
|
||
| final _leadingSpaces = RegExp('^ +'); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -450,7 +450,7 @@ void main() { | |
| predicate((e) { | ||
| if (e is FormatException) { | ||
| final pathExpression = | ||
| RegExp(r'root(\["[^"]*"]*\])*').firstMatch(e.message)![0]!; | ||
| RegExp(r'root(?:\["[^"]*"\])*').firstMatch(e.message)![0]!; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (I think the extra |
||
| final actualPath = | ||
| RegExp( | ||
| r'\["([^"]*)"\]', | ||
|
|
@@ -1230,6 +1230,12 @@ void main() { | |
| ..seconds = Int64(10) | ||
| ..nanos = 10, | ||
| ); | ||
| expect( | ||
| Duration()..mergeFromProto3Json('10.000000001999s'), | ||
| Duration() | ||
| ..seconds = Int64(10) | ||
| ..nanos = 1, | ||
| ); | ||
| expect( | ||
| Duration()..mergeFromProto3Json('-1.000099s'), | ||
| Duration() | ||
|
|
@@ -1255,6 +1261,18 @@ void main() { | |
| ..seconds = Int64(0) | ||
| ..nanos = 500000000, | ||
| ); | ||
| expect( | ||
| Duration()..mergeFromProto3Json('-.5s'), | ||
| Duration() | ||
| ..seconds = Int64(0) | ||
| ..nanos = -500000000, | ||
| ); | ||
| expect( | ||
| Duration()..mergeFromProto3Json('-0.0s'), | ||
| Duration() | ||
| ..seconds = Int64(0) | ||
| ..nanos = 0, | ||
| ); | ||
| expect( | ||
| Duration()..mergeFromProto3Json('5.s'), | ||
| Duration() | ||
|
|
@@ -1267,6 +1285,18 @@ void main() { | |
| ..seconds = Int64(0) | ||
| ..nanos = 0, | ||
| ); | ||
| expect( | ||
| Duration()..mergeFromProto3Json('-s'), | ||
| Duration() | ||
| ..seconds = Int64(0) | ||
| ..nanos = 0, | ||
| ); | ||
| expect( | ||
| Duration()..mergeFromProto3Json('s'), | ||
| Duration() | ||
| ..seconds = Int64(0) | ||
| ..nanos = 0, | ||
| ); | ||
| expect(() => Duration()..mergeFromProto3Json('0.5'), parseFailure([])); | ||
| expect(() => Duration()..mergeFromProto3Json(100), parseFailure([])); | ||
| }); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Only used for replacing with
'', no need to match an empty string.)