Skip to content

Commit

Permalink
Merge pull request #244 from Workiva/handle_non_arrow_functions
Browse files Browse the repository at this point in the history
INTL-1591: Handle non-arrow intl functions
  • Loading branch information
rmconsole6-wk authored Sep 20, 2023
2 parents 6cacd75 + 628707c commit 0b349b1
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 20 deletions.
40 changes: 24 additions & 16 deletions lib/src/intl_suggestors/message_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,17 @@ class MessageParser {
/// partially rewritten.
String withCorrectFunctionTypes(
MethodDeclaration declaration, String currentSource) {
// Split out the body and just do a simple string replace. The conditions
// for a false positive on this seem unlikely, so just do it and cross our
// fingers. If it's in a function name it will be followed by a parenthesis.
// This is only used for formattedMessage, so it won't be a getter. And if
// you name a parameter that ends in Function it'll presumably be followed
// by either a comma or a close-paren.
var declarationParts = currentSource.split('=>');
// Split out the body and just do a simple string replace on the header. The
// conditions for a false positive on this seem unlikely, so just do it and
// cross our fingers. If it's in a function name it will be followed by a
// parenthesis. This is only used for formattedMessage, so it won't be a
// getter. And if you name a parameter that ends in Function it'll
// presumably be followed by either a comma or a close-paren.
var splitString = (declaration.body is BlockFunctionBody) ? '{' : '=>';
var declarationParts = currentSource.split(splitString);
var newBeginning =
declarationParts.first.replaceAll('Function ', 'Object ');
return '$newBeginning=>${declarationParts.last}';
return '$newBeginning$splitString${declarationParts.last}';
}

/// Find the parameter `name:` from the invocation, or return null if there
Expand Down Expand Up @@ -125,16 +126,23 @@ class MessageParser {
}

/// The invocation of the internal Intl method. That is, the part after the
/// '=>'. We know there's only ever one. Used for determining what sort of
/// method this is message/plural/select/formattedMessage.
/// '=>' or the first statement inside the {}. We expect only one. Used for
/// determining what sort of method this is
/// message/plural/select/formattedMessage.
MethodInvocation intlMethodInvocation(MethodDeclaration method) {
var invocation = method.body.childEntities.toList()[1];
if (invocation is MethodInvocation) {
return invocation;
var node = method.body;
if (node is ExpressionFunctionBody) {
return node.expression as MethodInvocation;
} else if (node is BlockFunctionBody) {
var children = node.block.statements.first.childEntities.toList();
var methods = children.whereType<MethodInvocation>().toList();
if (methods.length > 1)
throw ArgumentError(
'A message can only contain a single call, which must be to an Intl function');
return methods.first;
} else {
print('ERROR: Invalid Intl method: $method');
// We expect this to throw
return invocation as MethodInvocation;
throw ArgumentError(
'Cannot parse $node. It needs to be a function with a single expression which is an Intl method invocation');
}
}

Expand Down
11 changes: 7 additions & 4 deletions test/intl_suggestors/intl_messages_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ void main() {
'function',
'aPlural',
'formatted',
'formattedNonArrow',
'someSelect'
]);
});
Expand Down Expand Up @@ -117,14 +118,15 @@ void main() {
});

test('messages found', () {
expect(messages.methods.length, 7);
expect(messages.methods.length, 8);
expect(messages.methods.keys, [
'orange',
'aquamarine',
'long',
'function',
'aPlural',
'formatted',
'formattedNonArrow',
'someSelect'
]);
expect(messages.methods.values.map((each) => each.source).toList(),
Expand Down Expand Up @@ -182,7 +184,7 @@ void main() {
var otherName = messages.nameForString('function', r'www${x}def');
tweakedMore = tweakedMore.replaceAll('function', otherName);
messages.addMethod(tweakedMore);
expect(messages.methods.length, 9);
expect(messages.methods.length, 10);
expect(messages.methods['function']?.source, sampleMethods[3]);
expect(messages.methods['function1']?.source, tweaked);
expect(messages.methods['function2']?.source, contains(r'www${x}def'));
Expand All @@ -202,20 +204,21 @@ class TestProjectIntl {${methods.isNotEmpty ? '\n' : ''}$methods
}''';

List<String> sampleMethods = [
" static String get orange => Intl.message('orange', name: 'TestProjectIntl_orange', desc: 'The color.');",
" static String get orange {return Intl.message('orange', name: 'TestProjectIntl_orange', desc: 'The color.');}",
" static String get aquamarine => Intl.message('aquamarine', name: 'TestProjectIntl_aquamarine', desc: 'The color', meaning: 'blueish');",
""" static String get long => Intl.message('''multi
line
string''', name: 'TestProjectIntl_long');""",
""" static String function(String x) => Intl.message('abc\${x}def', name: 'TestProjectIntl_function');""",
""" static String aPlural(int n) => Intl.plural(n, zero: 'zero', other: 'other', name: 'TestProjectIntl_aPlural', args: [n]);""",
""" static List<Object> formatted(Object f) => Intl.formattedMessage([f, 'foo'], name: 'TestProjectIntl_formatted', args: [f]);""",
""" static List<Object> formattedNonArrow(Object f) {Intl.formattedMessage([f, 'foo'], name: 'TestProjectIntl_formattedNonArrow', args: [f]);}""",
""" static String someSelect(Object choice) => Intl.select(choice, {'a' : 'b'}, name: 'TestProjectIntl_someSelect', args: [choice]);"""
];

// The sample methods in a hard-coded sorted order.
List<String> get sortedSampleMethods =>
[4, 1, 5, 3, 2, 0, 6].map((i) => sampleMethods[i]).toList();
[4, 1, 5, 6, 3, 2, 0, 7].map((i) => sampleMethods[i]).toList();

// A test utility to be invoked from the debug console to see where subtly-different long strings differ.
void firstDifference(String a, String b) {
Expand Down

0 comments on commit 0b349b1

Please sign in to comment.